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}