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 authorized 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 after 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 { return static_cast<std::size_t>(c); }
74};
75
76using Headers = std::unordered_map<std::string, std::string, utils::StrIcaseHash, utils::StrIcaseEqual>;
77
78std::string_view GetCodeDescription(HandlerErrorCode);
79
80std::string_view GetFallbackServiceCode(HandlerErrorCode);
81
83 std::string body;
84};
85
87 std::string body;
88};
89
91 std::string body;
92};
93
95 Headers headers;
96};
97
98namespace impl {
99
100template <typename T>
101using IsExternalBodyFormatted = std::bool_constant<T::kIsExternalBodyFormatted>;
102
103template <typename T>
104using HasServiceCode = decltype(std::declval<const T&>().GetServiceCode());
105
106template <typename T>
107using HasInternalMessage = decltype(std::declval<const T&>().GetInternalMessage());
108
109template <typename T>
110inline constexpr bool kHasInternalMessage = meta::kIsDetected<HasInternalMessage, T>;
111
112template <typename T>
113using HasExternalBody = decltype(std::declval<const T&>().GetExternalBody());
114
115template <typename T>
116inline constexpr bool kHasExternalBody = meta::kIsDetected<HasExternalBody, T>;
117
118template <typename T>
119inline constexpr bool kIsMessageBuilder = kHasExternalBody<T>;
120
121template <typename T>
122struct MessageExtractor {
123 static_assert(
124 meta::kIsDetected<HasExternalBody, T>,
125 "Please use your message builder to build external body for "
126 "your error. See server::handlers::CustomHandlerException "
127 "for more info"
128 );
129
130 const T& builder;
131
132 constexpr bool IsExternalBodyFormatted() const {
133 return meta::DetectedOr<std::false_type, impl::IsExternalBodyFormatted, T>::value;
134 }
135
136 std::string GetServiceCode() const {
137 if constexpr (meta::kIsDetected<HasServiceCode, T>) {
138 return builder.GetServiceCode();
139 } else {
140 return std::string{};
141 }
142 }
143
144 std::string GetExternalBody() const { return builder.GetExternalBody(); }
145
146 std::string GetInternalMessage() const {
147 if constexpr (kHasInternalMessage<T>) {
148 return builder.GetInternalMessage();
149 } else {
150 return std::string{};
151 }
152 }
153};
154
155struct CustomHandlerExceptionData final {
156 CustomHandlerExceptionData() = default;
157 CustomHandlerExceptionData(const CustomHandlerExceptionData&) = default;
158 CustomHandlerExceptionData(CustomHandlerExceptionData&&) noexcept = default;
159
160 template <typename... Args>
161 explicit CustomHandlerExceptionData(Args&&... args) {
162 (Apply(std::forward<Args>(args)), ...);
163 }
164
165 bool is_external_body_formatted{false};
167 std::string service_code;
168 std::string internal_message;
169 std::string external_body;
170 Headers headers;
171 formats::json::Value details;
172
173private:
174 void Apply(HandlerErrorCode handler_code_) { handler_code = handler_code_; }
175
176 void Apply(ServiceErrorCode service_code_) { service_code = std::move(service_code_.body); }
177
178 void Apply(InternalMessage internal_message_) { internal_message = std::move(internal_message_.body); }
179
180 void Apply(ExternalBody external_body_) { external_body = std::move(external_body_.body); }
181
182 void Apply(ExtraHeaders headers_) { headers = std::move(headers_.headers); }
183
184 void Apply(formats::json::Value details_) { details = std::move(details_); }
185
186 template <typename MessageBuilder>
187 void Apply(MessageBuilder&& builder) {
188 impl::MessageExtractor<MessageBuilder> extractor{builder};
189 is_external_body_formatted = extractor.IsExternalBodyFormatted();
190 service_code = extractor.GetServiceCode();
191 external_body = extractor.GetExternalBody();
192 internal_message = extractor.GetInternalMessage();
193 }
194};
195
196} // namespace impl
197
198/// @brief The generic base class for handler exceptions. Thrown exceptions
199/// should typically derive from ExceptionWithCode instead.
200class CustomHandlerException : public std::runtime_error {
201public:
202 // Type aliases for usage in descendent classes that are in other namespaces
203 using HandlerErrorCode = handlers::HandlerErrorCode;
204 using ServiceErrorCode = handlers::ServiceErrorCode;
205 using InternalMessage = handlers::InternalMessage;
206 using ExternalBody = handlers::ExternalBody;
207 using ExtraHeaders = handlers::ExtraHeaders;
208
209 /// @brief Construct manually from a set of (mostly optional) arguments, which
210 /// describe the error details.
211 ///
212 /// ## Allowed arguments
213 ///
214 /// - HandlerErrorCode - defaults for other fields are derived from it
215 /// - ServiceErrorCode - used e.g. in JSON error format
216 /// - http::HttpStatus
217 /// - InternalMessage - for logs
218 /// - ExternalBody
219 /// - ExtraHeaders
220 /// - formats::json::Value - details for JSON error format
221 /// - a message builder, see below
222 ///
223 /// Example:
224 /// @snippet server/handlers/exceptions_test.cpp Sample direct construction
225 ///
226 /// ## Message builders
227 ///
228 /// A message builder is a class that satisfies the following requirements:
229 /// - provides a `GetExternalBody() const` function to form an external body
230 /// - has an optional `kIsExternalBodyFormatted` set to true
231 /// to forbid changing the external body
232 /// - has an optional `GetServiceCode() const` function to return machine
233 /// readable error code
234 /// - has an optional `GetInternalMessage() const` function to form an message
235 /// for logging an error
236 ///
237 /// Some message builder data can be overridden by explicitly passed args, if
238 /// these args go *after* the message builder.
239 ///
240 /// Example:
241 /// @snippet server/handlers/exceptions_test.cpp Sample custom error builder
242 template <typename... Args>
243 CustomHandlerException(HandlerErrorCode handler_code, Args&&... args)
244 : CustomHandlerException(impl::CustomHandlerExceptionData{handler_code, std::forward<Args>(args)...}) {}
245
246 /// @overload
247 explicit CustomHandlerException(HandlerErrorCode handler_code)
248 : CustomHandlerException(impl::CustomHandlerExceptionData{handler_code}) {}
249
250 /// @deprecated Use the variadic constructor above instead.
252 ServiceErrorCode service_code,
253 ExternalBody external_body,
254 InternalMessage internal_message,
255 HandlerErrorCode handler_code,
256 ExtraHeaders headers = {},
257 formats::json::Value details = {}
258 )
264 std::move(headers),
265 std::move(details)}) {}
266
267 /// @deprecated Use the variadic constructor above instead.
268 template <typename MessageBuilder, typename = std::enable_if_t<impl::kIsMessageBuilder<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)) {
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) : CustomHandlerException(kDefaultCode, std::forward<Args>(args)...) {}
317};
318
319/// Exception class for situations when request preconditions have failed.
320/// Corresponds to HTTP code 400.
322public:
323 using BaseType::BaseType;
324};
325
326/// Exception class for situations when a request cannot be processed
327/// due to parsing errors. Corresponds to HTTP code 400.
329public:
330 using BaseType::BaseType;
331};
332
333/// Exception class for situations when a request does not have valid
334/// authentication credentials. Corresponds to HTTP code 401.
336public:
337 using BaseType::BaseType;
338};
339
340/// Exception class for situations when some requested entity could not be
341/// accessed, because it does not exist. Corresponds to HTTP code 404.
343public:
344 using BaseType::BaseType;
345};
346
347/// Exception class for situations when a conflict happens, e.g. the client
348/// attempts to create an entity that already exists. Corresponds to
349/// HTTP code 409.
351public:
352 using BaseType::BaseType;
353};
354
355/// Exception class for situations when an exception occurred while processing
356/// the request. Corresponds to HTTP code 500.
358public:
359 using BaseType::BaseType;
360};
361
362} // namespace server::handlers
363
364USERVER_NAMESPACE_END