1 module text.json.Validation;
2 
3 import std.conv;
4 import std.datetime;
5 import std.json;
6 import std..string;
7 import std.traits;
8 import std.typecons;
9 import text.time.Convert;
10 
11 /**
12  * This value object represents a JSON object of name/value pairs.
13  * It is named `JSONObject` rather than `JsonObject` to comply with the naming of `JSONValue`.
14  * Its main purpose is to avoid conflicts with the `require` function introduced in D 2.082.0.
15  */
16 public struct JSONObject
17 {
18     public JSONValue[string] attributes;
19 
20     alias attributes this;
21 
22     /**
23     * Throws: JSONException when the name is no attribute of the object.
24     */
25     public inout(JSONValue) require(string name) inout pure @safe
26     {
27         if (name !in this.attributes)
28         {
29             throw new JSONException(format!`"%s" value required`(name));
30         }
31         return this.attributes[name];
32     }
33 
34     /**
35     * Throws: JSONException when the name is no attribute of the object
36     * or when the value is not of the required type.
37     */
38     public inout(T) require(T)(string name) inout
39     {
40         JSONValue value = require(name);
41 
42         try
43         {
44             return value.require!T;
45         }
46         catch (JSONException exception)
47         {
48             throw new JSONException(format!`"%s" value: %s`(name, exception.msg));
49         }
50     }
51 
52     /**
53     * Throws: JSONException when the name is an attribute of the object but the value is not of the required type.
54     */
55     public inout(T) require(T)(string name, lazy T fallback) inout
56     {
57         if (name !in this.attributes)
58         {
59             return fallback;
60         }
61         try
62         {
63             return this.attributes[name].require!T;
64         }
65         catch (JSONException exception)
66         {
67             throw new JSONException(format!`"%s" value: %s`(name, exception.msg));
68         }
69     }
70 
71     /**
72     * Throws: JSONException when the name is an attribute of the object but the value is not of the required type.
73     */
74     public inout(Nullable!T) require(U : Nullable!T, T)(string name) inout
75     {
76         if (name !in this.attributes)
77         {
78             return Nullable!T();
79         }
80         try
81         {
82             return Nullable!T(this.attributes[name].require!T);
83         }
84         catch (JSONException exception)
85         {
86             throw new JSONException(format!`"%s" value: %s`(name, exception.msg));
87         }
88     }
89 }
90 
91 /**
92  * Throws: JSONException when the value is not a JSON object.
93  */
94 public JSONObject requireObject(JSONValue value)
95 {
96     if (value.type != JSONType.object)
97     {
98         throw new JSONException(format!"object required but got %s"(value));
99     }
100     return JSONObject(value.object);
101 }
102 
103 /**
104  * Throws: JSONException when the value is not a JSON object with values of the required type.
105  */
106 public T[string] requireObject(T)(JSONValue object)
107 {
108     T[string] array = null;
109 
110     foreach (name, value; object.requireObject)
111     {
112         array[name] = value.require!T;
113     }
114 
115     return array;
116 }
117 
118 /**
119  * Throws: JSONException when the value is not a JSON array.
120  */
121 public JSONValue[] requireArray(JSONValue value)
122 {
123     if (value.type != JSONType.array)
124     {
125         throw new JSONException(format!"array required but got %s"(value));
126     }
127     return value.array;
128 }
129 
130 /**
131  * Throws: JSONException when the value is not a JSON array with elements of the required type.
132  */
133 public T[] requireArray(T)(JSONValue value)
134 {
135     T[] array = null;
136 
137     foreach (element; value.requireArray)
138     {
139         array ~= element.require!T;
140     }
141     return array;
142 }
143 
144 /**
145  * Throws: JSONException when the value is not a boolean.
146  */
147 public T require(T)(JSONValue value)
148     if (is(T == bool))
149 {
150     if (value.type != JSONType.true_ && value.type != JSONType.false_)
151     {
152         throw new JSONException(format!"boolean required but got %s"(value));
153     }
154     return value.type == JSONType.true_;
155 }
156 
157 /**
158  * Throws: JSONException when the value is not a number or when the number is not of the required type.
159  */
160 public T require(T)(JSONValue value)
161     if (isIntegral!T && !is(T == enum))
162 {
163     try
164     {
165         switch (value.type)
166         {
167             case JSONType.integer:
168                 return value.integer.to!T;
169             case JSONType.uinteger:
170                 return value.uinteger.to!T;
171             default:
172                 throw new JSONException(format!"integral value required but got %s"(value));
173         }
174     }
175     catch (ConvException)
176     {
177         throw new JSONException(format!"%s is not a valid value of type %s"(value, T.stringof));
178     }
179 }
180 
181 /**
182  * Throws: JSONException when the value is not of the required float type.
183  */
184 public T require(T)(JSONValue value)
185     if (isFloatingPoint!T)
186 {
187     switch (value.type)
188     {
189         case JSONType.integer:
190             return value.integer.to!T;
191         case JSONType.uinteger:
192             return value.uinteger.to!T;
193         case JSONType.float_:
194             return value.floating.to!T;
195         default:
196             throw new JSONException(format!"numeric value required but got %s"(value));
197     }
198 }
199 
200 /**
201  * Throws: JSONException when the value is not a string.
202  */
203 public T require(T : string)(JSONValue value)
204     if (!is(T == enum))
205 {
206     if (value.type != JSONType..string)
207     {
208         throw new JSONException(format!"string required but got %s"(value));
209     }
210     return value.str;
211 }
212 
213 /**
214  * Throws: JSONException when the value is not a member of the required enumeration.
215  */
216 public T require(T)(JSONValue value)
217     if (is(T == enum) && !is(T : bool))
218 {
219     try
220     {
221         return value.require!string.to!T;
222     }
223     catch (ConvException)
224     {
225         throw new JSONException(format!"%s required but got %s"(T.stringof, value));
226     }
227 }
228 
229 /**
230  * Throws: JSONException when the value is not a boolean.
231  */
232 public T require(T)(JSONValue value)
233     // Flag!"Name" is enum Flag : bool
234     if (is(T == enum) && is(T : bool))
235 {
236     return value.require!bool ? T.yes : T.no;
237 }
238 
239 /**
240  * Throws: JSONException when the value is not of the required date-time type.
241  */
242 public T require(T)(JSONValue value)
243     if (is(T == Date) || is(T == Duration) || is(T == SysTime) || is(T == TimeOfDay))
244 {
245     try
246     {
247         return Convert.to!T(value.require!string);
248     }
249     catch (DateTimeException exception)
250     {
251         throw new JSONException(exception.msg);
252     }
253 }