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 }