1 /****************************************************************************** 2 3 Ocean Exceptions 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.Exception; 17 18 static import ocean.core.Enforce; 19 import ocean.meta.types.Qualifiers; 20 21 version (unittest) 22 { 23 import ocean.core.Test; 24 } 25 26 27 /****************************************************************************** 28 29 Creates an iteratable data structure over a chained sequence of 30 exceptions. 31 32 *******************************************************************************/ 33 34 struct ExceptionChain 35 { 36 /************************************************************************** 37 38 Exception that forms the root of the exception chain. This can 39 be passed in like a constructor argument: 40 41 foreach (e; ExceptionChain(myException)) 42 { 43 ... 44 } 45 46 ***************************************************************************/ 47 48 private Throwable root; 49 50 51 /************************************************************************** 52 53 Allows the user to iterate over the exception chain 54 55 ***************************************************************************/ 56 57 public int opApply (scope int delegate (ref Throwable) dg) 58 { 59 int result; 60 61 for (auto e = root; e !is null; e = e.next) 62 { 63 result = dg(e); 64 65 if (result) 66 { 67 break; 68 } 69 } 70 71 return result; 72 } 73 } 74 75 unittest 76 { 77 auto e1 = new Exception("1"); 78 auto e2 = new Exception("2", __FILE__, __LINE__, e1); 79 80 size_t counter; 81 foreach (e; ExceptionChain(e2)) 82 ++counter; 83 test (counter == 2); 84 } 85 86 /****************************************************************************** 87 88 Common code to implement reusable exception semantics - one that has 89 mutable message buffer where message data gets stored to 90 91 ******************************************************************************/ 92 93 public template ReusableExceptionImplementation() 94 { 95 import ocean.meta.types.Qualifiers; 96 import ocean.core.Buffer; 97 static import ocean.core.array.Mutation; 98 static import ocean.text.convert.Formatter; 99 static import ocean.text.convert.Integer_tango; 100 101 static assert (is(typeof(this) : Exception)); 102 103 /************************************************************************** 104 105 Fields used instead of `msg` for mutable messages. `Exception.msg` 106 has `istring` type thus can't be overwritten with new data 107 108 ***************************************************************************/ 109 110 protected Buffer!(char) reused_msg; 111 112 /*************************************************************************** 113 114 Constructs exception object with mutable buffer pre-allocated to length 115 `size` and other fields kept invalid. 116 117 Params: 118 size = initial size/length of mutable message buffer 119 120 ***************************************************************************/ 121 122 this (size_t size = 128) 123 { 124 this.reused_msg.length = size; 125 super(null); 126 } 127 128 /*************************************************************************** 129 130 Sets exception information for this instance. 131 132 Params: 133 msg = exception message 134 file = file where the exception has been thrown 135 line = line where the exception has been thrown 136 137 Returns: 138 this instance 139 140 ***************************************************************************/ 141 142 public typeof (this) set ( cstring msg, istring file = __FILE__, 143 long line = __LINE__ ) 144 { 145 ocean.core.array.Mutation.copy(this.reused_msg, msg); 146 this.msg = null; 147 this.file = file; 148 this.line = line; 149 return this; 150 } 151 152 /*************************************************************************** 153 154 Throws this instance if ok is false, 0 or null. 155 156 Params: 157 ok = condition to enforce 158 msg = exception message 159 file = file where the exception has been thrown 160 line = line where the exception has been thrown 161 162 Throws: 163 this instance if ok is false, 0 or null. 164 165 ***************************************************************************/ 166 167 public void enforce ( T ) ( T ok, lazy cstring msg, istring file = __FILE__, 168 long line = __LINE__ ) 169 { 170 if (!ok) 171 throw this.set(msg, file, line); 172 } 173 174 static if (is(typeof(Throwable.message))) 175 { 176 /************************************************************************** 177 178 Returns: 179 currently active exception message 180 181 ***************************************************************************/ 182 183 public override cstring message ( ) const 184 { 185 return this.msg[].ptr is null ? this.reused_msg[] : this.msg; 186 } 187 } 188 189 /************************************************************************** 190 191 Appends new substring to mutable exception message 192 193 Intended to be used in hosts that do dynamic formatting of the 194 error message and want to avoid repeating allocation with help 195 of ReusableException semantics 196 197 Leaves other fields untouched 198 199 Params: 200 msg = string to append to the message 201 202 Returns: 203 this instance 204 205 ***************************************************************************/ 206 207 public typeof (this) append ( cstring msg ) 208 { 209 ocean.core.array.Mutation.append(this.reused_msg, msg); 210 return this; 211 } 212 213 /************************************************************************** 214 215 Appends an integer to mutable exception message 216 217 Does not cause any appenditional allocations 218 219 Params: 220 num = number to be formatted 221 hex = optional, indicates that value needs to be formatted as hex 222 223 Returns: 224 this instance 225 226 **************************************************************************/ 227 228 public typeof (this) append ( long num, bool hex = false ) 229 { 230 char[long.max.stringof.length + 1] buff; 231 if (hex) 232 { 233 ocean.core.array.Mutation.append(this.reused_msg, "0x"[]); 234 ocean.core.array.Mutation.append( 235 this.reused_msg, 236 ocean.text.convert.Integer_tango.format(buff, num, "X")); 237 } 238 else 239 ocean.core.array.Mutation.append( 240 this.reused_msg, 241 ocean.text.convert.Integer_tango.format(buff, num)); 242 return this; 243 } 244 245 /************************************************************************** 246 247 Appends formatted string 248 249 Params: 250 fmt = string format to use 251 args = variadic args list to format 252 253 Returns: 254 this instance 255 256 **************************************************************************/ 257 258 public typeof (this) fmtAppend ( Args... ) ( cstring fmt, Args args ) 259 { 260 ocean.text.convert.Formatter.sformat(this.reused_msg, fmt, args); 261 return this; 262 } 263 } 264 265 /// 266 unittest 267 { 268 auto e = new SomeReusableException(100); 269 270 e.set("message"); 271 test (e.message() == "message"); 272 auto old_ptr = e.reused_msg[].ptr; 273 274 try 275 { 276 ocean.core.Enforce.enforce(e, false, "immutable"); 277 assert (false); 278 } 279 catch (SomeReusableException) { } 280 test (e.message() == "immutable"); 281 282 try 283 { 284 e.enforce(false, "longer message"); 285 } 286 catch (SomeReusableException) { } 287 test (e.message() == "longer message"); 288 test (old_ptr is e.reused_msg[].ptr); 289 290 try 291 { 292 e.badName("NAME", 42); 293 } 294 catch (SomeReusableException) { } 295 test (e.message() == "Wrong name (NAME) 0x2A 42"); 296 test (old_ptr is e.reused_msg[].ptr); 297 } 298 299 unittest 300 { 301 auto e = new SomeReusableException; 302 e.set("aaa").fmtAppend("{0}{0}", 42); 303 test!("==")(e.message(), "aaa4242"); 304 } 305 306 version (unittest) 307 { 308 private class SomeReusableException : Exception 309 { 310 void badName(istring name, uint id) 311 { 312 this.set("Wrong name (") 313 .append(name) 314 .append(") ") 315 .append(id, true) 316 .append(" ") 317 .append(id); 318 throw this; 319 } 320 321 mixin ReusableExceptionImplementation!(); 322 } 323 } 324 325 326 /****************************************************************************** 327 328 Common code to implement exception constructor, which takes a message as 329 a required parameter, and file and line with default value, and forward 330 it to the `super` constructor 331 332 ******************************************************************************/ 333 334 public template DefaultExceptionCtor() 335 { 336 import ocean.meta.types.Qualifiers; 337 338 public this (istring msg, istring file = __FILE__, 339 typeof(__LINE__) line = __LINE__) 340 { 341 super (msg, file, line); 342 } 343 } 344 345 version (unittest) 346 { 347 public class CardBoardException : Exception 348 { 349 mixin DefaultExceptionCtor; 350 } 351 } 352 353 /// 354 unittest 355 { 356 auto e = new CardBoardException("Transmogrification failed"); 357 try 358 { 359 throw e; 360 } 361 catch (CardBoardException e) 362 { 363 test!("==")(e.message(), "Transmogrification failed"[]); 364 test!("==")(e.file, __FILE__[]); 365 test!("==")(e.line, __LINE__ - 9); 366 } 367 } 368 369 // Check that the inherited `Exception.toString` is not 370 // shadowed by anything (like nested imports). 371 unittest 372 { 373 auto s = (new SomeReusableException).toString(); 374 }