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