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 }