1 /** 2 * __ __ __ __ 3 * / / /_/ _____ \ \ /_/ 4 * _____/ / _____ __ ___ __ / ___ \ _____ / / __ __ ___ _____ 5 * / ___ / / ___ \ \ \_/__/ / / / _____/ / ___ \ / / / / / /_/ / / ___ \ 6 * / /__/ / / /__/ \ / _/ / / / /__/ / / _____/ / / / / / _/ / / / /__/ / 7 * \_____/ \_____/\_\ /_/ __ / / \_____/ / /__/ / \_\ /_/ /_/ /_/ _\___ / 8 * / /__/ / \_____/ / /__/ / 9 * \_____/ \_____/ 10 * 11 * A module for results each of which is a value or an exception 12 * 13 * Copyright: (C) Kazuhiro Matsushima 2016. 14 * License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>. 15 * Authors: Kazuhiro Matsushima 16 */ 17 module darjeeling.trial; 18 19 import std.format : format; 20 import std.traits : fullyQualifiedName; 21 import darjeeling.either: Either; 22 import darjeeling.assertion; 23 24 /** 25 * A interface to represent whether a value or an exception. 26 * 27 * Example: 28 * ----- 29 * auto trial = Trial!int.trying({ return 9; }); 30 * if (trial.isSuccess) 31 * { 32 * writeln(trial.get()); 33 * } 34 * ----- 35 * Output: 36 * ----- 37 * 9 38 * ----- 39 */ 40 interface Trial(T) 41 { 42 /** 43 * Return true if $(D this) is the Success!T instance, otherwise false. 44 */ 45 @property bool isSuccess() nothrow pure @safe; 46 47 /** 48 * Return true if $(D this) is the Failure!T instance, otherwise false. 49 */ 50 @property bool isFailure() nothrow pure @safe; 51 52 /** 53 * Return $(D value) if $(D this) is the Success!T instance, 54 * otherwise throw Exception in the Failure!T instance. 55 */ 56 T get() pure @safe; 57 58 /** 59 * Return Right!(Exception, T) value if $(D this) is the Success!T instance, 60 * otherwise Left!(Exception, T) value. 61 */ 62 Either!(Exception, T) getOrLeft() nothrow pure @safe; 63 64 /** 65 * A factory method to create Trial(T) instance. 66 * 67 * Params: 68 * expr = a function block to return T value, and it may also raise exception. 69 * 70 * Returns: 71 * A Success!T instance if (D_PARAM expr) is evaluated successfully, 72 * a Failure!T instance if (D_PARAM expr) raises an exception(not an error), 73 * otherwise raise an error. 74 */ 75 static Trial!T trying(T function() expr) nothrow 76 { 77 try 78 { 79 auto value = expr(); 80 return new Success!T(value); 81 } 82 catch (Exception ex) 83 { 84 return new Failure!T(ex); 85 } 86 } 87 } 88 89 final class Success(T) : Trial!T 90 { 91 private T value; 92 93 package this(T value) 94 { 95 this.value = value; 96 } 97 98 @property bool isSuccess() nothrow pure @safe 99 { 100 return true; 101 } 102 103 unittest 104 { 105 auto success = new Success!int(9); 106 assert(success.isSuccess); 107 } 108 109 @property bool isFailure() nothrow pure @safe 110 { 111 return !this.isSuccess; 112 } 113 114 unittest 115 { 116 auto success = new Success!string("success"); 117 assert(!success.isFailure); 118 } 119 120 T get() pure @safe 121 { 122 return this.value; 123 } 124 125 unittest 126 { 127 auto expected = 3.14; 128 auto success = new Success!double(expected); 129 auto actual = success.get(); 130 assertEquals(expected, actual); 131 } 132 133 Either!(Exception, T) getOrLeft() nothrow pure @safe 134 { 135 return Either!(Exception, T).right(this.value); 136 } 137 138 unittest 139 { 140 auto expected = 3.14; 141 auto success = new Success!double(expected); 142 auto actual = success.getOrLeft(); 143 assert(actual.isRight); 144 assertEquals(expected, actual.right()); 145 } 146 147 override string toString() 148 { 149 return format("Success!%s(%s)", fullyQualifiedName!T, this.value); 150 } 151 152 override bool opEquals(Object o) 153 { 154 if (typeid(o) == typeid(this)) 155 { 156 auto other = cast(Success!T)(o); 157 return this.value == other.value; 158 } 159 else 160 { 161 return false; 162 } 163 } 164 } 165 166 final class Failure(T): Trial!T 167 { 168 private Exception exception; 169 170 package this(Exception exception) 171 { 172 this.exception = exception; 173 } 174 175 @property bool isSuccess() nothrow pure @safe 176 { 177 return !this.isFailure; 178 } 179 180 unittest 181 { 182 auto failure = new Failure!int(new Exception("10")); 183 assert(!failure.isSuccess); 184 } 185 186 @property bool isFailure() nothrow pure @safe 187 { 188 return true; 189 } 190 191 unittest 192 { 193 auto failure = new Failure!string(new Exception("failure")); 194 assert(failure.isFailure); 195 } 196 197 T get() pure @safe 198 { 199 throw this.exception; 200 } 201 202 unittest 203 { 204 auto expected = new Exception("2.718"); 205 auto failure = new Failure!double(expected); 206 auto actual = trap!(Failure!double, double)(failure, function(Failure!double fl) => fl.get()); 207 assertEquals(typeid(expected), typeid(actual)); 208 assertEquals(expected.msg, actual.msg); 209 } 210 211 Either!(Exception, T) getOrLeft() nothrow pure @safe 212 { 213 return Either!(Exception, T).left(this.exception); 214 } 215 216 unittest 217 { 218 auto expected = new Exception("2.718"); 219 auto failure = new Failure!double(expected); 220 auto actual = failure.getOrLeft(); 221 assert(actual.isLeft); 222 auto ex = actual.left(); 223 assertEquals(typeid(expected), typeid(ex)); 224 assertEquals(expected.msg, ex.msg); 225 } 226 227 override string toString() 228 { 229 return format("Failure!%s(%s(%s))", fullyQualifiedName!T, typeid(this.exception), this.exception.msg); 230 } 231 232 override bool opEquals(Object o) 233 { 234 if (typeid(o) == typeid(this)) 235 { 236 auto other = cast(Failure!T)(o); 237 return (typeid(this.exception) == typeid(other.exception)) 238 && (this.exception.msg == other.exception.msg); 239 } 240 else 241 { 242 return false; 243 } 244 } 245 } 246 247 unittest 248 { 249 // Success(T) 250 { 251 Trial!int trial = Trial!int.trying(function int() { return -12; }); 252 assert(trial.isSuccess); 253 assert(!trial.isFailure); 254 auto success = trial.getOrLeft(); 255 assert(success.isRight); 256 auto actual = success.right(); 257 assertEquals(-12, actual); 258 } 259 // Failure(T) 260 { 261 { 262 Trial!int trial = Trial!int.trying(function int() { throw new Exception("hoge"); }); 263 assert(!trial.isSuccess); 264 assert(trial.isFailure); 265 auto success = trial.getOrLeft(); 266 assert(success.isLeft); 267 auto actual = success.left(); 268 assertEquals(typeid(Exception), typeid(actual)); 269 assertEquals("hoge", actual.msg); 270 } 271 { 272 auto actual = trap!(Trial!int)(function Trial!int(){ 273 return Trial!int.trying({ 274 auto x = 1; 275 if (x > 0) throw new Error("It's error!"); 276 return 1; 277 }); 278 }); 279 auto expected = new Error("It's error!"); 280 assertEquals(typeid(expected), typeid(actual)); 281 assertEquals(expected.msg, actual.msg); 282 } 283 } 284 // equality 285 { 286 { 287 auto trial1 = Trial!int.trying({ return 0; }); 288 auto trial2 = Trial!int.trying({ return 0; }); 289 assertEquals(trial1, trial2); 290 } 291 { 292 auto trial1 = Trial!int.trying({ return 1; }); 293 auto trial2 = Trial!int.trying({ return -1; }); 294 assertNotEquals(trial1, trial2); 295 } 296 { 297 auto trial1 = Trial!short.trying(function short() { return 0; }); 298 auto trial2 = Trial!long.trying(function long() { return 0L; }); 299 assertNotObjEquals(trial1, trial2); 300 } 301 { 302 auto trial1 = Trial!int.trying(function int() { throw new Exception("fail"); }); 303 auto trial2 = Trial!int.trying(function int() { throw new Exception("fail"); }); 304 assertEquals(trial1, trial2); 305 } 306 { 307 auto trial1 = Trial!int.trying(function int() { throw new Exception("fail"); }); 308 auto trial2 = Trial!int.trying(function int() { throw new Exception("Fail"); }); 309 assertNotEquals(trial1, trial2); 310 } 311 { 312 import core.exception : UnicodeException; 313 314 auto trial1 = Trial!int.trying(function int() { throw new Exception("fail"); }); 315 auto trial2 = Trial!int.trying(function int() { throw new UnicodeException("fail", 0); }); 316 assertNotEquals(trial1, trial2); 317 } 318 { 319 auto trial1 = Trial!string.trying(function string() { throw new Exception("fail"); }); 320 auto trial2 = Trial!double.trying(function double() { throw new Exception("fail"); }); 321 assertNotObjEquals(trial1, trial2); 322 } 323 { 324 auto trial1 = Trial!int.trying({ return 0; }); 325 auto trial2 = Trial!int.trying({ 326 auto x = 1; 327 if (x > 0) throw new Exception("positive"); 328 return x; 329 }); 330 assertNotEquals(trial1, trial2); 331 } 332 } 333 }