userver: userver/server/handlers/exceptions.hpp Source File
Loading...
Searching...
No Matches
exceptions.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/server/handlers/exceptions.hpp
4/// @brief Helpers for throwing exceptions
5
6#include <stdexcept>
7#include <string>
8#include <unordered_map>
9
10#include <userver/formats/json.hpp>
11#include <userver/utils/assert.hpp>
12#include <userver/utils/str_icase.hpp>
13
14USERVER_NAMESPACE_BEGIN
15
16namespace server::handlers {
17
18/// Enumeration that defines protocol-agnostic handler error condition codes,
19/// used by server::handlers::CustomHandlerException.
20///
21/// Specific error formats can derive various defaults from the this code, e.g.
22/// HTTP status code, JSON service error code, and default error message texts.
23///
24/// For example, to provide an HTTP specific error code that is not presented
25/// in this enum the HTTP code should be provided via
26/// server::http::CustomHandlerException construction with the required
27/// server::http::HttpStatus.
28enum class HandlerErrorCode {
29 kUnknownError, //!< kUnknownError This value is to map possibly unknown codes
30 //!< to a description, shouldn't be used in client code
31 kClientError, //!< kInvalidRequest Invalid request data
32 kRequestParseError, //!< kRequestParseError
33 kUnauthorized, //!< kUnauthorized Client is not authorized to execute this
34 //!< handler
35 kForbidden, //!< kForbidden Requested action is forbidden
36 kResourceNotFound, //!< kResourceNoFound Requested resource doesn't exist
37 kInvalidUsage, //!< kInvalidUsage Invalid usage of the handler, e.g.
38 //!< unsupported HTTP method
39 kNotAcceptable, //!< kNotAcceptable The server cannot produce response,
40 //!< acceptable by the client
41 kConflictState, //!< kConflictState Request cannot be completed due to
42 //!< conflict resource state
43 kPayloadTooLarge, //!< kPayloadTooLarge The payload for the request exceeded
44 //!< handler's settings
45 kTooManyRequests, //!< kTooManyRequests Request limit exceeded
46
47 // Client error codes are declared before the server side error to be
48 // mapped correctly to a protocol-specific error code!
49
50 kServerSideError, //!< kServerSideError An error occurred while processing
51 //!< the request
52 kBadGateway, //!< kBadGateway An error occurred while passing the request to
53 //!< another service
54
55 kGatewayTimeout, //!< kGatewayTimeout A timeout occurred while passing the
56 //!< request to another service
57 kUnsupportedMediaType, //!< kUnsupportedMediaType Content-Encoding or
58 //!< Content-Type is not supported
59
60 // Server error codes are declared after the client side error to be
61 // mapped correctly to a protocol-specific error code!
62};
63// When adding enumerators here ^, please also add mappings to the
64// implementation of:
65// - server::handler::GetCodeDescription,
66// - server::handler::GetFallbackServiceCode,
67// - server::http::GetHttpStatus.
68
69/// Hasher class for HandlerErrorCode
71 std::size_t operator()(HandlerErrorCode c) const { return static_cast<std::size_t>(c); }
72};
73
74using Headers = std::unordered_map<std::string, std::string, utils::StrIcaseHash, utils::StrIcaseEqual>;
75
76std::string_view GetCodeDescription(HandlerErrorCode);
77
78std::string_view GetFallbackServiceCode(HandlerErrorCode);
79
81 std::string body;
82};
83
85 std::string body;
86};
87
89 std::string body;
90};
91
93 Headers headers;
94};
95
96namespace impl {
97
98template <typename T>
99concept HasExternalBodyFormatted = requires {
100 {
101 std::bool_constant<T::kIsExternalBodyFormatted>{}
102 } -> std::same_as<std::true_type>;
103};
104
105template <typename T>
106concept HasInternalMessage = requires(const T& t) { t.GetInternalMessage(); };
107
108template <typename T>
109concept HasExternalBody = requires(const T& t) { t.GetExternalBody(); };
110
111template <typename T>
112concept HasServiceCode = requires(const T& t) { t.GetServiceCode(); };
113
114template <typename T>
115concept IsMessageBuilder = HasExternalBody<T>;
116
117template <typename T>
118struct MessageExtractor {
119 static_assert(
120 HasExternalBody<T>,
121 "Please use your message builder to build external body for "
122 "your error. See server::handlers::CustomHandlerException "
123 "for more info"
124 );
125
126 const T& builder;
127
128 constexpr bool IsExternalBodyFormatted() const { return impl::HasExternalBodyFormatted<T>; }
129
130 std::string GetServiceCode() const {
131 if constexpr (HasServiceCode<T>) {
132 return builder.GetServiceCode();
133 } else {
134 return std::string{};
135 }
136 }
137
138 std::string GetExternalBody() const { return builder.GetExternalBody(); }
139
140 std::string GetInternalMessage() const {
141 if constexpr (HasInternalMessage<T>) {
142 return builder.GetInternalMessage();
143 } else {
144 return std::string{};
145 }
146 }
147};
148
149struct CustomHandlerExceptionData final {
150 CustomHandlerExceptionData() = default;
151 CustomHandlerExceptionData(const CustomHandlerExceptionData&) = default;
152 CustomHandlerExceptionData(CustomHandlerExceptionData&&) noexcept = default;
153
154 template <typename... Args>
155 explicit CustomHandlerExceptionData(Args&&... args) {
156 (Apply(std::forward<Args>(args)), ...);
157 }
158
159 bool is_external_body_formatted{false};
161 std::string service_code;
162 std::string internal_message;
163 std::string external_body;
164 Headers headers;
165 formats::json::Value details;
166
167private:
168 void Apply(HandlerErrorCode l_handler_code) { handler_code = l_handler_code; }
169
170 void Apply(ServiceErrorCode l_service_code) { service_code = std::move(l_service_code.body); }
171
172 void Apply(InternalMessage l_internal_message) { internal_message = std::move(l_internal_message.body); }
173
174 void Apply(ExternalBody l_external_body) { external_body = std::move(l_external_body.body); }
175
176 void Apply(ExtraHeaders l_headers) { headers = std::move(l_headers.headers); }
177
178 void Apply(formats::json::Value l_details) { details = std::move(l_details); }
179
180 template <typename MessageBuilder>
181 void Apply(MessageBuilder&& builder) {
182 const impl::MessageExtractor<MessageBuilder> extractor{builder};
183 is_external_body_formatted = extractor.IsExternalBodyFormatted();
184 service_code = extractor.GetServiceCode();
185 external_body = extractor.GetExternalBody();
186 internal_message = extractor.GetInternalMessage();
187 }
188};
189
190} // namespace impl
191
192/// @brief The generic base class for handler exceptions. Thrown exceptions
193/// should typically derive from ExceptionWithCode instead.
194class CustomHandlerException : public std::runtime_error {
195public:
196 // Type aliases for usage in descendent classes that are in other namespaces
197 using HandlerErrorCode = handlers::HandlerErrorCode;
198 using ServiceErrorCode = handlers::ServiceErrorCode;
199 using InternalMessage = handlers::InternalMessage;
200 using ExternalBody = handlers::ExternalBody;
201 using ExtraHeaders = handlers::ExtraHeaders;
202
203 /// @brief Construct manually from a set of (mostly optional) arguments, which
204 /// describe the error details.
205 ///
206 /// ## Allowed arguments
207 ///
208 /// - HandlerErrorCode - defaults for other fields are derived from it
209 /// - ServiceErrorCode - used e.g. in JSON error format
210 /// - http::HttpStatus
211 /// - InternalMessage - for logs
212 /// - ExternalBody
213 /// - ExtraHeaders
214 /// - formats::json::Value - details for JSON error format
215 /// - a message builder, see below
216 ///
217 /// Example:
218 /// @snippet server/handlers/exceptions_test.cpp Sample direct construction
219 ///
220 /// ## Message builders
221 ///
222 /// A message builder is a class that satisfies the following requirements:
223 /// - provides a `GetExternalBody() const` function to form an external body
224 /// - has an optional `kIsExternalBodyFormatted` set to true
225 /// to forbid changing the external body
226 /// - has an optional `GetServiceCode() const` function to return machine
227 /// readable error code
228 /// - has an optional `GetInternalMessage() const` function to form an message
229 /// for logging an error
230 ///
231 /// Some message builder data can be overridden by explicitly passed args, if
232 /// these args go *after* the message builder.
233 ///
234 /// Example:
235 /// @snippet server/handlers/exceptions_test.cpp Sample custom error builder
236 template <typename... Args>
237 CustomHandlerException(HandlerErrorCode handler_code, Args&&... args)
238 : CustomHandlerException(impl::CustomHandlerExceptionData{handler_code, std::forward<Args>(args)...})
239 {}
240
241 /// @overload
242 explicit CustomHandlerException(HandlerErrorCode handler_code)
243 : CustomHandlerException(impl::CustomHandlerExceptionData{handler_code})
244 {}
245
246 /// @deprecated Use the variadic constructor above instead.
248 ServiceErrorCode service_code,
249 ExternalBody external_body,
250 InternalMessage internal_message,
251 HandlerErrorCode handler_code,
252 ExtraHeaders headers = {},
253 formats::json::Value details = {}
254 )
255 : CustomHandlerException(impl::CustomHandlerExceptionData{
256 std::move(service_code),
257 std::move(external_body),
258 std::move(internal_message),
259 handler_code,
260 std::move(headers),
261 std::move(details)
262 })
263 {}
264
265 /// @deprecated Use the variadic constructor above instead.
266 template <typename MessageBuilder>
267 requires impl::IsMessageBuilder<MessageBuilder>
268 CustomHandlerException(MessageBuilder&& builder, HandlerErrorCode handler_code)
269 : CustomHandlerException(impl::CustomHandlerExceptionData{std::forward<MessageBuilder>(builder), handler_code})
270 {}
271
272 /// @cond
273 explicit CustomHandlerException(impl::CustomHandlerExceptionData&& data)
274 : runtime_error(
275 data.internal_message.empty() ? std::string{GetCodeDescription(data.handler_code)} : data.internal_message
276 ),
277 data_(std::move(data))
278 {
280 data_.details.IsNull() || data_.details.IsObject(),
281 "The details JSON value must be either null or an object"
282 );
283 }
284 /// @endcond
285
286 HandlerErrorCode GetCode() const { return data_.handler_code; }
287
288 const std::string& GetServiceCode() const { return data_.service_code; }
289
290 bool IsExternalErrorBodyFormatted() const { return data_.is_external_body_formatted; }
291
292 const std::string& GetExternalErrorBody() const { return data_.external_body; }
293
294 const formats::json::Value& GetDetails() const { return data_.details; }
295
296 const Headers& GetExtraHeaders() const { return data_.headers; }
297
298private:
299 impl::CustomHandlerExceptionData data_;
300};
301
302/// @brief Base class for handler exceptions. For common HandlerErrorCode values
303/// you can use more specific exception classes below. For less common
304/// HandlerErrorCode values, this class can be used directly.
305template <HandlerErrorCode Code>
307public:
308 constexpr static HandlerErrorCode kDefaultCode = Code;
309 using BaseType = ExceptionWithCode<kDefaultCode>;
310
311 ExceptionWithCode(const ExceptionWithCode<Code>&) = default;
312 ExceptionWithCode(ExceptionWithCode<Code>&&) noexcept = default;
313
314 /// @see CustomHandlerException::CustomHandlerException for allowed args
315 template <typename... Args>
316 explicit ExceptionWithCode(Args&&... args)
317 : CustomHandlerException(kDefaultCode, std::forward<Args>(args)...)
318 {}
319};
320
321/// Exception class for situations when request preconditions have failed.
322/// Corresponds to HTTP code 400.
324public:
325 using BaseType::BaseType;
326};
327
328/// Exception class for situations when a request cannot be processed
329/// due to parsing errors. Corresponds to HTTP code 400.
331public:
332 using BaseType::BaseType;
333};
334
335/// Exception class for situations when a request does not have valid
336/// authentication credentials. Corresponds to HTTP code 401.
338public:
339 using BaseType::BaseType;
340};
341
342/// Exception class for situations when some requested entity could not be
343/// accessed, because it does not exist. Corresponds to HTTP code 404.
345public:
346 using BaseType::BaseType;
347};
348
349/// Exception class for situations when a conflict happens, e.g. the client
350/// attempts to create an entity that already exists. Corresponds to
351/// HTTP code 409.
353public:
354 using BaseType::BaseType;
355};
356
357/// Exception class for situations when an exception occurred while processing
358/// the request. Corresponds to HTTP code 500.
360public:
361 using BaseType::BaseType;
362};
363
364} // namespace server::handlers
365
366USERVER_NAMESPACE_END