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 }