Json.NET
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


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
}