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