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