diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayFieldProperties.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayFieldProperties.cs new file mode 100644 index 000000000..46d7d5198 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayFieldProperties.cs @@ -0,0 +1,35 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Core.Schemas +{ + [TypeName("ArrayField")] + public sealed class ArrayFieldProperties : FieldProperties + { + public int? MinItems { get; set; } + + public int? MaxItems { get; set; } + + public override T Accept(IFieldPropertiesVisitor visitor) + { + throw new NotImplementedException(); + } + + public override T Accept(IFieldVisitor visitor, IField field) + { + throw new NotImplementedException(); + } + + public override Field CreateField(long id, string name, Partitioning partitioning) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/IArrayField.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/IArrayField.cs new file mode 100644 index 000000000..1aa0493a1 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/IArrayField.cs @@ -0,0 +1,16 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; + +namespace Squidex.Domain.Apps.Core.Schemas +{ + public interface IArrayField : IField + { + IReadOnlyCollection Fields { get; } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldPropertiesVisitor.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldPropertiesVisitor.cs index 108269359..c4593a450 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldPropertiesVisitor.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldPropertiesVisitor.cs @@ -9,6 +9,8 @@ namespace Squidex.Domain.Apps.Core.Schemas { public interface IFieldPropertiesVisitor { + T Visit(ArrayFieldProperties properties); + T Visit(AssetsFieldProperties properties); T Visit(BooleanFieldProperties properties); diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldVisitor.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldVisitor.cs index 3bfad913a..67142acc4 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldVisitor.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldVisitor.cs @@ -9,6 +9,8 @@ namespace Squidex.Domain.Apps.Core.Schemas { public interface IFieldVisitor { + T Visit(IArrayField field); + T Visit(IField field); T Visit(IField field); diff --git a/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs b/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs index 898a98431..a4f8be960 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs @@ -13,7 +13,7 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.EnrichContent { - public sealed class DefaultValueFactory : IFieldPropertiesVisitor + public sealed class DefaultValueFactory : IFieldVisitor { private readonly Instant now; @@ -26,62 +26,67 @@ namespace Squidex.Domain.Apps.Core.EnrichContent { Guard.NotNull(field, nameof(field)); - return field.RawProperties.Accept(new DefaultValueFactory(now)); + return field.Accept(new DefaultValueFactory(now)); } - public JToken Visit(AssetsFieldProperties properties) + public JToken Visit(IArrayField field) { return new JArray(); } - public JToken Visit(BooleanFieldProperties properties) + public JToken Visit(IField field) { - return properties.DefaultValue; + return new JArray(); + } + + public JToken Visit(IField field) + { + return field.Properties.DefaultValue; } - public JToken Visit(GeolocationFieldProperties properties) + public JToken Visit(IField field) { return JValue.CreateNull(); } - public JToken Visit(JsonFieldProperties properties) + public JToken Visit(IField field) { return JValue.CreateNull(); } - public JToken Visit(NumberFieldProperties properties) + public JToken Visit(IField field) { - return properties.DefaultValue; + return field.Properties.DefaultValue; } - public JToken Visit(ReferencesFieldProperties properties) + public JToken Visit(IField field) { return new JArray(); } - public JToken Visit(StringFieldProperties properties) + public JToken Visit(IField field) { - return properties.DefaultValue; + return field.Properties.DefaultValue; } - public JToken Visit(TagsFieldProperties properties) + public JToken Visit(IField field) { return new JArray(); } - public JToken Visit(DateTimeFieldProperties properties) + public JToken Visit(IField field) { - if (properties.CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Now) + if (field.Properties.CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Now) { return now.ToString(); } - if (properties.CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Today) + if (field.Properties.CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Today) { return now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); } - return properties.DefaultValue?.ToString(); + return field.Properties.DefaultValue?.ToString(); } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmTypeVisitor.cs b/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmTypeVisitor.cs index 83d3b1a47..789da3081 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmTypeVisitor.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmTypeVisitor.cs @@ -23,6 +23,11 @@ namespace Squidex.Domain.Apps.Core.GenerateEdmSchema return field.Accept(Instance); } + public IEdmTypeReference Visit(IArrayField field) + { + return null; + } + public IEdmTypeReference Visit(IField field) { return CreatePrimitive(EdmPrimitiveTypeKind.String, field); diff --git a/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs b/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs index baff80ccb..6c4e3afca 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs @@ -21,6 +21,30 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema this.schemaResolver = schemaResolver; } + public JsonProperty Visit(IArrayField field) + { + return CreateProperty(field, jsonProperty => + { + var itemSchema = new JsonSchema4 + { + Type = JsonObjectType.Object + }; + + foreach (var child in field.Fields) + { + var childProperty = field.Accept(this); + + childProperty.Description = child.RawProperties.Hints; + childProperty.IsRequired = child.RawProperties.IsRequired; + + itemSchema.Properties.Add(child.Name, childProperty); + } + + jsonProperty.Type = JsonObjectType.Object; + jsonProperty.Item = itemSchema; + }); + } + public JsonProperty Visit(IField field) { return CreateProperty(field, jsonProperty => diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs index 9de8f0716..39e020b96 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs @@ -28,11 +28,21 @@ namespace Squidex.Domain.Apps.Core.ValidateContent return field.Accept(new JsonValueConverter(json)); } + public object Visit(IArrayField field) + { + return Value.ToObject>(); + } + public object Visit(IField field) { return Value.ToObject>(); } + internal static object ConvertValue(IField field, object value) + { + throw new NotImplementedException(); + } + public object Visit(IField field) { return (bool?)Value; diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs new file mode 100644 index 000000000..449cd7652 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs @@ -0,0 +1,50 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Json; + +namespace Squidex.Domain.Apps.Core.ValidateContent.Validators +{ + public sealed class FieldValidator : IValidator + { + private readonly IEnumerable validators; + private readonly IField field; + + public FieldValidator(IEnumerable validators, IField field) + { + this.validators = validators; + this.field = field; + } + + public async Task ValidateAsync(object value, ValidationContext context, Action addError) + { + try + { + object typedValue = null; + + if (value is JToken jToken) + { + typedValue = jToken.IsNull() ? null : JsonValueConverter.ConvertValue(field, jToken); + } + + foreach (var validator in ValidatorsFactory.CreateValidators(field)) + { + await validator.ValidateAsync(typedValue, context, addError); + } + } + catch + { + addError(" is not a valid value."); + } + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs index 330c3f1bb..88c9dacb3 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs @@ -15,7 +15,7 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.ValidateContent { - public sealed class ValidatorsFactory : IFieldPropertiesVisitor> + public sealed class ValidatorsFactory : IFieldVisitor> { private static readonly ValidatorsFactory Instance = new ValidatorsFactory(); @@ -27,115 +27,123 @@ namespace Squidex.Domain.Apps.Core.ValidateContent { Guard.NotNull(field, nameof(field)); - return field.RawProperties.Accept(Instance); + return field.Accept(Instance); } - public IEnumerable Visit(AssetsFieldProperties properties) + public IEnumerable Visit(IArrayField field) { - if (properties.IsRequired || properties.MinItems.HasValue || properties.MaxItems.HasValue) + if (field.Properties.IsRequired || field.Properties.MinItems.HasValue || field.Properties.MaxItems.HasValue) { - yield return new CollectionValidator(properties.IsRequired, properties.MinItems, properties.MaxItems); + yield return new CollectionValidator(field.Properties.IsRequired, field.Properties.MinItems, field.Properties.MaxItems); + } + } + + public IEnumerable Visit(IField field) + { + if (field.Properties.IsRequired || field.Properties.MinItems.HasValue || field.Properties.MaxItems.HasValue) + { + yield return new CollectionValidator(field.Properties.IsRequired, field.Properties.MinItems, field.Properties.MaxItems); } - yield return new AssetsValidator(properties); + yield return new AssetsValidator(field.Properties); } - public IEnumerable Visit(BooleanFieldProperties properties) + public IEnumerable Visit(IField field) { - if (properties.IsRequired) + if (field.Properties.IsRequired) { yield return new RequiredValidator(); } } - public IEnumerable Visit(DateTimeFieldProperties properties) + public IEnumerable Visit(IField field) { - if (properties.IsRequired) + if (field.Properties.IsRequired) { yield return new RequiredValidator(); } - if (properties.MinValue.HasValue || properties.MaxValue.HasValue) + if (field.Properties.MinValue.HasValue || field.Properties.MaxValue.HasValue) { - yield return new RangeValidator(properties.MinValue, properties.MaxValue); + yield return new RangeValidator(field.Properties.MinValue, field.Properties.MaxValue); } } - public IEnumerable Visit(GeolocationFieldProperties properties) + public IEnumerable Visit(IField field) { - if (properties.IsRequired) + if (field.Properties.IsRequired) { yield return new RequiredValidator(); } } - public IEnumerable Visit(JsonFieldProperties properties) + public IEnumerable Visit(IField field) { - if (properties.IsRequired) + if (field.Properties.IsRequired) { yield return new RequiredValidator(); } } - public IEnumerable Visit(NumberFieldProperties properties) + public IEnumerable Visit(IField field) { - if (properties.IsRequired) + if (field.Properties.IsRequired) { yield return new RequiredValidator(); } - if (properties.MinValue.HasValue || properties.MaxValue.HasValue) + if (field.Properties.MinValue.HasValue || field.Properties.MaxValue.HasValue) { - yield return new RangeValidator(properties.MinValue, properties.MaxValue); + yield return new RangeValidator(field.Properties.MinValue, field.Properties.MaxValue); } - if (properties.AllowedValues != null) + if (field.Properties.AllowedValues != null) { - yield return new AllowedValuesValidator(properties.AllowedValues.ToArray()); + yield return new AllowedValuesValidator(field.Properties.AllowedValues.ToArray()); } } - public IEnumerable Visit(ReferencesFieldProperties properties) + public IEnumerable Visit(IField field) { - if (properties.IsRequired || properties.MinItems.HasValue || properties.MaxItems.HasValue) + if (field.Properties.IsRequired || field.Properties.MinItems.HasValue || field.Properties.MaxItems.HasValue) { - yield return new CollectionValidator(properties.IsRequired, properties.MinItems, properties.MaxItems); + yield return new CollectionValidator(field.Properties.IsRequired, field.Properties.MinItems, field.Properties.MaxItems); } - if (properties.SchemaId != Guid.Empty) + if (field.Properties.SchemaId != Guid.Empty) { - yield return new ReferencesValidator(properties.SchemaId); + yield return new ReferencesValidator(field.Properties.SchemaId); } } - public IEnumerable Visit(StringFieldProperties properties) + public IEnumerable Visit(IField field) { - if (properties.IsRequired) + if (field.Properties.IsRequired) { yield return new RequiredStringValidator(); } - if (properties.MinLength.HasValue || properties.MaxLength.HasValue) + if (field.Properties.MinLength.HasValue || field.Properties.MaxLength.HasValue) { - yield return new StringLengthValidator(properties.MinLength, properties.MaxLength); + yield return new StringLengthValidator(field.Properties.MinLength, field.Properties.MaxLength); } - if (!string.IsNullOrWhiteSpace(properties.Pattern)) + if (!string.IsNullOrWhiteSpace(field.Properties.Pattern)) { - yield return new PatternValidator(properties.Pattern, properties.PatternMessage); + yield return new PatternValidator(field.Properties.Pattern, field.Properties.PatternMessage); } - if (properties.AllowedValues != null) + if (field.Properties.AllowedValues != null) { - yield return new AllowedValuesValidator(properties.AllowedValues.ToArray()); + yield return new AllowedValuesValidator(field.Properties.AllowedValues.ToArray()); } } - public IEnumerable Visit(TagsFieldProperties properties) + public IEnumerable Visit(IField field) { - if (properties.IsRequired || properties.MinItems.HasValue || properties.MaxItems.HasValue) + if (field.Properties.IsRequired || field.Properties.MinItems.HasValue || field.Properties.MaxItems.HasValue) { - yield return new CollectionValidator(properties.IsRequired, properties.MinItems, properties.MaxItems); + yield return new CollectionValidator(field.Properties.IsRequired, field.Properties.MinItems, field.Properties.MaxItems); } yield return new CollectionItemValidator(new RequiredStringValidator()); diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs index e746580f9..dfecc2fbd 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs @@ -25,6 +25,16 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards return properties?.Accept(Instance) ?? Enumerable.Empty(); } + public IEnumerable Visit(ArrayFieldProperties properties) + { + if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value >= properties.MaxItems.Value) + { + yield return new ValidationError("Max items must be greater than min items.", + nameof(properties.MinItems), + nameof(properties.MaxItems)); + } + } + public IEnumerable Visit(AssetsFieldProperties properties) { if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value >= properties.MaxItems.Value)