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 }