diff --git a/Json-Schema-Test-Suite b/Json-Schema-Test-Suite index 5fb3d9f..67c7b4d 160000 --- a/Json-Schema-Test-Suite +++ b/Json-Schema-Test-Suite @@ -1 +1 @@ -Subproject commit 5fb3d9f1a1c4136f544fbd0029942ea559732f8e +Subproject commit 67c7b4dc84c783d204e0c3a9324a63f3f7d9444d diff --git a/Manatee.Json.Tests/DevTest.cs b/Manatee.Json.Tests/DevTest.cs index 56474a5..644d8ea 100644 --- a/Manatee.Json.Tests/DevTest.cs +++ b/Manatee.Json.Tests/DevTest.cs @@ -1,4 +1,5 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Manatee.Json.Tests { @@ -9,8 +10,10 @@ public class DevTest [TestMethod] public void Test1() { - var text = "{\"key\":4,int:\"no\"}"; - var json = JsonValue.Parse(text); + var text = "http://www.google.com/file/"; + var uri = new Uri(text); + + Console.WriteLine(uri); } } } \ No newline at end of file diff --git a/Manatee.Json.Tests/Schema/ArraySchemaTest.cs b/Manatee.Json.Tests/Schema/ArraySchemaTest.cs index 4c388e2..8c706ca 100644 --- a/Manatee.Json.Tests/Schema/ArraySchemaTest.cs +++ b/Manatee.Json.Tests/Schema/ArraySchemaTest.cs @@ -20,6 +20,17 @@ public void ValidateReturnsErrorOnNonArray() Assert.AreEqual(false, results.Valid); } [TestMethod] + public void ValidateReturnsErrorOnString() + { + var schema = new JsonSchema {Type = JsonSchemaTypeDefinition.Array}; + JsonValue json = "string"; + + var results = schema.Validate(json); + + Assert.AreNotEqual(0, results.Errors.Count()); + Assert.AreEqual(false, results.Valid); + } + [TestMethod] public void ValidateReturnsErrorOnTooFewItems() { var schema = new JsonSchema {Type = JsonSchemaTypeDefinition.Array, MinItems = 5}; diff --git a/Manatee.Json.Tests/Schema/TestSuite/JsonSchemaTestSuite.cs b/Manatee.Json.Tests/Schema/TestSuite/JsonSchemaTestSuite.cs index dc8f73b..c9593c2 100644 --- a/Manatee.Json.Tests/Schema/TestSuite/JsonSchemaTestSuite.cs +++ b/Manatee.Json.Tests/Schema/TestSuite/JsonSchemaTestSuite.cs @@ -13,9 +13,11 @@ public class JsonSchemaTestSuite private const string TestFolder = @"..\..\..\Json-Schema-Test-Suite\tests\draft4\"; private const string RemotesFolder = @"..\..\..\Json-Schema-Test-Suite\remotes\"; private static readonly Uri RemotesUri = new Uri(System.IO.Path.GetFullPath(RemotesFolder)); - private static readonly JsonSerializer _serializer; + private static readonly JsonSerializer Serializer; private int _failures; private int _passes; + private string _currentFile; + private string _currentTest; #pragma warning disable 649 private string _fileNameForDebugging; private string _testNameForDebugging; @@ -23,16 +25,18 @@ public class JsonSchemaTestSuite static JsonSchemaTestSuite() { - _serializer = new JsonSerializer(); + Serializer = new JsonSerializer(); } [TestMethod] public void RunSuite() { // uncomment and paste the filename of a test suite to debug it. - //_fileNameForDebugging = ""; + //_fileNameForDebugging = "ref.json"; // uncomment and paste the description of a test to debug it. - //_testNameForDebugging = "ref within ref valid"; + _testNameForDebugging = "object is invalid"; + + var ranAllTests = false; try { @@ -45,8 +49,19 @@ public void RunSuite() _RunFile(fileName); } + ranAllTests = true; Assert.AreEqual(0, _failures); } + catch + { + if (!ranAllTests) + { + _failures++; + Console.WriteLine(); + Console.WriteLine($"Failed on '{_currentTest}' in {_currentFile}"); + } + throw; + } finally { Console.WriteLine(); @@ -57,6 +72,7 @@ public void RunSuite() private void _RunFile(string fileName) { + _currentFile = fileName; if (fileName == _fileNameForDebugging) { System.Diagnostics.Debugger.Break(); @@ -65,7 +81,7 @@ private void _RunFile(string fileName) var contents = File.ReadAllText(fileName); var json = JsonValue.Parse(contents); - var testSets = _serializer.Deserialize>(json); + var testSets = Serializer.Deserialize>(json); foreach (var testSet in testSets) { @@ -78,6 +94,7 @@ private void _RunTestSet(SchemaTestSet testSet) { foreach (var test in testSet.Tests) { + _currentTest = test.Description; if (test.Description == _testNameForDebugging) { System.Diagnostics.Debugger.Break(); diff --git a/Manatee.Json.sln.DotSettings b/Manatee.Json.sln.DotSettings index f70fddb..1237c76 100644 --- a/Manatee.Json.sln.DotSettings +++ b/Manatee.Json.sln.DotSettings @@ -19,7 +19,7 @@ 200 <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="Private methods, properties, and events"><ElementKinds><Kind Name="METHOD" /><Kind Name="EVENT" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="Private methods"><ElementKinds><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="AaBb" /></Policy> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> True True diff --git a/Manatee.Json/Internal/GeneralExtensions.cs b/Manatee.Json/Internal/GeneralExtensions.cs index 0449427..f7fe727 100644 --- a/Manatee.Json/Internal/GeneralExtensions.cs +++ b/Manatee.Json/Internal/GeneralExtensions.cs @@ -256,5 +256,17 @@ public static bool IsNumber(this object value) value is long || value is ulong; } + public static Uri GetBase(this Uri uri) + { + Uri previous = uri; + do + { + uri = previous; + previous = uri.GetParentUri(); + } while (uri != previous); + + return uri; + } + } } \ No newline at end of file diff --git a/Manatee.Json/Schema/JsonSchema.cs b/Manatee.Json/Schema/JsonSchema.cs index 88f7ec6..986ef95 100644 --- a/Manatee.Json/Schema/JsonSchema.cs +++ b/Manatee.Json/Schema/JsonSchema.cs @@ -535,6 +535,7 @@ public virtual SchemaValidationResults Validate(JsonValue json, JsonValue root = var results = validators.Select(v => v.Validate(this, json, jValue)).ToList(); return new SchemaValidationResults(results); } + /// /// Builds an object from a . /// @@ -545,6 +546,14 @@ public virtual void FromJson(JsonValue json, JsonSerializer serializer) { var obj = json.Object; Id = obj.TryGetString("id"); + Uri uri; + var uriFolder = DocumentPath?.OriginalString.EndsWith("/") ?? true ? DocumentPath : DocumentPath?.GetParentUri(); + if (!Id.IsNullOrWhiteSpace() && + (Uri.TryCreate(Id, UriKind.Absolute, out uri) || Uri.TryCreate(uriFolder + Id, UriKind.Absolute, out uri))) + { + DocumentPath = uri; + JsonSchemaRegistry.Register(this); + } Schema = obj.TryGetString("$schema"); Title = obj.TryGetString("title"); Description = obj.TryGetString("description"); @@ -561,16 +570,14 @@ public virtual void FromJson(JsonValue json, JsonSerializer serializer) if (obj.ContainsKey("additionalItems")) { if (obj["additionalItems"].Type == JsonValueType.Boolean) - AdditionalItems = obj["additionalItems"].Boolean ? AdditionalItems.True : AdditionalItems.False; else - AdditionalItems = new AdditionalItems() { Definition = JsonSchemaFactory.FromJson(obj["additionalItems"], DocumentPath)}; + AdditionalItems = new AdditionalItems {Definition = JsonSchemaFactory.FromJson(obj["additionalItems"], DocumentPath) }; } MaxItems = (uint?) obj.TryGetNumber("maxItems"); MinItems = (uint?) obj.TryGetNumber("minItems"); if (obj.ContainsKey("items")) Items = JsonSchemaFactory.FromJson(obj["items"], DocumentPath); - UniqueItems = obj.TryGetBoolean("uniqueItems"); MaxProperties = (uint?) obj.TryGetNumber("maxProperties"); MinProperties = (uint?) obj.TryGetNumber("minProperties"); @@ -611,15 +618,14 @@ public virtual void FromJson(JsonValue json, JsonSerializer serializer) if (obj["additionalProperties"].Type == JsonValueType.Boolean) AdditionalProperties = obj["additionalProperties"].Boolean ? AdditionalProperties.True : AdditionalProperties.False; else - AdditionalProperties = new AdditionalProperties() { Definition = JsonSchemaFactory.FromJson(obj["additionalProperties"], DocumentPath)}; - + AdditionalProperties = new AdditionalProperties {Definition = JsonSchemaFactory.FromJson(obj["additionalProperties"], DocumentPath)}; } if (obj.ContainsKey("definitions")) { Definitions = new JsonSchemaTypeDefinitionCollection(); foreach (var defn in obj["definitions"].Object) { - var definition = new JsonSchemaTypeDefinition(defn.Key) { Definition = JsonSchemaFactory.FromJson(defn.Value, DocumentPath) }; + var definition = new JsonSchemaTypeDefinition(defn.Key) {Definition = JsonSchemaFactory.FromJson(defn.Value, DocumentPath) }; Definitions.Add(definition); } @@ -636,8 +642,7 @@ public virtual void FromJson(JsonValue json, JsonSerializer serializer) switch (v.Value.Type) { case JsonValueType.Object: - dependency = new SchemaDependency(v.Key, JsonSchemaFactory.FromJson(v.Value, DocumentPath)); - + dependency = new SchemaDependency(v.Key, JsonSchemaFactory.FromJson(v.Value, DocumentPath)); break; case JsonValueType.Array: dependency = new PropertyDependency(v.Key, v.Value.Array.Select(jv => jv.String)); diff --git a/Manatee.Json/Schema/JsonSchemaFactory.cs b/Manatee.Json/Schema/JsonSchemaFactory.cs index c1e198e..cba0f3d 100644 --- a/Manatee.Json/Schema/JsonSchemaFactory.cs +++ b/Manatee.Json/Schema/JsonSchemaFactory.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Manatee.Json.Internal; namespace Manatee.Json.Schema { @@ -56,7 +57,7 @@ public static IJsonSchema FromJson(JsonValue json, Uri documentPath = null) schema = new JsonSchemaCollection(); break; default: - throw new ArgumentOutOfRangeException("json.Type", "JSON Schema must be objects."); + throw new ArgumentOutOfRangeException(nameof(json.Type), "JSON Schema must be objects."); } schema.DocumentPath = documentPath; schema.FromJson(json, null); diff --git a/Manatee.Json/Schema/JsonSchemaReference.cs b/Manatee.Json/Schema/JsonSchemaReference.cs index ee5c818..4a6cb34 100644 --- a/Manatee.Json/Schema/JsonSchemaReference.cs +++ b/Manatee.Json/Schema/JsonSchemaReference.cs @@ -30,7 +30,7 @@ public class JsonSchemaReference : JsonSchema /// Exposes the schema at the references location. /// /// - /// The method must first be called. + /// The method must first be called. /// public IJsonSchema Resolved { get; private set; } @@ -54,12 +54,11 @@ public JsonSchemaReference(string reference) public override SchemaValidationResults Validate(JsonValue json, JsonValue root = null) { var jValue = root ?? ToJson(null); - var results = base.Validate(json, jValue); if (Resolved == null || root == null) - jValue = Resolve(jValue); + jValue = _Resolve(jValue); var refResults = Resolved?.Validate(json, jValue) ?? new SchemaValidationResults(null, "Error finding referenced schema."); - return new SchemaValidationResults(new[] {results, refResults}); + return new SchemaValidationResults(new[] {refResults}); } /// /// Builds an object from a . @@ -109,48 +108,52 @@ public override int GetHashCode() return Reference?.GetHashCode() ?? 0; } - private JsonValue Resolve(JsonValue root) + private JsonValue _Resolve(JsonValue root) { - var referenceParts = Reference.Split(new[] {'#'}, StringSplitOptions.None); - var address = referenceParts[0]; - var path = referenceParts.Length > 1 ? referenceParts[1] : string.Empty; + var referenceParts = Reference.Split(new[] { '#' }, StringSplitOptions.None); + var address = referenceParts[0].IsNullOrWhiteSpace() ? DocumentPath?.OriginalString : referenceParts[0]; + var fragment = referenceParts.Length > 1 ? referenceParts[1] : string.Empty; var jValue = root; if (!address.IsNullOrWhiteSpace()) { - Uri absolute; - if (DocumentPath != null && !Uri.TryCreate(address, UriKind.Absolute, out absolute)) + if (!Uri.TryCreate(address, UriKind.Absolute, out absolute)) { - DocumentPath = new Uri(DocumentPath.GetParentUri(), address); + address = Id + address; } - - Uri uri; - var search = JsonPathWith.Search("id"); - var allIds = new Stack(search.Evaluate(root ?? new JsonObject()).Select(jv => jv.String)); - while (allIds.Any() && !Uri.TryCreate(address, UriKind.Absolute, out uri)) + if (DocumentPath != null && !Uri.TryCreate(address, UriKind.Absolute, out absolute)) { - address = allIds.Pop() + address; + var uriFolder = DocumentPath.OriginalString.EndsWith("/") ? DocumentPath : DocumentPath.GetParentUri(); + absolute = new Uri(uriFolder, address); + address = absolute.OriginalString; } - - jValue = JsonSchemaRegistry.Get(DocumentPath?.ToString() ?? address).ToJson(null); + jValue = JsonSchemaRegistry.Get(address).ToJson(null); } if (jValue == null) return root; if (jValue == _rootJson) throw new ArgumentException("Cannot use a root reference as the base schema."); - Resolved = ResolveLocalReference(jValue, path, DocumentPath); + Resolved = _ResolveLocalReference(jValue, fragment, address.IsNullOrWhiteSpace() ? null : new Uri(address)); return jValue; } - private static IJsonSchema ResolveLocalReference(JsonValue root, string path, Uri documentPath) + private static IJsonSchema _ResolveLocalReference(JsonValue root, string path, Uri documentPath) { var properties = path.Split('/').Skip(1).ToList(); if (!properties.Any()) return JsonSchemaFactory.FromJson(root, documentPath); var value = root; foreach (var property in properties) { - var unescaped = Unescape(property); + var unescaped = _Unescape(property); if (value.Type == JsonValueType.Object) { if (!value.Object.ContainsKey(unescaped)) return null; + JsonValue id; + if (value.Object.TryGetValue("id", out id)) + { + Uri uri; + documentPath = Uri.TryCreate(id.String, UriKind.Absolute, out uri) + ? uri + : new Uri(documentPath, id.String); + } value = value.Object[unescaped]; } else if (value.Type == JsonValueType.Array) @@ -162,7 +165,7 @@ private static IJsonSchema ResolveLocalReference(JsonValue root, string path, Ur } return JsonSchemaFactory.FromJson(value, documentPath); } - private static string Unescape(string reference) + private static string _Unescape(string reference) { var unescaped = reference.Replace("~1", "/") .Replace("~0", "~"); diff --git a/Manatee.Json/Schema/JsonSchemaRegistry.cs b/Manatee.Json/Schema/JsonSchemaRegistry.cs index 8f6e0da..c8807dd 100644 --- a/Manatee.Json/Schema/JsonSchemaRegistry.cs +++ b/Manatee.Json/Schema/JsonSchemaRegistry.cs @@ -62,7 +62,7 @@ public static void Register(JsonSchema schema) if (schema.Id.IsNullOrWhiteSpace()) return; lock (_schemaLookup) { - _schemaLookup[schema.Id] = schema; + _schemaLookup[schema.DocumentPath.ToString()] = schema; } } diff --git a/Manatee.Json/Schema/Validators/TypeSchemaPropertyValidator.cs b/Manatee.Json/Schema/Validators/TypeSchemaPropertyValidator.cs index b933666..5e4386a 100644 --- a/Manatee.Json/Schema/Validators/TypeSchemaPropertyValidator.cs +++ b/Manatee.Json/Schema/Validators/TypeSchemaPropertyValidator.cs @@ -33,7 +33,7 @@ public SchemaValidationResults Validate(JsonSchema schema, JsonValue json, JsonV if (json.Number.IsInt() && Equals(schema.Type, JsonSchemaTypeDefinition.Integer)) break; return new SchemaValidationResults(string.Empty, $"Expected: {schema.Type.Name}; Actual: {json.Type}."); case JsonValueType.String: - if (JsonSchemaTypeDefinition.PrimitiveDefinitions.Any(d => d.Name == json.String)) break; + if (schema.Type.Name == json.String) break; if (Equals(schema.Type, JsonSchemaTypeDefinition.String)) break; return new SchemaValidationResults(string.Empty, $"Expected: {schema.Type.Name}; Actual: {json.Type}."); case JsonValueType.Boolean: diff --git a/Manatee.Json/UriExtensions.cs b/Manatee.Json/UriExtensions.cs index 0947c0a..7d12b52 100644 --- a/Manatee.Json/UriExtensions.cs +++ b/Manatee.Json/UriExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; namespace Manatee.Json { @@ -19,7 +20,11 @@ internal static class UriExtensions public static Uri GetParentUri(this Uri uri) { if (uri == null) throw new ArgumentNullException(nameof(uri)); - if (uri.Segments.Length == 1) throw new InvalidOperationException("Cannot get parent of root"); + if (!uri.IsAbsoluteUri && uri.Segments.Length == 1) throw new InvalidOperationException("Cannot get parent of root"); + + var path = uri.AbsoluteUri.Remove(uri.AbsoluteUri.Length - uri.Segments.Last().Length); + + return new Uri(path); string url = uri.ToString(); int index = url.Length - 1;