-
Notifications
You must be signed in to change notification settings - Fork 1
/
multifail.cpp
340 lines (311 loc) · 9.84 KB
/
multifail.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
#if defined(_MSC_VER)
#pragma warning( push )
#pragma warning( disable : 4244 )
#endif
#include <catch.hpp>
#if defined(_MSC_VER)
#pragma warning( pop )
#endif
#include <mz/piecewise/builder.hpp>
#include <mz/piecewise/callable_overload.hpp>
#include <mz/piecewise/factory.hpp>
#include <mz/piecewise/helpers.hpp>
#include <mz/piecewise/multifail.hpp>
#include <string>
#if __cplusplus >= 201703L
#define CONSTEXPR constexpr
#else
#define CONSTEXPR
#endif
namespace mp = mz::piecewise;
namespace {
// `A` simulates a type that could fail during creation.
class A final : public mp::Helpers<A>
{
private:
std::string a_string;
int an_int;
public:
// Two error types are used to distinguish separate error conditions. Below
// there are examples of handling both errors generically and of handling
// them individually.
struct StringEmptyError {
// It's a good idea to give errors a static description so that generic
// error handlers can print it.
static constexpr auto description = "String is empty";
};
struct IntNegativeError {
static constexpr auto description = "Int is negative";
};
std::string const &get_a_string() const { return a_string; }
int get_an_int() const { return an_int; }
private:
// Give BuilderHelper permission to call the private constructor and the
// factory member function.
friend class mp::Helpers<A>;
// The true logic for construction of `A` lives here. Error cases in this
// function result in calls to the `on_fail` callback, and successful
// construction results in the forwarding of what we will call a "builder"
// to the `on_success` callback via `mz::piecewise::forward`. A builder is
// basically a construction callback paired with a group of perfectly
// forwarded references to arguments.
static CONSTEXPR auto factory() {
return [](
auto constructor
, auto&& on_success, auto&& on_fail
, std::string a_string, int an_int
) {
// Validate arguments
if (a_string.empty()) return on_fail(A::StringEmptyError{});
if (an_int < 0) return on_fail(A::IntNegativeError{});
// Now the success callback gets a builder which creates a *valid*
// instance of `A`. We can now always assume that every instance of `A`
// satisfies these preconditions. (This doesn't necessarily hold for a
// moved-from instance of `A`, so it is up to the programmer to avoid
// such usage.)
return on_success(
// Create a builder
mp::builder(
// This is the actual creation callback.
constructor
, // The arguments to be passed to `A`'s constructor
std::move(a_string), an_int
)
);
};
}
public:
// The private constructor is the final step of construction an object of
// type `A`, and it is only called if `A`'s factory function has succeeded.
A(Private, std::string a_string_, int an_int_)
: a_string{std::move(a_string_)}, an_int{an_int_}
{}
};
// `B` can be constructed normally, so it needs no explicit factory function
// to be compatible with `mz::piecewise::factory`.
struct B {
int int_a;
int int_b;
};
// `Aggregate` demonstrates an aggregate type whose private members can all be
// injected as template parameters. If any of these members fail to be
// created, the failure callback will be called, and the aggregate will not be
// created.
template <typename T, typename U, typename V>
class Aggregate final : public mp::Helpers<Aggregate<T, U, V>> {
public:
T const &get_t() const { return t; }
U const &get_u() const { return u; }
V const &get_v() const { return v; }
int get_int() const { return an_int; }
private:
// Give the helpers permission to call the private constructor and the
// factory member function.
friend class mp::Helpers<Aggregate>;
static CONSTEXPR auto factory() {
return [](
auto constructor
, auto&& on_success, auto&& on_fail
, auto t_builder, auto u_builder, auto v_builder
, int an_int_
) {
return mp::multifail(
constructor
, on_success
, on_fail
, mp::builders(
std::move(t_builder), std::move(u_builder), std::move(v_builder)
)
, mp::arguments(an_int_)
);
};
}
public:
template <typename TBuilder, typename UBuilder, typename VBuilder>
Aggregate(
typename mp::Helpers<Aggregate>::Private
, int an_int_
, TBuilder t_builder, UBuilder u_builder, VBuilder v_builder
) : t{std::move(t_builder).construct()}
, an_int{an_int_}
, u{std::move(u_builder).construct()}
, v{std::move(v_builder).construct()}
{}
private:
T t;
int an_int;
U u;
V v;
};
}
SCENARIO("multifail aggregate") {
bool success = false;
bool failure1 = false;
bool failure2 = false;
WHEN("the first nested construction fails") {
// Here we specify all the information necessary to construct an
// `Aggregate<A, A, B>`.
Aggregate<A, A, B>::builder(
// Here we specify the arguments to construct each of the aggregate's
// nested types. Notice that in this case, the first call should fail
// validation.
A::builder("abc", -42)
, A::builder("def", 123)
, mp::wrapper<B>(5, 6)
, 3
)
// Here we pass one lambda to be invoked if the instance is successfully
// created and one lambda to be invoked if instantiation fails. In this
// case, the failure callback will receive an error type as an argument.
.construct(
[&](auto) { success = true; }
, // Here we construct a failure callback out of lambdas that pattern
// matches based on error type. Notice that order makes no difference.
mp::handler(
[&](A::IntNegativeError) { failure2 = true; }
, [&](A::StringEmptyError) { failure1 = true; }
)
);
THEN("the failure callback is called") {
REQUIRE(!success);
REQUIRE(!failure1);
REQUIRE(failure2);
}
}
WHEN("the second nested construction fails") {
Aggregate<A, A, B>::builder(
A::builder("abc", 42)
, // Should fail validation
A::builder("", 123)
, mp::wrapper<B>(5, 6)
, 3
).construct(
[&](auto) { success = true; }
, mp::handler(
[&](A::StringEmptyError) { failure1 = true; }
, [&](A::IntNegativeError) { failure2 = true; }
)
);
THEN("the failure callback is called") {
REQUIRE(!success);
REQUIRE(failure1);
REQUIRE(!failure2);
}
}
WHEN("construction succeeds") {
Aggregate<A, A, B>::builder(
A::builder("abc", 42)
, A::builder("def", 123)
, mp::wrapper<B>(5, 6)
, 3
).construct(
[&](auto builder) {
success = true;
auto res = std::move(builder).construct();
THEN("the nested types contain the correct values") {
REQUIRE(res.get_t().get_a_string() == "abc");
REQUIRE(res.get_t().get_an_int() == 42);
REQUIRE(res.get_u().get_a_string() == "def");
REQUIRE(res.get_u().get_an_int() == 123);
REQUIRE(res.get_v().int_a == 5);
REQUIRE(res.get_v().int_b == 6);
REQUIRE(res.get_int() == 3);
}
}
, [&](auto /* e */) {
// We could print the error generically here:
// ```
// std::cerr << "Error: " << decltype(e)::description << std::endl;
// ```
}
);
THEN("the success callback is called") {
REQUIRE(success);
}
}
#if __cplusplus >= 201703L
WHEN("variant") {
// Doesn't work with clang as of version 5.0.0 due to bug
// https://bugs.llvm.org//show_bug.cgi?id=33222
auto variant = Aggregate<A, A, B>::variant<
A::StringEmptyError
, A::IntNegativeError
>(
A::builder("abc", 42)
, A::builder("def", 123)
, mp::wrapper<B>(5, 6)
, 3
);
std::visit(
mp::handler(
[](Aggregate<A, A, B> const &result) {
REQUIRE(result.get_t().get_a_string() == "abc");
REQUIRE(result.get_t().get_an_int() == 42);
REQUIRE(result.get_u().get_a_string() == "def");
REQUIRE(result.get_u().get_an_int() == 123);
REQUIRE(result.get_v().int_a == 5);
REQUIRE(result.get_v().int_b == 6);
REQUIRE(result.get_int() == 3);
}
, [](auto) { REQUIRE(false); }
)
, variant
);
}
WHEN("invalid variant") {
// Doesn't work with clang as of version 5.0.0 due to bug
// https://bugs.llvm.org//show_bug.cgi?id=33222
auto variant = Aggregate<A, A, B>::variant<
A::StringEmptyError
, A::IntNegativeError
>(
A::builder("abc", 42)
, A::builder("def", -123)
, mp::wrapper<B>(5, 6)
, 3
);
bool failed = false;
std::visit(
mp::handler(
[](Aggregate<A, A, B> const &) {
REQUIRE(false);
}
, [&](auto) { failed = true; }
)
, variant
);
REQUIRE(failed);
}
WHEN("optional") {
auto optional = Aggregate<A, A, B>::optional(
A::builder("abc", 42)
, A::builder("def", 123)
, mp::wrapper<B>(5, 6)
, 3
).construct(
[](auto) { REQUIRE(false); }
);
REQUIRE(optional);
REQUIRE(optional->get_t().get_a_string() == "abc");
REQUIRE(optional->get_t().get_an_int() == 42);
REQUIRE(optional->get_u().get_a_string() == "def");
REQUIRE(optional->get_u().get_an_int() == 123);
REQUIRE(optional->get_v().int_a == 5);
REQUIRE(optional->get_v().int_b == 6);
REQUIRE(optional->get_int() == 3);
}
WHEN("invalid optional") {
bool failed = false;
auto optional = Aggregate<A, A, B>::optional(
A::builder("", 42)
, A::builder("def", 123)
, mp::wrapper<B>(5, 6)
, 3
).construct(
[&](auto) { failed = true; }
);
REQUIRE(failed);
REQUIRE(!optional);
}
#endif
}