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