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