userver: userver/ugrpc/rich_status.hpp Source File
Loading...
Searching...
No Matches
rich_status.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/ugrpc/rich_status.hpp
4/// @brief userver wrapper for `google::rpc::Status`
5
6#include <optional>
7
8#include <google/protobuf/any.pb.h>
9#include <google/protobuf/message.h>
10#include <google/rpc/error_details.pb.h>
11#include <google/rpc/status.pb.h>
12#include <grpcpp/support/status.h>
13
14USERVER_NAMESPACE_BEGIN
15
16namespace ugrpc {
17
18/// @brief A wrapper around `google::rpc::Status` that provides a convenient API
19/// for creating and managing gRPC status responses with rich error details.
20///
21/// Documentation: @see @ref scripts/docs/en/userver/grpc/rich_status.md
22///
23/// `RichStatus` allows you to create gRPC status responses with structured error
24/// information conforming to the Google RPC error model. It supports adding
25/// multiple error details of various types to provide comprehensive error
26/// information to clients.
27///
28/// @see https://google.aip.dev/193
29/// @see https://grpc.io/docs/guides/error/
30/// @see @ref ugrpc::ErrorInfo
31/// @see @ref ugrpc::RetryInfo
32/// @see @ref ugrpc::DebugInfo
33/// @see @ref ugrpc::QuotaFailure
34/// @see @ref ugrpc::PreconditionFailure
35/// @see @ref ugrpc::BadRequest
36/// @see @ref ugrpc::RequestInfo
37/// @see @ref ugrpc::ResourceInfo
38/// @see @ref ugrpc::Help
39/// @see @ref ugrpc::LocalizedMessage
40///
41/// ## Example usage:
42/// @snippet grpc/tests/detailed_error_test.cpp rich_status_usage
43class RichStatus final {
44public:
45 /// @brief Constructs an `OK` status with no error details.
46 RichStatus() = default;
47
48 /// @brief Constructs a `RichStatus` from a `grpc::Status`.
49 /// @param grpc_status The gRPC status to convert. If it contains serialized
50 /// `google::rpc::Status` in `error_details()`, it will be
51 /// parsed and used.
52 explicit RichStatus(const grpc::Status& grpc_status);
53
54 /// @brief Constructs a `RichStatus` with the given code, message, and details.
55 /// @param code The gRPC status code
56 /// @param message The error message
57 /// @param details Error details conforming to `google.rpc.error_details` types.
58 /// Must have a `ToGoogleErrorDetail()` method.
59 template <typename... TDetails>
60 RichStatus(grpc::StatusCode code, std::string message, TDetails&&... details);
61
62 /// @brief Adds an error detail to the status.
63 /// @param detail The error detail to add. Must have a `ToGoogleErrorDetail()` method.
64 /// @return Reference to this `RichStatus` for method chaining.
65 template <typename TDetail>
66 RichStatus& AddDetail(TDetail&& detail);
67
68 /// @brief Attempts to extract a specific rich error detail from `google::rpc::Status`
69 /// @return The extracted rich error detail if found, `std::nullopt` otherwise
70 ///
71 /// According to AIP-193 (https://google.aip.dev/193), each status
72 /// should contain at most one detail of each type. If multiple details
73 /// of the same type exist (violating AIP-193), this function will
74 /// return only the first encountered instance.
75 ///
76 /// ## Example usage:
77 /// @snippet grpc/tests/detailed_error_test.cpp try_get_rich_error_detail
78 template <typename TRichErrorDetail>
79 [[nodiscard]] static std::optional<TRichErrorDetail> TryGetDetail(const google::rpc::Status& status);
80
81 [[nodiscard]] const google::rpc::Status& GetGoogleStatus() const& { return google_status_; }
82 google::rpc::Status GetGoogleStatus() && { return std::move(google_status_); }
83
84 /// @brief Converts this `RichStatus` to a `grpc::Status`.
85 /// @return A `grpc::Status` with serialized error details.
86 [[nodiscard]] grpc::Status ToGrpcStatus() const;
87
88 [[nodiscard]] explicit operator grpc::Status() const { return ToGrpcStatus(); }
89
90private:
91 google::rpc::Status google_status_;
92};
93
94/// @brief Provides structured error information about the cause of an error.
95///
96/// @see https://google.aip.dev/193
97/// @see https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
98/// @see @ref ugrpc::RichStatus
99///
100/// Use this detail to provide a machine-readable error reason, domain, and
101/// additional metadata. This is useful for error handling logic on the client side.
102///
103/// Any request-specific information which contributes to the `Status.message` or `LocalizedMessage.message` messages
104/// must be represented within metadata. This practice is critical so that machine actors do not need to parse error
105/// messages to extract information.
106///
107/// ## Example usage:
108/// @snippet grpc/tests/rich_status_test.cpp error_info_example
109struct ErrorInfo {
110 /// @brief The reason of the error (e.g., "INVALID_TOKEN", "SERVICE_DISABLED").
111 ///
112 /// The reason field is a short snake_case description of the cause of the error. Error reasons are unique within a
113 /// particular domain of errors. The reason must be at most 63 characters and match a regular expression of
114 /// `[A-Z][A-Z0-9_]+[A-Z0-9]`. (This is UPPER_SNAKE_CASE, without leading or trailing underscores, and without
115 /// leading digits.)
116 std::string reason;
117
118 /// @brief The logical grouping to which the error belongs (e.g., "auth.example.com").
119 std::string domain;
120
121 /// @brief Additional structured details about the error.
122 std::unordered_map<std::string, std::string> metadata;
123
124 google::rpc::ErrorInfo ToGoogleErrorDetail() const&;
125 google::rpc::ErrorInfo ToGoogleErrorDetail() &&;
126
127 static std::optional<ErrorInfo> TryUnpack(const google::protobuf::Any& any);
128};
129
130/// @brief Describes when the client can retry a failed request.
131///
132/// @see https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
133/// @see @ref ugrpc::RichStatus
134///
135/// ## Example usage:
136/// @snippet grpc/tests/rich_status_test.cpp retry_info_example
137struct RetryInfo {
138 /// @brief Clients should wait at least this long between retrying the same request.
139 std::chrono::nanoseconds retry_delay;
140
141 google::rpc::RetryInfo ToGoogleErrorDetail() const;
142
143 static std::optional<RetryInfo> TryUnpack(const google::protobuf::Any& any);
144};
145
146/// @brief Provides debugging information such as stack traces.
147///
148/// @see @ref ugrpc::RichStatus
149///
150/// ## Example usage:
151/// @snippet grpc/tests/rich_status_test.cpp debug_info_example
152struct DebugInfo {
153 std::vector<std::string> stack_entries;
154 std::string detail;
155
156 google::rpc::DebugInfo ToGoogleErrorDetail() const&;
157 google::rpc::DebugInfo ToGoogleErrorDetail() &&;
158
159 static std::optional<DebugInfo> TryUnpack(const google::protobuf::Any& any);
160};
161
162/// @brief Describes a single quota violation.
163///
164/// @see @ref ugrpc::QuotaFailure
166 /// @brief The subject on which the quota check failed.
167 ///
168 /// Example: "clientip:<ip address>", "project:<project id>"
169 std::string subject;
170
171 /// @brief A description of how the quota check failed.
172 std::string description;
173};
174
175/// @brief Describes quota violations that caused the request to fail.
176///
177/// @see https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
178/// @see @ref ugrpc::RichStatus
179///
180/// ## Example usage:
181/// @snippet grpc/tests/rich_status_test.cpp quota_failure_example
183 std::vector<QuotaViolation> violations;
184
185 google::rpc::QuotaFailure ToGoogleErrorDetail() const&;
186 google::rpc::QuotaFailure ToGoogleErrorDetail() &&;
187
188 static std::optional<QuotaFailure> TryUnpack(const google::protobuf::Any& any);
189};
190
191/// @brief Describes a single precondition violation.
192///
193/// @see @ref ugrpc::PreconditionFailure
195 /// @brief The type of precondition that failed (e.g., "TOS", "AGE").
196 ///
197 /// Google recommends using a service-specific enum type to define the supported
198 /// precondition violation subjects. For example, "TOS" for "Terms of Service violation".
199 std::string type;
200
201 /// @brief The subject on which the precondition failed (e.g., "user:123").
202 ///
203 /// For example, "google.com/cloud" relative to the "TOS" type would indicate
204 /// which terms of service is being referenced.
205 std::string subject;
206
207 /// @brief A description of how the precondition failed.
208 ///
209 /// Developers can use this description to understand how to fix the failure.
210 /// For Example usage: "Terms of service not accepted".
211 std::string description;
212};
213
214/// @brief Describes preconditions that failed during request processing.
215///
216/// @see https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
217/// @see @ref ugrpc::RichStatus
218///
219/// For example, if an RPC failed because it required the Terms of Service to be
220/// acknowledged, it could list the terms of service violation in the
221/// PreconditionFailure message.
222///
223/// ## Example usage:
224/// @snippet grpc/tests/rich_status_test.cpp precondition_failure_example
226 /// @brief The list of precondition violations.
228
229 google::rpc::PreconditionFailure ToGoogleErrorDetail() const&;
230 google::rpc::PreconditionFailure ToGoogleErrorDetail() &&;
231
232 static std::optional<PreconditionFailure> TryUnpack(const google::protobuf::Any& any);
233};
234
235/// @brief Describes a single field validation error.
236///
237/// @see @ref ugrpc::BadRequest
239 /// @brief The field path (e.g., "user.email", "address.postal_code").
240 std::string field;
241
242 /// @brief A description of why the field is invalid.
243 std::string description;
244};
245
246/// @brief Describes violations in a client request. This error type focuses on the
247/// syntactic aspects of the request.
248///
249/// @see https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
250/// @see @ref ugrpc::RichStatus
251///
252/// ## Example usage:
253/// @snippet grpc/tests/rich_status_test.cpp bad_request_example
255 std::vector<FieldViolation> field_violations;
256
257 google::rpc::BadRequest ToGoogleErrorDetail() const&;
258 google::rpc::BadRequest ToGoogleErrorDetail() &&;
259
260 static std::optional<BadRequest> TryUnpack(const google::protobuf::Any& any);
261};
262
263/// @brief Contains metadata about the request for debugging and logging.
264///
265/// @see https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
266/// @see @ref ugrpc::RichStatus
267///
268///
269/// Use this detail to provide request identification information that can
270/// help with debugging and tracking requests across services.
271///
272/// ## Example usage:
273/// @snippet grpc/tests/rich_status_test.cpp request_info_example
275 std::string request_id;
276 std::string serving_data;
277
278 google::rpc::RequestInfo ToGoogleErrorDetail() const&;
279 google::rpc::RequestInfo ToGoogleErrorDetail() &&;
280
281 static std::optional<RequestInfo> TryUnpack(const google::protobuf::Any& any);
282};
283
284/// @brief Provides information about a resource that is related to the error.
285///
286/// @see https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
287/// @see @ref ugrpc::RichStatus
288///
289/// Use this detail to describe a resource that caused the error or is
290/// affected by the error. Commonly used with `NOT_FOUND`, `ALREADY_EXISTS`,
291/// or `PERMISSION_DENIED` status codes.
292///
293/// ## Example usage:
294/// @snippet grpc/tests/rich_status_test.cpp resource_info_example
296 /// @brief The type of resource (e.g., "storage.bucket", "compute.instance").
297 std::string resource_type;
298
299 /// @brief The name/identifier of the resource.
300 std::string resource_name;
301
302 /// @brief The owner of the resource.
303 std::string owner;
304
305 /// @brief The description of the error that is encountered when accessing this resource.
306 std::string description;
307
308 google::rpc::ResourceInfo ToGoogleErrorDetail() const&;
309 google::rpc::ResourceInfo ToGoogleErrorDetail() &&;
310
311 static std::optional<ResourceInfo> TryUnpack(const google::protobuf::Any& any);
312};
313
314/// @brief Describes a single help link.
315///
316/// @see @ref ugrpc::Help
317struct HelpLink {
318 std::string description;
319 std::string url;
320};
321
322/// @brief Provides links to documentation and help resources.
323///
324/// @see https://google.aip.dev/193
325/// @see https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
326/// @see https://nda.ya.ru/t/PmNiceWp7NKDXb
327/// @see @ref ugrpc::RichStatus
328///
329/// Use this detail to direct users to relevant documentation, FAQs,
330/// or support resources that can help them resolve the error.
331///
332/// ## Example usage:
333/// @snippet grpc/tests/rich_status_test.cpp help_example
334struct Help {
335 /// @brief The list of help links.
336 std::vector<HelpLink> links;
337
338 google::rpc::Help ToGoogleErrorDetail() const&;
339 google::rpc::Help ToGoogleErrorDetail() &&;
340
341 static std::optional<Help> TryUnpack(const google::protobuf::Any& any);
342};
343
344/// @brief Provides a localized error message that is safe to return to the user.
345///
346/// @see https://google.aip.dev/193
347/// @see https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
348/// @see @ref ugrpc::RichStatus
349///
350/// Provides a localized error message that is safe to return to the user
351/// which can be attached to an RPC error.
352///
353/// The `LocalizedMessage` payload should contain the complete resolution to the error. If more information is needed
354/// than can reasonably fit in this payload, then additional resolution information must be provided in a @ref Help
355/// payload.
356///
357/// ## Example usage:
358/// @snippet grpc/tests/rich_status_test.cpp localized_message_example
360 /// @brief The locale (e.g., "en-US", "ru-RU", "ja-JP").
361 std::string locale;
362
363 /// @brief The localized error message.
364 ///
365 /// This should include a brief description of the error and a call to action to resolve the error. The message
366 /// should include contextual information to make the message as specific as possible. Any contextual information in
367 /// the message must be included in @ref ErrorInfo.metadata.
368 std::string message;
369
370 google::rpc::LocalizedMessage ToGoogleErrorDetail() const&;
371 google::rpc::LocalizedMessage ToGoogleErrorDetail() &&;
372
373 static std::optional<LocalizedMessage> TryUnpack(const google::protobuf::Any& any);
374};
375
376template <typename... TDetails>
377RichStatus::RichStatus(grpc::StatusCode code, std::string message, TDetails&&... details) {
378 google_status_.set_code(static_cast<int>(code));
379 google_status_.set_message(std::move(message));
380 (AddDetail(std::forward<TDetails>(details)), ...);
381}
382
383template <typename TDetail>
384RichStatus& RichStatus::AddDetail(TDetail&& detail) {
385 const auto pb_google_detail = std::forward<TDetail>(detail).ToGoogleErrorDetail();
386 google_status_.add_details()->PackFrom(pb_google_detail);
387 return *this;
388}
389
390template <typename TRichErrorDetail>
391std::optional<TRichErrorDetail> RichStatus::TryGetDetail(const google::rpc::Status& status) {
392 for (const auto& detail : status.details()) {
393 const auto rich_error_detail_opt = TRichErrorDetail::TryUnpack(detail);
394 if (rich_error_detail_opt) {
395 return *rich_error_detail_opt;
396 }
397 }
398 return std::nullopt;
399}
400
401} // namespace ugrpc
402
403USERVER_NAMESPACE_END