Code Coverage Statistics for Source File
Newtonsoft.Json\Schema\JsonSchemaBuilder.cs
Symbol Coverage: 90.86% (169 of 186)
Branch Coverage: 75.79% (72 of 95)
Cyclomatic Complexity Avg: 4.45 Max:29
Code Lines: 184
Symbol Coverage Trend
View:
L | V | Source |
---|---|---|
1 |
#region License |
|
2 |
// Copyright (c) 2007 James Newton-King |
|
3 |
// |
|
4 |
// Permission is hereby granted, free of charge, to any person |
|
5 |
// obtaining a copy of this software and associated documentation |
|
6 |
// files (the "Software"), to deal in the Software without |
|
7 |
// restriction, including without limitation the rights to use, |
|
8 |
// copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
9 |
// copies of the Software, and to permit persons to whom the |
|
10 |
// Software is furnished to do so, subject to the following |
|
11 |
// conditions: |
|
12 |
// |
|
13 |
// The above copyright notice and this permission notice shall be |
|
14 |
// included in all copies or substantial portions of the Software. |
|
15 |
// |
|
16 |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|
17 |
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
|
18 |
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|
19 |
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
|
20 |
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
|
21 |
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|
22 |
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
|
23 |
// OTHER DEALINGS IN THE SOFTWARE. |
|
24 |
#endregion |
|
25 |
||
26 |
using System; |
|
27 |
using System.Collections.Generic; |
|
28 |
using System.Linq; |
|
29 |
using System.Text; |
|
30 |
using System.Globalization; |
|
31 |
using Newtonsoft.Json.Utilities; |
|
32 |
using Newtonsoft.Json.Linq; |
|
33 |
||
34 |
namespace Newtonsoft.Json.Schema |
|
35 |
{ |
|
36 |
internal class JsonSchemaBuilder |
|
37 |
{ |
|
38 |
private JsonReader _reader; |
|
39 |
private readonly IList<JsonSchema> _stack; |
|
40 |
private readonly JsonSchemaResolver _resolver; |
|
41 |
private JsonSchema _currentSchema; |
|
42 |
||
43 |
private void Push(JsonSchema value) |
|
44 |
{ |
|
45 |
166 |
_currentSchema = value;
|
46 |
166 |
_stack.Add(value);
|
47 |
166 |
_resolver.LoadedSchemas.Add(value);
|
48 |
166 |
}
|
49 |
||
50 |
private JsonSchema Pop() |
|
51 |
{ |
|
52 |
165 |
JsonSchema poppedSchema = _currentSchema;
|
53 |
165 |
_stack.RemoveAt(_stack.Count - 1);
|
54 |
165 |
_currentSchema = _stack.LastOrDefault();
|
55 |
||
56 |
165 |
return poppedSchema;
|
57 |
165 |
}
|
58 |
||
59 |
private JsonSchema CurrentSchema |
|
60 |
{ |
|
61 |
414 |
get { return _currentSchema; }
|
62 |
} |
|
63 |
||
64 |
78 |
public JsonSchemaBuilder(JsonSchemaResolver resolver)
|
65 |
{ |
|
66 |
78 |
_stack = new List<JsonSchema>();
|
67 |
78 |
_resolver = resolver;
|
68 |
78 |
}
|
69 |
||
70 |
internal JsonSchema Parse(JsonReader reader) |
|
71 |
{ |
|
72 |
78 |
_reader = reader;
|
73 |
||
74 |
78 |
if (reader.TokenType == JsonToken.None)
|
75 |
78 |
_reader.Read();
|
76 |
||
77 |
78 |
return BuildSchema();
|
78 |
77 |
}
|
79 |
||
80 |
private JsonSchema BuildSchema() |
|
81 |
{ |
|
82 |
178 |
if (_reader.TokenType != JsonToken.StartObject)
|
83 |
0 |
throw new Exception("Expected StartObject while parsing schema object, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
|
84 |
||
85 |
178 |
_reader.Read();
|
86 |
// empty schema object |
|
87 |
178 |
if (_reader.TokenType == JsonToken.EndObject)
|
88 |
{ |
|
89 |
14 |
Push(new JsonSchema());
|
90 |
14 |
return Pop();
|
91 |
} |
|
92 |
||
93 |
164 |
string propertyName = Convert.ToString(_reader.Value, CultureInfo.InvariantCulture);
|
94 |
164 |
_reader.Read();
|
95 |
|
|
96 |
// schema reference |
|
97 |
164 |
if (propertyName == JsonSchemaConstants.ReferencePropertyName)
|
98 |
{ |
|
99 |
12 |
string id = (string)_reader.Value;
|
100 |
12 |
_reader.Read();
|
101 |
12 |
JsonSchema referencedSchema = _resolver.GetSchema(id);
|
102 |
12 |
if (referencedSchema == null)
|
103 |
1 |
throw new Exception("Could not resolve schema reference for Id '{0}'.".FormatWith(CultureInfo.InvariantCulture, id));
|
104 |
||
105 |
11 |
return referencedSchema;
|
106 |
} |
|
107 |
||
108 |
// regular ol' schema object |
|
109 |
152 |
Push(new JsonSchema());
|
110 |
||
111 |
152 |
ProcessSchemaProperty(propertyName);
|
112 |
||
113 |
349 |
while (_reader.Read() && _reader.TokenType != JsonToken.EndObject)
|
114 |
{ |
|
115 |
198 |
propertyName = Convert.ToString(_reader.Value, CultureInfo.InvariantCulture);
|
116 |
198 |
_reader.Read();
|
117 |
||
118 |
198 |
ProcessSchemaProperty(propertyName);
|
119 |
} |
|
120 |
||
121 |
151 |
return Pop();
|
122 |
176 |
}
|
123 |
||
124 |
private void ProcessSchemaProperty(string propertyName) |
|
125 |
{ |
|
126 |
350 |
switch (propertyName)
|
127 |
{ |
|
128 |
case JsonSchemaConstants.TypePropertyName: |
|
129 |
119 |
CurrentSchema.Type = ProcessType();
|
130 |
119 |
break;
|
131 |
case JsonSchemaConstants.IdPropertyName: |
|
132 |
19 |
CurrentSchema.Id = (string) _reader.Value;
|
133 |
19 |
break;
|
134 |
case JsonSchemaConstants.TitlePropertyName: |
|
135 |
1 |
CurrentSchema.Title = (string) _reader.Value;
|
136 |
1 |
break;
|
137 |
case JsonSchemaConstants.DescriptionPropertyName: |
|
138 |
37 |
CurrentSchema.Description = (string)_reader.Value;
|
139 |
37 |
break;
|
140 |
case JsonSchemaConstants.PropertiesPropertyName: |
|
141 |
29 |
ProcessProperties();
|
142 |
29 |
break;
|
143 |
case JsonSchemaConstants.ItemsPropertyName: |
|
144 |
23 |
ProcessItems();
|
145 |
22 |
break;
|
146 |
case JsonSchemaConstants.AdditionalPropertiesPropertyName: |
|
147 |
21 |
ProcessAdditionalProperties();
|
148 |
21 |
break;
|
149 |
case JsonSchemaConstants.OptionalPropertyName: |
|
150 |
5 |
CurrentSchema.Optional = (bool)_reader.Value;
|
151 |
5 |
break;
|
152 |
case JsonSchemaConstants.RequiresPropertyName: |
|
153 |
1 |
CurrentSchema.Requires = (string) _reader.Value;
|
154 |
1 |
break;
|
155 |
case JsonSchemaConstants.IdentityPropertyName: |
|
156 |
2 |
ProcessIdentity();
|
157 |
2 |
break;
|
158 |
case JsonSchemaConstants.MinimumPropertyName: |
|
159 |
3 |
CurrentSchema.Minimum = Convert.ToDouble(_reader.Value, CultureInfo.InvariantCulture);
|
160 |
3 |
break;
|
161 |
case JsonSchemaConstants.MaximumPropertyName: |
|
162 |
4 |
CurrentSchema.Maximum = Convert.ToDouble(_reader.Value, CultureInfo.InvariantCulture);
|
163 |
4 |
break;
|
164 |
case JsonSchemaConstants.MaximumLengthPropertyName: |
|
165 |
16 |
CurrentSchema.MaximumLength = Convert.ToInt32(_reader.Value, CultureInfo.InvariantCulture);
|
166 |
16 |
break;
|
167 |
case JsonSchemaConstants.MinimumLengthPropertyName: |
|
168 |
12 |
CurrentSchema.MinimumLength = Convert.ToInt32(_reader.Value, CultureInfo.InvariantCulture);
|
169 |
12 |
break;
|
170 |
case JsonSchemaConstants.MaximumItemsPropertyName: |
|
171 |
10 |
CurrentSchema.MaximumItems = Convert.ToInt32(_reader.Value, CultureInfo.InvariantCulture);
|
172 |
10 |
break;
|
173 |
case JsonSchemaConstants.MinimumItemsPropertyName: |
|
174 |
4 |
CurrentSchema.MinimumItems = Convert.ToInt32(_reader.Value, CultureInfo.InvariantCulture);
|
175 |
4 |
break;
|
176 |
case JsonSchemaConstants.MaximumDecimalsPropertyName: |
|
177 |
2 |
CurrentSchema.MaximumDecimals = Convert.ToInt32(_reader.Value, CultureInfo.InvariantCulture);
|
178 |
2 |
break;
|
179 |
case JsonSchemaConstants.DisallowPropertyName: |
|
180 |
5 |
CurrentSchema.Disallow = ProcessType();
|
181 |
5 |
break;
|
182 |
case JsonSchemaConstants.DefaultPropertyName: |
|
183 |
2 |
ProcessDefault();
|
184 |
2 |
break;
|
185 |
case JsonSchemaConstants.HiddenPropertyName: |
|
186 |
1 |
CurrentSchema.Hidden = (bool) _reader.Value;
|
187 |
1 |
break;
|
188 |
case JsonSchemaConstants.ReadOnlyPropertyName: |
|
189 |
1 |
CurrentSchema.ReadOnly = (bool) _reader.Value;
|
190 |
1 |
break;
|
191 |
case JsonSchemaConstants.FormatPropertyName: |
|
192 |
1 |
CurrentSchema.Format = (string) _reader.Value;
|
193 |
1 |
break;
|
194 |
case JsonSchemaConstants.PatternPropertyName: |
|
195 |
9 |
CurrentSchema.Pattern = (string) _reader.Value;
|
196 |
9 |
break;
|
197 |
case JsonSchemaConstants.OptionsPropertyName: |
|
198 |
1 |
ProcessOptions();
|
199 |
1 |
break;
|
200 |
case JsonSchemaConstants.EnumPropertyName: |
|
201 |
11 |
ProcessEnum();
|
202 |
11 |
break;
|
203 |
case JsonSchemaConstants.ExtendsPropertyName: |
|
204 |
11 |
ProcessExtends();
|
205 |
11 |
break;
|
206 |
default: |
|
207 |
0 |
_reader.Skip();
|
208 |
0 |
break;
|
209 |
} |
|
210 |
349 |
}
|
211 |
||
212 |
private void ProcessExtends() |
|
213 |
{ |
|
214 |
11 |
CurrentSchema.Extends = BuildSchema();
|
215 |
11 |
}
|
216 |
||
217 |
private void ProcessEnum() |
|
218 |
{ |
|
219 |
11 |
if (_reader.TokenType != JsonToken.StartArray)
|
220 |
0 |
throw new Exception("Expected StartArray token while parsing enum values, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
|
221 |
||
222 |
11 |
CurrentSchema.Enum = new List<JToken>();
|
223 |
||
224 |
42 |
while (_reader.Read() && _reader.TokenType != JsonToken.EndArray)
|
225 |
{ |
|
226 |
31 |
JToken value = JToken.ReadFrom(_reader);
|
227 |
31 |
CurrentSchema.Enum.Add(value);
|
228 |
} |
|
229 |
11 |
}
|
230 |
||
231 |
private void ProcessOptions() |
|
232 |
{ |
|
233 |
1 |
CurrentSchema.Options = new Dictionary<JToken, string>(new JTokenEqualityComparer());
|
234 |
||
235 |
1 |
switch (_reader.TokenType)
|
236 |
{ |
|
237 |
case JsonToken.StartArray: |
|
238 |
3 |
while (_reader.Read() && _reader.TokenType != JsonToken.EndArray)
|
239 |
{ |
|
240 |
2 |
if (_reader.TokenType != JsonToken.StartObject)
|
241 |
0 |
throw new Exception("Expect object token, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
|
242 |
||
243 |
2 |
string label = null;
|
244 |
2 |
JToken value = null;
|
245 |
||
246 |
6 |
while (_reader.Read() && _reader.TokenType != JsonToken.EndObject)
|
247 |
{ |
|
248 |
4 |
string propertyName = Convert.ToString(_reader.Value, CultureInfo.InvariantCulture);
|
249 |
4 |
_reader.Read();
|
250 |
||
251 |
4 |
switch (propertyName)
|
252 |
{ |
|
253 |
case JsonSchemaConstants.OptionValuePropertyName: |
|
254 |
2 |
value = JToken.ReadFrom(_reader);
|
255 |
2 |
break;
|
256 |
case JsonSchemaConstants.OptionLabelPropertyName: |
|
257 |
2 |
label = (string) _reader.Value;
|
258 |
2 |
break;
|
259 |
default: |
|
260 |
0 |
throw new Exception("Unexpected property in JSON schema option: {0}.".FormatWith(CultureInfo.InvariantCulture, propertyName));
|
261 |
} |
|
262 |
} |
|
263 |
||
264 |
2 |
if (value == null)
|
265 |
0 |
throw new Exception("No value specified for JSON schema option.");
|
266 |
||
267 |
2 |
if (CurrentSchema.Options.ContainsKey(value))
|
268 |
0 |
throw new Exception("Duplicate value in JSON schema option collection: {0}".FormatWith(CultureInfo.InvariantCulture, value));
|
269 |
||
270 |
2 |
CurrentSchema.Options.Add(value, label);
|
271 |
} |
|
272 |
1 |
break;
|
273 |
default: |
|
274 |
0 |
throw new Exception("Expected array token, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
|
275 |
} |
|
276 |
1 |
}
|
277 |
||
278 |
private void ProcessDefault() |
|
279 |
{ |
|
280 |
2 |
CurrentSchema.Default = JToken.ReadFrom(_reader);
|
281 |
2 |
}
|
282 |
||
283 |
private void ProcessIdentity() |
|
284 |
{ |
|
285 |
2 |
CurrentSchema.Identity = new List<string>();
|
286 |
||
287 |
2 |
switch (_reader.TokenType)
|
288 |
{ |
|
289 |
case JsonToken.String: |
|
290 |
1 |
CurrentSchema.Identity.Add(_reader.Value.ToString());
|
291 |
1 |
break;
|
292 |
case JsonToken.StartArray: |
|
293 |
3 |
while (_reader.Read() && _reader.TokenType != JsonToken.EndArray)
|
294 |
{ |
|
295 |
2 |
if (_reader.TokenType != JsonToken.String)
|
296 |
0 |
throw new Exception("Exception JSON property name string token, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
|
297 |
||
298 |
2 |
CurrentSchema.Identity.Add(_reader.Value.ToString());
|
299 |
} |
|
300 |
1 |
break;
|
301 |
default: |
|
302 |
0 |
throw new Exception("Expected array or JSON property name string token, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
|
303 |
} |
|
304 |
2 |
}
|
305 |
||
306 |
private void ProcessAdditionalProperties() |
|
307 |
{ |
|
308 |
21 |
if (_reader.TokenType == JsonToken.Boolean)
|
309 |
8 |
CurrentSchema.AllowAdditionalProperties = (bool)_reader.Value;
|
310 |
else |
|
311 |
13 |
CurrentSchema.AdditionalProperties = BuildSchema();
|
312 |
21 |
}
|
313 |
||
314 |
private void ProcessItems() |
|
315 |
{ |
|
316 |
23 |
CurrentSchema.Items = new List<JsonSchema>();
|
317 |
||
318 |
23 |
switch (_reader.TokenType)
|
319 |
{ |
|
320 |
case JsonToken.StartObject: |
|
321 |
20 |
CurrentSchema.Items.Add(BuildSchema());
|
322 |
19 |
break;
|
323 |
case JsonToken.StartArray: |
|
324 |
9 |
while (_reader.Read() && _reader.TokenType != JsonToken.EndArray)
|
325 |
{ |
|
326 |
6 |
CurrentSchema.Items.Add(BuildSchema());
|
327 |
} |
|
328 |
3 |
break;
|
329 |
default: |
|
330 |
0 |
throw new Exception("Expected array or JSON schema object token, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
|
331 |
} |
|
332 |
22 |
}
|
333 |
||
334 |
private void ProcessProperties() |
|
335 |
{ |
|
336 |
29 |
IDictionary<string, JsonSchema> properties = new Dictionary<string, JsonSchema>();
|
337 |
||
338 |
29 |
if (_reader.TokenType != JsonToken.StartObject)
|
339 |
0 |
throw new Exception("Expected StartObject token while parsing schema properties, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
|
340 |
||
341 |
79 |
while (_reader.Read() && _reader.TokenType != JsonToken.EndObject)
|
342 |
{ |
|
343 |
50 |
string propertyName = Convert.ToString(_reader.Value, CultureInfo.InvariantCulture);
|
344 |
50 |
_reader.Read();
|
345 |
||
346 |
50 |
if (properties.ContainsKey(propertyName))
|
347 |
0 |
throw new Exception("Property {0} has already been defined in schema.".FormatWith(CultureInfo.InvariantCulture, propertyName));
|
348 |
||
349 |
50 |
properties.Add(propertyName, BuildSchema());
|
350 |
} |
|
351 |
||
352 |
29 |
CurrentSchema.Properties = properties;
|
353 |
29 |
}
|
354 |
||
355 |
private JsonSchemaType? ProcessType() |
|
356 |
{ |
|
357 |
124 |
switch (_reader.TokenType)
|
358 |
{ |
|
359 |
case JsonToken.String: |
|
360 |
107 |
return MapType(_reader.Value.ToString());
|
361 |
case JsonToken.StartArray: |
|
362 |
// ensure type is in blank state before ORing values |
|
363 |
17 |
JsonSchemaType? type = JsonSchemaType.None;
|
364 |
||
365 |
46 |
while (_reader.Read() && _reader.TokenType != JsonToken.EndArray)
|
366 |
{ |
|
367 |
29 |
if (_reader.TokenType != JsonToken.String)
|
368 |
0 |
throw new Exception("Exception JSON schema type string token, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
|
369 |
||
370 |
29 |
type = type | MapType(_reader.Value.ToString());
|
371 |
} |
|
372 |
||
373 |
17 |
return type;
|
374 |
default: |
|
375 |
0 |
throw new Exception("Expected array or JSON schema type string token, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
|
376 |
} |
|
377 |
124 |
}
|
378 |
||
379 |
internal static JsonSchemaType MapType(string type) |
|
380 |
{ |
|
381 |
JsonSchemaType mappedType; |
|
382 |
136 |
if (!JsonSchemaConstants.JsonSchemaTypeMapping.TryGetValue(type, out mappedType))
|
383 |
0 |
throw new Exception("Invalid JSON schema type: {0}".FormatWith(CultureInfo.InvariantCulture, type));
|
384 |
||
385 |
136 |
return mappedType;
|
386 |
136 |
}
|
387 |
||
388 |
internal static string MapType(JsonSchemaType type) |
|
389 |
{ |
|
390 |
235 |
return JsonSchemaConstants.JsonSchemaTypeMapping.Single(kv => kv.Value == type).Key;
|
391 |
235 |
}
|
392 |
} |
|
393 |
} |