1 /**
2  * Exception formatting
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.utils.exception;
15 
16 import std.algorithm;
17 import std.string;
18 
19 string formatException(Throwable e)
20 {
21 	string[] descriptions;
22 	while (e)
23 		descriptions ~= e.toString(),
24 		e = e.next;
25 	return descriptions.join("\n===================================\n");
26 }
27 
28 // --------------------------------------------------------------------------
29 
30 import ae.utils.meta;
31 
32 mixin template DeclareException(string NAME, BASE = Exception)
33 {
34 	import ae.utils.meta.x;
35 
36 	mixin(mixin(X!q{
37 		class @(NAME) : BASE
38 		{
39 			this(string s, string fn = __FILE__, size_t ln = __LINE__)
40 			{
41 				super(s, fn, ln);
42 			}
43 
44 			this(string s, Throwable next, string fn = __FILE__, size_t ln = __LINE__)
45 			{
46 				super(s, next, fn, ln);
47 			}
48 		}
49 	}));
50 }
51 
52 unittest
53 {
54 	mixin DeclareException!q{OutOfCheeseException};
55 	try
56 		throw new OutOfCheeseException("*** OUT OF CHEESE ***");
57 	catch (Exception e)
58 		assert(e.classinfo.name.indexOf("Cheese") > 0);
59 }
60 
61 // --------------------------------------------------------------------------
62 
63 /// This exception can never be thrown.
64 /// Useful for a temporary or aliased catch block exception type.
65 class NoException : Exception
66 {
67 	@disable this()
68 	{
69 		super(null);
70 	}
71 }
72 
73 /// Allows toggling catch blocks with -debug=NO_CATCH.
74 /// To use, catch CaughtException instead of Exception in catch blocks.
75 debug(NO_CATCH)
76 	alias CaughtException = NoException;
77 else
78 	alias CaughtException = Exception;
79 
80 // --------------------------------------------------------------------------
81 
82 import std.conv;
83 
84 /// Returns string mixin for adding a chained exception
85 string exceptionContext(string messageExpr, string name = text(__LINE__))
86 {
87 	name = "exceptionContext_" ~ name;
88 	return mixin(X!q{
89 		bool @(name);
90 		scope(exit) if (@(name)) throw new Exception(@(messageExpr));
91 		scope(failure) @(name) = true;
92 	});
93 }
94 
95 unittest
96 {
97 	try
98 	{
99 		mixin(exceptionContext(q{"Second"}));
100 		throw new Exception("First");
101 	}
102 	catch (Exception e)
103 	{
104 		assert(e.msg == "First");
105 		assert(e.next.msg == "Second");
106 	}
107 }
108 
109 // --------------------------------------------------------------------------
110 
111 string[] getStackTrace(string until = __FUNCTION__, string since = "_d_run_main")
112 {
113 	string[] lines;
114 	try
115 		throw new Exception(null);
116 	catch (Exception e)
117 		lines = e.toString().splitLines()[1..$];
118 
119 	auto start = lines.countUntil!(line => line.canFind(until));
120 	auto end   = lines.countUntil!(line => line.canFind(since));
121 	if (start < 0) start = -1;
122 	if (end   < 0) end   = lines.length;
123 	return lines[start+1..end];
124 }
125 
126 // --------------------------------------------------------------------------
127 
128 import core.exception;
129 import std.exception;
130 
131 template assertOp(string op)
132 {
133 	void assertOp(A, B)(auto ref A a, auto ref B b, string file=__FILE__, int line=__LINE__)
134 	{
135 		if (!(mixin("a " ~ op ~ " b")))
136 			throw new AssertError("Assertion failed: %s %s %s".format(a, op, b), file, line);
137 	}
138 }
139 alias assertEqual = assertOp!"==";
140 
141 unittest
142 {
143 	assertThrown!AssertError(assertEqual(1, 2));
144 }