diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/IArrayField.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/IArrayField.cs index 47d517d69..4ee839be5 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/IArrayField.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/IArrayField.cs @@ -11,6 +11,8 @@ namespace Squidex.Domain.Apps.Core.Schemas { public interface IArrayField : IField { + IReadOnlyList Fields { get; } + IReadOnlyDictionary FieldsById { get; } IReadOnlyDictionary FieldsByName { get; } diff --git a/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs b/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs index 781754f69..20ed81ca5 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs @@ -7,6 +7,7 @@ using System; using System.Collections.ObjectModel; +using System.Linq; using NJsonSchema; using Squidex.Domain.Apps.Core.Schemas; @@ -30,14 +31,14 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema Type = JsonObjectType.Object }; - foreach (var child in field.FieldsByName.Values) + foreach (var nestedField in field.Fields.Where(x => !x.IsHidden)) { var childProperty = field.Accept(this); - childProperty.Description = child.RawProperties.Hints; - childProperty.IsRequired = child.RawProperties.IsRequired; + childProperty.Description = nestedField.RawProperties.Hints; + childProperty.IsRequired = nestedField.RawProperties.IsRequired; - itemSchema.Properties.Add(child.Name, childProperty); + itemSchema.Properties.Add(nestedField.Name, childProperty); } jsonProperty.Type = JsonObjectType.Object; diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs index d2e9cb1fb..678842c37 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs @@ -74,7 +74,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent fieldsValidators[field.Key] = (!field.Value.RawProperties.IsRequired, CreateFieldValidator(field.Value, isPartial)); } - return new ObjectValidator(fieldsValidators, isPartial, "field", DefaultFieldData); + return new ObjectValidator(fieldsValidators, isPartial, "field", DefaultFieldData, Formatter.CombineForLanguage); } private IValidator CreateFieldValidator(Field field, bool isPartial) @@ -89,9 +89,11 @@ namespace Squidex.Domain.Apps.Core.ValidateContent fieldsValidators[partition.Key] = (partition.IsOptional, fieldValidator); } - var type = field.Partitioning.Equals(Partitioning.Language) ? "language" : "invariant value"; + var isLanguage = field.Partitioning.Equals(Partitioning.Language); - return new ObjectValidator(fieldsValidators, isPartial, type, DefaultValue); + var type = isLanguage ? "language" : "invariant value"; + + return new ObjectValidator(fieldsValidators, isPartial, type, DefaultValue, Formatter.Combine); } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs index 51923dd68..9cd0a56dc 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs @@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.allowedValues = allowedValues; } - public Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) + public Task ValidateAsync(object value, ValidationContext context, AddError addError) { if (value == null) { diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs index 898e1de74..21a701ed2 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs @@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.properties = properties; } - public async Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) + public async Task ValidateAsync(object value, ValidationContext context, AddError addError) { if (value is ICollection assetIds && assetIds.Count > 0) { diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionItemValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionItemValidator.cs index c7d55a7c3..e4d928cd2 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionItemValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionItemValidator.cs @@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.itemValidators = itemValidators; } - public async Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) + public async Task ValidateAsync(object value, ValidationContext context, AddError addError) { if (value is ICollection items && items.Count > 0) { @@ -35,9 +35,11 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators foreach (var item in items) { + var itemFormatter = Formatter.Combine($"[{index}]", addError); + foreach (var itemValidator in itemValidators) { - innerTasks.Add(itemValidator.ValidateAsync(item, innerContext, Formatter.Combine($"[{index}]", addError))); + innerTasks.Add(itemValidator.ValidateAsync(item, innerContext, itemFormatter)); } index++; diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs index dc4fe98d4..a5cca83cd 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs @@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.maxItems = maxItems; } - public Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) + public Task ValidateAsync(object value, ValidationContext context, AddError addError) { if (!(value is ICollection items) || items.Count == 0) { diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs index a825c1ec1..b49d790e4 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs @@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.field = field; } - public async Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) + public async Task ValidateAsync(object value, ValidationContext context, AddError addError) { try { diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/Formatter.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/Formatter.cs index 764421edf..e6debf3ae 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/Formatter.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/Formatter.cs @@ -5,21 +5,40 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; + namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { public static class Formatter { - public static ErrorFormatter Combine(string field, ErrorFormatter formatter) + private static readonly string IV = InvariantPartitioning.Instance.Master.Key; + + public static AddError Combine(string field, AddError formatter) + { + return (f, m) => + { + if (!string.IsNullOrWhiteSpace(f)) + { + formatter($"{field}.{f}", m); + } + else + { + formatter(field, m); + } + }; + } + + public static AddError CombineForLanguage(string field, AddError formatter) { - return (innerField, message) => + return (f, m) => { - if (!string.IsNullOrWhiteSpace(innerField)) + if (!string.IsNullOrWhiteSpace(f) && !string.Equals(f, IV, StringComparison.OrdinalIgnoreCase)) { - formatter($"{field}.{innerField}", message); + formatter($"{field}.{f}", m); } else { - formatter(field, message); + formatter(field, m); } }; } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/IValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/IValidator.cs index 8061a6ce2..d9faf958c 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/IValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/IValidator.cs @@ -9,10 +9,12 @@ using System.Threading.Tasks; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { - public delegate void ErrorFormatter(string field, string message); + public delegate void AddError(string field, string message); + + public delegate AddError CombineFields(string field, AddError formatter); public interface IValidator { - Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError); + Task ValidateAsync(object value, ValidationContext context, AddError addError); } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs index 0e95a0903..5426b40de 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs @@ -16,16 +16,18 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators private readonly bool isPartial; private readonly string fieldType; private readonly TValue fieldDefault; + private readonly CombineFields combiner; - public ObjectValidator(IDictionary schema, bool isPartial, string fieldType, TValue fieldDefault) + public ObjectValidator(IDictionary schema, bool isPartial, string fieldType, TValue fieldDefault, CombineFields combiner) { this.schema = schema; + this.combiner = combiner; this.fieldDefault = fieldDefault; this.fieldType = fieldType; this.isPartial = isPartial; } - public async Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) + public async Task ValidateAsync(object value, ValidationContext context, AddError addError) { if (value is IReadOnlyDictionary values) { @@ -35,7 +37,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators if (!schema.ContainsKey(name)) { - Formatter.Combine(name, addError)(null, $"Not a known {fieldType}."); + var fieldFormatter = combiner?.Invoke(name, addError) ?? Formatter.Combine(name, addError); + + fieldFormatter(null, $"Not a known {fieldType}."); } } @@ -58,7 +62,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators var (isOptional, validator) = field.Value; var fieldContext = context.Optional(isOptional); - var fieldFormatter = Formatter.Combine(name, addError); + var fieldFormatter = combiner(name, addError); tasks.Add(validator.ValidateAsync(fieldValue, fieldContext, fieldFormatter)); } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs index 84ecbede1..981f1e3e1 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs @@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators regex = new Regex("^" + pattern + "$", RegexOptions.None, Timeout); } - public Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) + public Task ValidateAsync(object value, ValidationContext context, AddError addError) { if (value is string stringValue) { diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs index 70ae219b8..f000f3cd8 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs @@ -27,7 +27,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.max = max; } - public Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) + public Task ValidateAsync(object value, ValidationContext context, AddError addError) { if (value == null) { diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs index 8735aaef1..417ca6107 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs @@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.schemaId = schemaId; } - public async Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) + public async Task ValidateAsync(object value, ValidationContext context, AddError addError) { if (value is ICollection contentIds) { diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs index 8f5ab8f74..c21523458 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs @@ -19,7 +19,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.validateEmptyStrings = validateEmptyStrings; } - public Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) + public Task ValidateAsync(object value, ValidationContext context, AddError addError) { if (context.IsOptional || (value != null && !(value is string))) { diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs index 2f5237580..efd862c77 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs @@ -12,7 +12,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { public class RequiredValidator : IValidator { - public Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) + public Task ValidateAsync(object value, ValidationContext context, AddError addError) { if (value == null && !context.IsOptional) { diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs index 45ba37507..569236751 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs @@ -27,7 +27,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.maxLength = maxLength; } - public Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) + public Task ValidateAsync(object value, ValidationContext context, AddError addError) { if (value is string stringValue && !string.IsNullOrEmpty(stringValue)) { diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs index 704f850ce..3e415bdbf 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs @@ -40,12 +40,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent var fieldsValidators = new Dictionary(); - foreach (var kvp in field.FieldsByName) + foreach (var nestedField in field.Fields) { - fieldsValidators[kvp.Key] = (false, new FieldValidator(kvp.Value.Accept(this), kvp.Value)); + fieldsValidators[nestedField.Name] = (false, new FieldValidator(nestedField.Accept(this), nestedField)); } - yield return new CollectionItemValidator(new ObjectValidator(fieldsValidators, false, "field", JValue.CreateNull())); + yield return new CollectionItemValidator(new ObjectValidator(fieldsValidators, false, "field", JValue.CreateNull(), Formatter.Combine)); } public IEnumerable Visit(IField field) diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs index 6546572fe..3e4801ae0 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs @@ -12,8 +12,8 @@ using System.Threading.Tasks; using GraphQL; using GraphQL.Resolvers; using GraphQL.Types; +using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core; -using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Assets; @@ -35,6 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL private readonly PartitionResolver partitionResolver; private readonly IAppEntity app; private readonly IGraphType assetType; + private readonly IGraphType assetListType; private readonly GraphQLSchema graphQLSchema; public bool CanGenerateAssetSourceUrl { get; private set; } @@ -48,8 +49,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL CanGenerateAssetSourceUrl = urlGenerator.CanGenerateAssetSourceUrl; assetType = new AssetGraphType(this); + assetListType = new ListGraphType(new NonNullGraphType(assetType)); + schemasById = schemas.ToDictionary(x => x.Id); - schemaTypes = new QueryGraphTypeVisitor(GetContentType, new ListGraphType(new NonNullGraphType(assetType))); graphQLSchema = BuildSchema(this); @@ -78,7 +80,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL private static (IGraphType ResolveType, IFieldResolver Resolver) ResolveDefault(IGraphType type) { - return (type, new FuncFieldResolver(c => c.Source.GetOrDefault(c.FieldName))); + return (type, new FuncFieldResolver, object>(c => c.Source.GetOrDefault(c.FieldName))); } public IFieldResolver ResolveAssetUrl() @@ -134,9 +136,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL return partitionResolver(key); } - public (IGraphType ResolveType, IFieldResolver Resolver) GetGraphType(IField field) + public (IGraphType ResolveType, IFieldResolver Resolver) GetGraphType(ISchemaEntity schema, IField field) { - return field.Accept(schemaTypes); + return field.Accept(new QueryGraphTypeVisitor(schema, GetContentType, this, assetListType)); } public IGraphType GetInputGraphType(IField field) diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphModel.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphModel.cs index 91acea708..0e155b65b 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphModel.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphModel.cs @@ -36,6 +36,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL IGraphType GetInputGraphType(IField field); - (IGraphType ResolveType, IFieldResolver Resolver) GetGraphType(IField field); + (IGraphType ResolveType, IFieldResolver Resolver) GetGraphType(ISchemaEntity schema, IField field); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphInputType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphInputType.cs index 42512c861..be0f0ef74 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphInputType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphInputType.cs @@ -5,9 +5,11 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; using System.Linq; using GraphQL.Resolvers; using GraphQL.Types; +using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; @@ -56,7 +58,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types fieldGraphType.Description = $"The input structure of the {fieldName} of a {schemaName} content type."; - var fieldResolver = new FuncFieldResolver(c => c.Source.GetOrDefault(field.Name)); + var fieldResolver = new FuncFieldResolver>(c => + { + return c.Source.GetOrDefault(field.Name); + }); AddField(new FieldType { diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs index d74c6e383..1ee1d48b9 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs @@ -5,9 +5,11 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; using System.Linq; using GraphQL.Resolvers; using GraphQL.Types; +using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; @@ -25,15 +27,16 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types foreach (var field in schema.SchemaDef.Fields.Where(x => !x.IsHidden)) { - var fieldInfo = model.GetGraphType(field); + var fieldInfo = model.GetGraphType(schema, field); if (fieldInfo.ResolveType != null) { - var fieldName = field.RawProperties.Label.WithFallback(field.Name); + var fieldType = field.TypeName(); + var fieldName = field.DisplayName(); var fieldGraphType = new ObjectGraphType { - Name = $"{schemaType}Data{field.Name.ToPascalCase()}Dto" + Name = $"{schemaType}Data{fieldType}Dto" }; var partition = model.ResolvePartition(field.Partitioning); @@ -49,9 +52,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types }); } - fieldGraphType.Description = $"The structure of the {fieldName} of a {schemaName} content type."; + fieldGraphType.Description = $"The structure of the {fieldName} field of a {schemaName} content type."; - var fieldResolver = new FuncFieldResolver(c => c.Source.GetOrDefault(field.Name)); + var fieldResolver = new FuncFieldResolver>(c => + { + return c.Source.GetOrDefault(field.Name); + }); AddField(new FieldType { diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldVisitor.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldVisitor.cs index 80ea21c4c..3ccbc04b2 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldVisitor.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldVisitor.cs @@ -18,6 +18,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { } + public IGraphType Visit(IArrayField field) + { + return AllTypes.NoopJson; + } + public IGraphType Visit(IField field) { return AllTypes.ListOfNonNullGuid; diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedObjectGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedObjectGraphType.cs new file mode 100644 index 000000000..455d91ac4 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedObjectGraphType.cs @@ -0,0 +1,48 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Linq; +using GraphQL.Types; +using Newtonsoft.Json.Linq; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types +{ + public sealed class NestedObjectGraphType : ObjectGraphType + { + public NestedObjectGraphType(IGraphModel model, ISchemaEntity schema, IArrayField field) + { + var schemaType = schema.TypeName(); + var schemaName = schema.DisplayName(); + + var fieldType = field.TypeName(); + var fieldName = field.DisplayName(); + + Name = $"{schemaType}{fieldName}ChildDto"; + + foreach (var nestedField in field.Fields.Where(x => !x.IsHidden)) + { + var fieldInfo = model.GetGraphType(schema, nestedField); + + if (fieldInfo.ResolveType != null) + { + AddField(new FieldType + { + Name = nestedField.Name.ToCamelCase(), + Resolver = fieldInfo.Resolver, + ResolvedType = fieldInfo.ResolveType, + Description = $"The {fieldName}/{nestedField.DisplayName()} nested field." + }); + } + } + + Description = $"The structure of a {schemaName}.{fieldName} child schema."; + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs index 4ca4bae5a..91cee6505 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs @@ -6,25 +6,36 @@ // ========================================================================== using System; +using System.Collections.Generic; using GraphQL.Resolvers; using GraphQL.Types; -using Squidex.Domain.Apps.Core.Contents; +using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { public sealed class QueryGraphTypeVisitor : IFieldVisitor<(IGraphType ResolveType, IFieldResolver Resolver)> { + private readonly ISchemaEntity schema; private readonly Func schemaResolver; + private readonly IGraphModel model; private readonly IGraphType assetListType; - public QueryGraphTypeVisitor(Func schemaResolver, IGraphType assetListType) + public QueryGraphTypeVisitor(ISchemaEntity schema, Func schemaResolver, IGraphModel model, IGraphType assetListType) { + this.model = model; this.assetListType = assetListType; + this.schema = schema; this.schemaResolver = schemaResolver; } + public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IArrayField field) + { + return ResolveNested(field); + } + public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField field) { return ResolveAssets(assetListType); @@ -72,12 +83,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types private static (IGraphType ResolveType, IFieldResolver Resolver) ResolveDefault(IGraphType type) { - return (type, new FuncFieldResolver(c => c.Source.GetOrDefault(c.FieldName))); + return (type, new FuncFieldResolver, object>(c => c.Source.GetOrDefault(c.FieldName))); } private static ValueTuple ResolveAssets(IGraphType assetListType) { - var resolver = new FuncFieldResolver(c => + var resolver = new FuncFieldResolver, object>(c => { var context = (GraphQLExecutionContext)c.UserContext; var contentIds = c.Source.GetOrDefault(c.FieldName); @@ -88,6 +99,18 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types return (assetListType, resolver); } + private ValueTuple ResolveNested(IArrayField field) + { + var resolver = new FuncFieldResolver, object>(c => + { + return c.Source.GetOrDefault(c.FieldName); + }); + + var schemaFieldType = new ListGraphType(new NonNullGraphType(new NestedObjectGraphType(model, schema, field))); + + return (schemaFieldType, resolver); + } + private ValueTuple ResolveReferences(IField field) { var schemaId = ((ReferencesFieldProperties)field.RawProperties).SchemaId; @@ -99,7 +122,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types return (null, null); } - var resolver = new FuncFieldResolver(c => + var resolver = new FuncFieldResolver, object>(c => { var context = (GraphQLExecutionContext)c.UserContext; var contentIds = c.Source.GetOrDefault(c.FieldName); diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs index 5d86e5682..4d45ac893 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs @@ -18,11 +18,21 @@ namespace Squidex.Domain.Apps.Entities.Schemas return new NamedId(schema.Id, schema.Name); } + public static string TypeName(this IField field) + { + return field.Name.ToPascalCase(); + } + public static string TypeName(this ISchemaEntity schema) { return schema.SchemaDef.Name.ToPascalCase(); } + public static string DisplayName(this IField field) + { + return field.RawProperties.Label.WithFallback(field.TypeName()); + } + public static string DisplayName(this ISchemaEntity schema) { return schema.SchemaDef.Properties.Label.WithFallback(schema.TypeName()); diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs index 5dd3b2129..a2953af7e 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs @@ -20,6 +20,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Converters { } + public FieldPropertiesDto Visit(ArrayFieldProperties properties) + { + throw new System.NotImplementedException(); + } + public static FieldPropertiesDto Create(FieldProperties properties) { return properties.Accept(Instance); diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs index 1a2509771..c9f9bf4b1 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs @@ -58,7 +58,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent errors.ShouldBeEquivalentTo( new List { - new ValidationError("my-field.iv: Must be less or equals than '100'.", "my-field.iv") + new ValidationError("my-field: Must be less or equals than '100'.", "my-field") }); } @@ -117,7 +117,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent errors.ShouldBeEquivalentTo( new List { - new ValidationError("my-field.iv: Field is required.", "my-field.iv") + new ValidationError("my-field: Field is required.", "my-field") }); } @@ -220,7 +220,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent errors.ShouldBeEquivalentTo( new List { - new ValidationError("my-field.iv: Must be less or equals than '100'.", "my-field.iv") + new ValidationError("my-field: Must be less or equals than '100'.", "my-field") }); } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs index d6e8c27ca..ad5fccf34 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs @@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent CreateFormatter(errors)); } - private static ErrorFormatter CreateFormatter(IList errors) + private static AddError CreateFormatter(IList errors) { return (field, message) => {