Skip to content

Commit

Permalink
Fix json schema generation for components. (#790)
Browse files Browse the repository at this point in the history
* Fix json schema generation for components.

* Simplified.

* Fix OpenAPI.

* Mapping fix.

* Cleanup
  • Loading branch information
SebastianStehle authored Nov 16, 2021
1 parent 4452769 commit 61bb542
Show file tree
Hide file tree
Showing 15 changed files with 348 additions and 249 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,9 @@ public static IEnumerable<IRootField> GetSharedFields(this ResolvedComponents co
return Enumerable.Empty<IRootField>();
}

var allFields =
schemaIds
.Select(x => components.Get(x)).NotNull()
.SelectMany(x => x.Fields.ForApi(withHidden))
.GroupBy(x => new { x.Name, Type = x.RawProperties.GetType() }).Where(x => x.Count() == 1)
.Select(x => x.First());

return allFields;
var allFields = components.Resolve(schemaIds).Values.SelectMany(x => x.Fields.ForApi(withHidden));

return allFields.GroupBy(x => new { x.Name, Type = x.RawProperties }).SingleGroups();
}

public static bool IsForApi<T>(this T field, bool withHidden = false) where T : IField
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,28 @@ public ResolvedComponents(IDictionary<DomainId, Schema> inner)
{
}

public Schema? Get(DomainId schemaId)
public ResolvedComponents Resolve(IEnumerable<DomainId>? schemaIds)
{
return this.GetOrDefault(schemaId);
var result = (Dictionary<DomainId, Schema>?)null;

if (schemaIds != null)
{
foreach (var schemaId in schemaIds)
{
if (TryGetValue(schemaId, out var schema))
{
result ??= new Dictionary<DomainId, Schema>();
result[schemaId] = schema;
}
}
}

if (result == null)
{
return Empty;
}

return new ResolvedComponents(result);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,17 @@

namespace Squidex.Domain.Apps.Core.GenerateEdmSchema
{
public delegate (EdmComplexType Type, bool Created) EdmTypeFactory(string names);
public delegate (EdmComplexType Type, bool Created) EdmTypeFactory(string name);

public static class EdmSchemaExtensions
{
public static string EscapeEdmField(this string field)
{
return field.Replace("-", "_", StringComparison.Ordinal);
}

public static string UnescapeEdmField(this string field)
{
return field.Replace("_", "-", StringComparison.Ordinal);
}

public static EdmComplexType BuildEdmType(this Schema schema, bool withHidden, PartitionResolver partitionResolver, EdmTypeFactory typeFactory,
public static EdmComplexType BuildEdmType(this Schema schema, bool withHidden, PartitionResolver partitionResolver, EdmTypeFactory factory,
ResolvedComponents components)
{
Guard.NotNull(typeFactory, nameof(typeFactory));
Guard.NotNull(factory, nameof(factory));
Guard.NotNull(partitionResolver, nameof(partitionResolver));

var (edmType, _) = typeFactory("Data");
var (edmType, _) = factory("Data");

foreach (var field in schema.FieldsByName.Values)
{
Expand All @@ -42,14 +32,14 @@ public static EdmComplexType BuildEdmType(this Schema schema, bool withHidden, P
continue;
}

var fieldEdmType = EdmTypeVisitor.BuildType(field, typeFactory, components);
var fieldEdmType = EdmTypeVisitor.BuildType(field, factory, components);

if (fieldEdmType == null)
{
continue;
}

var (partitionType, created) = typeFactory($"Data.{field.Name.ToPascalCase()}");
var (partitionType, created) = factory($"Data.{field.Name.ToPascalCase()}");

if (created)
{
Expand All @@ -66,5 +56,15 @@ public static EdmComplexType BuildEdmType(this Schema schema, bool withHidden, P

return edmType;
}

public static string EscapeEdmField(this string field)
{
return field.Replace("-", "_", StringComparison.Ordinal);
}

public static string UnescapeEdmField(this string field)
{
return field.Replace("_", "-", StringComparison.Ordinal);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,107 +12,151 @@

namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
{
public delegate (JsonSchema Reference, JsonSchema? Actual) JsonTypeFactory(string name);

public static class JsonSchemaExtensions
{
public static JsonSchema BuildFlatJsonSchema(this Schema schema, SchemaResolver schemaResolver,
ResolvedComponents components)
private static readonly JsonTypeFactory DefaultFactory = _ =>
{
Guard.NotNull(schemaResolver, nameof(schemaResolver));
var schema = JsonTypeBuilder.Object();

var schemaName = schema.TypeName();
return (schema, schema);
};

var jsonSchema = SchemaBuilder.Object();
public static JsonSchema BuildJsonSchemaFlat(this Schema schema, PartitionResolver partitionResolver,
ResolvedComponents components,
JsonTypeFactory? factory = null,
bool withHidden = false,
bool withComponents = false)
{
Guard.NotNull(partitionResolver, nameof(partitionResolver));
Guard.NotNull(components, nameof(components));

foreach (var field in schema.Fields.ForApi())
{
var property = JsonTypeVisitor.BuildProperty(field, components);
factory ??= DefaultFactory;

var jsonSchema = JsonTypeBuilder.Object();

foreach (var field in schema.Fields.ForApi(withHidden))
{
var property =
JsonTypeVisitor.BuildProperty(
field, components, schema,
factory,
withHidden,
withComponents);

// Property is null for UI fields.
if (property != null)
{
var propertyReference = schemaResolver($"{schemaName}{field.Name.ToPascalCase()}FlatPropertyDto", () => property);
property.SetRequired(false);
property.SetDescription(field);

jsonSchema.Properties.Add(field.Name, CreateProperty(field, propertyReference));
jsonSchema.Properties.Add(field.Name, property);
}
}

return jsonSchema;
}

public static JsonSchema BuildDynamicJsonSchema(this Schema schema, SchemaResolver schemaResolver,
ResolvedComponents components, bool withHidden = false)
public static JsonSchema BuildJsonSchemaDynamic(this Schema schema, PartitionResolver partitionResolver,
ResolvedComponents components,
JsonTypeFactory? factory = null,
bool withHidden = false,
bool withComponents = false)
{
Guard.NotNull(schemaResolver, nameof(schemaResolver));
Guard.NotNull(partitionResolver, nameof(partitionResolver));
Guard.NotNull(components, nameof(components));

factory ??= DefaultFactory;

var jsonSchema = SchemaBuilder.Object();
var jsonSchema = JsonTypeBuilder.Object();

foreach (var field in schema.Fields.ForApi(withHidden))
{
var propertyItem = JsonTypeVisitor.BuildProperty(field, components, schemaResolver, withHidden);

if (propertyItem != null)
var property =
JsonTypeVisitor.BuildProperty(
field, components, schema,
factory,
withHidden,
withComponents);

// Property is null for UI fields.
if (property != null)
{
var property =
SchemaBuilder.ObjectProperty(propertyItem)
.SetDescription(field)
.SetRequired(field.RawProperties.IsRequired);
var propertyObj = JsonTypeBuilder.ObjectProperty(property);

jsonSchema.Properties.Add(field.Name, property);
// Property is not required because not all languages might be required.
propertyObj.SetRequired(false);
propertyObj.SetDescription(field);

jsonSchema.Properties.Add(field.Name, propertyObj);
}
}

return jsonSchema;
}

public static JsonSchema BuildJsonSchema(this Schema schema, PartitionResolver partitionResolver,
ResolvedComponents components, bool withHidden = false)
ResolvedComponents components,
JsonTypeFactory? factory = null,
bool withHidden = false,
bool withComponents = false)
{
Guard.NotNull(partitionResolver, nameof(partitionResolver));
Guard.NotNull(components, nameof(components));

factory ??= DefaultFactory;

var jsonSchema = SchemaBuilder.Object();
var jsonSchema = JsonTypeBuilder.Object();

foreach (var field in schema.Fields.ForApi(withHidden))
{
var propertyObject = SchemaBuilder.Object();
var typeName = $"{schema.TypeName()}{field.Name.ToPascalCase()}PropertyDto";

var partitioning = partitionResolver(field.Partitioning);
// Create a reference to give it a nice name in code generation.
var (reference, actual) = factory(typeName);

foreach (var partitionKey in partitioning.AllKeys)
if (actual != null)
{
var propertyItem = JsonTypeVisitor.BuildProperty(field, components, withHiddenFields: withHidden);
var partitioning = partitionResolver(field.Partitioning);

if (propertyItem != null)
foreach (var partitionKey in partitioning.AllKeys)
{
var isOptional = partitioning.IsOptional(partitionKey);

var name = partitioning.GetName(partitionKey);

propertyItem.SetDescription(name);
propertyItem.SetRequired(field.RawProperties.IsRequired && !isOptional);

propertyObject.Properties.Add(partitionKey, propertyItem);
var property =
JsonTypeVisitor.BuildProperty(
field, components, schema,
factory,
withHidden,
withComponents);

// Property is null for UI fields.
if (property != null)
{
var isOptional = partitioning.IsOptional(partitionKey);

var name = partitioning.GetName(partitionKey);

// Required if property is required and language/partitioning is not optional.
property.SetRequired(field.RawProperties.IsRequired && !isOptional);
property.SetDescription(name);

actual.Properties.Add(partitionKey, property);
}
}
}

if (propertyObject.Properties.Count > 0)
{
jsonSchema.Properties.Add(field.Name, CreateProperty(field, propertyObject));
}
var propertyReference =
JsonTypeBuilder.ReferenceProperty(reference)
.SetDescription(field)
.SetRequired(field.RawProperties.IsRequired);

jsonSchema.Properties.Add(field.Name, propertyReference);
}

return jsonSchema;
}

public static JsonSchemaProperty CreateProperty(IField field, JsonSchema reference)
{
var jsonProperty =
SchemaBuilder.ReferenceProperty(reference)
.SetDescription(field)
.SetRequired(field.RawProperties.IsRequired);

return jsonProperty;
}

private static JsonSchemaProperty SetDescription(this JsonSchemaProperty jsonProperty, IField field)
public static JsonSchemaProperty SetDescription(this JsonSchemaProperty jsonProperty, IField field)
{
if (!string.IsNullOrWhiteSpace(field.RawProperties.Hints))
{
Expand Down
Loading

0 comments on commit 61bb542

Please sign in to comment.