xref: /tonic/tonic-types/src/lib.rs (revision 8ee85fc4)
1 //! A collection of useful protobuf types that can be used with `tonic`.
2 //!
3 //! This crate also introduces the [`StatusExt`] trait and implements it in
4 //! [`tonic::Status`], allowing the implementation of the
5 //! [gRPC Richer Error Model] with [`tonic`] in a convenient way.
6 //!
7 //! # Usage
8 //!
9 //! Useful protobuf types are available through the [`pb`] module. They can be
10 //! imported and worked with directly.
11 //!
12 //! The [`StatusExt`] trait adds associated functions to [`tonic::Status`] that
13 //! can be used on the server side to create a status with error details, which
14 //! can then be returned to gRPC clients. Moreover, the trait also adds methods
15 //! to [`tonic::Status`] that can be used by a tonic client to extract error
16 //! details, and handle them with ease.
17 //!
18 //! # Getting Started
19 //!
20 //! ```toml
21 //! [dependencies]
22 //! tonic = <tonic-version>
23 //! tonic-types = <tonic-types-version>
24 //! ```
25 //!
26 //! # Examples
27 //!
28 //! The examples bellow cover a basic use case of the [gRPC Richer Error Model].
29 //! More complete server and client implementations are provided in the
30 //! **Richer Error example**, located in the main repo [examples] directory.
31 //!
32 //! ## Server Side: Generating [`tonic::Status`] with an [`ErrorDetails`] struct
33 //!
34 //! ```
35 //! use tonic::{Code, Status};
36 //! use tonic_types::{ErrorDetails, StatusExt};
37 //!
38 //! # async fn endpoint() -> Result<tonic::Response<()>, Status> {
39 //! // ...
40 //! // Inside a gRPC server endpoint that returns `Result<Response<T>, Status>`
41 //!
42 //! // Create empty `ErrorDetails` struct
43 //! let mut err_details = ErrorDetails::new();
44 //!
45 //! // Add error details conditionally
46 //! # let some_condition = true;
47 //! if some_condition {
48 //!     err_details.add_bad_request_violation(
49 //!         "field_a",
50 //!         "description of why the field_a is invalid"
51 //!     );
52 //! }
53 //!
54 //! # let other_condition = true;
55 //! if other_condition {
56 //!     err_details.add_bad_request_violation(
57 //!         "field_b",
58 //!         "description of why the field_b is invalid",
59 //!     );
60 //! }
61 //!
62 //! // Check if any error details were set and return error status if so
63 //! if err_details.has_bad_request_violations() {
64 //!     // Add additional error details if necessary
65 //!     err_details
66 //!         .add_help_link("description of link", "https://resource.example.local")
67 //!         .set_localized_message("en-US", "message for the user");
68 //!
69 //!     let status = Status::with_error_details(
70 //!         Code::InvalidArgument,
71 //!         "bad request",
72 //!         err_details,
73 //!     );
74 //!     return Err(status);
75 //! }
76 //!
77 //! // Handle valid request
78 //! // ...
79 //! # Ok(tonic::Response::new(()))
80 //! # }
81 //! ```
82 //!
83 //! ## Client Side: Extracting an [`ErrorDetails`] struct from `tonic::Status`
84 //!
85 //! ```
86 //! use tonic::{Response, Status};
87 //! use tonic_types::StatusExt;
88 //!
89 //! // ...
90 //! // Where `req_result` was returned by a gRPC client endpoint method
91 //! fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
92 //!     match req_result {
93 //!         Ok(response) => {
94 //!             // Handle successful response
95 //!         },
96 //!         Err(status) => {
97 //!             let err_details = status.get_error_details();
98 //!             if let Some(bad_request) = err_details.bad_request() {
99 //!                 // Handle bad_request details
100 //!             }
101 //!             if let Some(help) = err_details.help() {
102 //!                 // Handle help details
103 //!             }
104 //!             if let Some(localized_message) = err_details.localized_message() {
105 //!                 // Handle localized_message details
106 //!             }
107 //!         }
108 //!     };
109 //! }
110 //! ```
111 //!
112 //! # Working with different error message types
113 //!
114 //! Multiple examples are provided at the [`ErrorDetails`] doc. Instructions
115 //! about how to use the fields of the standard error message types correctly
116 //! are provided at [error_details.proto].
117 //!
118 //! # Alternative `tonic::Status` associated functions and methods
119 //!
120 //! In the [`StatusExt`] doc, an alternative way of interacting with
121 //! [`tonic::Status`] is presented, using vectors of error details structs
122 //! wrapped with the [`ErrorDetail`] enum. This approach can provide more
123 //! control over the vector of standard error messages that will be generated or
124 //! that was received, if necessary. To see how to adopt this approach, please
125 //! check the [`StatusExt::with_error_details_vec`] and
126 //! [`StatusExt::get_error_details_vec`] docs, and also the main repo's
127 //! [Richer Error example] directory.
128 //!
129 //! Besides that, multiple examples with alternative error details extraction
130 //! methods are provided in the [`StatusExt`] doc, which can be specially
131 //! useful if only one type of standard error message is being handled by the
132 //! client. For example, using [`StatusExt::get_details_bad_request`] is a
133 //! more direct way of extracting a [`BadRequest`] error message from
134 //! [`tonic::Status`].
135 //!
136 //! [`tonic::Status`]: https://docs.rs/tonic/latest/tonic/struct.Status.html
137 //! [`tonic`]: https://docs.rs/tonic/latest/tonic/
138 //! [gRPC Richer Error Model]: https://www.grpc.io/docs/guides/error/
139 //! [examples]: https://github.com/hyperium/tonic/tree/master/examples
140 //! [error_details.proto]: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
141 //! [Richer Error example]: https://github.com/hyperium/tonic/tree/master/examples/src/richer-error
142 
143 #![doc(
144     html_logo_url = "https://raw.githubusercontent.com/tokio-rs/website/master/public/img/icons/tonic.svg"
145 )]
146 #![doc(issue_tracker_base_url = "https://github.com/hyperium/tonic/issues/")]
147 
148 mod generated {
149     #![allow(unreachable_pub)]
150     #![allow(rustdoc::invalid_html_tags)]
151     #[rustfmt::skip]
152     pub mod google_rpc;
153     #[rustfmt::skip]
154     pub mod types_fds;
155 
156     pub use types_fds::FILE_DESCRIPTOR_SET;
157 
158     #[cfg(test)]
159     mod tests {
160         use super::FILE_DESCRIPTOR_SET;
161         use prost::Message as _;
162 
163         #[test]
file_descriptor_set_is_valid()164         fn file_descriptor_set_is_valid() {
165             prost_types::FileDescriptorSet::decode(FILE_DESCRIPTOR_SET).unwrap();
166         }
167     }
168 }
169 
170 /// Useful protobuf types
171 pub mod pb {
172     pub use crate::generated::{google_rpc::*, FILE_DESCRIPTOR_SET};
173 }
174 
175 pub use pb::Status;
176 
177 mod richer_error;
178 
179 pub use richer_error::{
180     BadRequest, DebugInfo, ErrorDetail, ErrorDetails, ErrorInfo, FieldViolation, Help, HelpLink,
181     LocalizedMessage, PreconditionFailure, PreconditionViolation, QuotaFailure, QuotaViolation,
182     RequestInfo, ResourceInfo, RetryInfo, RpcStatusExt, StatusExt,
183 };
184 
185 mod sealed {
186     pub trait Sealed {}
187 }
188