Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use allOf for all schemas in inheritance #733

Merged
merged 15 commits into from Sep 21, 2018
Merged
Expand Up @@ -278,7 +278,7 @@ public async Task When_class_has_description_then_csharp_has_xml_comment()
{
//// Arrange
var schema = await JsonSchema4.FromTypeAsync<Teacher>();
schema.Description = "ClassDesc.";
schema.ActualSchema.Description = "ClassDesc.";
var generator = new CSharpGenerator(schema);

//// Act
Expand All @@ -295,7 +295,7 @@ public async Task When_property_has_description_then_csharp_has_xml_comment()
{
//// Arrange
var schema = await JsonSchema4.FromTypeAsync<Teacher>();
schema.Properties["Class"].Description = "PropertyDesc.";
schema.ActualProperties["Class"].Description = "PropertyDesc.";
var generator = new CSharpGenerator(schema, new CSharpGeneratorSettings { ClassStyle = CSharpClassStyle.Poco });

//// Act
Expand Down
Expand Up @@ -19,13 +19,18 @@ public async Task When_class_is_abstract_then_is_abstract_TypeScript_keyword_is_
{
/// Arrange
var schema = await JsonSchema4.FromTypeAsync<AbstractClass>();
var json = schema.ToJson();

/// Act
var generator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings { TypeScriptVersion = 2.0m });
var code = generator.GenerateFile("AbstractClass");

/// 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
Expand Down Expand Up @@ -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]
Expand Down
Expand Up @@ -154,7 +154,9 @@ public async Task When_property_has_description_then_csharp_has_xml_comment()
{
//// Arrange
var schema = await JsonSchema4.FromTypeAsync<Teacher>();
schema.Properties["Class"].Description = "PropertyDesc.";
schema.ActualProperties["Class"].Description = "PropertyDesc.";
var json = schema.ToJson();

var generator = new TypeScriptGenerator(schema);

//// Act
Expand Down
Expand Up @@ -33,7 +33,7 @@ protected ClassTemplateModelBase(TypeResolverBase resolver, JsonSchema4 schema,
public abstract string ClassName { get; }

/// <summary>Gets or sets a value indicating whether the type is abstract.</summary>
public bool IsAbstract => _schema.IsAbstract;
public bool IsAbstract => _schema.ActualTypeSchema.IsAbstract;

/// <summary>Gets the property extension data.</summary>
public IDictionary<string, object> ExtensionData => _schema.ExtensionData;
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/NJsonSchema.Tests/Conversion/ArrayTypeToSchemaTests.cs
Expand Up @@ -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);
}
Expand Down
4 changes: 2 additions & 2 deletions src/NJsonSchema.Tests/Generation/ExceptionTypeTests.cs
Expand Up @@ -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"));
}
}
}
44 changes: 40 additions & 4 deletions 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;
Expand Down Expand Up @@ -88,9 +89,9 @@ public async Task When_generating_type_with_inheritance_then_allOf_has_one_item(
var schema = await JsonSchema4.FromTypeAsync<Teacher>();

//// 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"]);
}
Expand Down Expand Up @@ -298,10 +299,45 @@ public async Task Existing_non_string_property_cant_be_discriminant()
//// Arrange

//// Act
Task<JsonSchema4> getSchema() => JsonSchema4.FromTypeAsync<BaseClass_WithIntDiscriminant>();
Task<JsonSchema4> GetSchema() => JsonSchema4.FromTypeAsync<BaseClass_WithIntDiscriminant>();

//// Assert
await Assert.ThrowsAsync<InvalidOperationException>(getSchema);
await Assert.ThrowsAsync<InvalidOperationException>(GetSchema);
}

public class Foo
{
public Bar Bar { get; set; }
}

public class Bar : Dictionary<string, string>
{
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<Foo>(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
}
}
}
66 changes: 44 additions & 22 deletions src/NJsonSchema/Generation/JsonSchemaGenerator.cs
Expand Up @@ -146,9 +146,7 @@ public virtual async Task GenerateAsync<TSchemaType>(Type type, IEnumerable<Attr
schema.Reference = schemaResolver.GetSchema(type, false);
else if (schema.GetType() == typeof(JsonSchema4))
{
typeDescription.ApplyType(schema);
schema.Description = await type.GetTypeInfo().GetDescriptionAsync(type.GetTypeInfo().GetCustomAttributes()).ConfigureAwait(false);
await GenerateObjectAsync(type, schema, schemaResolver).ConfigureAwait(false);
await GenerateObjectAsync(type, typeDescription, schema, schemaResolver).ConfigureAwait(false);
}
else
schema.Reference = await GenerateAsync(type, parentAttributes, schemaResolver).ConfigureAwait(false);
Expand Down Expand Up @@ -311,22 +309,35 @@ public virtual string GetPropertyName(Newtonsoft.Json.Serialization.JsonProperty
}

/// <summary>Generates the properties for the given type and schema.</summary>
/// <typeparam name="TSchemaType">The type of the schema type.</typeparam>
/// <param name="type">The types.</param>
/// <param name="typeDescription">The type description.</param>
/// <param name="schema">The properties</param>
/// <param name="schemaResolver">The schema resolver.</param>
/// <returns>The task.</returns>
protected virtual async Task GenerateObjectAsync<TSchemaType>(
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 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);

Expand Down Expand Up @@ -510,7 +521,7 @@ private async Task GenerateDictionaryAsync<TSchemaType>(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()
Expand Down Expand Up @@ -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);
}

/// <summary>Gets the properties of the given type or null to take all properties.</summary>
Expand Down Expand Up @@ -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<bool> GenerateInheritanceAsync(Type type, JsonSchema4 schema, JsonSchemaResolver schemaResolver)
{
var baseType = type.GetTypeInfo().BaseType;
if (baseType != null && baseType != typeof(object) && baseType != typeof(ValueType))
Expand All @@ -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
{
Expand All @@ -687,6 +701,8 @@ private async Task GenerateInheritanceAsync(Type type, JsonSchema4 schema, JsonS
}
else
schema.AllOf.Add(baseSchema);

return true;
}
}
}
Expand All @@ -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)
Expand Down Expand Up @@ -1002,15 +1024,15 @@ private void ApplyRangeAttribute(JsonSchema4 schema, IEnumerable<Attribute> pare
{
if (rangeAttribute.OperandType == typeof(double))
{
var minimum = (double) Convert.ChangeType(rangeAttribute.Minimum, typeof(double));
var minimum = (double)Convert.ChangeType(rangeAttribute.Minimum, typeof(double));
if (minimum > double.MinValue)
{
schema.Minimum = (decimal) minimum;
schema.Minimum = (decimal)minimum;
}
}
else
{
var minimum = (decimal) Convert.ChangeType(rangeAttribute.Minimum, typeof(decimal));
var minimum = (decimal)Convert.ChangeType(rangeAttribute.Minimum, typeof(decimal));
if (minimum > decimal.MinValue)
{
schema.Minimum = minimum;
Expand All @@ -1022,15 +1044,15 @@ private void ApplyRangeAttribute(JsonSchema4 schema, IEnumerable<Attribute> pare
{
if (rangeAttribute.OperandType == typeof(double))
{
var maximum = (double) Convert.ChangeType(rangeAttribute.Maximum, typeof(double));
var maximum = (double)Convert.ChangeType(rangeAttribute.Maximum, typeof(double));
if (maximum < double.MaxValue)
{
schema.Maximum = (decimal) maximum;
schema.Maximum = (decimal)maximum;
}
}
else
{
var maximum = (decimal) Convert.ChangeType(rangeAttribute.Maximum, typeof(decimal));
var maximum = (decimal)Convert.ChangeType(rangeAttribute.Maximum, typeof(decimal));
if (maximum < decimal.MaxValue)
{
schema.Maximum = maximum;
Expand Down
6 changes: 5 additions & 1 deletion src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs
Expand Up @@ -463,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":
Expand Down
15 changes: 14 additions & 1 deletion src/NJsonSchema/JsonSchema4.Reference.cs
Expand Up @@ -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
Expand All @@ -26,7 +28,18 @@ public partial class JsonSchema4 : JsonReferenceBase<JsonSchema4>, IJsonReferenc
/// <exception cref="InvalidOperationException">Cyclic references detected.</exception>
/// <exception cref="InvalidOperationException">The schema reference path has not been resolved.</exception>
[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;
}
}

/// <summary>Gets a value indicating whether this is a schema reference ($ref or <see cref="HasAllOfSchemaReference"/>).</summary>
[JsonIgnore]
Expand Down