tonic_richer_error/
lib.rs

1/*!
2Assets for implementation of the gRPC Richer Error Model with tonic.
3
4This crate introduces the [`WithErrorDetails`] trait and implements it in
5[`tonic::Status`], allowing the implementation of the [gRPC Richer Error Model]
6with [`tonic`] in a convenient way.
7
8# Usage
9The [`WithErrorDetails`] trait adds associated functions to [`tonic::Status`]
10that can be used on the server side to create a status with error details, that
11can then be returned to the gRPC client. Moreover, the trait also adds methods
12to [`tonic::Status`] that can be used by a tonic client to extract error
13details, and handle them with ease.
14
15# Getting Started
16To build this crate you must have the Protocol Buffer Compiler, `protoc`,
17installed. Instructions can be found [here][protoc-install].
18
19```toml
20[dependencies]
21tonic = "0.8"
22tonic-richer-error = "0.3"
23```
24
25# Examples
26The examples bellow cover a basic use case. More complete server and client
27implementations can be found at the [github examples] directory.
28
29## Server Side: Generating [`tonic::Status`] with an [`ErrorDetails`] struct
30```
31use tonic::{Code, Status};
32use tonic_richer_error::{ErrorDetails, WithErrorDetails};
33
34# async fn endpoint() -> Result<tonic::Response<()>, Status> {
35// ...
36// Inside a gRPC server endpoint that returns `Result<Response<T>, Status>`
37
38// Create empty `ErrorDetails` struct
39let mut err_details = ErrorDetails::new();
40
41// Add error details conditionally
42# let some_condition = true;
43if some_condition {
44    err_details.add_bad_request_violation(
45        "field_a",
46        "description of why the field_a is invalid"
47    );
48}
49
50# let other_condition = true;
51if other_condition {
52    err_details.add_bad_request_violation(
53        "field_b",
54        "description of why the field_b is invalid",
55    );
56}
57
58// Check if any error details were set and return error status if so
59if err_details.has_bad_request_violations() {
60
61    // Add additional error details if necessary
62    err_details
63        .add_help_link("description of link", "https://resource.example.local")
64        .set_localized_message("en-US", "message for the user");
65
66    let status = Status::with_error_details(
67        Code::InvalidArgument,
68        "bad request",
69        err_details,
70    );
71
72    return Err(status);
73}
74
75// Handle valid request
76// ...
77
78# Ok(tonic::Response::new(()))
79# }
80```
81
82## Client Side: Extracting an [`ErrorDetails`] struct from `tonic::Status`
83```
84use tonic::{Response, Status};
85use tonic_richer_error::{WithErrorDetails};
86
87// ...
88
89// Where `req_result` was returned by a gRPC client endpoint method
90fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
91    match req_result {
92        Ok(response) => {
93            // Handle successful response
94        },
95        Err(status) => {
96            let err_details = status.get_error_details();
97            if let Some(bad_request) = err_details.bad_request {
98                // Handle bad_request details
99            }
100            if let Some(help) = err_details.help {
101                // Handle help details
102            }
103            if let Some(localized_message) = err_details.localized_message {
104                // Handle localized_message details
105            }
106        }
107    };
108}
109```
110
111## Send different standard error messages
112Multiple examples are provided at the [`ErrorDetails`] doc. Instructions about
113how to use the fields of the standard error message types correctly are
114provided at [error_details.proto].
115
116## Alternative `tonic::Status` associated functions and methods
117In the [`WithErrorDetails`] doc, an alternative way of interacting with
118[`tonic::Status`] is presented, using vectors of error details structs wrapped
119with the [`ErrorDetail`] enum. This approach can provide more control over the
120vector of standard error messages that will be generated or that was received,
121if necessary. To see how to adopt this approach, please check the
122[`WithErrorDetails::with_error_details_vec`] and
123[`WithErrorDetails::get_error_details_vec`] docs, and also the
124[github examples] directory.\
125
126Besides that, multiple examples with alternative error details extraction
127methods are provided in the [`WithErrorDetails`] doc, which can be specially
128useful if only one type of standard error message is being handled by the
129client. For example, using [`WithErrorDetails::get_details_bad_request`] is a
130more direct way of extracting a [`BadRequest`] error message from
131[`tonic::Status`].
132
133[`tonic::Status`]: https://docs.rs/tonic/0.8.0/tonic/struct.Status.html
134[`tonic`]: https://docs.rs/tonic/0.8.0/tonic/
135[gRPC Richer Error Model]: https://www.grpc.io/docs/guides/error/
136[protoc-install]: https://grpc.io/docs/protoc-installation/
137[github examples]: https://github.com/flemosr/tonic-richer-error/tree/main/examples
138[error_details.proto]: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
139*/
140
141#![warn(
142    missing_debug_implementations,
143    missing_docs,
144    rust_2018_idioms,
145    unreachable_pub
146)]
147
148use prost::{DecodeError, Message};
149use prost_types::Any;
150use tonic::{codegen::Bytes, Code, Status};
151
152/// Compiled `google.rpc` protos
153pub mod pb {
154    include!(concat!(env!("OUT_DIR"), "/google.rpc.rs"));
155}
156
157mod error_details;
158mod error_details_vec;
159mod std_messages;
160
161pub use std_messages::*;
162
163pub use error_details::ErrorDetails;
164
165pub use error_details_vec::ErrorDetail;
166
167trait IntoAny {
168    fn into_any(self) -> Any;
169}
170
171trait FromAny {
172    fn from_any(any: Any) -> Result<Self, DecodeError>
173    where
174        Self: Sized;
175}
176
177/// Used to implement associated functions and methods on `tonic::Status`, that
178/// allow the addition and extraction of standard error details.
179pub trait WithErrorDetails {
180    /// Generates a `tonic::Status` with error details obtained from an
181    /// [`ErrorDetails`] struct.
182    /// # Examples
183    ///
184    /// ```
185    /// use tonic::{Code, Status};
186    /// use tonic_richer_error::{ErrorDetails, WithErrorDetails};
187    ///
188    /// let status = Status::with_error_details(
189    ///     Code::InvalidArgument,
190    ///     "bad request",
191    ///     ErrorDetails::with_bad_request_violation("field", "description"),
192    /// );
193    /// ```
194    fn with_error_details(
195        code: tonic::Code,
196        message: impl Into<String>,
197        details: ErrorDetails,
198    ) -> Status;
199
200    /// Generates a `tonic::Status` with error details provided in a vector of
201    /// [`ErrorDetail`] enums.
202    /// # Examples
203    ///
204    /// ```
205    /// use tonic::{Code, Status};
206    /// use tonic_richer_error::{BadRequest, WithErrorDetails};
207    ///
208    /// let status = Status::with_error_details_vec(
209    ///     Code::InvalidArgument,
210    ///     "bad request",
211    ///     vec![
212    ///         BadRequest::with_violation("field", "description").into(),
213    ///     ]
214    /// );
215    /// ```
216    fn with_error_details_vec(
217        code: tonic::Code,
218        message: impl Into<String>,
219        details: Vec<ErrorDetail>,
220    ) -> Status;
221
222    /// Can be used to check if the error details contained in `tonic::Status`
223    /// are malformed or not. Tries to get an [`ErrorDetails`] struct from a
224    /// `tonic::Status`. If some `prost::DecodeError` occurs, it will be
225    /// returned. If not debugging, consider using
226    /// [`WithErrorDetails::get_error_details`] or
227    /// [`WithErrorDetails::get_error_details_vec`].
228    /// # Examples
229    ///
230    /// ```
231    /// use tonic::{Status, Response};
232    /// use tonic_richer_error::{WithErrorDetails};
233    ///
234    /// fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
235    ///     match req_result {
236    ///         Ok(_) => {},
237    ///         Err(status) => {
238    ///             let err_details = status.get_error_details();
239    ///             if let Some(bad_request) = err_details.bad_request {
240    ///                 // Handle bad_request details
241    ///             }
242    ///             // ...
243    ///         }
244    ///     };
245    /// }
246    /// ```
247    fn check_error_details(&self) -> Result<ErrorDetails, DecodeError>;
248
249    /// Get an [`ErrorDetails`] struct from `tonic::Status`. If some
250    /// `prost::DecodeError` occurs, an empty [`ErrorDetails`] struct will be
251    /// returned.
252    /// # Examples
253    ///
254    /// ```
255    /// use tonic::{Status, Response};
256    /// use tonic_richer_error::{WithErrorDetails};
257    ///
258    /// fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
259    ///     match req_result {
260    ///         Ok(_) => {},
261    ///         Err(status) => {
262    ///             let err_details = status.get_error_details();
263    ///             if let Some(bad_request) = err_details.bad_request {
264    ///                 // Handle bad_request details
265    ///             }
266    ///             // ...
267    ///         }
268    ///     };
269    /// }
270    /// ```
271    fn get_error_details(&self) -> ErrorDetails;
272
273    /// Can be used to check if the error details contained in `tonic::Status`
274    /// are malformed or not. Tries to get a vector of [`ErrorDetail`] enums
275    /// from a `tonic::Status`. If some `prost::DecodeError` occurs, it will be
276    /// returned. If not debugging, consider using
277    /// [`WithErrorDetails::get_error_details_vec`] or
278    /// [`WithErrorDetails::get_error_details`].
279    /// # Examples
280    ///
281    /// ```
282    /// use tonic::{Status, Response};
283    /// use tonic_richer_error::{ErrorDetail, WithErrorDetails};
284    ///
285    /// fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
286    ///     match req_result {
287    ///         Ok(_) => {},
288    ///         Err(status) => {
289    ///             match status.check_error_details_vec() {
290    ///                 Ok(err_details) => {
291    ///                     // Handle extracted details
292    ///                 }
293    ///                 Err(decode_error) => {
294    ///                     // Handle decode_error
295    ///                 }
296    ///             }
297    ///         }
298    ///     };
299    /// }
300    /// ```
301    fn check_error_details_vec(&self) -> Result<Vec<ErrorDetail>, DecodeError>;
302
303    /// Get a vector of [`ErrorDetail`] enums from `tonic::Status`. If some
304    /// `prost::DecodeError` occurs, an empty vector will be returned.
305    /// # Examples
306    ///
307    /// ```
308    /// use tonic::{Status, Response};
309    /// use tonic_richer_error::{ErrorDetail, WithErrorDetails};
310    ///
311    /// fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
312    ///     match req_result {
313    ///         Ok(_) => {},
314    ///         Err(status) => {
315    ///             let err_details = status.get_error_details_vec();
316    ///             for err_detail in err_details.iter() {
317    ///                  match err_detail {
318    ///                     ErrorDetail::BadRequest(bad_request) => {
319    ///                         // Handle bad_request details
320    ///                     }
321    ///                     // ...
322    ///                     _ => {}
323    ///                  }
324    ///             }
325    ///         }
326    ///     };
327    /// }
328    /// ```
329    fn get_error_details_vec(&self) -> Vec<ErrorDetail>;
330
331    /// Get first [`RetryInfo`] details found on `tonic::Status`, if any. If
332    /// some `prost::DecodeError` occurs, returns `None`.
333    /// # Examples
334    ///
335    /// ```
336    /// use tonic::{Status, Response};
337    /// use tonic_richer_error::{WithErrorDetails};
338    ///
339    /// fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
340    ///     match req_result {
341    ///         Ok(_) => {},
342    ///         Err(status) => {
343    ///             if let Some(retry_info) = status.get_details_retry_info() {
344    ///                 // Handle retry_info details
345    ///             }
346    ///         }
347    ///     };
348    /// }
349    /// ```
350    fn get_details_retry_info(&self) -> Option<RetryInfo>;
351
352    /// Get first [`DebugInfo`] details found on `tonic::Status`, if any. If
353    /// some `prost::DecodeError` occurs, returns `None`.
354    /// # Examples
355    ///
356    /// ```
357    /// use tonic::{Status, Response};
358    /// use tonic_richer_error::{WithErrorDetails};
359    ///
360    /// fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
361    ///     match req_result {
362    ///         Ok(_) => {},
363    ///         Err(status) => {
364    ///             if let Some(debug_info) = status.get_details_debug_info() {
365    ///                 // Handle debug_info details
366    ///             }
367    ///         }
368    ///     };
369    /// }
370    /// ```
371    fn get_details_debug_info(&self) -> Option<DebugInfo>;
372
373    /// Get first [`QuotaFailure`] details found on `tonic::Status`, if any.
374    /// If some `prost::DecodeError` occurs, returns `None`.
375    /// # Examples
376    ///
377    /// ```
378    /// use tonic::{Status, Response};
379    /// use tonic_richer_error::{WithErrorDetails};
380    ///
381    /// fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
382    ///     match req_result {
383    ///         Ok(_) => {},
384    ///         Err(status) => {
385    ///             if let Some(quota_failure) = status.get_details_quota_failure() {
386    ///                 // Handle quota_failure details
387    ///             }
388    ///         }
389    ///     };
390    /// }
391    /// ```
392    fn get_details_quota_failure(&self) -> Option<QuotaFailure>;
393
394    /// Get first [`ErrorInfo`] details found on `tonic::Status`, if any. If
395    /// some `prost::DecodeError` occurs, returns `None`.
396    /// # Examples
397    ///
398    /// ```
399    /// use tonic::{Status, Response};
400    /// use tonic_richer_error::{WithErrorDetails};
401    ///
402    /// fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
403    ///     match req_result {
404    ///         Ok(_) => {},
405    ///         Err(status) => {
406    ///             if let Some(error_info) = status.get_details_error_info() {
407    ///                 // Handle error_info details
408    ///             }
409    ///         }
410    ///     };
411    /// }
412    /// ```
413    fn get_details_error_info(&self) -> Option<ErrorInfo>;
414
415    /// Get first [`PreconditionFailure`] details found on `tonic::Status`,
416    /// if any. If some `prost::DecodeError` occurs, returns `None`.
417    /// # Examples
418    ///
419    /// ```
420    /// use tonic::{Status, Response};
421    /// use tonic_richer_error::{WithErrorDetails};
422    ///
423    /// fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
424    ///     match req_result {
425    ///         Ok(_) => {},
426    ///         Err(status) => {
427    ///             if let Some(precondition_failure) = status.get_details_precondition_failure() {
428    ///                 // Handle precondition_failure details
429    ///             }
430    ///         }
431    ///     };
432    /// }
433    /// ```
434    fn get_details_precondition_failure(&self) -> Option<PreconditionFailure>;
435
436    /// Get first [`BadRequest`] details found on `tonic::Status`, if any. If
437    /// some `prost::DecodeError` occurs, returns `None`.
438    /// # Examples
439    ///
440    /// ```
441    /// use tonic::{Status, Response};
442    /// use tonic_richer_error::{WithErrorDetails};
443    ///
444    /// fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
445    ///     match req_result {
446    ///         Ok(_) => {},
447    ///         Err(status) => {
448    ///             if let Some(bad_request) = status.get_details_bad_request() {
449    ///                 // Handle bad_request details
450    ///             }
451    ///         }
452    ///     };
453    /// }
454    /// ```
455    fn get_details_bad_request(&self) -> Option<BadRequest>;
456
457    /// Get first [`RequestInfo`] details found on `tonic::Status`, if any.
458    /// If some `prost::DecodeError` occurs, returns `None`.
459    /// # Examples
460    ///
461    /// ```
462    /// use tonic::{Status, Response};
463    /// use tonic_richer_error::{WithErrorDetails};
464    ///
465    /// fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
466    ///     match req_result {
467    ///         Ok(_) => {},
468    ///         Err(status) => {
469    ///             if let Some(request_info) = status.get_details_request_info() {
470    ///                 // Handle request_info details
471    ///             }
472    ///         }
473    ///     };
474    /// }
475    /// ```
476    fn get_details_request_info(&self) -> Option<RequestInfo>;
477
478    /// Get first [`ResourceInfo`] details found on `tonic::Status`, if any.
479    /// If some `prost::DecodeError` occurs, returns `None`.
480    /// # Examples
481    ///
482    /// ```
483    /// use tonic::{Status, Response};
484    /// use tonic_richer_error::{WithErrorDetails};
485    ///
486    /// fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
487    ///     match req_result {
488    ///         Ok(_) => {},
489    ///         Err(status) => {
490    ///             if let Some(resource_info) = status.get_details_resource_info() {
491    ///                 // Handle resource_info details
492    ///             }
493    ///         }
494    ///     };
495    /// }
496    /// ```
497    fn get_details_resource_info(&self) -> Option<ResourceInfo>;
498
499    /// Get first [`Help`] details found on `tonic::Status`, if any. If some
500    /// `prost::DecodeError` occurs, returns `None`.
501    /// # Examples
502    ///
503    /// ```
504    /// use tonic::{Status, Response};
505    /// use tonic_richer_error::{WithErrorDetails};
506    ///
507    /// fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
508    ///     match req_result {
509    ///         Ok(_) => {},
510    ///         Err(status) => {
511    ///             if let Some(help) = status.get_details_help() {
512    ///                 // Handle help details
513    ///             }
514    ///         }
515    ///     };
516    /// }
517    /// ```
518    fn get_details_help(&self) -> Option<Help>;
519
520    /// Get first [`LocalizedMessage`] details found on `tonic::Status`, if
521    /// any. If some `prost::DecodeError` occurs, returns `None`.
522    /// # Examples
523    ///
524    /// ```
525    /// use tonic::{Status, Response};
526    /// use tonic_richer_error::{WithErrorDetails};
527    ///
528    /// fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
529    ///     match req_result {
530    ///         Ok(_) => {},
531    ///         Err(status) => {
532    ///             if let Some(localized_message) = status.get_details_localized_message() {
533    ///                 // Handle localized_message details
534    ///             }
535    ///         }
536    ///     };
537    /// }
538    /// ```
539    fn get_details_localized_message(&self) -> Option<LocalizedMessage>;
540}
541
542impl WithErrorDetails for Status {
543    fn with_error_details(code: Code, message: impl Into<String>, details: ErrorDetails) -> Self {
544        let message: String = message.into();
545
546        let mut conv_details: Vec<Any> = Vec::with_capacity(10);
547
548        if let Some(retry_info) = details.retry_info {
549            conv_details.push(retry_info.into_any());
550        }
551
552        if let Some(debug_info) = details.debug_info {
553            conv_details.push(debug_info.into_any());
554        }
555
556        if let Some(quota_failure) = details.quota_failure {
557            conv_details.push(quota_failure.into_any());
558        }
559
560        if let Some(error_info) = details.error_info {
561            conv_details.push(error_info.into_any());
562        }
563
564        if let Some(precondition_failure) = details.precondition_failure {
565            conv_details.push(precondition_failure.into_any());
566        }
567
568        if let Some(bad_request) = details.bad_request {
569            conv_details.push(bad_request.into_any());
570        }
571
572        if let Some(request_info) = details.request_info {
573            conv_details.push(request_info.into_any());
574        }
575
576        if let Some(resource_info) = details.resource_info {
577            conv_details.push(resource_info.into_any());
578        }
579
580        if let Some(help) = details.help {
581            conv_details.push(help.into_any());
582        }
583
584        if let Some(localized_message) = details.localized_message {
585            conv_details.push(localized_message.into_any());
586        }
587
588        let status = pb::Status {
589            code: code as i32,
590            message: message.clone(),
591            details: conv_details,
592        };
593
594        Status::with_details(code, message, Bytes::from(status.encode_to_vec()))
595    }
596
597    fn with_error_details_vec(
598        code: Code,
599        message: impl Into<String>,
600        details: Vec<ErrorDetail>,
601    ) -> Self {
602        let message: String = message.into();
603
604        let mut conv_details: Vec<Any> = Vec::with_capacity(details.len());
605
606        for error_detail in details.into_iter() {
607            match error_detail {
608                ErrorDetail::RetryInfo(retry_info) => {
609                    conv_details.push(retry_info.into_any());
610                }
611                ErrorDetail::DebugInfo(debug_info) => {
612                    conv_details.push(debug_info.into_any());
613                }
614                ErrorDetail::QuotaFailure(quota_failure) => {
615                    conv_details.push(quota_failure.into_any());
616                }
617                ErrorDetail::ErrorInfo(error_info) => {
618                    conv_details.push(error_info.into_any());
619                }
620                ErrorDetail::PreconditionFailure(prec_failure) => {
621                    conv_details.push(prec_failure.into_any());
622                }
623                ErrorDetail::BadRequest(bad_req) => {
624                    conv_details.push(bad_req.into_any());
625                }
626                ErrorDetail::RequestInfo(req_info) => {
627                    conv_details.push(req_info.into_any());
628                }
629                ErrorDetail::ResourceInfo(res_info) => {
630                    conv_details.push(res_info.into_any());
631                }
632                ErrorDetail::Help(help) => {
633                    conv_details.push(help.into_any());
634                }
635                ErrorDetail::LocalizedMessage(loc_message) => {
636                    conv_details.push(loc_message.into_any());
637                }
638            }
639        }
640
641        let status = pb::Status {
642            code: code as i32,
643            message: message.clone(),
644            details: conv_details,
645        };
646
647        Status::with_details(code, message, Bytes::from(status.encode_to_vec()))
648    }
649
650    fn check_error_details(&self) -> Result<ErrorDetails, DecodeError> {
651        let status = pb::Status::decode(self.details())?;
652
653        let mut details = ErrorDetails::new();
654
655        for any in status.details.into_iter() {
656            match any.type_url.as_str() {
657                RetryInfo::TYPE_URL => {
658                    details.retry_info = Some(RetryInfo::from_any(any)?);
659                }
660                DebugInfo::TYPE_URL => {
661                    details.debug_info = Some(DebugInfo::from_any(any)?);
662                }
663                QuotaFailure::TYPE_URL => {
664                    details.quota_failure = Some(QuotaFailure::from_any(any)?);
665                }
666                ErrorInfo::TYPE_URL => {
667                    details.error_info = Some(ErrorInfo::from_any(any)?);
668                }
669                PreconditionFailure::TYPE_URL => {
670                    details.precondition_failure = Some(PreconditionFailure::from_any(any)?);
671                }
672                BadRequest::TYPE_URL => {
673                    details.bad_request = Some(BadRequest::from_any(any)?);
674                }
675                RequestInfo::TYPE_URL => {
676                    details.request_info = Some(RequestInfo::from_any(any)?);
677                }
678                ResourceInfo::TYPE_URL => {
679                    details.resource_info = Some(ResourceInfo::from_any(any)?);
680                }
681                Help::TYPE_URL => {
682                    details.help = Some(Help::from_any(any)?);
683                }
684                LocalizedMessage::TYPE_URL => {
685                    details.localized_message = Some(LocalizedMessage::from_any(any)?);
686                }
687                _ => {}
688            }
689        }
690
691        Ok(details)
692    }
693
694    fn get_error_details(&self) -> ErrorDetails {
695        self.check_error_details().unwrap_or(ErrorDetails::new())
696    }
697
698    fn check_error_details_vec(&self) -> Result<Vec<ErrorDetail>, DecodeError> {
699        let status = pb::Status::decode(self.details())?;
700
701        let mut details: Vec<ErrorDetail> = Vec::with_capacity(status.details.len());
702
703        for any in status.details.into_iter() {
704            match any.type_url.as_str() {
705                RetryInfo::TYPE_URL => {
706                    details.push(RetryInfo::from_any(any)?.into());
707                }
708                DebugInfo::TYPE_URL => {
709                    details.push(DebugInfo::from_any(any)?.into());
710                }
711                QuotaFailure::TYPE_URL => {
712                    details.push(QuotaFailure::from_any(any)?.into());
713                }
714                ErrorInfo::TYPE_URL => {
715                    details.push(ErrorInfo::from_any(any)?.into());
716                }
717                PreconditionFailure::TYPE_URL => {
718                    details.push(PreconditionFailure::from_any(any)?.into());
719                }
720                BadRequest::TYPE_URL => {
721                    details.push(BadRequest::from_any(any)?.into());
722                }
723                RequestInfo::TYPE_URL => {
724                    details.push(RequestInfo::from_any(any)?.into());
725                }
726                ResourceInfo::TYPE_URL => {
727                    details.push(ResourceInfo::from_any(any)?.into());
728                }
729                Help::TYPE_URL => {
730                    details.push(Help::from_any(any)?.into());
731                }
732                LocalizedMessage::TYPE_URL => {
733                    details.push(LocalizedMessage::from_any(any)?.into());
734                }
735                _ => {}
736            }
737        }
738
739        Ok(details)
740    }
741
742    fn get_error_details_vec(&self) -> Vec<ErrorDetail> {
743        self.check_error_details_vec().unwrap_or(Vec::new())
744    }
745
746    fn get_details_retry_info(&self) -> Option<RetryInfo> {
747        let status = pb::Status::decode(self.details()).ok()?;
748
749        for any in status.details.into_iter() {
750            match any.type_url.as_str() {
751                RetryInfo::TYPE_URL => match RetryInfo::from_any(any) {
752                    Ok(detail) => return Some(detail),
753                    Err(_) => {}
754                },
755                _ => {}
756            }
757        }
758
759        None
760    }
761
762    fn get_details_debug_info(&self) -> Option<DebugInfo> {
763        let status = pb::Status::decode(self.details()).ok()?;
764
765        for any in status.details.into_iter() {
766            match any.type_url.as_str() {
767                DebugInfo::TYPE_URL => match DebugInfo::from_any(any) {
768                    Ok(detail) => return Some(detail),
769                    Err(_) => {}
770                },
771                _ => {}
772            }
773        }
774
775        None
776    }
777
778    fn get_details_quota_failure(&self) -> Option<QuotaFailure> {
779        let status = pb::Status::decode(self.details()).ok()?;
780
781        for any in status.details.into_iter() {
782            match any.type_url.as_str() {
783                QuotaFailure::TYPE_URL => match QuotaFailure::from_any(any) {
784                    Ok(detail) => return Some(detail),
785                    Err(_) => {}
786                },
787                _ => {}
788            }
789        }
790
791        None
792    }
793
794    fn get_details_error_info(&self) -> Option<ErrorInfo> {
795        let status = pb::Status::decode(self.details()).ok()?;
796
797        for any in status.details.into_iter() {
798            match any.type_url.as_str() {
799                ErrorInfo::TYPE_URL => match ErrorInfo::from_any(any) {
800                    Ok(detail) => return Some(detail),
801                    Err(_) => {}
802                },
803                _ => {}
804            }
805        }
806
807        None
808    }
809
810    fn get_details_precondition_failure(&self) -> Option<PreconditionFailure> {
811        let status = pb::Status::decode(self.details()).ok()?;
812
813        for any in status.details.into_iter() {
814            match any.type_url.as_str() {
815                PreconditionFailure::TYPE_URL => match PreconditionFailure::from_any(any) {
816                    Ok(detail) => return Some(detail),
817                    Err(_) => {}
818                },
819                _ => {}
820            }
821        }
822
823        None
824    }
825
826    fn get_details_bad_request(&self) -> Option<BadRequest> {
827        let status = pb::Status::decode(self.details()).ok()?;
828
829        for any in status.details.into_iter() {
830            match any.type_url.as_str() {
831                BadRequest::TYPE_URL => match BadRequest::from_any(any) {
832                    Ok(detail) => return Some(detail),
833                    Err(_) => {}
834                },
835                _ => {}
836            }
837        }
838
839        None
840    }
841
842    fn get_details_request_info(&self) -> Option<RequestInfo> {
843        let status = pb::Status::decode(self.details()).ok()?;
844
845        for any in status.details.into_iter() {
846            match any.type_url.as_str() {
847                RequestInfo::TYPE_URL => match RequestInfo::from_any(any) {
848                    Ok(detail) => return Some(detail),
849                    Err(_) => {}
850                },
851                _ => {}
852            }
853        }
854
855        None
856    }
857
858    fn get_details_resource_info(&self) -> Option<ResourceInfo> {
859        let status = pb::Status::decode(self.details()).ok()?;
860
861        for any in status.details.into_iter() {
862            match any.type_url.as_str() {
863                ResourceInfo::TYPE_URL => match ResourceInfo::from_any(any) {
864                    Ok(detail) => return Some(detail),
865                    Err(_) => {}
866                },
867                _ => {}
868            }
869        }
870
871        None
872    }
873
874    fn get_details_help(&self) -> Option<Help> {
875        let status = pb::Status::decode(self.details()).ok()?;
876
877        for any in status.details.into_iter() {
878            match any.type_url.as_str() {
879                Help::TYPE_URL => match Help::from_any(any) {
880                    Ok(detail) => return Some(detail),
881                    Err(_) => {}
882                },
883                _ => {}
884            }
885        }
886
887        None
888    }
889
890    fn get_details_localized_message(&self) -> Option<LocalizedMessage> {
891        let status = pb::Status::decode(self.details()).ok()?;
892
893        for any in status.details.into_iter() {
894            match any.type_url.as_str() {
895                LocalizedMessage::TYPE_URL => match LocalizedMessage::from_any(any) {
896                    Ok(detail) => return Some(detail),
897                    Err(_) => {}
898                },
899                _ => {}
900            }
901        }
902
903        None
904    }
905}
906
907#[cfg(test)]
908mod tests {
909    use std::collections::HashMap;
910    use std::time::Duration;
911    use tonic::{Code, Status};
912
913    use super::{
914        BadRequest, DebugInfo, ErrorDetails, ErrorInfo, Help, LocalizedMessage,
915        PreconditionFailure, QuotaFailure, RequestInfo, ResourceInfo, RetryInfo, WithErrorDetails,
916    };
917
918    #[test]
919    fn gen_status_with_details() {
920        let mut metadata = HashMap::new();
921        metadata.insert("limitPerRequest".to_string(), "100".into());
922
923        let mut err_details = ErrorDetails::new();
924
925        err_details
926            .set_retry_info(Some(Duration::from_secs(5)))
927            .set_debug_info(
928                vec![
929                    "trace3".to_string(),
930                    "trace2".to_string(),
931                    "trace1".to_string(),
932                ],
933                "details",
934            )
935            .add_quota_failure_violation("clientip:<ip address>", "description")
936            .set_error_info("SOME_INFO", "example.local", metadata.clone())
937            .add_precondition_failure_violation("TOS", "example.local", "description")
938            .add_bad_request_violation("field", "description")
939            .set_request_info("request-id", "some-request-data")
940            .set_resource_info("resource-type", "resource-name", "owner", "description")
941            .add_help_link("link to resource", "resource.example.local")
942            .set_localized_message("en-US", "message for the user");
943
944        let fmt_details = format!("{:?}", err_details);
945
946        println!("{fmt_details}\n");
947
948        let err_details_vec = vec![
949            RetryInfo::new(Some(Duration::from_secs(5))).into(),
950            DebugInfo::new(
951                vec![
952                    "trace3".to_string(),
953                    "trace2".to_string(),
954                    "trace1".to_string(),
955                ],
956                "details",
957            )
958            .into(),
959            QuotaFailure::with_violation("clientip:<ip address>", "description").into(),
960            ErrorInfo::new("SOME_INFO", "example.local", metadata).into(),
961            PreconditionFailure::with_violation("TOS", "example.local", "description").into(),
962            BadRequest::with_violation("field", "description").into(),
963            RequestInfo::new("request-id", "some-request-data").into(),
964            ResourceInfo::new("resource-type", "resource-name", "owner", "description").into(),
965            Help::with_link("link to resource", "resource.example.local").into(),
966            LocalizedMessage::new("en-US", "message for the user").into(),
967        ];
968
969        let fmt_details_vec = format!("{:?}", err_details_vec);
970
971        println!("{fmt_details_vec}\n");
972
973        let status_from_struct = Status::with_error_details(
974            Code::InvalidArgument,
975            "error with bad request details",
976            err_details,
977        );
978
979        let fmt_status_with_details = format!("{:?}", status_from_struct);
980
981        println!("{:?}\n", fmt_status_with_details);
982
983        let status_from_vec = Status::with_error_details_vec(
984            Code::InvalidArgument,
985            "error with bad request details",
986            err_details_vec,
987        );
988
989        let fmt_status_with_details_vec = format!("{:?}", status_from_vec);
990
991        println!("{:?}\n", fmt_status_with_details_vec);
992
993        let ext_details = match status_from_vec.check_error_details() {
994            Ok(ext_details) => ext_details,
995            Err(err) => panic!(
996                "Error extracting details struct from status_from_vec: {:?}",
997                err
998            ),
999        };
1000
1001        let fmt_ext_details = format!("{:?}", ext_details);
1002
1003        println!("{:?}\n", ext_details.debug_info);
1004        println!("{fmt_ext_details}\n");
1005
1006        assert!(
1007            fmt_ext_details.eq(&fmt_details),
1008            "Extracted details struct differs from original details struct"
1009        );
1010
1011        let ext_details_vec = match status_from_struct.check_error_details_vec() {
1012            Ok(ext_details) => ext_details,
1013            Err(err) => panic!(
1014                "Error extracting details_vec from status_from_struct: {:?}",
1015                err
1016            ),
1017        };
1018
1019        let fmt_ext_details_vec = format!("{:?}", ext_details_vec);
1020
1021        println!("fmt_ext_details_vec: {:?}\n", fmt_ext_details_vec);
1022
1023        assert!(
1024            fmt_ext_details_vec.eq(&fmt_details_vec),
1025            "Extracted details vec differs from original details vec"
1026        );
1027    }
1028}