1 /** 2 An expected result type 3 4 Useful for functions that are expected to return something but could result in an error. The expected type is parameterized over 5 two types - the expected one and the `Unexpected`. When you want to assign an unexpected type you must use the provided type constructor 6 to make an unexpected assignment. 7 8 An `Expect!(T, U)` type also has a static `expected` and `unexpected` methods to create the given `Expect!(U, V)` with the desired 9 state. 10 */ 11 module ddash.utils.expect; 12 13 /// 14 @nogc unittest { 15 Expect!(int, string) even(int i) @nogc { 16 if (i % 2 == 0) { 17 return typeof(return).expected(i); 18 } else { 19 return typeof(return).unexpected("not even"); 20 } 21 } 22 23 assert(even(1) == unexpected("not even")); 24 assert(even(2) == 2); 25 } 26 27 import std.variant; 28 import ddash.common; 29 30 struct AnyUnexpected {} 31 32 /** 33 Can be used to compare for any unexpected, i.e. `Expected<U, V> == anyUnexpected` 34 */ 35 immutable anyUnexpected = AnyUnexpected(); 36 37 /** 38 Used in the `Expect` type to denote an unexpected value 39 */ 40 private struct Unexpected(E) if (!is(E == AnyUnexpected)) { 41 E value = E.init; 42 alias value this; 43 } 44 45 /** 46 Type constructor for an Unexpected value. This must be used when assigning or passing 47 an unexpected type to an `Expect!(T, U)` 48 */ 49 auto unexpected(E)(E value) { 50 return Unexpected!E(value); 51 } 52 53 /** 54 The expect type can be used to return values and error codes from functions 55 56 The "expected" value is the success value and the unexpected (which is also typed 57 as `Unexpected`) is the failure value. 58 59 The default value is always the init value of the expected case 60 */ 61 struct Expect(T, E = Variant) if (!is(E == void)) { 62 import sumtype; 63 import ddash.lang: Void, isVoid; 64 65 static if (isVoid!T) { 66 alias Expected = Void; 67 } else { 68 alias Expected = T; 69 } 70 alias Unexpected = .Unexpected!E; 71 72 private SumType!(Expected, Unexpected) data = Expected.init; 73 ref get() { return data; } 74 alias get this; 75 76 /** 77 Constructor takes a Expected and creates a success result. Or takes an E and 78 creates an unexpected result 79 */ 80 static if (!isVoid!Expected) { 81 this(Expected value) { 82 data = value; 83 } 84 } 85 86 /// Ditto 87 this(Unexpected value) { 88 data = value; 89 } 90 91 /** 92 Pass in 2 handlers, one that handles `Expected`` and another that 93 handles `Unexpected` 94 */ 95 auto match(handlers...)() { 96 return data.match!handlers; 97 } 98 99 /// Create an `Expect` with an expected value 100 static expected(V : Expected)(auto ref V value) { 101 return Expect!(Expected, E)(value); 102 } 103 104 /// Create an `Expect` with an unexpected value 105 static unexpected(V)(auto ref V value) if (is(E == Variant) || is(V : E)) { 106 // If E is a variant type than then we allow any V and just store it as a variant 107 static if (is(E == Variant) && !is(V == Variant)) { 108 return Expect!(Expected, E)(Unexpected(Variant(value))); 109 } else { 110 return Expect!(Expected, E)(Unexpected(value)); 111 } 112 } 113 114 /// Returns true if the value is expected 115 bool isExpected() const { 116 return data.match!( 117 (const Expected _) => true, 118 (const Unexpected _) => false, 119 ); 120 } 121 122 /** 123 compares a value or an unexpected value. To compare with an unepexted value 124 you must used either `Unexpected!E` as the rhs or it's type contructor. 125 126 If you do not care about the value of the unexpected then you can compare 127 against `anyUnexpected` 128 */ 129 bool opEquals(Expected rhs) const { 130 if (!isExpected) return false; 131 return data.match!( 132 (const Expected lhs) => lhs, 133 (const Unexpected _) => Expected.init, 134 ) == rhs; 135 } 136 137 /// Ditto 138 bool opEquals(Unexpected rhs) const { 139 if (isExpected) return false; 140 return data.match!( 141 (const Expected _) => Unexpected.init, 142 (const Unexpected lhs) => lhs, 143 ) == rhs; 144 } 145 146 /// Ditto 147 bool opEquals(AnyUnexpected) const { 148 return !isExpected; 149 } 150 151 /// Calls std.conv.to!string on T or E 152 string toString() { 153 import std.conv: to; 154 return data.match!( 155 (ref Expected value) => value.to!string, 156 (ref Unexpected value) => value.to!string, 157 ); 158 } 159 } 160 161 /// 162 unittest { 163 Expect!int toInt(string str) { 164 alias Result = typeof(return); 165 import std.conv: to; 166 try { 167 return Result.expected(str.to!int); 168 } catch (Exception ex) { 169 return Result.unexpected(ex.msg); 170 } 171 } 172 173 assert(toInt("33") == 33); 174 assert(toInt("!33") == anyUnexpected); 175 } 176