diff --git a/build/04_RunTests.bat b/build/04_RunTests.bat index 7a7369efd..c3179c101 100644 --- a/build/04_RunTests.bat +++ b/build/04_RunTests.bat @@ -1,3 +1,4 @@ +dotnet test "%~dp0/../src\NJsonSchema.Benchmark\NJsonSchema.Benchmark.csproj" -c Release || goto :error dotnet test "%~dp0/../src\NJsonSchema.Tests\NJsonSchema.Tests.csproj" -c Release || goto :error dotnet test "%~dp0/../src\NJsonSchema.CodeGeneration.Tests\NJsonSchema.CodeGeneration.Tests.csproj" -c Release || goto :error dotnet test "%~dp0/../src\NJsonSchema.CodeGeneration.CSharp.Tests\NJsonSchema.CodeGeneration.CSharp.Tests.csproj" -c Release || goto :error diff --git a/src/NJsonSchema.Benchmark/GeneratorPerformance.cs b/src/NJsonSchema.Benchmark/GeneratorPerformance.cs new file mode 100644 index 000000000..35169a204 --- /dev/null +++ b/src/NJsonSchema.Benchmark/GeneratorPerformance.cs @@ -0,0 +1,25 @@ +using BenchmarkDotNet.Attributes; +using NJsonSchema.Tests.Generation; +using System.Threading.Tasks; +using NJsonSchema.Infrastructure; + +using static NJsonSchema.Tests.Generation.XmlDocTests; + +namespace NJsonSchema.Benchmark +{ + public class GeneratorPerformance + { + private readonly XmlDocTests _tests; + + public GeneratorPerformance() + { + _tests = new XmlDocTests(); + } + + [Benchmark] + public async Task XmlDocTests() + { + await typeof(ClassWithInheritdoc).GetMethod("Bar").GetXmlSummaryAsync(); + } + } +} \ No newline at end of file diff --git a/src/NJsonSchema.Benchmark/GeneratorPerformanceTests.cs b/src/NJsonSchema.Benchmark/GeneratorPerformanceTests.cs new file mode 100644 index 000000000..b67eab77d --- /dev/null +++ b/src/NJsonSchema.Benchmark/GeneratorPerformanceTests.cs @@ -0,0 +1,65 @@ +using System.Diagnostics; +using System.Threading.Tasks; +using NBench; +using NJsonSchema.Infrastructure; +using Pro.NBench.xUnit.XunitExtensions; +using Xunit; +using Xunit.Abstractions; + +namespace NJsonSchema.Benchmark +{ + public class GeneratorPerformanceTests + { + private readonly GeneratorPerformance _generatorPerformance = new GeneratorPerformance(); + private Counter _counter; + + public GeneratorPerformanceTests(ITestOutputHelper output) + { + Trace.Listeners.Clear(); + Trace.Listeners.Add(new XunitTraceListener(output)); + } + + [PerfSetup] +#pragma warning disable xUnit1013 // Public method should be marked as test + public void Setup(BenchmarkContext context) +#pragma warning restore xUnit1013 // Public method should be marked as test + { + _counter = context.GetCounter("Iterations"); + } + + /// + /// Ensure that we can serialise at least 200 times per second (5ms). + /// + [NBenchFact] + [PerfBenchmark( + Description = "Xml Docs (with cache)", + NumberOfIterations = 3, + RunTimeMilliseconds = 1000, + RunMode = RunMode.Throughput, + TestMode = TestMode.Test)] + [CounterThroughputAssertion("Iterations", MustBe.GreaterThan, 200)] + public void XmlDocTestsWithCache() + { + _generatorPerformance.XmlDocTests().GetAwaiter().GetResult(); + _counter.Increment(); + } + + /// + /// Ensure that we can serialise at least 200 times per second (5ms). + /// + [NBenchFact] + [PerfBenchmark( + Description = "Xml Docs (without cache)", + NumberOfIterations = 3, + RunTimeMilliseconds = 1000, + RunMode = RunMode.Throughput, + TestMode = TestMode.Test)] + [CounterThroughputAssertion("Iterations", MustBe.GreaterThan, 200)] + public void XmlDocTestsWithoutCache() + { + XmlDocumentationExtensions.ClearCacheAsync().GetAwaiter().GetResult(); + _generatorPerformance.XmlDocTests().GetAwaiter().GetResult(); + _counter.Increment(); + } + } +} diff --git a/src/NJsonSchema.Benchmark/NJsonSchema.Benchmark.csproj b/src/NJsonSchema.Benchmark/NJsonSchema.Benchmark.csproj index b594854be..e2ecd95a9 100644 --- a/src/NJsonSchema.Benchmark/NJsonSchema.Benchmark.csproj +++ b/src/NJsonSchema.Benchmark/NJsonSchema.Benchmark.csproj @@ -22,6 +22,7 @@ + diff --git a/src/NJsonSchema.CodeGeneration.CSharp.Tests/GeneralGeneratorTests.cs b/src/NJsonSchema.CodeGeneration.CSharp.Tests/GeneralGeneratorTests.cs index 44742c959..5b6fa72ec 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp.Tests/GeneralGeneratorTests.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp.Tests/GeneralGeneratorTests.cs @@ -278,7 +278,7 @@ public async Task When_class_has_description_then_csharp_has_xml_comment() { //// Arrange var schema = await JsonSchema4.FromTypeAsync(); - schema.Description = "ClassDesc."; + schema.ActualSchema.Description = "ClassDesc."; var generator = new CSharpGenerator(schema); //// Act @@ -295,7 +295,7 @@ public async Task When_property_has_description_then_csharp_has_xml_comment() { //// Arrange var schema = await JsonSchema4.FromTypeAsync(); - schema.Properties["Class"].Description = "PropertyDesc."; + schema.ActualProperties["Class"].Description = "PropertyDesc."; var generator = new CSharpGenerator(schema, new CSharpGeneratorSettings { ClassStyle = CSharpClassStyle.Poco }); //// Act diff --git a/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj b/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj index 153412140..793252fb0 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj +++ b/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj @@ -2,7 +2,7 @@ netstandard1.3;net451 JSON Schema reader, generator and validator for .NET - 9.10.73 + 9.10.74 json schema validation generator .net Copyright © Rico Suter, 2017 https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md diff --git a/src/NJsonSchema.CodeGeneration.TypeScript.Tests/AbstractGenerationTests.cs b/src/NJsonSchema.CodeGeneration.TypeScript.Tests/AbstractGenerationTests.cs index bda9c81fa..74c8b5a8c 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript.Tests/AbstractGenerationTests.cs +++ b/src/NJsonSchema.CodeGeneration.TypeScript.Tests/AbstractGenerationTests.cs @@ -19,6 +19,7 @@ public async Task When_class_is_abstract_then_is_abstract_TypeScript_keyword_is_ { /// Arrange var schema = await JsonSchema4.FromTypeAsync(); + var json = schema.ToJson(); /// Act var generator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings { TypeScriptVersion = 2.0m }); @@ -26,6 +27,10 @@ public async Task When_class_is_abstract_then_is_abstract_TypeScript_keyword_is_ /// Assert Assert.Contains("export abstract class AbstractClass", code); + + Assert.Contains("base: string", code); + Assert.Contains("super: string", code); + Assert.Contains("foo: string", code); } public class ContainerClass @@ -53,12 +58,12 @@ public async Task When_property_is_required_and_abstract_then_it_is_not_instanti [JsonConverter(typeof(JsonInheritanceConverter))] public class BaseClass { - + public string Base { get; set; } } public class SuperClass : AbstractClass { - + public string Super { get; set; } } [Fact] diff --git a/src/NJsonSchema.CodeGeneration.TypeScript.Tests/TypeScriptGeneratorTests.cs b/src/NJsonSchema.CodeGeneration.TypeScript.Tests/TypeScriptGeneratorTests.cs index 844253b86..5ca3fddbc 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript.Tests/TypeScriptGeneratorTests.cs +++ b/src/NJsonSchema.CodeGeneration.TypeScript.Tests/TypeScriptGeneratorTests.cs @@ -154,7 +154,9 @@ public async Task When_property_has_description_then_csharp_has_xml_comment() { //// Arrange var schema = await JsonSchema4.FromTypeAsync(); - schema.Properties["Class"].Description = "PropertyDesc."; + schema.ActualProperties["Class"].Description = "PropertyDesc."; + var json = schema.ToJson(); + var generator = new TypeScriptGenerator(schema); //// Act diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/NJsonSchema.CodeGeneration.TypeScript.csproj b/src/NJsonSchema.CodeGeneration.TypeScript/NJsonSchema.CodeGeneration.TypeScript.csproj index 974bf15e4..d116b402d 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/NJsonSchema.CodeGeneration.TypeScript.csproj +++ b/src/NJsonSchema.CodeGeneration.TypeScript/NJsonSchema.CodeGeneration.TypeScript.csproj @@ -2,7 +2,7 @@ netstandard1.3;net451 JSON Schema reader, generator and validator for .NET - 9.10.73 + 9.10.74 json schema validation generator .net Copyright © Rico Suter, 2017 https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md diff --git a/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs b/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs index 1e3eee897..416153c96 100644 --- a/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs +++ b/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs @@ -33,7 +33,7 @@ protected ClassTemplateModelBase(TypeResolverBase resolver, JsonSchema4 schema, public abstract string ClassName { get; } /// Gets or sets a value indicating whether the type is abstract. - public bool IsAbstract => _schema.IsAbstract; + public bool IsAbstract => _schema.ActualTypeSchema.IsAbstract; /// Gets the property extension data. public IDictionary ExtensionData => _schema.ExtensionData; @@ -49,7 +49,7 @@ public class DerivedClassModel { internal DerivedClassModel(string typeName, JsonSchema4 schema, OpenApiDiscriminator discriminator, TypeResolverBase resolver) { - var mapping = discriminator.Mapping.SingleOrDefault(m => m.Value.ActualSchema == schema.ActualSchema); + var mapping = discriminator.Mapping.SingleOrDefault(m => m.Value.ActualTypeSchema == schema.ActualTypeSchema); ClassName = resolver.GetOrGenerateTypeName(schema, typeName); IsAbstract = schema.ActualTypeSchema.IsAbstract; diff --git a/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj b/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj index 7a93afb1e..61e425979 100644 --- a/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj +++ b/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj @@ -2,7 +2,7 @@ netstandard1.3;net451 JSON Schema reader, generator and validator for .NET - 9.10.73 + 9.10.74 json schema validation generator .net Copyright © Rico Suter, 2017 https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md diff --git a/src/NJsonSchema.Tests/Conversion/ArrayTypeToSchemaTests.cs b/src/NJsonSchema.Tests/Conversion/ArrayTypeToSchemaTests.cs index 800a5570a..4b11814a0 100644 --- a/src/NJsonSchema.Tests/Conversion/ArrayTypeToSchemaTests.cs +++ b/src/NJsonSchema.Tests/Conversion/ArrayTypeToSchemaTests.cs @@ -32,7 +32,7 @@ public async Task When_converting_type_inheriting_from_dictionary_then_it_should var data = schema.ToJson(); //// Assert - Assert.Equal(JsonObjectType.Object, schema.Type); + Assert.Equal(JsonObjectType.Object, schema.ActualTypeSchema.Type); Assert.DoesNotContain("Foo", json); Assert.DoesNotContain("foo", json); } diff --git a/src/NJsonSchema.Tests/Generation/AttributeGenerationTests.cs b/src/NJsonSchema.Tests/Generation/AttributeGenerationTests.cs index 9f78ef448..20ea4663f 100644 --- a/src/NJsonSchema.Tests/Generation/AttributeGenerationTests.cs +++ b/src/NJsonSchema.Tests/Generation/AttributeGenerationTests.cs @@ -188,5 +188,25 @@ public class AttributeTestClass [ReadOnly(true)] public bool ReadOnly { get; set; } } + + public class ClassWithTypedRange + { + [Range(typeof(decimal), "0", "1")] + public decimal Foo { get; set; } + } + + [Fact] + public async Task When_range_has_type_and_strings_then_it_is_processed_correctly() + { + //// Arrange + + //// Act + var schema = await JsonSchema4.FromTypeAsync(); + var property = schema.Properties["Foo"]; + + //// Assert + Assert.Equal(0.0m, property.Minimum); + Assert.Equal(1.0m, property.Maximum); + } } } \ No newline at end of file diff --git a/src/NJsonSchema.Tests/Generation/ExceptionTypeTests.cs b/src/NJsonSchema.Tests/Generation/ExceptionTypeTests.cs index c2c7d65cc..4e3afc741 100644 --- a/src/NJsonSchema.Tests/Generation/ExceptionTypeTests.cs +++ b/src/NJsonSchema.Tests/Generation/ExceptionTypeTests.cs @@ -24,8 +24,8 @@ public async Task When_exception_schema_is_generated_then_special_properties_are var exceptionSchema = schema.InheritedSchema.ActualSchema; //// Assert - Assert.True(schema.Properties.ContainsKey("foo")); - Assert.True(exceptionSchema.Properties.ContainsKey("InnerException")); + Assert.True(schema.ActualProperties.ContainsKey("foo")); + Assert.True(exceptionSchema.ActualProperties.ContainsKey("InnerException")); } } } diff --git a/src/NJsonSchema.Tests/Generation/InheritanceTests.cs b/src/NJsonSchema.Tests/Generation/InheritanceTests.cs index e7eb9a9c8..9b1157e12 100644 --- a/src/NJsonSchema.Tests/Generation/InheritanceTests.cs +++ b/src/NJsonSchema.Tests/Generation/InheritanceTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Threading.Tasks; @@ -88,9 +89,9 @@ public async Task When_generating_type_with_inheritance_then_allOf_has_one_item( var schema = await JsonSchema4.FromTypeAsync(); //// Assert - Assert.NotNull(schema.Properties["Class"]); + Assert.NotNull(schema.ActualProperties["Class"]); - Assert.Equal(1, schema.AllOf.Count); + Assert.Equal(2, schema.AllOf.Count); Assert.Contains(schema.Definitions, d => d.Key == "Person"); Assert.NotNull(schema.AllOf.First().ActualSchema.Properties["Name"]); } @@ -298,10 +299,45 @@ public async Task Existing_non_string_property_cant_be_discriminant() //// Arrange //// Act - Task getSchema() => JsonSchema4.FromTypeAsync(); + Task GetSchema() => JsonSchema4.FromTypeAsync(); //// Assert - await Assert.ThrowsAsync(getSchema); + await Assert.ThrowsAsync(GetSchema); + } + + public class Foo + { + public Bar Bar { get; set; } + } + + public class Bar : Dictionary + { + public string Baz { get; set; } + } + + [Fact] + public async Task When_class_inherits_from_dictionary_then_allOf_contains_base_dictionary_schema_and_actual_schema() + { + //// Arrange + var settings = new JsonSchemaGeneratorSettings + { + SchemaType = SchemaType.OpenApi3 + }; + + //// Act + var schema = await JsonSchema4.FromTypeAsync(settings); + var json = schema.ToJson(); + + //// Assert + var bar = schema.Definitions["Bar"]; + + Assert.Equal(2, bar.AllOf.Count); + + Assert.Equal(bar.AllOf.Last(), bar.ActualTypeSchema); + Assert.Equal(bar.AllOf.First(), bar.InheritedSchema); + + Assert.True(bar.AllOf.First().IsDictionary); // base class (dictionary) + Assert.True(bar.AllOf.Last().ActualProperties.Any()); // actual class } } } diff --git a/src/NJsonSchema.Tests/Generation/SchemaGenerationTests.cs b/src/NJsonSchema.Tests/Generation/SchemaGenerationTests.cs index b9bebe982..dd2f07bb9 100644 --- a/src/NJsonSchema.Tests/Generation/SchemaGenerationTests.cs +++ b/src/NJsonSchema.Tests/Generation/SchemaGenerationTests.cs @@ -152,6 +152,25 @@ public async Task When_property_is_object_then_it_should_not_be_a_dictonary_but_ Assert.False(property.IsDictionary); } + public class ClassWithStaticProperty + { + public static string Foo { get; set; } + + public string Bar { get; set; } + } + + [Fact] + public async Task When_property_is_static_then_it_is_ignored() + { + /// Act + var schema = await JsonSchema4.FromTypeAsync(); + var json = schema.ToJson(); + + /// Assert + Assert.Equal(1, schema.ActualProperties.Count); + Assert.True(schema.ActualProperties.ContainsKey("Bar")); + } + // Used as demo for https://github.com/swagger-api/swagger-ui/issues/1056 //public class TestClass diff --git a/src/NJsonSchema.Tests/Generation/XmlDocTests.cs b/src/NJsonSchema.Tests/Generation/XmlDocTests.cs index ec10d8767..d6de7103d 100644 --- a/src/NJsonSchema.Tests/Generation/XmlDocTests.cs +++ b/src/NJsonSchema.Tests/Generation/XmlDocTests.cs @@ -118,7 +118,7 @@ public async Task When_xml_doc_is_missing_then_summary_is_missing() //// Assert Assert.Empty(summary); } - + public abstract class BaseBaseClass { /// Foo. diff --git a/src/NJsonSchema.Yaml/NJsonSchema.Yaml.csproj b/src/NJsonSchema.Yaml/NJsonSchema.Yaml.csproj index a96b7e705..d03a5670f 100644 --- a/src/NJsonSchema.Yaml/NJsonSchema.Yaml.csproj +++ b/src/NJsonSchema.Yaml/NJsonSchema.Yaml.csproj @@ -2,7 +2,7 @@ netstandard1.3;net45 JSON Schema reader, generator and validator for .NET - 9.10.73 + 9.10.74 json schema validation generator .net Copyright © Rico Suter, 2017 https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md diff --git a/src/NJsonSchema/Generation/JsonSchemaGenerator.cs b/src/NJsonSchema/Generation/JsonSchemaGenerator.cs index bbb7ef276..6335cfb4d 100644 --- a/src/NJsonSchema/Generation/JsonSchemaGenerator.cs +++ b/src/NJsonSchema/Generation/JsonSchemaGenerator.cs @@ -146,9 +146,7 @@ public virtual async Task GenerateAsync(Type type, IEnumerableGenerates the properties for the given type and schema. - /// The type of the schema type. /// The types. + /// The type description. /// The properties /// The schema resolver. /// The task. - protected virtual async Task GenerateObjectAsync( - Type type, TSchemaType schema, JsonSchemaResolver schemaResolver) - where TSchemaType : JsonSchema4, new() + protected virtual async Task GenerateObjectAsync(Type type, + JsonTypeDescription typeDescription, JsonSchema4 schema, JsonSchemaResolver schemaResolver) { schemaResolver.AddSchema(type, false, schema); + var rootSchema = schema; + + var hasInheritance = await GenerateInheritanceAsync(type, schema, schemaResolver).ConfigureAwait(false); + if (hasInheritance) + { + var actualSchema = new JsonSchema4(); + schema.AllOf.Add(actualSchema); + schema = actualSchema; + } + typeDescription.ApplyType(schema); + schema.Description = await type.GetTypeInfo().GetDescriptionAsync(type.GetTypeInfo().GetCustomAttributes()).ConfigureAwait(false); + schema.AllowAdditionalProperties = false; schema.IsAbstract = type.GetTypeInfo().IsAbstract; - await GeneratePropertiesAndInheritanceAsync(type, schema, schemaResolver).ConfigureAwait(false); - await ApplyAdditionalPropertiesAsync(type, schema, schemaResolver); + await GeneratePropertiesAsync(type, schema, schemaResolver).ConfigureAwait(false); + await ApplyAdditionalPropertiesAsync(type, schema, schemaResolver).ConfigureAwait(false); + + GenerateInheritanceDiscriminator(type, rootSchema); if (Settings.GenerateKnownTypes) await GenerateKnownTypesAsync(type, schemaResolver).ConfigureAwait(false); @@ -345,7 +356,7 @@ private async Task ApplyAdditionalPropertiesAsync(Type type, TSchem var genericTypeArguments = extensionDataProperty.PropertyType.GetGenericTypeArguments(); var extensionDataPropertyType = genericTypeArguments.Length == 2 ? genericTypeArguments[1] : typeof(object); - schema.AdditionalPropertiesSchema = await GenerateWithReferenceAndNullabilityAsync(extensionDataPropertyType, null, schemaResolver); + schema.AdditionalPropertiesSchema = await GenerateWithReferenceAndNullabilityAsync(extensionDataPropertyType, null, schemaResolver).ConfigureAwait(false); } else schema.AllowAdditionalProperties = false; @@ -510,7 +521,7 @@ private async Task GenerateDictionaryAsync(TSchemaType schema, Type schema.AllowAdditionalProperties = true; } - private async Task GeneratePropertiesAndInheritanceAsync(Type type, JsonSchema4 schema, JsonSchemaResolver schemaResolver) + private async Task GeneratePropertiesAsync(Type type, JsonSchema4 schema, JsonSchemaResolver schemaResolver) { #if !LEGACY var propertiesAndFields = type.GetTypeInfo() @@ -603,8 +614,6 @@ private async Task GeneratePropertiesAndInheritanceAsync(Type type, JsonSchema4 await LoadPropertyOrFieldAsync(property, info, type, schema, schemaResolver).ConfigureAwait(false); } } - - await GenerateInheritanceAsync(type, schema, schemaResolver).ConfigureAwait(false); } /// Gets the properties of the given type or null to take all properties. @@ -656,7 +665,7 @@ private async Task AddKnownTypeAsync(Type type, JsonSchemaResolver schemaResolve await GenerateAsync(type, schemaResolver).ConfigureAwait(false); } - private async Task GenerateInheritanceAsync(Type type, JsonSchema4 schema, JsonSchemaResolver schemaResolver) + private async Task GenerateInheritanceAsync(Type type, JsonSchema4 schema, JsonSchemaResolver schemaResolver) { var baseType = type.GetTypeInfo().BaseType; if (baseType != null && baseType != typeof(object) && baseType != typeof(ValueType)) @@ -669,7 +678,12 @@ private async Task GenerateInheritanceAsync(Type type, JsonSchema4 schema, JsonS { var typeDescription = Settings.ReflectionService.GetDescription(baseType, null, Settings); if (!typeDescription.IsDictionary && !type.IsArray) - await GeneratePropertiesAndInheritanceAsync(baseType, schema, schemaResolver).ConfigureAwait(false); + { + await GeneratePropertiesAsync(baseType, schema, schemaResolver).ConfigureAwait(false); + await GenerateInheritanceAsync(baseType, schema, schemaResolver).ConfigureAwait(false); + + GenerateInheritanceDiscriminator(baseType, schema); + } } else { @@ -687,6 +701,8 @@ private async Task GenerateInheritanceAsync(Type type, JsonSchema4 schema, JsonS } else schema.AllOf.Add(baseSchema); + + return true; } } } @@ -700,12 +716,18 @@ private async Task GenerateInheritanceAsync(Type type, JsonSchema4 schema, JsonS #endif { var typeDescription = Settings.ReflectionService.GetDescription(i, null, Settings); - if (!typeDescription.IsDictionary && !type.IsArray && !typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(i.GetTypeInfo())) - await GeneratePropertiesAndInheritanceAsync(i, schema, schemaResolver).ConfigureAwait(false); + if (!typeDescription.IsDictionary && !type.IsArray && + !typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(i.GetTypeInfo())) + { + await GeneratePropertiesAsync(i, schema, schemaResolver).ConfigureAwait(false); + await GenerateInheritanceAsync(i, schema, schemaResolver).ConfigureAwait(false); + + GenerateInheritanceDiscriminator(i, schema); + } } } - GenerateInheritanceDiscriminator(type, schema); + return false; } private void GenerateInheritanceDiscriminator(Type type, JsonSchema4 schema) @@ -949,14 +971,7 @@ public virtual void ApplyDataAnnotations(JsonSchema4 schema, JsonTypeDescription if (typeDescription.Type == JsonObjectType.Number || typeDescription.Type == JsonObjectType.Integer) { - dynamic rangeAttribute = parentAttributes.TryGetIfAssignableTo("System.ComponentModel.DataAnnotations.RangeAttribute"); - if (rangeAttribute != null) - { - if (rangeAttribute.Minimum != null && rangeAttribute.Minimum > double.MinValue) - schema.Minimum = (decimal?)(double)rangeAttribute.Minimum; - if (rangeAttribute.Maximum != null && rangeAttribute.Maximum < double.MaxValue) - schema.Maximum = (decimal?)(double)rangeAttribute.Maximum; - } + ApplyRangeAttribute(schema, parentAttributes); var multipleOfAttribute = parentAttributes.OfType().SingleOrDefault(); if (multipleOfAttribute != null) @@ -1000,6 +1015,53 @@ public virtual void ApplyDataAnnotations(JsonSchema4 schema, JsonTypeDescription } } + private void ApplyRangeAttribute(JsonSchema4 schema, IEnumerable parentAttributes) + { + dynamic rangeAttribute = parentAttributes.TryGetIfAssignableTo("System.ComponentModel.DataAnnotations.RangeAttribute"); + if (rangeAttribute != null) + { + if (rangeAttribute.Minimum != null) + { + if (rangeAttribute.OperandType == typeof(double)) + { + var minimum = (double)Convert.ChangeType(rangeAttribute.Minimum, typeof(double)); + if (minimum > double.MinValue) + { + schema.Minimum = (decimal)minimum; + } + } + else + { + var minimum = (decimal)Convert.ChangeType(rangeAttribute.Minimum, typeof(decimal)); + if (minimum > decimal.MinValue) + { + schema.Minimum = minimum; + } + } + } + + if (rangeAttribute.Maximum != null) + { + if (rangeAttribute.OperandType == typeof(double)) + { + var maximum = (double)Convert.ChangeType(rangeAttribute.Maximum, typeof(double)); + if (maximum < double.MaxValue) + { + schema.Maximum = (decimal)maximum; + } + } + else + { + var maximum = (decimal)Convert.ChangeType(rangeAttribute.Maximum, typeof(decimal)); + if (maximum < decimal.MaxValue) + { + schema.Maximum = maximum; + } + } + } + } + } + private object ConvertDefaultValue(Newtonsoft.Json.Serialization.JsonProperty property) { if (property.DefaultValue != null && property.DefaultValue.GetType().GetTypeInfo().IsEnum) diff --git a/src/NJsonSchema/Infrastructure/DynamicApis.cs b/src/NJsonSchema/Infrastructure/DynamicApis.cs index 68eb7608b..8743f4a2d 100644 --- a/src/NJsonSchema/Infrastructure/DynamicApis.cs +++ b/src/NJsonSchema/Infrastructure/DynamicApis.cs @@ -121,7 +121,7 @@ public static async Task DirectoryExistsAsync(string filePath) return false; return await FromResult((bool)DirectoryType.GetRuntimeMethod("Exists", - new[] { typeof(string) }).Invoke(null, new object[] { filePath })); + new[] { typeof(string) }).Invoke(null, new object[] { filePath })).ConfigureAwait(false); } /// Checks whether a file exists. @@ -137,7 +137,7 @@ public static async Task FileExistsAsync(string filePath) return false; return await FromResult((bool)FileType.GetRuntimeMethod("Exists", - new[] { typeof(string) }).Invoke(null, new object[] { filePath })); + new[] { typeof(string) }).Invoke(null, new object[] { filePath })).ConfigureAwait(false); } /// Reads all content of a file (UTF8). @@ -206,13 +206,13 @@ public static object XPathEvaluate(XDocument document, string path) } #if LEGACY - private static async Task FromResult(T result) + internal static async Task FromResult(T result) { return result; } #else [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Task FromResult(T result) + internal static Task FromResult(T result) { return Task.FromResult(result); } diff --git a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs index d05b6f47c..d8047a017 100644 --- a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs +++ b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs @@ -23,10 +23,8 @@ namespace NJsonSchema.Infrastructure /// This class currently works only on the desktop .NET framework. public static class XmlDocumentationExtensions { - private static readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1); - - private static readonly Dictionary Cache = - new Dictionary(StringComparer.OrdinalIgnoreCase); + private static readonly AsyncLock Lock = new AsyncLock(); + private static readonly Dictionary Cache = new Dictionary(StringComparer.OrdinalIgnoreCase); #if !LEGACY @@ -82,91 +80,6 @@ public static async Task GetXmlRemarksAsync(this MemberInfo member) return await GetXmlDocumentationTagAsync(member, "remarks").ConfigureAwait(false); } - /// Returns the contents of an XML documentation tag for the specified member. - /// The reflected member. - /// The contents of the "summary" tag for the member. - public static Task GetXmlDocumentationAsync(this MemberInfo member) - { - return GetXmlDocumentationAsync(member, true); - } - - /// Returns the contents of an XML documentation tag for the specified member. - /// The reflected member. - /// Name of the tag. - /// The contents of the "summary" tag for the member. - public static async Task GetXmlDocumentationTagAsync(this MemberInfo member, string tagName) - { - if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) - return string.Empty; - - var assemblyName = member.Module.Assembly.GetName(); - if (await IgnoreAssemblyAsync(assemblyName, true)) - return string.Empty; - - var documentationPath = await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false); - var element = await GetXmlDocumentationAsync(member, documentationPath).ConfigureAwait(false); - return RemoveLineBreakWhiteSpaces(GetXmlDocumentationText(element?.Element(tagName))); - } - - /// Returns the contents of the "returns" or "param" XML documentation tag for the specified parameter. - /// The reflected parameter or return info. - /// The contents of the "returns" or "param" tag. - public static async Task GetXmlDocumentationAsync(this ParameterInfo parameter) - { - if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) - return string.Empty; - - var assemblyName = parameter.Member.Module.Assembly.GetName(); - if (await IgnoreAssemblyAsync(assemblyName, true)) - return string.Empty; - - var documentationPath = await GetXmlDocumentationPathAsync(parameter.Member.Module.Assembly).ConfigureAwait(false); - var element = await GetXmlDocumentationAsync(parameter, documentationPath).ConfigureAwait(false); - return RemoveLineBreakWhiteSpaces(GetXmlDocumentationText(element)); - } - - /// Returns the contents of the "summary" XML documentation tag for the specified member. - /// The type. - /// The path to the XML documentation file. - /// The contents of the "summary" tag for the member. - public static Task GetXmlDocumentationAsync(this Type type, string pathToXmlFile) - { - return ((MemberInfo)type.GetTypeInfo()).GetXmlDocumentationAsync(pathToXmlFile); - } - - /// Returns the contents of the "summary" XML documentation tag for the specified member. - /// The reflected member. - /// The path to the XML documentation file. - /// The contents of the "summary" tag for the member. - public static Task GetXmlDocumentationAsync(this MemberInfo member, string pathToXmlFile) - { - return GetXmlDocumentationAsync(member, pathToXmlFile, true); - } - - /// Returns the contents of the "returns" or "param" XML documentation tag for the specified parameter. - /// The reflected parameter or return info. - /// The path to the XML documentation file. - /// The contents of the "returns" or "param" tag. - public static async Task GetXmlDocumentationAsync(this ParameterInfo parameter, string pathToXmlFile) - { - try - { - if (pathToXmlFile == null || DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) - return null; - - var assemblyName = parameter.Member.Module.Assembly.GetName(); - var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile, true); - if (document == null) - return null; - - return await GetXmlDocumentationAsync(parameter, document); - } - catch - { - return null; - } - } - /// Gets the description of the given member (based on the DescriptionAttribute, DisplayAttribute or XML Documentation). /// The member info /// The attributes. @@ -269,52 +182,84 @@ public static string GetXmlDocumentationText(this XElement element) /// Clears the cache. /// The task. - public static async Task ClearCacheAsync() + public static Task ClearCacheAsync() { -#if !LEGACY - await _lock.WaitAsync(); -#else - _lock.Wait(); -#endif - - try + using (Lock.Lock()) { Cache.Clear(); + return DynamicApis.FromResult(null); } - finally + } + + /// Returns the contents of an XML documentation tag for the specified member. + /// The reflected member. + /// Name of the tag. + /// The contents of the "summary" tag for the member. + public static async Task GetXmlDocumentationTagAsync(this MemberInfo member, string tagName) + { + if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) + return string.Empty; + + var assemblyName = member.Module.Assembly.GetName(); + using (Lock.Lock()) { - _lock.Release(); + if (IgnoreAssembly(assemblyName)) + return string.Empty; + + var documentationPath = await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false); + var element = await GetXmlDocumentationWithoutLockAsync(member, documentationPath).ConfigureAwait(false); + return RemoveLineBreakWhiteSpaces(GetXmlDocumentationText(element?.Element(tagName))); } } - private static async Task GetXmlDocumentationAsync(this MemberInfo member, bool useLock) + /// Returns the contents of the "returns" or "param" XML documentation tag for the specified parameter. + /// The reflected parameter or return info. + /// The contents of the "returns" or "param" tag. + public static async Task GetXmlDocumentationAsync(this ParameterInfo parameter) { if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) - return null; + return string.Empty; - var assemblyName = member.Module.Assembly.GetName(); - if (await IgnoreAssemblyAsync(assemblyName, useLock)) - return null; + var assemblyName = parameter.Member.Module.Assembly.GetName(); + using (Lock.Lock()) + { + if (IgnoreAssembly(assemblyName)) + return string.Empty; - var documentationPath = await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false); - return await GetXmlDocumentationAsync(member, documentationPath, useLock).ConfigureAwait(false); + var documentationPath = await GetXmlDocumentationPathAsync(parameter.Member.Module.Assembly).ConfigureAwait(false); + var element = await GetXmlDocumentationWithoutLockAsync(parameter, documentationPath).ConfigureAwait(false); + return RemoveLineBreakWhiteSpaces(GetXmlDocumentationText(element)); + } } - private static async Task GetXmlDocumentationAsync(this MemberInfo member, string pathToXmlFile, bool useLock) + /// Returns the contents of the "summary" XML documentation tag for the specified member. + /// The type. + /// The path to the XML documentation file. + /// The contents of the "summary" tag for the member. + public static async Task GetXmlDocumentationAsync(this Type type, string pathToXmlFile) + { + using (Lock.Lock()) + { + return await ((MemberInfo)type.GetTypeInfo()).GetXmlDocumentationWithoutLockAsync(pathToXmlFile).ConfigureAwait(false); + } + } + + /// Returns the contents of the "returns" or "param" XML documentation tag for the specified parameter. + /// The reflected parameter or return info. + /// The path to the XML documentation file. + /// The contents of the "returns" or "param" tag. + public static async Task GetXmlDocumentationAsync(this ParameterInfo parameter, string pathToXmlFile) { try { if (pathToXmlFile == null || DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) return null; - var assemblyName = member.Module.Assembly.GetName(); - var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile, useLock); - if (document == null) - return null; - - var element = GetXmlDocumentation(member, document); - await ReplaceInheritdocElementsAsync(member, element, useLock); - return element; + var assemblyName = parameter.Member.Module.Assembly.GetName(); + using (Lock.Lock()) + { + return await GetXmlDocumentationWithoutLockAsync(parameter, pathToXmlFile).ConfigureAwait(false); + } } catch { @@ -322,61 +267,105 @@ private static async Task GetXmlDocumentationAsync(this MemberInfo mem } } - private static async Task TryGetXmlDocumentAsync(AssemblyName assemblyName, string pathToXmlFile, bool useLock) + /// Returns the contents of an XML documentation tag for the specified member. + /// The reflected member. + /// The contents of the "summary" tag for the member. + public static async Task GetXmlDocumentationAsync(this MemberInfo member) { - if (useLock) + using (Lock.Lock()) { -#if !LEGACY - await _lock.WaitAsync(); -#else - _lock.Wait(); -#endif + return await GetXmlDocumentationWithoutLockAsync(member).ConfigureAwait(false); + } + } + + /// Returns the contents of the "summary" XML documentation tag for the specified member. + /// The reflected member. + /// The path to the XML documentation file. + /// The contents of the "summary" tag for the member. + public static async Task GetXmlDocumentationAsync(this MemberInfo member, string pathToXmlFile) + { + using (Lock.Lock()) + { + return await GetXmlDocumentationWithoutLockAsync(member, pathToXmlFile).ConfigureAwait(false); } + } + private static async Task GetXmlDocumentationWithoutLockAsync(this ParameterInfo parameter, string pathToXmlFile) + { try { - if (!Cache.ContainsKey(assemblyName.FullName)) - { - if (await DynamicApis.FileExistsAsync(pathToXmlFile).ConfigureAwait(false) == false) - { - Cache[assemblyName.FullName] = null; - return null; - } + if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) + return null; - Cache[assemblyName.FullName] = await Task.Factory.StartNew(() => XDocument.Load(pathToXmlFile, LoadOptions.PreserveWhitespace)).ConfigureAwait(false); - } + var assemblyName = parameter.Member.Module.Assembly.GetName(); + var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile).ConfigureAwait(false); + if (document == null) + return null; - return Cache[assemblyName.FullName]; + return await GetXmlDocumentationAsync(parameter, document).ConfigureAwait(false); } - finally + catch { - if (useLock) - _lock.Release(); + return null; } } - private static async Task IgnoreAssemblyAsync(AssemblyName assemblyName, bool useLock) + private static async Task GetXmlDocumentationWithoutLockAsync(this MemberInfo member) { - if (useLock) - { -#if !LEGACY - await _lock.WaitAsync(); -#else - _lock.Wait(); -#endif - } + if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) + return null; + + var assemblyName = member.Module.Assembly.GetName(); + if (IgnoreAssembly(assemblyName)) + return null; + var documentationPath = await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false); + return await GetXmlDocumentationWithoutLockAsync(member, documentationPath).ConfigureAwait(false); + } + + private static async Task GetXmlDocumentationWithoutLockAsync(this MemberInfo member, string pathToXmlFile) + { try { - if (Cache.ContainsKey(assemblyName.FullName) && Cache[assemblyName.FullName] == null) - return true; + if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) + return null; + + var assemblyName = member.Module.Assembly.GetName(); + var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile).ConfigureAwait(false); + if (document == null) + return null; + + var element = GetXmlDocumentation(member, document); + await ReplaceInheritdocElementsAsync(member, element).ConfigureAwait(false); + return element; + } + catch + { + return null; } - finally + } + + private static async Task TryGetXmlDocumentAsync(AssemblyName assemblyName, string pathToXmlFile) + { + if (!Cache.ContainsKey(assemblyName.FullName)) { - if (useLock) - _lock.Release(); + if (await DynamicApis.FileExistsAsync(pathToXmlFile).ConfigureAwait(false) == false) + { + Cache[assemblyName.FullName] = null; + return null; + } + + Cache[assemblyName.FullName] = await Task.Factory.StartNew(() => XDocument.Load(pathToXmlFile, LoadOptions.PreserveWhitespace)).ConfigureAwait(false); } + return Cache[assemblyName.FullName]; + } + + private static bool IgnoreAssembly(AssemblyName assemblyName) + { + if (Cache.ContainsKey(assemblyName.FullName) && Cache[assemblyName.FullName] == null) + return true; + return false; } @@ -393,7 +382,7 @@ private static async Task GetXmlDocumentationAsync(this ParameterInfo var result = (IEnumerable)DynamicApis.XPathEvaluate(xml, $"/doc/members/member[@name='{name}']"); var element = result.OfType().First(); - await ReplaceInheritdocElementsAsync(parameter.Member, element, true); + await ReplaceInheritdocElementsAsync(parameter.Member, element).ConfigureAwait(false); if (parameter.IsRetval || string.IsNullOrEmpty(parameter.Name)) result = (IEnumerable)DynamicApis.XPathEvaluate(xml, $"/doc/members/member[@name='{name}']/returns"); @@ -403,49 +392,38 @@ private static async Task GetXmlDocumentationAsync(this ParameterInfo return result.OfType().FirstOrDefault(); } - private static async Task ReplaceInheritdocElementsAsync(this MemberInfo member, XElement element, bool useLock) + private static async Task ReplaceInheritdocElementsAsync(this MemberInfo member, XElement element) { #if !LEGACY if (element == null) return; - if (useLock) - await _lock.WaitAsync(); - - try + var children = element.Nodes().ToList(); + foreach (var child in children.OfType()) { - var children = element.Nodes().ToList(); - foreach (var child in children.OfType()) + if (child.Name.LocalName.ToLowerInvariant() == "inheritdoc") { - if (child.Name.LocalName.ToLowerInvariant() == "inheritdoc") + var baseType = member.DeclaringType.GetTypeInfo().BaseType; + var baseMember = baseType?.GetTypeInfo().DeclaredMembers.SingleOrDefault(m => m.Name == member.Name); + if (baseMember != null) { - var baseType = member.DeclaringType.GetTypeInfo().BaseType; - var baseMember = baseType?.GetTypeInfo().DeclaredMembers.SingleOrDefault(m => m.Name == member.Name); - if (baseMember != null) + var baseDoc = await baseMember.GetXmlDocumentationWithoutLockAsync().ConfigureAwait(false); + if (baseDoc != null) { - var baseDoc = await baseMember.GetXmlDocumentationAsync(false); - if (baseDoc != null) - { - var nodes = baseDoc.Nodes().OfType().ToArray(); - child.ReplaceWith(nodes); - } - else - { - await ProcessInheritdocInterfaceElementsAsync(member, child); - } + var nodes = baseDoc.Nodes().OfType().ToArray(); + child.ReplaceWith(nodes); } else { - await ProcessInheritdocInterfaceElementsAsync(member, child); + await ProcessInheritdocInterfaceElementsAsync(member, child).ConfigureAwait(false); } } + else + { + await ProcessInheritdocInterfaceElementsAsync(member, child).ConfigureAwait(false); + } } } - finally - { - if (useLock) - _lock.Release(); - } #endif } @@ -457,7 +435,7 @@ private static async Task ProcessInheritdocInterfaceElementsAsync(this MemberInf var baseMember = baseInterface?.GetTypeInfo().DeclaredMembers.SingleOrDefault(m => m.Name == member.Name); if (baseMember != null) { - var baseDoc = await baseMember.GetXmlDocumentationAsync(false); + var baseDoc = await baseMember.GetXmlDocumentationWithoutLockAsync().ConfigureAwait(false); if (baseDoc != null) { var nodes = baseDoc.Nodes().OfType().ToArray(); @@ -485,7 +463,11 @@ private static string RemoveLineBreakWhiteSpaces(string documentation) private static string GetMemberElementName(dynamic member) { char prefixCode; - string memberName = member is Type ? ((Type)member).FullName : (member.DeclaringType.FullName + "." + member.Name); + + var memberName = member is Type memberType && !string.IsNullOrEmpty(memberType.FullName) ? + memberType.FullName : + member.DeclaringType.FullName + "." + member.Name; + switch ((string)member.MemberType.ToString()) { case "Constructor": @@ -546,6 +528,9 @@ private static async Task GetXmlDocumentationPathAsync(dynamic assembly) if (string.IsNullOrEmpty(assemblyName.Name)) return null; + if (Cache.ContainsKey(assemblyName.FullName)) + return null; + var assemblyDirectory = DynamicApis.PathGetDirectoryName((string)assembly.Location); var path = DynamicApis.PathCombine(assemblyDirectory, (string)assemblyName.Name + ".xml"); if (await DynamicApis.FileExistsAsync(path).ConfigureAwait(false)) @@ -579,5 +564,21 @@ private static async Task GetXmlDocumentationPathAsync(dynamic assembly) return null; } } + + private class AsyncLock : IDisposable + { + private SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); + + public AsyncLock Lock() + { + _semaphoreSlim.Wait(); + return this; + } + + public void Dispose() + { + _semaphoreSlim.Release(); + } + } } -} +} \ No newline at end of file diff --git a/src/NJsonSchema/JsonReferenceResolver.cs b/src/NJsonSchema/JsonReferenceResolver.cs index dc22ad9ae..d6e26c068 100644 --- a/src/NJsonSchema/JsonReferenceResolver.cs +++ b/src/NJsonSchema/JsonReferenceResolver.cs @@ -58,7 +58,7 @@ public void AddDocumentReference(string documentPath, IJsonReference schema) /// Could not resolve the JSON path. public async Task ResolveReferenceAsync(object rootObject, string jsonPath) { - return await ResolveReferenceAsync(rootObject, jsonPath, true); + return await ResolveReferenceAsync(rootObject, jsonPath, true).ConfigureAwait(false); } /// Gets the object from the given JSON path. @@ -69,7 +69,7 @@ public async Task ResolveReferenceAsync(object rootObject, strin /// Could not resolve the JSON path. public async Task ResolveReferenceWithoutAppendAsync(object rootObject, string jsonPath) { - return await ResolveReferenceAsync(rootObject, jsonPath, false); + return await ResolveReferenceAsync(rootObject, jsonPath, false).ConfigureAwait(false); } /// Resolves a document reference. diff --git a/src/NJsonSchema/JsonSchema4.Reference.cs b/src/NJsonSchema/JsonSchema4.Reference.cs index 89028c263..646bc88c5 100644 --- a/src/NJsonSchema/JsonSchema4.Reference.cs +++ b/src/NJsonSchema/JsonSchema4.Reference.cs @@ -8,8 +8,10 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using Newtonsoft.Json; +using NJsonSchema.Collections; using NJsonSchema.References; namespace NJsonSchema @@ -26,7 +28,18 @@ public partial class JsonSchema4 : JsonReferenceBase, IJsonReferenc /// Cyclic references detected. /// The schema reference path has not been resolved. [JsonIgnore] - public virtual JsonSchema4 ActualTypeSchema => OneOf.FirstOrDefault(o => !o.IsNullable(SchemaType.JsonSchema))?.ActualSchema ?? ActualSchema; + public virtual JsonSchema4 ActualTypeSchema + { + get + { + if (AllOf.Count > 1 && AllOf.Count(s => !s.HasReference && !s.IsDictionary) == 1) + { + return AllOf.First(s => !s.HasReference && !s.IsDictionary); + } + + return OneOf.FirstOrDefault(o => !o.IsNullable(SchemaType.JsonSchema))?.ActualSchema ?? ActualSchema; + } + } /// Gets a value indicating whether this is a schema reference ($ref or ). [JsonIgnore] diff --git a/src/NJsonSchema/JsonSchema4.cs b/src/NJsonSchema/JsonSchema4.cs index e9a230b91..8af744c4e 100644 --- a/src/NJsonSchema/JsonSchema4.cs +++ b/src/NJsonSchema/JsonSchema4.cs @@ -158,7 +158,7 @@ public static async Task FromUrlAsync(string url) /// The HttpClient.GetAsync API is not available on this platform. public static async Task FromUrlAsync(string url, Func referenceResolverFactory) { - var data = await DynamicApis.HttpGetAsync(url); + var data = await DynamicApis.HttpGetAsync(url).ConfigureAwait(false); return await FromJsonAsync(data, url, referenceResolverFactory).ConfigureAwait(false); } @@ -756,6 +756,7 @@ public bool InheritsSchema(JsonSchema4 parentSchema) /// Validates the given JSON data against this schema. /// The JSON data to validate. + /// Could not deserialize the JSON data. /// The collection of validation errors. public ICollection Validate(string jsonData) { diff --git a/src/NJsonSchema/JsonSchemaReferenceUtilities.cs b/src/NJsonSchema/JsonSchemaReferenceUtilities.cs index 13e0f9c1c..018fcb35a 100644 --- a/src/NJsonSchema/JsonSchemaReferenceUtilities.cs +++ b/src/NJsonSchema/JsonSchemaReferenceUtilities.cs @@ -96,9 +96,9 @@ public JsonReferenceUpdater(object rootObject, JsonReferenceResolver referenceRe public override async Task VisitAsync(object obj) { _replaceRefsRound = true; - await base.VisitAsync(obj); + await base.VisitAsync(obj).ConfigureAwait(false); _replaceRefsRound = false; - await base.VisitAsync(obj); + await base.VisitAsync(obj).ConfigureAwait(false); } protected override async Task VisitJsonReferenceAsync(IJsonReference reference, string path, string typeNameHint) diff --git a/src/NJsonSchema/NJsonSchema.csproj b/src/NJsonSchema/NJsonSchema.csproj index 3a90d3784..d76b91d7e 100644 --- a/src/NJsonSchema/NJsonSchema.csproj +++ b/src/NJsonSchema/NJsonSchema.csproj @@ -2,7 +2,7 @@ netstandard1.0;net40;net45 JSON Schema reader, generator and validator for .NET - 9.10.73 + 9.10.74 json schema validation generator .net Copyright © Rico Suter, 2017 https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md diff --git a/src/NJsonSchema/Validation/JsonSchemaValidator.cs b/src/NJsonSchema/Validation/JsonSchemaValidator.cs index 8685eef01..fef54ee29 100644 --- a/src/NJsonSchema/Validation/JsonSchemaValidator.cs +++ b/src/NJsonSchema/Validation/JsonSchemaValidator.cs @@ -11,6 +11,7 @@ using System.Globalization; using System.Linq; using System.Text.RegularExpressions; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace NJsonSchema.Validation @@ -21,6 +22,7 @@ public class JsonSchemaValidator /// Validates the given JSON data. /// The json data. /// The schema. + /// Could not deserialize the JSON data. /// The list of validation errors. public ICollection Validate(string jsonData, JsonSchema4 schema) {