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 }