Skip to content

Commit

Permalink
Improve allOf inheritance support (#783)
Browse files Browse the repository at this point in the history
* Improve allOf inheritance support

* Keep order

* Fix OpenAPI null handling

* Add braces
  • Loading branch information
RicoSuter committed Sep 25, 2018
1 parent e61686c commit 3a00302
Show file tree
Hide file tree
Showing 11 changed files with 159 additions and 43 deletions.
Binary file modified build/NuGet.exe
Binary file not shown.
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
}
}
}
87 changes: 63 additions & 24 deletions src/NJsonSchema/Generation/JsonSchemaGenerator.cs
Expand Up @@ -143,15 +143,17 @@ public virtual async Task GenerateAsync<TSchemaType>(Type type, IEnumerable<Attr
else
{
if (schemaResolver.HasSchema(type, false))
{
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);
}
}
}
else if (typeDescription.IsEnum)
Expand Down Expand Up @@ -238,15 +240,23 @@ public virtual async Task GenerateAsync<TSchemaType>(Type type, IEnumerable<Attr
else
schema.Type = schema.Type | JsonObjectType.Null;
}
else if (Settings.SchemaType == SchemaType.OpenApi3)
{
schema.IsNullableRaw = isNullable;
}
}

return schema;
}
else
else // TODO: Is this else needed?
{
referencedSchema = schema.ActualSchema;
}
}
else
{
referencedSchema = await GenerateAsync<JsonSchema4>(type, parentAttributes, schemaResolver).ConfigureAwait(false);
}

var referencingSchema = new TSchemaType();
if (transformation != null)
Expand Down Expand Up @@ -311,21 +321,34 @@ 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 actualSchema = await GenerateInheritanceAsync(type, schema, schemaResolver).ConfigureAwait(false);
if (actualSchema != null)
{
schema = actualSchema;
}
else
{
await GeneratePropertiesAsync(type, schema, schemaResolver).ConfigureAwait(false);
await ApplyAdditionalPropertiesAsync(type, schema, schemaResolver).ConfigureAwait(false);
}

typeDescription.ApplyType(schema);

schema.Description = await type.GetTypeInfo().GetDescriptionAsync(type.GetTypeInfo().GetCustomAttributes()).ConfigureAwait(false);
schema.IsAbstract = type.GetTypeInfo().IsAbstract;

await GeneratePropertiesAndInheritanceAsync(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 +533,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 +626,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 +677,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<JsonSchema4> GenerateInheritanceAsync(Type type, JsonSchema4 schema, JsonSchemaResolver schemaResolver)
{
var baseType = type.GetTypeInfo().BaseType;
if (baseType != null && baseType != typeof(object) && baseType != typeof(ValueType))
Expand All @@ -669,10 +690,19 @@ 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
{
var actualSchema = new JsonSchema4();
await GeneratePropertiesAsync(type, actualSchema, schemaResolver).ConfigureAwait(false);
await ApplyAdditionalPropertiesAsync(type, actualSchema, schemaResolver).ConfigureAwait(false);

var baseSchema = await GenerateAsync(baseType, schemaResolver).ConfigureAwait(false);
var baseTypeInfo = Settings.ReflectionService.GetDescription(baseType, null, Settings);
if (baseTypeInfo.RequiresSchemaReference(Settings.TypeMappers))
Expand All @@ -687,6 +717,9 @@ private async Task GenerateInheritanceAsync(Type type, JsonSchema4 schema, JsonS
}
else
schema.AllOf.Add(baseSchema);

schema.AllOf.Add(actualSchema);
return actualSchema;
}
}
}
Expand All @@ -700,12 +733,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 null;
}

private void GenerateInheritanceDiscriminator(Type type, JsonSchema4 schema)
Expand Down Expand Up @@ -1002,15 +1041,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 +1061,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

0 comments on commit 3a00302

Please sign in to comment.