1 /** 2 * Defines the exceptions that Jsonizer may throw. 3 * 4 * Authors: <a href="https://github.com/rcorre">rcorre</a> 5 * License: <a href="http://opensource.org/licenses/MIT">MIT</a> 6 * Copyright: Copyright © 2015, rcorre 7 * Date: 3/24/15 8 */ 9 module jsonizer.exceptions; 10 11 import std.json : JSONValue, JSON_TYPE; 12 import std.string : format, join; 13 import std.traits : ParameterTypeTuple, ParameterIdentifierTuple; 14 import std.typecons : staticIota; 15 import std.typetuple : staticMap; 16 17 /// Base class of any exception thrown by `jsonizer`. 18 class JsonizeException : Exception { 19 this(string msg) { 20 super(msg); 21 } 22 } 23 24 /// Thrown when `fromJSON` cannot convert a `JSONValue` into the requested type. 25 class JsonizeTypeException : Exception { 26 private enum fmt = 27 "fromJSON!%s expected json type to be one of %s but got json type %s. json input: %s"; 28 29 const { 30 TypeInfo targetType; /// Type jsonizer was attempting to deserialize to. 31 JSONValue json; /// The json value that was being deserialized 32 JSON_TYPE[] expected; /// The JSON_TYPEs that would have been acceptable 33 } 34 35 this(TypeInfo targetType, JSONValue json, JSON_TYPE[] expected ...) { 36 super(fmt.format(targetType, expected, json.type, json)); 37 38 this.targetType = targetType; 39 this.json = json; 40 this.expected = expected; 41 } 42 } 43 44 unittest { 45 import std.algorithm : canFind; 46 47 auto json = JSONValue(4.2f); 48 auto targetType = typeid(bool); 49 auto expected = [JSON_TYPE.TRUE, JSON_TYPE.FALSE]; 50 51 auto e = new JsonizeTypeException(targetType, json, JSON_TYPE.TRUE, JSON_TYPE.FALSE); 52 53 assert(e.json == json); 54 assert(e.targetType == targetType); 55 assert(e.expected == expected); 56 assert(e.msg.canFind("fromJSON!bool"), 57 "JsonizeTypeException should report type argument"); 58 assert(e.msg.canFind("TRUE") && e.msg.canFind("FALSE"), 59 "JsonizeTypeException should report all acceptable json types"); 60 } 61 62 /// Thrown when the keys of a json object fail to match up with the members of the target type. 63 class JsonizeMismatchException : JsonizeException { 64 private enum fmt = 65 "Failed to deserialize %s.\n" ~ 66 "Missing non-optional members: %s.\n" ~ 67 "Extra keys in json: %s.\n"; 68 69 const { 70 TypeInfo targetType; /// Type jsonizer was attempting to deserialize to. 71 string[] extraKeys; /// keys present in json that do not match up to a member. 72 string[] missingKeys; /// non-optional members that were not found in the json. 73 } 74 75 this(TypeInfo targetType, string[] extraKeys, string[] missingKeys) { 76 super(fmt.format(targetType, missingKeys, extraKeys)); 77 78 this.targetType = targetType; 79 this.extraKeys = extraKeys; 80 this.missingKeys = missingKeys; 81 } 82 } 83 84 unittest { 85 import std.algorithm : all, canFind; 86 import std.conv : to; 87 88 static class MyClass { } 89 90 auto targetType = typeid(MyClass); 91 auto extraKeys = [ "who", "what" ]; 92 auto missingKeys = [ "where" ]; 93 94 auto e = new JsonizeMismatchException(targetType, extraKeys, missingKeys); 95 assert(e.targetType == targetType); 96 assert(e.extraKeys == extraKeys); 97 assert(e.missingKeys == missingKeys); 98 assert(e.msg.canFind(targetType.to!string), 99 "JsonizeMismatchException should report type argument"); 100 assert(extraKeys.all!(x => e.msg.canFind(x)), 101 "JsonizeTypeException should report all extra keys"); 102 assert(missingKeys.all!(x => e.msg.canFind(x)), 103 "JsonizeTypeException should report all missing keys"); 104 } 105 106 /// Thrown when a type has no default constructor and the custom constructor cannot be fulfilled. 107 class JsonizeConstructorException : JsonizeException { 108 private enum fmt = 109 "%s has no default constructor, and none of the following constructors could be fulfilled: \n" ~ 110 "%s\n" ~ 111 "json object:\n %s"; 112 113 const { 114 TypeInfo targetType; /// Tye type jsonizer was attempting to deserialize to. 115 JSONValue json; /// The json value that was being deserialized 116 } 117 118 /// Construct and throw a `JsonizeConstructorException` 119 /// Params: 120 /// T = Type being deserialized 121 /// Ctors = constructors that were attempted 122 /// json = json object being deserialized 123 static void doThrow(T, Ctors ...)(JSONValue json) { 124 static if (Ctors.length > 0) { 125 auto signatures = [staticMap!(ctorSignature, Ctors)].join("\n"); 126 } 127 else { 128 auto signatures = "<no @jsonized constructors>"; 129 } 130 131 throw new JsonizeConstructorException(typeid(T), signatures, json); 132 } 133 134 private this(TypeInfo targetType, string ctorSignatures, JSONValue json) { 135 super(fmt.format(targetType, ctorSignatures, json)); 136 137 this.targetType = targetType; 138 this.json = json; 139 } 140 } 141 142 private: 143 // Represent the function signature of a constructor as a string. 144 template ctorSignature(alias ctor) { 145 alias params = ParameterIdentifierTuple!ctor; 146 alias types = ParameterTypeTuple!ctor; 147 148 // build a string "type1 param1, type2 param2, ..., typeN paramN" 149 static string paramString() { 150 string s = ""; 151 152 foreach(i ; staticIota!(0, params.length)) { 153 s ~= types[i].stringof ~ " " ~ params[i]; 154 155 static if (i < params.length - 1) { 156 s ~= ", "; 157 } 158 } 159 160 return s; 161 } 162 163 enum ctorSignature = "this(%s)".format(paramString); 164 } 165 166 unittest { 167 static class Foo { 168 this(string s, int i, float f) { } 169 } 170 171 assert(ctorSignature!(Foo.__ctor) == "this(string s, int i, float f)"); 172 }