1 #pragma once
2 #include "./unspecified_category.hpp"
3
4 #include <initializer_list>
5 #include <string>
6 #include <system_error>
7 #include <type_traits>
8 #include <utility>
9 #include <vector>
10
11 namespace estd {
12
13 namespace detail {
14 template<typename T, typename = void>
15 struct can_make_error_code : std::false_type {};
16
17 template<typename T>
18 struct can_make_error_code<T, std::void_t<decltype(make_error_code(T{}))>> : std::true_type {};
19 }
20
21 template<typename T>
22 constexpr bool can_make_error_code = detail::can_make_error_code<T>::value;
23
24 /// General purpose error.
25 /**
26 * An error combines an std::error_code with a description stack.
27 *
28 * The description stack can be used to add more details to the raw error code, to be displayed to a human.
29 * The description stack is not meant to be inspected programmatically to determine how to process the error.
30 * In general, code not meant for logging or displaying the error should only look at the error code.
31 */
32 struct error {
33 /// The error code to indicate the type of error.
34 std::error_code code;
35
36 /// A stack of descriptions to clarify the error.
37 /**
38 * In general, descriptions are pushed to the back.
39 * This means that information closer to the error point is at the front of the vector.
40 */
41 std::vector<std::string> description;
42
43 /// Construct an empty error representing success.
errorestd::error44 error() {}
45
46 /// Construct an error with a code and description stack.
errorestd::error47 error(std::error_code code, std::vector<std::string> description = {}) :
48 code{code},
49 description(std::move(description)) {}
50
51 /// Construct an error with a code and description stack.
errorestd::error52 error(std::error_code code, std::initializer_list<std::string> description) :
53 code{code},
54 description(std::vector<std::string>{std::move(description)}) {}
55
56 /// Construct an error with a code and description.
errorestd::error57 error(std::error_code code, std::string description) :
58 code{code},
59 description{std::move(description)} {}
60
61 /// Construct an error with a code and description stack.
62 template<typename T, typename = std::enable_if_t<can_make_error_code<T>>>
errorestd::error63 error(T code, std::vector<std::string> description = {}) : error{make_error_code(code), std::move(description)} {}
64
65 /// Construct an error with a code and description stack.
66 template<typename T, typename = std::enable_if_t<can_make_error_code<T>>>
errorestd::error67 error(T code, std::initializer_list<std::string> description) : error{make_error_code(code), std::vector<std::string>{std::move(description)}} {}
68
69 /// Construct an error with a code and description.
70 template<typename T, typename = std::enable_if_t<can_make_error_code<T>>>
errorestd::error71 error(T code, std::string description) : error{make_error_code(code), std::move(description)} {}
72
73 /// Create an unspecified error with a description.
errorestd::error74 explicit error(std::string description) : error{unspecified_errc::unspecified, std::move(description)} {};
75
76 ///// Create an unspecified error with a description stack.
errorestd::error77 explicit error(std::vector<std::string> description) : error{unspecified_errc::unspecified, std::move(description)} {}
78
79 /// Create an unspecified error with a description stack.
errorestd::error80 explicit error(std::initializer_list<std::string> description) : error(std::vector<std::string>{std::move(description)}) {};
81
82 /// Check if this error represents an error and not sucess.
operator boolestd::error83 explicit operator bool() const noexcept {
84 return !!code;
85 }
86
87 /// Check if the error code is the same as another one.
operator ==estd::error88 bool operator== (std::error_code other) const { return code == other; }
operator !=estd::error89 bool operator!= (std::error_code other) const { return code != other; }
90
91 /// Check if the error code is equivalent to an error condition.
operator ==estd::error92 bool operator== (std::error_condition other) const { return code == other; }
operator !=estd::error93 bool operator!= (std::error_condition other) const { return code != other; }
94
95 /// Check if the error code is equivalent to an std::errc.
operator ==estd::error96 bool operator== (std::errc other) const { return code == other; }
operator !=estd::error97 bool operator!= (std::errc other) const { return code != other; }
98
99 /// Create a new error with the same description but a different error code.
with_codeestd::error100 error with_code(std::error_code code) const & { return {code, description}; }
with_codeestd::error101 error with_code(std::error_code code) && { return {code, std::move(description)}; }
102
103 /// Create a new error with the same code, but with a description pushed to the stack.
push_descriptionestd::error104 error push_description(std::string action) const & {
105 std::vector<std::string> new_trace = description;
106 new_trace.push_back(std::move(action));
107 return {code, new_trace};
108 }
109
110 /// Create a new error with the same code, but with a description pushed to the stack.
111 /**
112 * This overload destroys the original error to re-use the description stack.
113 */
push_descriptionestd::error114 error push_description(std::string action) && {
115 std::vector<std::string> new_trace = std::move(description);
116 new_trace.push_back(std::move(action));
117 return {code, std::move(new_trace)};
118 }
119
120 /// Format the error code as a string.
121 /**
122 * Returns a string in the format of:
123 * {category name} error {error number}: {error message}
124 */
format_codeestd::error125 std::string format_code() const {
126 // If the error is from the special unspecified category,
127 // only format the category name and integer value, not the message.
128 if (code.category() == unspecified_error_category()) {
129 return std::string{code.category().name()} + " error " + std::to_string(code.value());
130 }
131 return std::string{code.category().name()} + " error " + std::to_string(code.value()) + ": " + code.message();
132 }
133
134 /// Format the description stack without the error code.
135 /**
136 * Returns a string in the format of:
137 * {description N}: {description N-1}: ... {description 1}
138 */
format_descriptionestd::error139 std::string format_description(std::size_t additional_size = 0) const {
140 // Determine the total message size.
141 std::size_t size = additional_size;
142 for (std::string const & string : description) {
143 size += string.size() + 2;
144 }
145 if (size > 2) size -= 2;
146
147 // Make a string with the right capacity.
148 std::string result;
149 result.reserve(size);
150
151 // Add all descriptions, back to front.
152 for (auto i = description.rbegin(); i != description.rend(); ++i) {
153 if (&*i == &description.front()) {
154 result += *i;
155 } else {
156 result += *i + ": ";
157 }
158 }
159
160 return result;
161 }
162
163 /// Format the error code along with the description stack.
164 /**
165 * Returns a string in the format of:
166 * {description N}: {description N-1}: ... {description 1}: {error}
167 *
168 * Where {error} is the output of `format_code()`.
169 */
formatestd::error170 std::string format() const {
171 // If the error is the special unspecified error and there is a description,
172 // show only the description.
173 if (code == unspecified_errc::unspecified && !description.empty()) {
174 return format_description();
175 }
176
177 std::string code_description = format_code();
178 if (description.empty()) return code_description;
179
180 std::string result = format_description(code_description.size() + 2);
181 result += ": ";
182 result += code_description;
183 return result;
184 }
185 };
186
187 class error_exception : public std::exception {
188 estd::error error_;
189 std::string formatted_;
190
191 public:
error_exception(estd::error error)192 error_exception(estd::error error) : error_{std::move(error)} {
193 formatted_ = error_.format();
194 }
195
what() const196 char const * what() const noexcept override {
197 return formatted_.c_str();
198 }
199
error() const200 estd::error const & error() const & { return error_; }
error()201 estd::error & error() & { return error_; }
error()202 estd::error && error() && { return std::move(error_); }
203
code() const204 std::error_code const & code() const { return error_.code; }
205 };
206
make_default_exception(error error)207 inline error_exception make_default_exception(error error) {
208 return error_exception{std::move(error)};
209 }
210
211 }
212