1 /****************************************************************************** 2 3 Exception utilities to write enforcements/ 4 5 Copyright: 6 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 7 All rights reserved. 8 9 License: 10 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 11 Alternatively, this file may be distributed under the terms of the Tango 12 3-Clause BSD License (see LICENSE_BSD.txt for details). 13 14 *******************************************************************************/ 15 16 module ocean.core.Enforce; 17 18 19 import ocean.transition; 20 import ocean.text.convert.Formatter; 21 22 23 /****************************************************************************** 24 25 Enforces that given expression evaluates to boolean `true` after 26 implicit conversion. 27 28 Params: 29 E = exception type to create and throw 30 T = type of expression to test 31 ok = result of expression 32 msg = optional custom message for exception 33 file = file of origin 34 line = line of origin 35 36 Throws: 37 E if expression evaluates to false 38 39 ******************************************************************************/ 40 41 public void enforceImpl ( E : Exception = Exception, T ) ( 42 T ok, lazy istring msg, istring file, int line ) 43 { 44 // duplicate msg/file/line mention to both conform Exception cnstructor 45 // signature and fit our reusable exceptions. 46 47 E exception = null; 48 49 if (!ok) 50 { 51 static if (is(typeof(new E((char[]).init, file, line)))) 52 { 53 exception = new E(null, file, line); 54 } 55 else static if (is(typeof(new E(file, line)))) 56 { 57 exception = new E(file, line); 58 } 59 else static if (is(typeof(new E((char[]).init)))) 60 { 61 exception = new E(null); 62 } 63 else static if (is(typeof(new E()))) 64 { 65 exception = new E(); 66 } 67 else 68 { 69 static assert (false, "Unsupported constructor signature"); 70 } 71 } 72 73 enforceImpl!(T)(exception, ok, msg, file, line); 74 } 75 76 /****************************************************************************** 77 78 Thin wrapper for enforceImpl that deduces file/line as template arguments 79 to avoid ambiguity between overloads. 80 81 ******************************************************************************/ 82 83 public void enforce ( E : Exception = Exception, T, 84 istring file = __FILE__, int line = __LINE__ ) ( T ok, lazy istring msg = "" ) 85 { 86 enforceImpl!(E, T)(ok, msg, file, line); 87 } 88 89 unittest 90 { 91 // uses 'assert' to avoid dependency on itself 92 93 enforce(true); 94 95 try 96 { 97 enforce(false); 98 assert(false); 99 } 100 catch (Exception e) 101 { 102 assert(e.message() == "enforcement has failed"); 103 assert(e.line == __LINE__ - 6); 104 } 105 106 try 107 { 108 enforce(3 > 4, "custom message"); 109 assert(false); 110 } 111 catch (Exception e) 112 { 113 assert(e.message() == "custom message"); 114 assert(e.line == __LINE__ - 6); 115 } 116 } 117 118 /****************************************************************************** 119 120 Enforces that given expression evaluates to boolean `true` after 121 implicit conversion. 122 123 NB! When present 'msg' is used instead of existing 'e.message()' 124 125 In D2 we will be able to call this via UFCS: 126 exception.enforce(1 == 1); 127 128 Params: 129 E = exception type to create and throw 130 T = type of expression to test 131 e = exception instance to throw in case of an error (evaluated once in 132 case of an error, not evaluated otherwise) 133 ok = result of expression 134 msg = optional custom message for exception 135 file = file of origin 136 line = line of origin 137 138 Throws: 139 e if expression evaluates to false 140 141 ******************************************************************************/ 142 143 public void enforceImpl ( T ) ( lazy Exception e, T ok, lazy istring msg, 144 istring file, int line) 145 { 146 if (!ok) 147 { 148 auto exception = e; 149 auto message = msg; 150 151 if (message.length) 152 { 153 exception.msg = message; 154 } 155 else 156 { 157 if (!exception.message().length) 158 { 159 exception.msg = "enforcement has failed"; 160 } 161 } 162 163 exception.file = file; 164 exception.line = line; 165 166 throw exception; 167 } 168 } 169 170 /****************************************************************************** 171 172 Thin wrapper for enforceImpl that deduces file/line as template arguments 173 to avoid ambiguity between overloads. 174 175 ******************************************************************************/ 176 177 public void enforce ( T, E : Exception, istring file = __FILE__, 178 int line = __LINE__ ) ( lazy E e, T ok, lazy istring msg = "" ) 179 { 180 enforceImpl!(T)(e, ok, msg, file, line); 181 } 182 183 unittest 184 { 185 class MyException : Exception 186 { 187 this ( istring msg, istring file = __FILE__, int line = __LINE__ ) 188 { 189 super ( msg, file, line ); 190 } 191 } 192 193 auto reusable = new MyException(null); 194 195 enforce(reusable, true); 196 197 try 198 { 199 enforce(reusable, false); 200 assert(false); 201 } 202 catch (MyException e) 203 { 204 assert(e.message() == "enforcement has failed"); 205 assert(e.line == __LINE__ - 6); 206 } 207 208 try 209 { 210 enforce(reusable, false, "custom message"); 211 assert(false); 212 } 213 catch (MyException e) 214 { 215 assert(e.message() == "custom message"); 216 assert(e.line == __LINE__ - 6); 217 } 218 219 try 220 { 221 enforce(reusable, false); 222 assert(false); 223 } 224 catch (MyException e) 225 { 226 // preserved from previous enforcement 227 assert(e.message() == "custom message"); 228 assert(e.line == __LINE__ - 7); 229 } 230 231 // Check that enforce won't try to modify the exception reference 232 static assert(is(typeof(enforce(new Exception("test"), true)))); 233 234 // Check that enforce() with doesn't evaluate its lazy exception parameter 235 // if the sanity condition is true. 236 237 enforce( 238 delegate MyException() 239 { 240 assert(false, 241 "enforce() evaluated its exception parameter without error"); 242 }(), 243 true 244 ); 245 246 // call enforce() with condition "2 != 2" and verify it does evaluate its 247 // lazy exception parameter exactly once 248 249 bool returned_reusable = false; 250 251 try 252 { 253 enforce( 254 { 255 assert(!returned_reusable, 256 "enforce() evaluated its exception pararmeter more " ~ 257 "than once"); 258 returned_reusable = true; 259 return reusable; 260 }(), 261 false 262 ); 263 } 264 catch (Exception e) 265 { 266 assert(e is reusable, "expected enforce() to throw reusable"); 267 } 268 269 assert(returned_reusable, 270 "enforce() didn't evaluate its exception parameter"); 271 } 272 273 /****************************************************************************** 274 275 enforcement that builds error message string automatically based on value 276 of operands and supplied "comparison" operation. 277 278 'op' can be any binary operation. 279 280 Params: 281 op = binary operator string 282 E = exception type to create and throw 283 T1 = type of left operand 284 T2 = type of right operand 285 a = left operand 286 b = right operand 287 file = file of origin 288 line = line of origin 289 290 Throws: 291 E if expression evaluates to false 292 293 ******************************************************************************/ 294 295 public void enforceImpl ( istring op, E : Exception = Exception, T1, T2 ) ( 296 T1 a, T2 b, istring file, int line ) 297 { 298 mixin("auto ok = a " ~ op ~ " b;"); 299 300 if (!ok) 301 { 302 static if (is(typeof(new E((char[]).init, file, line)))) 303 { 304 auto exception = new E(null, file, line); 305 } 306 else static if (is(typeof(new E(file, line)))) 307 { 308 auto exception = new E(file, line); 309 } 310 else static if (is(typeof(new E((char[]).init)))) 311 { 312 auto exception = new E(null); 313 } 314 else static if (is(typeof(new E()))) 315 { 316 auto exception = new E(); 317 } 318 else 319 { 320 static assert (false, "Unsupported constructor signature"); 321 } 322 323 enforceImpl!(op, T1, T2)(exception, a, b, file, line); 324 } 325 } 326 327 /****************************************************************************** 328 329 Thin wrapper for enforceImpl that deduces file/line as template arguments 330 to avoid ambiguity between overloads. 331 332 ******************************************************************************/ 333 334 public void enforce ( istring op, E : Exception = Exception, T1, T2, 335 istring file = __FILE__ , int line = __LINE__) ( T1 a, T2 b ) 336 { 337 enforceImpl!(op, E, T1, T2)(a, b, file, line); 338 } 339 340 unittest 341 { 342 // uses 'assert' to avoid dependency on itself 343 344 enforce!("==")(2, 2); 345 346 try 347 { 348 enforce!("==")(2, 3); 349 assert(false); 350 } 351 catch (Exception e) 352 { 353 assert(e.message() == "expression '2 == 3' evaluates to false"); 354 assert(e.line == __LINE__ - 6); 355 } 356 357 try 358 { 359 enforce!(">")(3, 4); 360 assert(false); 361 } 362 catch (Exception e) 363 { 364 assert(e.message() == "expression '3 > 4' evaluates to false"); 365 assert(e.line == __LINE__ - 6); 366 } 367 368 try 369 { 370 enforce!("!is")(null, null); 371 assert(false); 372 } 373 catch (Exception e) 374 { 375 assert(e.line == __LINE__ - 5); 376 assert(e.message() == "expression 'null !is null' evaluates to false"); 377 } 378 379 // Check that enforce won't try to modify the exception reference 380 static assert(is(typeof(enforce!("==")(new Exception("test"), 2, 3)))); 381 } 382 383 384 385 /****************************************************************************** 386 387 ditto 388 389 Params: 390 op = binary operator string 391 E = exception type to create and throw 392 T1 = type of left operand 393 T2 = type of right operand 394 e = exception instance to throw in case of an error (evaluated once in 395 case of an error, not evaluated otherwise) 396 a = left operand 397 b = right operand 398 file = file of origin 399 line = line of origin 400 401 Throws: 402 e if expression evaluates to false 403 404 ******************************************************************************/ 405 406 public void enforceImpl ( istring op, T1, T2 ) ( lazy Exception e, T1 a, 407 T2 b, istring file, int line ) 408 { 409 mixin("auto ok = a " ~ op ~ " b;"); 410 411 if (!ok) 412 { 413 auto exception = e; 414 exception.msg = format("expression '{} {} {}' evaluates to false", a, op, b); 415 exception.file = file; 416 exception.line = line; 417 throw exception; 418 } 419 } 420 421 /****************************************************************************** 422 423 Thin wrapper for enforceImpl that deduces file/line as template arguments 424 to avoid ambiguity between overloads. 425 426 ******************************************************************************/ 427 428 public void enforce ( istring op, E : Exception, T1, T2, istring file = __FILE__, 429 int line = __LINE__ ) ( lazy E e, T1 a, T2 b ) 430 { 431 enforceImpl!(op, T1, T2)(e, a, b, file, line); 432 } 433 434 unittest 435 { 436 class MyException : Exception 437 { 438 this ( istring msg, istring file = __FILE__, int line = __LINE__ ) 439 { 440 super ( msg, file, line ); 441 } 442 } 443 444 auto reusable = new MyException(null); 445 446 enforce!("==")(reusable, 2, 2); 447 enforce!("==")(reusable, "2"[], "2"[]); 448 449 try 450 { 451 enforce!("==")(reusable, 2, 3); 452 assert(false); 453 } 454 catch (MyException e) 455 { 456 assert(e.message() == "expression '2 == 3' evaluates to false"); 457 assert(e.line == __LINE__ - 6); 458 } 459 460 try 461 { 462 enforce!("is")(reusable, cast(void*)43, cast(void*)42); 463 assert(false); 464 } 465 catch (MyException e) 466 { 467 assert(e.line == __LINE__ - 5); 468 assert(e.message() == "expression '0X000000000000002B is 0X000000000000002A' evaluates to false"); 469 } 470 471 // call enforce() with condition "2 == 2" and verify it doesn't evaluate its 472 // lazy exception parameter 473 474 enforce!("==")( 475 delegate MyException() 476 { 477 assert(false, 478 "enforce() evaluated its exception parameter without error"); 479 }(), 480 2, 2 481 ); 482 483 // call enforce() with condition "2 != 2" and verify it does evaluate its 484 // lazy exception parameter exactly once 485 486 bool returned_reusable = false; 487 488 try 489 { 490 enforce!("!=")( 491 { 492 assert(!returned_reusable, 493 "enforce() evaluated its exception pararmeter more " ~ 494 "than once"); 495 returned_reusable = true; 496 return reusable; 497 }(), 498 2, 2 499 ); 500 } 501 catch (Exception e) 502 { 503 assert(e is reusable, "expected enforce() to throw reusable"); 504 } 505 506 assert(returned_reusable, 507 "enforce() didn't evaluate its exception parameter"); 508 } 509 510 511 /****************************************************************************** 512 513 Throws a new exception E chained together with an existing exception. 514 515 Params: 516 E = type of exception to throw 517 e = existing exception to chain with new exception 518 msg = message to pass to exception constructor 519 file = file from which this exception was thrown 520 line = line from which this exception was thrown 521 522 *******************************************************************************/ 523 524 void throwChained ( E : Throwable = Exception) 525 ( lazy Throwable e, lazy istring msg, 526 istring file = __FILE__, int line = __LINE__ ) 527 { 528 throw new E(msg, file, line, e); 529 } 530 531 /// 532 unittest 533 { 534 auto next_e = new Exception("1"); 535 536 try 537 { 538 throwChained!(Exception)(next_e, "2"); 539 assert (false); 540 } 541 catch (Exception e) 542 { 543 assert (e.next is next_e); 544 assert (e.message() == "2"); 545 assert (e.next.msg == "1"); 546 } 547 }