diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs index cbc877f2c..65d013989 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs @@ -60,11 +60,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types public GraphQLSchema BuildSchema(IEnumerable schemas) { - var schemaInfos = - schemas - .Where(x => x.SchemaDef.IsPublished).Select(SchemaInfo.Build) - .Where(x => x.Fields.Count > 0) - .ToList(); + var schemaInfos = SchemaInfo.Build(schemas).ToList(); foreach (var schemaInfo in schemaInfos) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataGraphType.cs index 4b868022f..feb35b3dc 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataGraphType.cs @@ -26,7 +26,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents { var fieldGraphType = new ObjectGraphType { - Name = fieldInfo.TypeName + Name = fieldInfo.LocalizedType }; var partitioning = builder.ResolvePartition(((RootField)fieldInfo.Field).Partitioning); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/SchemaInfos.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/SchemaInfos.cs index 9d42acba6..d9d6b6b6d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/SchemaInfos.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/SchemaInfos.cs @@ -5,79 +5,182 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using System.Collections.Generic; using System.Linq; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure; +using Squidex.Text; -#pragma warning disable SA1313 // Parameter names should begin with lower-case letter +#pragma warning disable SA1649 // File name should match first type name namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { - public sealed record SchemaInfo(ISchemaEntity Schema, string TypeName, IReadOnlyList Fields) + internal sealed class SchemaInfo { - public string DisplayName { get; set; } = Schema.DisplayName(); + public ISchemaEntity Schema { get; } - public string ContentType { get; } = TypeName.SafeTypeName(); + public string TypeName { get; } - public string DataType { get; } = $"{TypeName}DataDto"; + public string DisplayName { get; } - public string DataInputType { get; } = $"{TypeName}DataInputDto"; + public string ContentType { get; } - public string DataFlatType { get; } = $"{TypeName}FlatDataDto"; + public string DataType { get; } - public string ResultType { get; } = $"{TypeName}ResultDto"; + public string DataInputType { get; } - public static SchemaInfo Build(ISchemaEntity schema) + public string DataFlatType { get; } + + public string ResultType { get; } + + public IReadOnlyList Fields { get; } + + private SchemaInfo(ISchemaEntity schema, string typeName, IReadOnlyList fields, Names names) + { + Schema = schema; + ContentType = names[typeName]; + DataFlatType = names[$"{typeName}FlatDataDto"]; + DataInputType = names[$"{typeName}DataInputDto"]; + ResultType = names[$"{typeName}ResultDto"]; + DataType = names[$"{typeName}DataDto"]; + DisplayName = schema.DisplayName(); + Fields = fields; + TypeName = typeName; + } + + public override string ToString() + { + return TypeName; + } + + public static IEnumerable Build(IEnumerable schemas) { - var typeName = schema.TypeName(); + var names = new Names(); - var fields = - schema.SchemaDef.Fields.SafeFields() - .Select(x => FieldInfo.Build(x.Field, x.Name, $"{typeName}{x.Type}")) - .ToList(); + foreach (var schema in schemas.Where(x => x.SchemaDef.IsPublished && x.SchemaDef.Fields.Count > 0).OrderBy(x => x.Created)) + { + var typeName = schema.TypeName(); + + var fields = FieldInfo.EmptyFields; + + if (schema.SchemaDef.Fields.Count > 0) + { + var fieldNames = new Names(); + + fields = new List(schema.SchemaDef.Fields.Count); + + foreach (var field in schema.SchemaDef.Fields) + { + fields.Add(FieldInfo.Build(field, fieldNames[field], names[$"{typeName}Data{field.TypeName()}"], names)); + } + } - return new SchemaInfo( - schema, - schema.TypeName(), - fields); + yield return new SchemaInfo(schema, typeName, fields, names); + } } } - public sealed record FieldInfo(IField Field, string FieldName, string TypeName, IReadOnlyList Fields) + internal sealed class FieldInfo { - private static readonly IReadOnlyList EmptyFields = new List(); + public static readonly List EmptyFields = new List(); + + public IField Field { get; set; } + + public string FieldName { get; } + + public string DisplayName { get; } - public string DisplayName { get; set; } = Field.DisplayName(); + public string LocalizedType { get; } - public string LocalizedType { get; } = $"{TypeName}Dto"; + public string LocalizedInputType { get; } - public string LocalizedInputType { get; } = $"{TypeName}InputDto"; + public string NestedType { get; } - public string NestedType { get; } = $"{TypeName}ChildDto"; + public string NestedInputType { get; } - public string NestedInputType { get; } = $"{TypeName}ChildInputDto"; + public string UnionType { get; } - public string UnionType { get; } = $"{TypeName}UnionDto"; + public IReadOnlyList Fields { get; } - public static FieldInfo Build(IRootField rootField, string fieldName, string typeName) + private FieldInfo(IField field, string fieldName, string typeName, IReadOnlyList fields, Names names) + { + DisplayName = field.DisplayName(); + Field = field; + Fields = fields; + FieldName = fieldName; + LocalizedType = names[$"{typeName}Dto"]; + LocalizedInputType = names[$"{typeName}InputDto"]; + NestedInputType = names[$"{typeName}ChildInputDto"]; + NestedType = names[$"{typeName}ChildDto"]; + UnionType = names[$"{typeName}UnionDto"]; + } + + public override string ToString() + { + return FieldName; + } + + internal static FieldInfo Build(IRootField rootField, string fieldName, string typeName, Names names) { var fields = EmptyFields; - if (rootField is IArrayField arrayField) + if (rootField is IArrayField arrayField && arrayField.Fields.Count > 0) { - fields = - arrayField.Fields.SafeFields() - .Select(x => Build(x.Field, x.Name, $"{typeName}{x.Type}")) - .ToList(); + var fieldNames = new Names(); + + fields = new List(arrayField.Fields.Count); + + foreach (var field in arrayField.Fields) + { + fields.Add(new FieldInfo(field, fieldNames[field], $"{typeName}{field.TypeName()}", EmptyFields, names)); + } } - return new FieldInfo(rootField, fieldName, typeName, fields); + return new FieldInfo(rootField, fieldName, typeName, fields, names); + } + } + + internal sealed class Names + { + private readonly Dictionary takenNames = new Dictionary(); + + public string this[IField field] + { + get + { + return this[field.Name.ToCamelCase()]; + } } - public static FieldInfo Build(INestedField nestedField, string fieldName, string fieldTypeName) + public string this[string name] { - return new FieldInfo(nestedField, fieldName, fieldTypeName, EmptyFields); + get + { + Guard.NotNullOrEmpty(name, nameof(name)); + + if (!char.IsLetter(name[0])) + { + name = "gql_" + name; + } + else if (name.Equals("Content", StringComparison.OrdinalIgnoreCase)) + { + name = $"{name}Entity"; + } + + // Avoid duplicate names. + if (!takenNames.TryGetValue(name, out var offset)) + { + takenNames[name] = 0; + return name; + } + + takenNames[name] = ++offset; + + // Add + 1 to all offset for backwars compatibility. + return $"{name}{offset + 1}"; + } } } } \ No newline at end of file diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs index acea111da..012148141 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using System.Collections.Generic; using System.Linq; using GraphQL; @@ -15,56 +14,12 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.ObjectPool; -using Squidex.Text; using GraphQLSchema = GraphQL.Types.Schema; -#pragma warning disable RECS0015 // If an extension method is called as static method convert it to method syntax - namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { - public static class Extensions + internal static class Extensions { - public static string SafeTypeName(this string typeName) - { - if (typeName.Equals("Content", StringComparison.Ordinal)) - { - return $"{typeName}Entity"; - } - - return typeName; - } - - public static IEnumerable<(T Field, string Name, string Type)> SafeFields(this IEnumerable fields) where T : IField - { - var allFields = - fields.FieldNames() - .GroupBy(x => x.Name) - .Select(g => g.Select((f, i) => (f.Field, f.Name.SafeString(i), f.Type.SafeString(i)))) - .SelectMany(x => x); - - return allFields; - } - - private static IEnumerable<(T Field, string Name, string Type)> FieldNames(this IEnumerable fields) where T : IField - { - return fields.ForApi(true).Select(field => (field, CasingExtensions.ToCamelCase(field.Name), field.TypeName())); - } - - private static string SafeString(this string value, int index) - { - if (value.Length > 0 && !char.IsLetter(value[0])) - { - value = "gql_" + value; - } - - if (index > 0) - { - return value + (index + 1); - } - - return value; - } - public static string BuildODataQuery(this IResolveFieldContext context) { var sb = DefaultPools.StringBuilder.Get();