1 module dshould.thrown;
2 
3 import std.format : format;
4 import std.traits : CommonType;
5 import std.typecons;
6 import dshould.ShouldType;
7 import dshould.basic : be, equal, not, should;
8 
9 /**
10  * The phrase `.should.throwA!Type` (or `.throwAn!Exception`, depending on grammar) expects the left-hand side expression
11  * to throw an exception of the given type.
12  * The exception is caught. If no exception was thrown, `.throwA` itself throws a `FluentException` to complain.
13  * If the left-hand side threw an exception, the word `.where` may be used to inspect this exception further.
14  * The meaning of `.throwA` may be negated with `.not`, in which case nothing is returned.
15  */
16 public template throwA(T : Throwable)
17 {
18     auto throwA(Should)(Should should, Fence _ = Fence(), string file = __FILE__, size_t line = __LINE__)
19     if (isInstanceOf!(ShouldType, Should))
20     {
21         should.allowOnlyWords!("not").before!"throwA";
22 
23         should.terminateChain;
24 
25         FluentException innerError = null;
26 
27         auto inner()
28         {
29             try
30             {
31                 should.got();
32             }
33             catch (T throwable)
34             {
35                 static if (should.hasWord!"not")
36                 {
37                     innerError = new FluentException(
38                         format!`no exception of type %s`(T.stringof),
39                         format!`%s`(throwable),
40                         file, line
41                     );
42                 }
43                 else
44                 {
45                     return throwable;
46                 }
47             }
48 
49             static if (!should.hasWord!"not")
50             {
51                 return null;
52             }
53         }
54 
55         try
56         {
57             static if (is(typeof(inner()) == void))
58             {
59                 inner;
60             }
61             else
62             {
63                 if (auto throwable = inner())
64                 {
65                     return tuple!("where", "which")(throwable, throwable);
66                 }
67             }
68         }
69         // don't go up beyond Exception unless we're not from beneath it:
70         // keeps us from needlessly breaking purity.
71         catch (CommonType!(Exception, T) otherThrowable)
72         {
73             static if (should.hasWord!"not")
74             {
75                 return;
76             }
77             else
78             {
79                 throw new FluentException(
80                     format!`exception of type %s`(T.stringof),
81                     format!`%s`(otherThrowable),
82                     file, line
83                 );
84             }
85         }
86 
87         static if (should.hasWord!"not")
88         {
89             if (innerError !is null)
90             {
91                 throw innerError;
92             }
93         }
94         else
95         {
96             throw new FluentException(
97                 format!`exception of type %s`(T.stringof),
98                 `no exception`,
99                 file, line
100             );
101         }
102     }
103 }
104 
105 /// ditto
106 public alias throwAn = throwA;
107 
108 ///
109 unittest
110 {
111     auto exception = new Exception("");
112 
113     /**
114      * Throws: Exception
115      */
116     void throwsException() { throw exception; }
117 
118     throwsException.should.throwAn!Exception.which.should.be(exception);
119     throwsException.should.throwAn!Exception.which.should.not.be(null);
120 
121     2.should.be(5).should.throwA!FluentException;
122     2.should.be(5).should.throwAn!Error.should.throwA!FluentException;
123 
124     2.should.be(2).should.not.throwA!FluentException;
125 }