Line data Source code
1 : /**
2 : * Copyright Soramitsu Co., Ltd. All Rights Reserved.
3 : * SPDX-License-Identifier: Apache-2.0
4 : */
5 :
6 : #ifndef IROHA_RESULT_HPP
7 : #define IROHA_RESULT_HPP
8 :
9 : #include <ciso646>
10 :
11 : #include <boost/optional.hpp>
12 : #include <boost/variant.hpp>
13 :
14 : #include "common/visitor.hpp"
15 :
16 : /*
17 : * Result is a type which represents value or an error, and values and errors
18 : * are template parametrized. Working with value wrapped in result is done using
19 : * match() function, which accepts 2 functions: for value and error cases. No
20 : * accessor functions are provided.
21 : */
22 :
23 : namespace iroha {
24 : namespace expected {
25 :
26 : /*
27 : * Value and error types can be constructed from any value or error, if
28 : * underlying types are constructible. Example:
29 : *
30 : * @code
31 : * Value<std::string> v = Value<const char *>("hello");
32 : * @nocode
33 : */
34 :
35 : template <typename T>
36 : struct Value {
37 : T value;
38 : template <typename V>
39 : operator Value<V>() {
40 2090 : return {value};
41 : }
42 : };
43 :
44 : template <>
45 : struct Value<void> {};
46 :
47 : template <typename E>
48 : struct Error {
49 : E error;
50 : template <typename V>
51 : operator Error<V>() {
52 64 : return {error};
53 : }
54 : };
55 :
56 : template <>
57 : struct Error<void> {};
58 :
59 : /**
60 : * Result is a specialization of a variant type with value or error
61 : * semantics.
62 : * @tparam V type of value
63 : * @tparam E error type
64 : */
65 : template <typename V, typename E>
66 : class Result : public boost::variant<Value<V>, Error<E>> {
67 : using variant_type = boost::variant<Value<V>, Error<E>>;
68 : using variant_type::variant_type; // inherit constructors
69 :
70 : public:
71 : using ValueType = Value<V>;
72 : using ErrorType = Error<E>;
73 :
74 : /**
75 : * match is a function which allows working with result's underlying
76 : * types, you must provide 2 functions to cover success and failure cases.
77 : * Return type of both functions must be the same. Example usage:
78 : * @code
79 : * result.match([](Value<int> v) { std::cout << v.value; },
80 : * [](Error<std::string> e) { std::cout << e.error; });
81 : * @nocode
82 : */
83 : template <typename ValueMatch, typename ErrorMatch>
84 : constexpr auto match(ValueMatch &&value_func, ErrorMatch &&error_func) {
85 40410 : return visit_in_place(*this,
86 40410 : std::forward<ValueMatch>(value_func),
87 40410 : std::forward<ErrorMatch>(error_func));
88 : }
89 :
90 : /**
91 : * Const alternative for match function
92 : */
93 : template <typename ValueMatch, typename ErrorMatch>
94 : constexpr auto match(ValueMatch &&value_func,
95 : ErrorMatch &&error_func) const {
96 3341 : return visit_in_place(*this,
97 3341 : std::forward<ValueMatch>(value_func),
98 3341 : std::forward<ErrorMatch>(error_func));
99 : }
100 :
101 : /**
102 : * Lazy error AND-chaining
103 : * Works by the following table (aka boolean lazy AND):
104 : * err1 * any -> err1
105 : * val1 * err2 -> err2
106 : * val1 * val2 -> val2
107 : *
108 : * @param new_res second chain argument
109 : * @return new_res if this Result contains a value
110 : * otherwise return this
111 : */
112 : template <typename Value>
113 : constexpr Result<Value, E> and_res(const Result<Value, E> &new_res) const
114 : noexcept {
115 2 : return visit_in_place(
116 : *this,
117 : [res = new_res](ValueType) { return res; },
118 : [](ErrorType err) -> Result<Value, E> { return err; });
119 0 : }
120 :
121 : /**
122 : * Lazy error OR-chaining
123 : * Works by the following table (aka boolean lazy OR):
124 : * val1 * any -> val1
125 : * err1 * val2 -> val2
126 : * err1 * err2 -> err2
127 : *
128 : * @param new_res second chain argument
129 : * @return new_res if this Result contains a error
130 : * otherwise return this
131 : */
132 : template <typename Value>
133 : constexpr Result<Value, E> or_res(const Result<Value, E> &new_res) const
134 : noexcept {
135 2 : return visit_in_place(
136 : *this,
137 : [](ValueType val) -> Result<Value, E> { return val; },
138 : [res = new_res](ErrorType) { return res; });
139 0 : }
140 : };
141 :
142 : template <typename ResultType>
143 : using ValueOf = typename ResultType::ValueType;
144 : template <typename ResultType>
145 : using ErrorOf = typename ResultType::ErrorType;
146 :
147 : /**
148 : * Get a new result with the copied value or mapped error
149 : * @param res base Result for getting new one
150 : * @param map callback for error mapping
151 : * @return result with changed error
152 : */
153 : template <typename Err1, typename Err2, typename V, typename Fn>
154 : Result<V, Err1> map_error(const Result<V, Err2> &res, Fn &&map) noexcept {
155 1 : return visit_in_place(res,
156 : [](Value<V> val) -> Result<V, Err1> { return val; },
157 : [map](Error<Err2> err) -> Result<V, Err1> {
158 1 : return Error<Err1>{map(err.error)};
159 : });
160 : }
161 :
162 : // Factory methods for avoiding type specification
163 : template <typename T>
164 : Value<T> makeValue(T &&value) {
165 25731 : return Value<T>{std::forward<T>(value)};
166 : }
167 :
168 : template <typename E>
169 : Error<E> makeError(E &&error) {
170 1287 : return Error<E>{std::forward<E>(error)};
171 : }
172 :
173 : /**
174 : * Bind operator allows chaining several functions which return result. If
175 : * result contains error, it returns this error, if it contains value,
176 : * function f is called.
177 : * @param f function which return type must be compatible with original
178 : * result
179 : */
180 : template <typename T, typename E, typename Transform>
181 : constexpr auto operator|(Result<T, E> r, Transform &&f) ->
182 : typename std::enable_if<
183 : not std::is_same<decltype(f(std::declval<T>())), void>::value,
184 : decltype(f(std::declval<T>()))>::type {
185 : using return_type = decltype(f(std::declval<T>()));
186 249 : return r.match(
187 : [&f](const Value<T> &v) { return f(v.value); },
188 : [](const Error<E> &e) { return return_type(makeError(e.error)); });
189 : }
190 :
191 : /**
192 : * Bind operator overload for functions which do not accept anything as a
193 : * parameter. Allows execution of a sequence of unrelated functions, given
194 : * that all of them return Result
195 : * @param f function which accepts no parameters and returns result
196 : */
197 : template <typename T, typename E, typename Procedure>
198 : constexpr auto operator|(Result<T, E> r, Procedure f) ->
199 : typename std::enable_if<not std::is_same<decltype(f()), void>::value,
200 : decltype(f())>::type {
201 : using return_type = decltype(f());
202 732 : return r.match(
203 : [&f](const Value<T> &v) { return f(); },
204 : [](const Error<E> &e) { return return_type(makeError(e.error)); });
205 : }
206 :
207 : /**
208 : * Polymorphic Result is simple alias for result type, which can be used to
209 : * work with polymorphic objects. It is achieved by wrapping V and E in a
210 : * polymorphic container (std::shared_ptr is used by default). This
211 : * simplifies declaration of polymorphic result.
212 : *
213 : * Note: ordinary result itself stores both V and E directly inside itself
214 : * (on the stack), polymorphic result stores objects wherever VContainer and
215 : * EContainer store them, but since you need polymorphic behavior, it will
216 : * probably be on the heap. That is why polymorphic result is generally
217 : * slower, and should be used ONLY when polymorphic behaviour is required,
218 : * hence the name. For all other use cases, stick to basic Result
219 : */
220 : template <typename V,
221 : typename E,
222 : typename VContainer = std::shared_ptr<V>,
223 : typename EContainer = std::shared_ptr<E>>
224 : using PolymorphicResult = Result<VContainer, EContainer>;
225 :
226 : } // namespace expected
227 : } // namespace iroha
228 : #endif // IROHA_RESULT_HPP
|