You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1039 lines
38 KiB

4 years ago
4 years ago
  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. using System;
  26. using System.Collections.Generic;
  27. #if HAVE_BIG_INTEGER
  28. using System.Numerics;
  29. #endif
  30. using Newtonsoft.Json.Linq;
  31. using Newtonsoft.Json.Schema;
  32. using Newtonsoft.Json.Utilities;
  33. using System.Globalization;
  34. using System.Text.RegularExpressions;
  35. using System.IO;
  36. #if NET20
  37. using Newtonsoft.Json.Utilities.LinqBridge;
  38. #else
  39. using System.Linq;
  40. #endif
  41. namespace Newtonsoft.Json
  42. {
  43. /// <summary>
  44. /// <para>
  45. /// Represents a reader that provides <see cref="JsonSchema"/> validation.
  46. /// </para>
  47. /// <note type="caution">
  48. /// JSON Schema validation has been moved to its own package. See <see href="http://www.newtonsoft.com/jsonschema">http://www.newtonsoft.com/jsonschema</see> for more details.
  49. /// </note>
  50. /// </summary>
  51. [Obsolete("JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details.")]
  52. internal class JsonValidatingReader : JsonReader, IJsonLineInfo
  53. {
  54. private class SchemaScope
  55. {
  56. private readonly JTokenType _tokenType;
  57. private readonly IList<JsonSchemaModel> _schemas;
  58. private readonly Dictionary<string, bool> _requiredProperties;
  59. public string CurrentPropertyName { get; set; }
  60. public int ArrayItemCount { get; set; }
  61. public bool IsUniqueArray { get; }
  62. public IList<JToken> UniqueArrayItems { get; }
  63. public JTokenWriter CurrentItemWriter { get; set; }
  64. public IList<JsonSchemaModel> Schemas => _schemas;
  65. public Dictionary<string, bool> RequiredProperties => _requiredProperties;
  66. public JTokenType TokenType => _tokenType;
  67. public SchemaScope(JTokenType tokenType, IList<JsonSchemaModel> schemas)
  68. {
  69. _tokenType = tokenType;
  70. _schemas = schemas;
  71. _requiredProperties = schemas.SelectMany<JsonSchemaModel, string>(GetRequiredProperties).Distinct().ToDictionary(p => p, p => false);
  72. if (tokenType == JTokenType.Array && schemas.Any(s => s.UniqueItems))
  73. {
  74. IsUniqueArray = true;
  75. UniqueArrayItems = new List<JToken>();
  76. }
  77. }
  78. private IEnumerable<string> GetRequiredProperties(JsonSchemaModel schema)
  79. {
  80. if (schema?.Properties == null)
  81. {
  82. return Enumerable.Empty<string>();
  83. }
  84. return schema.Properties.Where(p => p.Value.Required).Select(p => p.Key);
  85. }
  86. }
  87. private readonly JsonReader _reader;
  88. private readonly Stack<SchemaScope> _stack;
  89. private JsonSchema _schema;
  90. private JsonSchemaModel _model;
  91. private SchemaScope _currentScope;
  92. /// <summary>
  93. /// Sets an event handler for receiving schema validation errors.
  94. /// </summary>
  95. public event ValidationEventHandler ValidationEventHandler;
  96. /// <summary>
  97. /// Gets the text value of the current JSON token.
  98. /// </summary>
  99. /// <value></value>
  100. public override object Value => _reader.Value;
  101. /// <summary>
  102. /// Gets the depth of the current token in the JSON document.
  103. /// </summary>
  104. /// <value>The depth of the current token in the JSON document.</value>
  105. public override int Depth => _reader.Depth;
  106. /// <summary>
  107. /// Gets the path of the current JSON token.
  108. /// </summary>
  109. public override string Path => _reader.Path;
  110. /// <summary>
  111. /// Gets the quotation mark character used to enclose the value of a string.
  112. /// </summary>
  113. /// <value></value>
  114. public override char QuoteChar
  115. {
  116. get { return _reader.QuoteChar; }
  117. protected internal set { }
  118. }
  119. /// <summary>
  120. /// Gets the type of the current JSON token.
  121. /// </summary>
  122. /// <value></value>
  123. public override JsonToken TokenType => _reader.TokenType;
  124. /// <summary>
  125. /// Gets the .NET type for the current JSON token.
  126. /// </summary>
  127. /// <value></value>
  128. public override Type ValueType => _reader.ValueType;
  129. private void Push(SchemaScope scope)
  130. {
  131. _stack.Push(scope);
  132. _currentScope = scope;
  133. }
  134. private SchemaScope Pop()
  135. {
  136. SchemaScope poppedScope = _stack.Pop();
  137. _currentScope = (_stack.Count != 0)
  138. ? _stack.Peek()
  139. : null;
  140. return poppedScope;
  141. }
  142. private IList<JsonSchemaModel> CurrentSchemas => _currentScope.Schemas;
  143. private static readonly IList<JsonSchemaModel> EmptySchemaList = new List<JsonSchemaModel>();
  144. private IList<JsonSchemaModel> CurrentMemberSchemas
  145. {
  146. get
  147. {
  148. if (_currentScope == null)
  149. {
  150. return new List<JsonSchemaModel>(new[] { _model });
  151. }
  152. if (_currentScope.Schemas == null || _currentScope.Schemas.Count == 0)
  153. {
  154. return EmptySchemaList;
  155. }
  156. switch (_currentScope.TokenType)
  157. {
  158. case JTokenType.None:
  159. return _currentScope.Schemas;
  160. case JTokenType.Object:
  161. {
  162. if (_currentScope.CurrentPropertyName == null)
  163. {
  164. throw new JsonReaderException("CurrentPropertyName has not been set on scope.");
  165. }
  166. IList<JsonSchemaModel> schemas = new List<JsonSchemaModel>();
  167. foreach (JsonSchemaModel schema in CurrentSchemas)
  168. {
  169. JsonSchemaModel propertySchema;
  170. if (schema.Properties != null && schema.Properties.TryGetValue(_currentScope.CurrentPropertyName, out propertySchema))
  171. {
  172. schemas.Add(propertySchema);
  173. }
  174. if (schema.PatternProperties != null)
  175. {
  176. foreach (KeyValuePair<string, JsonSchemaModel> patternProperty in schema.PatternProperties)
  177. {
  178. if (Regex.IsMatch(_currentScope.CurrentPropertyName, patternProperty.Key))
  179. {
  180. schemas.Add(patternProperty.Value);
  181. }
  182. }
  183. }
  184. if (schemas.Count == 0 && schema.AllowAdditionalProperties && schema.AdditionalProperties != null)
  185. {
  186. schemas.Add(schema.AdditionalProperties);
  187. }
  188. }
  189. return schemas;
  190. }
  191. case JTokenType.Array:
  192. {
  193. IList<JsonSchemaModel> schemas = new List<JsonSchemaModel>();
  194. foreach (JsonSchemaModel schema in CurrentSchemas)
  195. {
  196. if (!schema.PositionalItemsValidation)
  197. {
  198. if (schema.Items != null && schema.Items.Count > 0)
  199. {
  200. schemas.Add(schema.Items[0]);
  201. }
  202. }
  203. else
  204. {
  205. if (schema.Items != null && schema.Items.Count > 0)
  206. {
  207. if (schema.Items.Count > (_currentScope.ArrayItemCount - 1))
  208. {
  209. schemas.Add(schema.Items[_currentScope.ArrayItemCount - 1]);
  210. }
  211. }
  212. if (schema.AllowAdditionalItems && schema.AdditionalItems != null)
  213. {
  214. schemas.Add(schema.AdditionalItems);
  215. }
  216. }
  217. }
  218. return schemas;
  219. }
  220. case JTokenType.Constructor:
  221. return EmptySchemaList;
  222. default:
  223. throw new ArgumentOutOfRangeException("TokenType", "Unexpected token type: {0}".FormatWith(CultureInfo.InvariantCulture, _currentScope.TokenType));
  224. }
  225. }
  226. }
  227. private void RaiseError(string message, JsonSchemaModel schema)
  228. {
  229. IJsonLineInfo lineInfo = this;
  230. string exceptionMessage = (lineInfo.HasLineInfo())
  231. ? message + " Line {0}, position {1}.".FormatWith(CultureInfo.InvariantCulture, lineInfo.LineNumber, lineInfo.LinePosition)
  232. : message;
  233. OnValidationEvent(new JsonSchemaException(exceptionMessage, null, Path, lineInfo.LineNumber, lineInfo.LinePosition));
  234. }
  235. private void OnValidationEvent(JsonSchemaException exception)
  236. {
  237. ValidationEventHandler handler = ValidationEventHandler;
  238. if (handler != null)
  239. {
  240. handler(this, new ValidationEventArgs(exception));
  241. }
  242. else
  243. {
  244. throw exception;
  245. }
  246. }
  247. /// <summary>
  248. /// Initializes a new instance of the <see cref="JsonValidatingReader"/> class that
  249. /// validates the content returned from the given <see cref="JsonReader"/>.
  250. /// </summary>
  251. /// <param name="reader">The <see cref="JsonReader"/> to read from while validating.</param>
  252. public JsonValidatingReader(JsonReader reader)
  253. {
  254. ValidationUtils.ArgumentNotNull(reader, nameof(reader));
  255. _reader = reader;
  256. _stack = new Stack<SchemaScope>();
  257. }
  258. /// <summary>
  259. /// Gets or sets the schema.
  260. /// </summary>
  261. /// <value>The schema.</value>
  262. public JsonSchema Schema
  263. {
  264. get => _schema;
  265. set
  266. {
  267. if (TokenType != JsonToken.None)
  268. {
  269. throw new InvalidOperationException("Cannot change schema while validating JSON.");
  270. }
  271. _schema = value;
  272. _model = null;
  273. }
  274. }
  275. /// <summary>
  276. /// Gets the <see cref="JsonReader"/> used to construct this <see cref="JsonValidatingReader"/>.
  277. /// </summary>
  278. /// <value>The <see cref="JsonReader"/> specified in the constructor.</value>
  279. public JsonReader Reader => _reader;
  280. /// <summary>
  281. /// Changes the reader's state to <see cref="JsonReader.State.Closed"/>.
  282. /// If <see cref="JsonReader.CloseInput"/> is set to <c>true</c>, the underlying <see cref="JsonReader"/> is also closed.
  283. /// </summary>
  284. public override void Close()
  285. {
  286. base.Close();
  287. if (CloseInput)
  288. {
  289. _reader?.Close();
  290. }
  291. }
  292. private void ValidateNotDisallowed(JsonSchemaModel schema)
  293. {
  294. if (schema == null)
  295. {
  296. return;
  297. }
  298. JsonSchemaType? currentNodeType = GetCurrentNodeSchemaType();
  299. if (currentNodeType != null)
  300. {
  301. if (JsonSchemaGenerator.HasFlag(schema.Disallow, currentNodeType.GetValueOrDefault()))
  302. {
  303. RaiseError("Type {0} is disallowed.".FormatWith(CultureInfo.InvariantCulture, currentNodeType), schema);
  304. }
  305. }
  306. }
  307. private JsonSchemaType? GetCurrentNodeSchemaType()
  308. {
  309. switch (_reader.TokenType)
  310. {
  311. case JsonToken.StartObject:
  312. return JsonSchemaType.Object;
  313. case JsonToken.StartArray:
  314. return JsonSchemaType.Array;
  315. case JsonToken.Integer:
  316. return JsonSchemaType.Integer;
  317. case JsonToken.Float:
  318. return JsonSchemaType.Float;
  319. case JsonToken.String:
  320. return JsonSchemaType.String;
  321. case JsonToken.Boolean:
  322. return JsonSchemaType.Boolean;
  323. case JsonToken.Null:
  324. return JsonSchemaType.Null;
  325. default:
  326. return null;
  327. }
  328. }
  329. /// <summary>
  330. /// Reads the next JSON token from the underlying <see cref="JsonReader"/> as a <see cref="Nullable{T}"/> of <see cref="Int32"/>.
  331. /// </summary>
  332. /// <returns>A <see cref="Nullable{T}"/> of <see cref="Int32"/>.</returns>
  333. public override int? ReadAsInt32()
  334. {
  335. int? i = _reader.ReadAsInt32();
  336. ValidateCurrentToken();
  337. return i;
  338. }
  339. /// <summary>
  340. /// Reads the next JSON token from the underlying <see cref="JsonReader"/> as a <see cref="Byte"/>[].
  341. /// </summary>
  342. /// <returns>
  343. /// A <see cref="Byte"/>[] or <c>null</c> if the next JSON token is null.
  344. /// </returns>
  345. public override byte[] ReadAsBytes()
  346. {
  347. byte[] data = _reader.ReadAsBytes();
  348. ValidateCurrentToken();
  349. return data;
  350. }
  351. /// <summary>
  352. /// Reads the next JSON token from the underlying <see cref="JsonReader"/> as a <see cref="Nullable{T}"/> of <see cref="Decimal"/>.
  353. /// </summary>
  354. /// <returns>A <see cref="Nullable{T}"/> of <see cref="Decimal"/>.</returns>
  355. public override decimal? ReadAsDecimal()
  356. {
  357. decimal? d = _reader.ReadAsDecimal();
  358. ValidateCurrentToken();
  359. return d;
  360. }
  361. /// <summary>
  362. /// Reads the next JSON token from the underlying <see cref="JsonReader"/> as a <see cref="Nullable{T}"/> of <see cref="Double"/>.
  363. /// </summary>
  364. /// <returns>A <see cref="Nullable{T}"/> of <see cref="Double"/>.</returns>
  365. public override double? ReadAsDouble()
  366. {
  367. double? d = _reader.ReadAsDouble();
  368. ValidateCurrentToken();
  369. return d;
  370. }
  371. /// <summary>
  372. /// Reads the next JSON token from the underlying <see cref="JsonReader"/> as a <see cref="Nullable{T}"/> of <see cref="Boolean"/>.
  373. /// </summary>
  374. /// <returns>A <see cref="Nullable{T}"/> of <see cref="Boolean"/>.</returns>
  375. public override bool? ReadAsBoolean()
  376. {
  377. bool? b = _reader.ReadAsBoolean();
  378. ValidateCurrentToken();
  379. return b;
  380. }
  381. /// <summary>
  382. /// Reads the next JSON token from the underlying <see cref="JsonReader"/> as a <see cref="String"/>.
  383. /// </summary>
  384. /// <returns>A <see cref="String"/>. This method will return <c>null</c> at the end of an array.</returns>
  385. public override string ReadAsString()
  386. {
  387. string s = _reader.ReadAsString();
  388. ValidateCurrentToken();
  389. return s;
  390. }
  391. /// <summary>
  392. /// Reads the next JSON token from the underlying <see cref="JsonReader"/> as a <see cref="Nullable{T}"/> of <see cref="DateTime"/>.
  393. /// </summary>
  394. /// <returns>A <see cref="Nullable{T}"/> of <see cref="DateTime"/>. This method will return <c>null</c> at the end of an array.</returns>
  395. public override DateTime? ReadAsDateTime()
  396. {
  397. DateTime? dateTime = _reader.ReadAsDateTime();
  398. ValidateCurrentToken();
  399. return dateTime;
  400. }
  401. #if !NET20
  402. /// <summary>
  403. /// Reads the next JSON token from the underlying <see cref="JsonReader"/> as a <see cref="Nullable{T}"/> of <see cref="DateTimeOffset"/>.
  404. /// </summary>
  405. /// <returns>A <see cref="Nullable{T}"/> of <see cref="DateTimeOffset"/>.</returns>
  406. public override DateTimeOffset? ReadAsDateTimeOffset()
  407. {
  408. DateTimeOffset? dateTimeOffset = _reader.ReadAsDateTimeOffset();
  409. ValidateCurrentToken();
  410. return dateTimeOffset;
  411. }
  412. #endif
  413. /// <summary>
  414. /// Reads the next JSON token from the underlying <see cref="JsonReader"/>.
  415. /// </summary>
  416. /// <returns>
  417. /// <c>true</c> if the next token was read successfully; <c>false</c> if there are no more tokens to read.
  418. /// </returns>
  419. public override bool Read()
  420. {
  421. if (!_reader.Read())
  422. {
  423. return false;
  424. }
  425. if (_reader.TokenType == JsonToken.Comment)
  426. {
  427. return true;
  428. }
  429. ValidateCurrentToken();
  430. return true;
  431. }
  432. private void ValidateCurrentToken()
  433. {
  434. // first time validate has been called. build model
  435. if (_model == null)
  436. {
  437. JsonSchemaModelBuilder builder = new JsonSchemaModelBuilder();
  438. _model = builder.Build(_schema);
  439. if (!JsonTokenUtils.IsStartToken(_reader.TokenType))
  440. {
  441. Push(new SchemaScope(JTokenType.None, CurrentMemberSchemas));
  442. }
  443. }
  444. switch (_reader.TokenType)
  445. {
  446. case JsonToken.StartObject:
  447. ProcessValue();
  448. IList<JsonSchemaModel> objectSchemas = CurrentMemberSchemas.Where(ValidateObject).List();
  449. Push(new SchemaScope(JTokenType.Object, objectSchemas));
  450. WriteToken(CurrentSchemas);
  451. break;
  452. case JsonToken.StartArray:
  453. ProcessValue();
  454. IList<JsonSchemaModel> arraySchemas = CurrentMemberSchemas.Where(ValidateArray).List();
  455. Push(new SchemaScope(JTokenType.Array, arraySchemas));
  456. WriteToken(CurrentSchemas);
  457. break;
  458. case JsonToken.StartConstructor:
  459. ProcessValue();
  460. Push(new SchemaScope(JTokenType.Constructor, null));
  461. WriteToken(CurrentSchemas);
  462. break;
  463. case JsonToken.PropertyName:
  464. WriteToken(CurrentSchemas);
  465. foreach (JsonSchemaModel schema in CurrentSchemas)
  466. {
  467. ValidatePropertyName(schema);
  468. }
  469. break;
  470. case JsonToken.Raw:
  471. ProcessValue();
  472. break;
  473. case JsonToken.Integer:
  474. ProcessValue();
  475. WriteToken(CurrentMemberSchemas);
  476. foreach (JsonSchemaModel schema in CurrentMemberSchemas)
  477. {
  478. ValidateInteger(schema);
  479. }
  480. break;
  481. case JsonToken.Float:
  482. ProcessValue();
  483. WriteToken(CurrentMemberSchemas);
  484. foreach (JsonSchemaModel schema in CurrentMemberSchemas)
  485. {
  486. ValidateFloat(schema);
  487. }
  488. break;
  489. case JsonToken.String:
  490. ProcessValue();
  491. WriteToken(CurrentMemberSchemas);
  492. foreach (JsonSchemaModel schema in CurrentMemberSchemas)
  493. {
  494. ValidateString(schema);
  495. }
  496. break;
  497. case JsonToken.Boolean:
  498. ProcessValue();
  499. WriteToken(CurrentMemberSchemas);
  500. foreach (JsonSchemaModel schema in CurrentMemberSchemas)
  501. {
  502. ValidateBoolean(schema);
  503. }
  504. break;
  505. case JsonToken.Null:
  506. ProcessValue();
  507. WriteToken(CurrentMemberSchemas);
  508. foreach (JsonSchemaModel schema in CurrentMemberSchemas)
  509. {
  510. ValidateNull(schema);
  511. }
  512. break;
  513. case JsonToken.EndObject:
  514. WriteToken(CurrentSchemas);
  515. foreach (JsonSchemaModel schema in CurrentSchemas)
  516. {
  517. ValidateEndObject(schema);
  518. }
  519. Pop();
  520. break;
  521. case JsonToken.EndArray:
  522. WriteToken(CurrentSchemas);
  523. foreach (JsonSchemaModel schema in CurrentSchemas)
  524. {
  525. ValidateEndArray(schema);
  526. }
  527. Pop();
  528. break;
  529. case JsonToken.EndConstructor:
  530. WriteToken(CurrentSchemas);
  531. Pop();
  532. break;
  533. case JsonToken.Undefined:
  534. case JsonToken.Date:
  535. case JsonToken.Bytes:
  536. // these have no equivalent in JSON schema
  537. WriteToken(CurrentMemberSchemas);
  538. break;
  539. case JsonToken.None:
  540. // no content, do nothing
  541. break;
  542. default:
  543. throw new ArgumentOutOfRangeException();
  544. }
  545. }
  546. private void WriteToken(IList<JsonSchemaModel> schemas)
  547. {
  548. foreach (SchemaScope schemaScope in _stack)
  549. {
  550. bool isInUniqueArray = (schemaScope.TokenType == JTokenType.Array && schemaScope.IsUniqueArray && schemaScope.ArrayItemCount > 0);
  551. if (isInUniqueArray || schemas.Any(s => s.Enum != null))
  552. {
  553. if (schemaScope.CurrentItemWriter == null)
  554. {
  555. if (JsonTokenUtils.IsEndToken(_reader.TokenType))
  556. {
  557. continue;
  558. }
  559. schemaScope.CurrentItemWriter = new JTokenWriter();
  560. }
  561. schemaScope.CurrentItemWriter.WriteToken(_reader, false);
  562. // finished writing current item
  563. if (schemaScope.CurrentItemWriter.Top == 0 && _reader.TokenType != JsonToken.PropertyName)
  564. {
  565. JToken finishedItem = schemaScope.CurrentItemWriter.Token;
  566. // start next item with new writer
  567. schemaScope.CurrentItemWriter = null;
  568. if (isInUniqueArray)
  569. {
  570. if (schemaScope.UniqueArrayItems.Contains(finishedItem, JToken.EqualityComparer))
  571. {
  572. RaiseError("Non-unique array item at index {0}.".FormatWith(CultureInfo.InvariantCulture, schemaScope.ArrayItemCount - 1), schemaScope.Schemas.First(s => s.UniqueItems));
  573. }
  574. schemaScope.UniqueArrayItems.Add(finishedItem);
  575. }
  576. else if (schemas.Any(s => s.Enum != null))
  577. {
  578. foreach (JsonSchemaModel schema in schemas)
  579. {
  580. if (schema.Enum != null)
  581. {
  582. if (!schema.Enum.ContainsValue(finishedItem, JToken.EqualityComparer))
  583. {
  584. StringWriter sw = new StringWriter(CultureInfo.InvariantCulture);
  585. finishedItem.WriteTo(new JsonTextWriter(sw));
  586. RaiseError("Value {0} is not defined in enum.".FormatWith(CultureInfo.InvariantCulture, sw.ToString()), schema);
  587. }
  588. }
  589. }
  590. }
  591. }
  592. }
  593. }
  594. }
  595. private void ValidateEndObject(JsonSchemaModel schema)
  596. {
  597. if (schema == null)
  598. {
  599. return;
  600. }
  601. Dictionary<string, bool> requiredProperties = _currentScope.RequiredProperties;
  602. if (requiredProperties != null && requiredProperties.Values.Any(v => !v))
  603. {
  604. IEnumerable<string> unmatchedRequiredProperties = requiredProperties.Where(kv => !kv.Value).Select(kv => kv.Key);
  605. RaiseError("Required properties are missing from object: {0}.".FormatWith(CultureInfo.InvariantCulture, string.Join(", ", unmatchedRequiredProperties
  606. #if NET20
  607. .ToArray()
  608. #endif
  609. )), schema);
  610. }
  611. }
  612. private void ValidateEndArray(JsonSchemaModel schema)
  613. {
  614. if (schema == null)
  615. {
  616. return;
  617. }
  618. int arrayItemCount = _currentScope.ArrayItemCount;
  619. if (schema.MaximumItems != null && arrayItemCount > schema.MaximumItems)
  620. {
  621. RaiseError("Array item count {0} exceeds maximum count of {1}.".FormatWith(CultureInfo.InvariantCulture, arrayItemCount, schema.MaximumItems), schema);
  622. }
  623. if (schema.MinimumItems != null && arrayItemCount < schema.MinimumItems)
  624. {
  625. RaiseError("Array item count {0} is less than minimum count of {1}.".FormatWith(CultureInfo.InvariantCulture, arrayItemCount, schema.MinimumItems), schema);
  626. }
  627. }
  628. private void ValidateNull(JsonSchemaModel schema)
  629. {
  630. if (schema == null)
  631. {
  632. return;
  633. }
  634. if (!TestType(schema, JsonSchemaType.Null))
  635. {
  636. return;
  637. }
  638. ValidateNotDisallowed(schema);
  639. }
  640. private void ValidateBoolean(JsonSchemaModel schema)
  641. {
  642. if (schema == null)
  643. {
  644. return;
  645. }
  646. if (!TestType(schema, JsonSchemaType.Boolean))
  647. {
  648. return;
  649. }
  650. ValidateNotDisallowed(schema);
  651. }
  652. private void ValidateString(JsonSchemaModel schema)
  653. {
  654. if (schema == null)
  655. {
  656. return;
  657. }
  658. if (!TestType(schema, JsonSchemaType.String))
  659. {
  660. return;
  661. }
  662. ValidateNotDisallowed(schema);
  663. string value = _reader.Value.ToString();
  664. if (schema.MaximumLength != null && value.Length > schema.MaximumLength)
  665. {
  666. RaiseError("String '{0}' exceeds maximum length of {1}.".FormatWith(CultureInfo.InvariantCulture, value, schema.MaximumLength), schema);
  667. }
  668. if (schema.MinimumLength != null && value.Length < schema.MinimumLength)
  669. {
  670. RaiseError("String '{0}' is less than minimum length of {1}.".FormatWith(CultureInfo.InvariantCulture, value, schema.MinimumLength), schema);
  671. }
  672. if (schema.Patterns != null)
  673. {
  674. foreach (string pattern in schema.Patterns)
  675. {
  676. if (!Regex.IsMatch(value, pattern))
  677. {
  678. RaiseError("String '{0}' does not match regex pattern '{1}'.".FormatWith(CultureInfo.InvariantCulture, value, pattern), schema);
  679. }
  680. }
  681. }
  682. }
  683. private void ValidateInteger(JsonSchemaModel schema)
  684. {
  685. if (schema == null)
  686. {
  687. return;
  688. }
  689. if (!TestType(schema, JsonSchemaType.Integer))
  690. {
  691. return;
  692. }
  693. ValidateNotDisallowed(schema);
  694. object value = _reader.Value;
  695. if (schema.Maximum != null)
  696. {
  697. if (JValue.Compare(JTokenType.Integer, value, schema.Maximum) > 0)
  698. {
  699. RaiseError("Integer {0} exceeds maximum value of {1}.".FormatWith(CultureInfo.InvariantCulture, value, schema.Maximum), schema);
  700. }
  701. if (schema.ExclusiveMaximum && JValue.Compare(JTokenType.Integer, value, schema.Maximum) == 0)
  702. {
  703. RaiseError("Integer {0} equals maximum value of {1} and exclusive maximum is true.".FormatWith(CultureInfo.InvariantCulture, value, schema.Maximum), schema);
  704. }
  705. }
  706. if (schema.Minimum != null)
  707. {
  708. if (JValue.Compare(JTokenType.Integer, value, schema.Minimum) < 0)
  709. {
  710. RaiseError("Integer {0} is less than minimum value of {1}.".FormatWith(CultureInfo.InvariantCulture, value, schema.Minimum), schema);
  711. }
  712. if (schema.ExclusiveMinimum && JValue.Compare(JTokenType.Integer, value, schema.Minimum) == 0)
  713. {
  714. RaiseError("Integer {0} equals minimum value of {1} and exclusive minimum is true.".FormatWith(CultureInfo.InvariantCulture, value, schema.Minimum), schema);
  715. }
  716. }
  717. if (schema.DivisibleBy != null)
  718. {
  719. bool notDivisible;
  720. #if HAVE_BIG_INTEGER
  721. if (value is BigInteger)
  722. {
  723. // not that this will lose any decimal point on DivisibleBy
  724. // so manually raise an error if DivisibleBy is not an integer and value is not zero
  725. BigInteger i = (BigInteger)value;
  726. bool divisibleNonInteger = !Math.Abs(schema.DivisibleBy.Value - Math.Truncate(schema.DivisibleBy.Value)).Equals(0);
  727. if (divisibleNonInteger)
  728. {
  729. notDivisible = i != 0;
  730. }
  731. else
  732. {
  733. notDivisible = i % new BigInteger(schema.DivisibleBy.Value) != 0;
  734. }
  735. }
  736. else
  737. #endif
  738. {
  739. notDivisible = !IsZero(Convert.ToInt64(value, CultureInfo.InvariantCulture) % schema.DivisibleBy.GetValueOrDefault());
  740. }
  741. if (notDivisible)
  742. {
  743. RaiseError("Integer {0} is not evenly divisible by {1}.".FormatWith(CultureInfo.InvariantCulture, JsonConvert.ToString(value), schema.DivisibleBy), schema);
  744. }
  745. }
  746. }
  747. private void ProcessValue()
  748. {
  749. if (_currentScope != null && _currentScope.TokenType == JTokenType.Array)
  750. {
  751. _currentScope.ArrayItemCount++;
  752. foreach (JsonSchemaModel currentSchema in CurrentSchemas)
  753. {
  754. // if there is positional validation and the array index is past the number of item validation schemas and there are no additional items then error
  755. if (currentSchema != null
  756. && currentSchema.PositionalItemsValidation
  757. && !currentSchema.AllowAdditionalItems
  758. && (currentSchema.Items == null || _currentScope.ArrayItemCount - 1 >= currentSchema.Items.Count))
  759. {
  760. RaiseError("Index {0} has not been defined and the schema does not allow additional items.".FormatWith(CultureInfo.InvariantCulture, _currentScope.ArrayItemCount), currentSchema);
  761. }
  762. }
  763. }
  764. }
  765. private void ValidateFloat(JsonSchemaModel schema)
  766. {
  767. if (schema == null)
  768. {
  769. return;
  770. }
  771. if (!TestType(schema, JsonSchemaType.Float))
  772. {
  773. return;
  774. }
  775. ValidateNotDisallowed(schema);
  776. double value = Convert.ToDouble(_reader.Value, CultureInfo.InvariantCulture);
  777. if (schema.Maximum != null)
  778. {
  779. if (value > schema.Maximum)
  780. {
  781. RaiseError("Float {0} exceeds maximum value of {1}.".FormatWith(CultureInfo.InvariantCulture, JsonConvert.ToString(value), schema.Maximum), schema);
  782. }
  783. if (schema.ExclusiveMaximum && value == schema.Maximum)
  784. {
  785. RaiseError("Float {0} equals maximum value of {1} and exclusive maximum is true.".FormatWith(CultureInfo.InvariantCulture, JsonConvert.ToString(value), schema.Maximum), schema);
  786. }
  787. }
  788. if (schema.Minimum != null)
  789. {
  790. if (value < schema.Minimum)
  791. {
  792. RaiseError("Float {0} is less than minimum value of {1}.".FormatWith(CultureInfo.InvariantCulture, JsonConvert.ToString(value), schema.Minimum), schema);
  793. }
  794. if (schema.ExclusiveMinimum && value == schema.Minimum)
  795. {
  796. RaiseError("Float {0} equals minimum value of {1} and exclusive minimum is true.".FormatWith(CultureInfo.InvariantCulture, JsonConvert.ToString(value), schema.Minimum), schema);
  797. }
  798. }
  799. if (schema.DivisibleBy != null)
  800. {
  801. double remainder = FloatingPointRemainder(value, schema.DivisibleBy.GetValueOrDefault());
  802. if (!IsZero(remainder))
  803. {
  804. RaiseError("Float {0} is not evenly divisible by {1}.".FormatWith(CultureInfo.InvariantCulture, JsonConvert.ToString(value), schema.DivisibleBy), schema);
  805. }
  806. }
  807. }
  808. private static double FloatingPointRemainder(double dividend, double divisor)
  809. {
  810. return dividend - Math.Floor(dividend / divisor) * divisor;
  811. }
  812. private static bool IsZero(double value)
  813. {
  814. const double epsilon = 2.2204460492503131e-016;
  815. return Math.Abs(value) < 20.0 * epsilon;
  816. }
  817. private void ValidatePropertyName(JsonSchemaModel schema)
  818. {
  819. if (schema == null)
  820. {
  821. return;
  822. }
  823. string propertyName = Convert.ToString(_reader.Value, CultureInfo.InvariantCulture);
  824. if (_currentScope.RequiredProperties.ContainsKey(propertyName))
  825. {
  826. _currentScope.RequiredProperties[propertyName] = true;
  827. }
  828. if (!schema.AllowAdditionalProperties)
  829. {
  830. bool propertyDefinied = IsPropertyDefinied(schema, propertyName);
  831. if (!propertyDefinied)
  832. {
  833. RaiseError("Property '{0}' has not been defined and the schema does not allow additional properties.".FormatWith(CultureInfo.InvariantCulture, propertyName), schema);
  834. }
  835. }
  836. _currentScope.CurrentPropertyName = propertyName;
  837. }
  838. private bool IsPropertyDefinied(JsonSchemaModel schema, string propertyName)
  839. {
  840. if (schema.Properties != null && schema.Properties.ContainsKey(propertyName))
  841. {
  842. return true;
  843. }
  844. if (schema.PatternProperties != null)
  845. {
  846. foreach (string pattern in schema.PatternProperties.Keys)
  847. {
  848. if (Regex.IsMatch(propertyName, pattern))
  849. {
  850. return true;
  851. }
  852. }
  853. }
  854. return false;
  855. }
  856. private bool ValidateArray(JsonSchemaModel schema)
  857. {
  858. if (schema == null)
  859. {
  860. return true;
  861. }
  862. return (TestType(schema, JsonSchemaType.Array));
  863. }
  864. private bool ValidateObject(JsonSchemaModel schema)
  865. {
  866. if (schema == null)
  867. {
  868. return true;
  869. }
  870. return (TestType(schema, JsonSchemaType.Object));
  871. }
  872. private bool TestType(JsonSchemaModel currentSchema, JsonSchemaType currentType)
  873. {
  874. if (!JsonSchemaGenerator.HasFlag(currentSchema.Type, currentType))
  875. {
  876. RaiseError("Invalid type. Expected {0} but got {1}.".FormatWith(CultureInfo.InvariantCulture, currentSchema.Type, currentType), currentSchema);
  877. return false;
  878. }
  879. return true;
  880. }
  881. bool IJsonLineInfo.HasLineInfo()
  882. {
  883. IJsonLineInfo lineInfo = _reader as IJsonLineInfo;
  884. return lineInfo != null && lineInfo.HasLineInfo();
  885. }
  886. int IJsonLineInfo.LineNumber
  887. {
  888. get
  889. {
  890. IJsonLineInfo lineInfo = _reader as IJsonLineInfo;
  891. return (lineInfo != null) ? lineInfo.LineNumber : 0;
  892. }
  893. }
  894. int IJsonLineInfo.LinePosition
  895. {
  896. get
  897. {
  898. IJsonLineInfo lineInfo = _reader as IJsonLineInfo;
  899. return (lineInfo != null) ? lineInfo.LinePosition : 0;
  900. }
  901. }
  902. }
  903. }