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