1 module universal.extras.errors;
2 
3 import universal.core.product;
4 import universal.core.coproduct;
5 import universal.core.apply;
6 import universal.meta;
7 import std.typetuple;
8 import std.typecons;
9 import std.format;
10 import std.conv;
11 import std.array;
12 
13 /*
14 	For executing code in nothrow, multithreaded, or failsafe contexts
15 */
16 
17 /*
18 	represents a caught throwable
19 	a union whose field names are the exception names, with all qualifications removed, in camelCase.
20 */
21 
22 struct Caught(Throwables...)
23 {
24 	mixin UnionInstance!(Interleave!(
25 		staticMap!(simpleName, Throwables)
26 			.tuple.tlift!toCamelCase[],
27 		Throwables
28 	));
29 }
30 alias Caught() = Caught!(Error, Exception);
31 
32 /*
33 	convenience function on Caughts, useful for rethrowing exceptions passed out of nothrow environments and such
34 */
35 template rethrow(Throwables...)
36 {
37 	void rethrow(Caught!Throwables failure)
38 	{
39 		failure.visit!( // TODO extend to handle all exceptions
40 			q{error}, (err) { assert(0, err.text); },
41 			q{exception}, (ex) { throw ex; }
42 		);
43 	}
44 }
45 
46 alias Failure = Caught!();
47 
48 /*
49 	Represents an operation which may have failed.
50 */
51 struct Result(A, Throwables...)
52 {
53 	alias Failure = Caught!Throwables;
54 	alias Success = A;
55 
56 	mixin UnionInstance!(
57 		q{failure}, Failure,
58 		q{success}, Success,
59 	);
60 }
61 template success(A...)
62 {
63 	Result!A success(A a) 
64 	{ return a.apply!(_ => Result!A().success(_)); }
65 }
66 template failure(A...)
67 {
68 	Result!A failure(Error err) 
69 	{ return Result!A().failure(Result!A.Failure().error(err)); }
70 	Result!A failure(Exception ex) 
71 	{ return Result!A().failure(Result!A.Failure().exception(ex)); }
72 }
73 template isSuccess(A, Throwables...)
74 {
75 	bool isSuccess(Result!(A, Throwables) result)
76 	{ return result.isCase!q{success}; }
77 }
78 template isFailure(A, Throwables...)
79 {
80 	bool isFailure(Result!(A, Throwables) result)
81 	{ return result.isCase!q{failure}; }
82 }
83 alias Result() = Result!Unit;
84 
85 /*
86 	execute a function in a failsafe context
87 	by default, catches Error and Exception
88 	This can be overridden as template parameters following the lifted function
89 */
90 template tryCatch(alias f, Throwables...)
91 {
92 	template tryCatch(A...)
93 	{
94 		alias B = Universal!(typeof(f(A.init)));
95 		alias R = Result!(B, Throwables);
96 
97 		string cases()
98 		{
99             import std.range : iota;
100 
101 			template catchCase(uint i)
102 			{
103 				enum catchCase = format(q{
104 					catch(R.Failure.Union.Args!%d[0] thrown)
105 						return R.init.failure(R.Failure.init.%s(thrown));
106 				}, i, R.Failure.Union.ctorNames[i]);
107 			}
108 
109 			return [
110 				q{ try return R.init.success(apply!f(args)); },
111 				staticMap!(catchCase, aliasSeqOf!(R.Failure.Union.width.iota))
112 			].join("\n");
113 		}
114 
115 		R tryCatch(A args) { mixin(cases); }
116 	}
117 }
118 
119 @("EXAMPLES") unittest
120 {
121 	void err() { assert(0); }
122 	assert(tryCatch!err.isFailure);
123 	assert(tryCatch!err.failure.isCase!"error");
124 
125 	void exc() { throw new Exception(""); }
126 	assert(tryCatch!exc.isFailure);
127 	assert(tryCatch!exc.failure.isCase!"exception");
128 
129 	class MyExcept : Exception { this() { super(""); } }
130 
131 	void exc2() { throw new MyExcept; }
132 	assert(tryCatch!exc2.failure.isCase!"exception");
133 	assert(tryCatch!(exc2, MyExcept).failure.isCase!"myExcept");
134 
135 	int succ(int a) { return a+1; }
136 	assert(tryCatch!succ(2).success == 3);
137 }