From 2f747ec81db2578ce527331b54a26de2a72e6bb5 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 23 May 2018 23:20:27 +0200 Subject: [PATCH] Refactored validators for nested schemas. --- .../Schemas/IArrayField.cs | 4 +- .../GenerateJsonSchema/JsonTypeVisitor.cs | 2 +- .../ValidateContent/ContentValidator.cs | 98 +++++-------------- .../ValidateContent/FieldExtensions.cs | 57 ----------- .../ValidateContent/JsonValueConverter.cs | 5 - .../Validators/AllowedValuesValidator.cs | 5 +- .../Validators/AssetsValidator.cs | 6 +- .../Validators/CollectionItemValidator.cs | 13 ++- .../Validators/CollectionValidator.cs | 15 ++- .../Validators/FieldValidator.cs | 5 +- .../ValidateContent/Validators/Formatter.cs | 27 +++++ .../ValidateContent/Validators/IValidator.cs | 5 +- .../Validators/ObjectValidator.cs | 70 +++++++++++++ .../Validators/PatternValidator.cs | 8 +- .../Validators/RangeValidator.cs | 6 +- .../Validators/ReferencesValidator.cs | 4 +- .../Validators/RequiredStringValidator.cs | 5 +- .../Validators/RequiredValidator.cs | 5 +- .../Validators/StringLengthValidator.cs | 6 +- .../ValidateContent/ValidatorsFactory.cs | 20 +++- .../ValidateContent/AssetsFieldTests.cs | 32 +++--- .../ValidateContent/BooleanFieldTests.cs | 4 +- .../ValidateContent/ContentValidationTests.cs | 34 +++---- .../ValidateContent/DateTimeFieldTests.cs | 10 +- .../ValidateContent/GeolocationFieldTests.cs | 8 +- .../ValidateContent/JsonFieldTests.cs | 2 +- .../ValidateContent/NumberFieldTests.cs | 10 +- .../ValidateContent/ReferencesFieldTests.cs | 12 +-- .../ValidateContent/StringFieldTests.cs | 10 +- .../ValidateContent/TagsFieldTests.cs | 10 +- .../ValidationTestExtensions.cs | 36 ++++++- .../Validators/AllowedValuesValidatorTests.cs | 2 +- .../CollectionItemValidatorTests.cs | 10 +- .../Validators/CollectionValidatorTests.cs | 16 +-- .../Validators/PatternValidatorTests.cs | 4 +- .../Validators/RangeValidatorTests.cs | 4 +- .../RequiredStringValidatorTests.cs | 4 +- .../Validators/RequiredValidatorTests.cs | 2 +- .../Validators/StringLengthValidatorTests.cs | 4 +- 39 files changed, 305 insertions(+), 275 deletions(-) delete mode 100644 src/Squidex.Domain.Apps.Core.Operations/ValidateContent/FieldExtensions.cs create mode 100644 src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/Formatter.cs create mode 100644 src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/IArrayField.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/IArrayField.cs index 1aa0493a1..47d517d69 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 { - IReadOnlyCollection 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 6c4e3afca..781754f69 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs @@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema Type = JsonObjectType.Object }; - foreach (var child in field.Fields) + foreach (var child in field.FieldsByName.Values) { var childProperty = field.Accept(this); diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs index 5c0b24b81..d2e9cb1fb 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs @@ -11,14 +11,17 @@ using System.Threading.Tasks; using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Core.ValidateContent.Validators; using Squidex.Infrastructure; -#pragma warning disable 168 +#pragma warning disable SA1028, IDE0004 // Code must not contain trailing whitespace namespace Squidex.Domain.Apps.Core.ValidateContent { public sealed class ContentValidator { + private static readonly ContentFieldData DefaultFieldData = new ContentFieldData(); + private static readonly JToken DefaultValue = JValue.CreateNull(); private readonly Schema schema; private readonly PartitionResolver partitionResolver; private readonly ValidationContext context; @@ -39,103 +42,56 @@ namespace Squidex.Domain.Apps.Core.ValidateContent this.partitionResolver = partitionResolver; } - public Task ValidatePartialAsync(NamedContentData data) + private void AddError(string field, string message) { - Guard.NotNull(data, nameof(data)); - - var tasks = new List(); - - foreach (var fieldData in data) - { - var fieldName = fieldData.Key; - - if (!schema.FieldsByName.TryGetValue(fieldData.Key, out var field)) - { - errors.AddError(" is not a known field.", fieldName); - } - else - { - tasks.Add(ValidateFieldPartialAsync(field, fieldData.Value)); - } - } - - return Task.WhenAll(tasks); + errors.Add(new ValidationError($"{field}: {message}", field)); } - private Task ValidateFieldPartialAsync(Field field, ContentFieldData fieldData) + public Task ValidatePartialAsync(NamedContentData data) { - var partitioning = field.Partitioning; - var partition = partitionResolver(partitioning); - - var tasks = new List(); + Guard.NotNull(data, nameof(data)); - foreach (var partitionValues in fieldData) - { - if (partition.TryGetItem(partitionValues.Key, out var item)) - { - tasks.Add(field.ValidateAsync(partitionValues.Value, context.Optional(item.IsOptional), m => errors.AddError(m, field, item))); - } - else - { - errors.AddError($" has an unsupported {partitioning.Key} value '{partitionValues.Key}'.", field); - } - } + var validator = CreateSchemaValidator(true); - return Task.WhenAll(tasks); + return validator.ValidateAsync(data, context, AddError); } public Task ValidateAsync(NamedContentData data) { Guard.NotNull(data, nameof(data)); - ValidateUnknownFields(data); - - var tasks = new List(); - - foreach (var field in schema.FieldsByName.Values) - { - var fieldData = data.GetOrCreate(field.Name, k => new ContentFieldData()); - - tasks.Add(ValidateFieldAsync(field, fieldData)); - } + var validator = CreateSchemaValidator(false); - return Task.WhenAll(tasks); + return validator.ValidateAsync(data, context, AddError); } - private void ValidateUnknownFields(NamedContentData data) + private IValidator CreateSchemaValidator(bool isPartial) { - foreach (var fieldData in data) + var fieldsValidators = new Dictionary(); + + foreach (var field in schema.FieldsByName) { - if (!schema.FieldsByName.ContainsKey(fieldData.Key)) - { - errors.AddError(" is not a known field.", fieldData.Key); - } + fieldsValidators[field.Key] = (!field.Value.RawProperties.IsRequired, CreateFieldValidator(field.Value, isPartial)); } + + return new ObjectValidator(fieldsValidators, isPartial, "field", DefaultFieldData); } - private Task ValidateFieldAsync(Field field, ContentFieldData fieldData) + private IValidator CreateFieldValidator(Field field, bool isPartial) { - var partitioning = field.Partitioning; - var partition = partitionResolver(partitioning); + var partitioning = partitionResolver(field.Partitioning); - var tasks = new List(); + var fieldValidator = new FieldValidator(ValidatorsFactory.CreateValidators(field), field); + var fieldsValidators = new Dictionary(); - foreach (var partitionValues in fieldData) + foreach (var partition in partitioning) { - if (!partition.TryGetItem(partitionValues.Key, out var _)) - { - errors.AddError($" has an unsupported {partitioning.Key} value '{partitionValues.Key}'.", field); - } + fieldsValidators[partition.Key] = (partition.IsOptional, fieldValidator); } - foreach (var item in partition) - { - var value = fieldData.GetOrCreate(item.Key, k => JValue.CreateNull()); - - tasks.Add(field.ValidateAsync(value, context.Optional(item.IsOptional), m => errors.AddError(m, field, item))); - } + var type = field.Partitioning.Equals(Partitioning.Language) ? "language" : "invariant value"; - return Task.WhenAll(tasks); + return new ObjectValidator(fieldsValidators, isPartial, type, DefaultValue); } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/FieldExtensions.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/FieldExtensions.cs deleted file mode 100644 index ac2c46ca3..000000000 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/FieldExtensions.cs +++ /dev/null @@ -1,57 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Concurrent; -using System.Threading.Tasks; -using Newtonsoft.Json.Linq; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Json; - -namespace Squidex.Domain.Apps.Core.ValidateContent -{ - public static class FieldExtensions - { - public static void AddError(this ConcurrentBag errors, string message, IField field, IFieldPartitionItem partitionItem = null) - { - AddError(errors, message, !string.IsNullOrWhiteSpace(field.RawProperties.Label) ? field.RawProperties.Label : field.Name, field.Name, partitionItem); - } - - public static void AddError(this ConcurrentBag errors, string message, string fieldName, IFieldPartitionItem partitionItem = null) - { - AddError(errors, message, fieldName, fieldName, partitionItem); - } - - public static void AddError(this ConcurrentBag errors, string message, string displayName, string fieldName, IFieldPartitionItem partitionItem = null) - { - if (partitionItem != null && partitionItem != InvariantPartitioning.Instance.Master) - { - displayName += $" ({partitionItem.Key})"; - } - - errors.Add(new ValidationError(message.Replace("", displayName), fieldName)); - } - - public static async Task ValidateAsync(this IField field, JToken value, ValidationContext context, Action addError) - { - try - { - var typedValue = value.IsNull() ? null : JsonValueConverter.ConvertValue(field, value); - - 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/JsonValueConverter.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs index 39e020b96..80841803b 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs @@ -38,11 +38,6 @@ namespace Squidex.Domain.Apps.Core.ValidateContent 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/AllowedValuesValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs index 44aba8c60..51923dd68 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using System.Linq; using System.Threading.Tasks; using Squidex.Infrastructure; @@ -24,7 +23,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.allowedValues = allowedValues; } - public Task ValidateAsync(object value, ValidationContext context, Action addError) + public Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) { if (value == null) { @@ -35,7 +34,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators if (!allowedValues.Contains(typedValue)) { - addError(" is not an allowed value."); + addError(null, "Not an allowed value."); } return TaskHelper.Done; 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 2c6db10d7..898e1de74 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs @@ -23,9 +23,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.properties = properties; } - public async Task ValidateAsync(object value, ValidationContext context, Action addError) + public async Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) { - if (value is ICollection assetIds) + if (value is ICollection assetIds && assetIds.Count > 0) { var assets = await context.GetAssetInfosAsync(assetIds); var i = 0; @@ -38,7 +38,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators void Error(string message) { - addError($" has invalid asset #{i}: {message}"); + addError($"[{i}]", message); } if (asset == null) 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 ea75cdd53..c7d55a7c3 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionItemValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionItemValidator.cs @@ -5,14 +5,14 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; +using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { - public sealed class CollectionItemValidator : IValidator + public sealed class CollectionItemValidator : IValidator { private readonly IValidator[] itemValidators; @@ -24,10 +24,11 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.itemValidators = itemValidators; } - public async Task ValidateAsync(object value, ValidationContext context, Action addError) + public async Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) { - if (value is ICollection items) + if (value is ICollection items && items.Count > 0) { + var innerTasks = new List(); var innerContext = context.Optional(false); var index = 1; @@ -36,11 +37,13 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { foreach (var itemValidator in itemValidators) { - await itemValidator.ValidateAsync(item, innerContext, e => addError(e.Replace("", $" item #{index}"))); + innerTasks.Add(itemValidator.ValidateAsync(item, innerContext, Formatter.Combine($"[{index}]", addError))); } index++; } + + await Task.WhenAll(innerTasks); } } } 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 a1699e802..dc4fe98d4 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs @@ -5,14 +5,13 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; -using System.Collections.Generic; +using System.Collections; using System.Threading.Tasks; using Squidex.Infrastructure.Tasks; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { - public sealed class CollectionValidator : IValidator + public sealed class CollectionValidator : IValidator { private readonly bool isRequired; private readonly int? minItems; @@ -25,13 +24,13 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.maxItems = maxItems; } - public Task ValidateAsync(object value, ValidationContext context, Action addError) + public Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) { - if (!(value is ICollection items) || items.Count == 0) + if (!(value is ICollection items) || items.Count == 0) { if (isRequired && !context.IsOptional) { - addError(" is required."); + addError(null, "Field is required."); } return TaskHelper.Done; @@ -39,12 +38,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators if (minItems.HasValue && items.Count < minItems.Value) { - addError($" must have at least {minItems} item(s)."); + addError(null, $"Must have at least {minItems} item(s)."); } if (maxItems.HasValue && items.Count > maxItems.Value) { - addError($" must have not more than {maxItems} item(s)."); + addError(null, $"Must have not more than {maxItems} item(s)."); } return TaskHelper.Done; 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 449cd7652..a825c1ec1 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using System.Collections.Generic; using System.Threading.Tasks; using Newtonsoft.Json.Linq; @@ -25,7 +24,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.field = field; } - public async Task ValidateAsync(object value, ValidationContext context, Action addError) + public async Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) { try { @@ -43,7 +42,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators } catch { - addError(" is not a valid value."); + addError(null, "Not a valid value."); } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/Formatter.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/Formatter.cs new file mode 100644 index 000000000..764421edf --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/Formatter.cs @@ -0,0 +1,27 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Core.ValidateContent.Validators +{ + public static class Formatter + { + public static ErrorFormatter Combine(string field, ErrorFormatter formatter) + { + return (innerField, message) => + { + if (!string.IsNullOrWhiteSpace(innerField)) + { + formatter($"{field}.{innerField}", message); + } + else + { + formatter(field, message); + } + }; + } + } +} 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 e0d7c49f8..8061a6ce2 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/IValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/IValidator.cs @@ -5,13 +5,14 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using System.Threading.Tasks; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { + public delegate void ErrorFormatter(string field, string message); + public interface IValidator { - Task ValidateAsync(object value, ValidationContext context, Action addError); + Task ValidateAsync(object value, ValidationContext context, ErrorFormatter 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 new file mode 100644 index 000000000..0e95a0903 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs @@ -0,0 +1,70 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Squidex.Domain.Apps.Core.ValidateContent.Validators +{ + public sealed class ObjectValidator : IValidator + { + private readonly IDictionary schema; + private readonly bool isPartial; + private readonly string fieldType; + private readonly TValue fieldDefault; + + public ObjectValidator(IDictionary schema, bool isPartial, string fieldType, TValue fieldDefault) + { + this.schema = schema; + this.fieldDefault = fieldDefault; + this.fieldType = fieldType; + this.isPartial = isPartial; + } + + public async Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) + { + if (value is IReadOnlyDictionary values) + { + foreach (var fieldData in values) + { + var name = fieldData.Key; + + if (!schema.ContainsKey(name)) + { + Formatter.Combine(name, addError)(null, $"Not a known {fieldType}."); + } + } + + var tasks = new List(); + + foreach (var field in schema) + { + var name = field.Key; + + if (!values.TryGetValue(name, out var fieldValue)) + { + if (isPartial) + { + continue; + } + + fieldValue = fieldDefault; + } + + var (isOptional, validator) = field.Value; + + var fieldContext = context.Optional(isOptional); + var fieldFormatter = Formatter.Combine(name, addError); + + tasks.Add(validator.ValidateAsync(fieldValue, fieldContext, fieldFormatter)); + } + + await Task.WhenAll(tasks); + } + } + } +} 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 ec799c0bc..84ecbede1 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, Action addError) + public Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) { if (value is string stringValue) { @@ -37,17 +37,17 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { if (string.IsNullOrWhiteSpace(errorMessage)) { - addError(" is not valid."); + addError(null, "Not valid."); } else { - addError(errorMessage); + addError(null, errorMessage); } } } catch { - addError(" has a regex that is too slow."); + addError(null, "Regex is too slow."); } } } 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 47507c696..70ae219b8 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, Action addError) + public Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) { if (value == null) { @@ -38,12 +38,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators if (min.HasValue && typedValue.CompareTo(min.Value) < 0) { - addError($" must be greater or equals than '{min}'."); + addError(null, $"Must be greater or equals than '{min}'."); } if (max.HasValue && typedValue.CompareTo(max.Value) > 0) { - addError($" must be less or equals than '{max}'."); + addError(null, $"Must be less or equals than '{max}'."); } return TaskHelper.Done; 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 a47f0f279..8735aaef1 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, Action addError) + public async Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) { if (value is ICollection contentIds) { @@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators foreach (var invalidId in invalidIds) { - addError($" contains invalid reference '{invalidId}'."); + addError(null, $"Contains invalid reference '{invalidId}'."); } } } 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 fd7a39ad2..8f5ab8f74 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using System.Threading.Tasks; using Squidex.Infrastructure.Tasks; @@ -20,7 +19,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.validateEmptyStrings = validateEmptyStrings; } - public Task ValidateAsync(object value, ValidationContext context, Action addError) + public Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) { if (context.IsOptional || (value != null && !(value is string))) { @@ -31,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators if (valueAsString == null || (validateEmptyStrings && string.IsNullOrWhiteSpace(valueAsString))) { - addError(" is required."); + addError(null, "Field is required."); } return TaskHelper.Done; 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 4dfeb1849..2f5237580 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using System.Threading.Tasks; using Squidex.Infrastructure.Tasks; @@ -13,11 +12,11 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { public class RequiredValidator : IValidator { - public Task ValidateAsync(object value, ValidationContext context, Action addError) + public Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) { if (value == null && !context.IsOptional) { - addError(" is required."); + addError(null, "Field is required."); } return TaskHelper.Done; 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 acc8e13e4..45ba37507 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs @@ -27,18 +27,18 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.maxLength = maxLength; } - public Task ValidateAsync(object value, ValidationContext context, Action addError) + public Task ValidateAsync(object value, ValidationContext context, ErrorFormatter addError) { if (value is string stringValue && !string.IsNullOrEmpty(stringValue)) { if (minLength.HasValue && stringValue.Length < minLength.Value) { - addError($" must have more than '{minLength}' characters."); + addError(null, $"Must have more than '{minLength}' characters."); } if (maxLength.HasValue && stringValue.Length > maxLength.Value) { - addError($" must have less than '{maxLength}' characters."); + addError(null, $"Must have less than '{maxLength}' characters."); } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs index 88c9dacb3..704f850ce 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json.Linq; using NodaTime; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.ValidateContent.Validators; @@ -34,15 +35,24 @@ namespace Squidex.Domain.Apps.Core.ValidateContent { 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 CollectionValidator(field.Properties.IsRequired, field.Properties.MinItems, field.Properties.MaxItems); } + + var fieldsValidators = new Dictionary(); + + foreach (var kvp in field.FieldsByName) + { + fieldsValidators[kvp.Key] = (false, new FieldValidator(kvp.Value.Accept(this), kvp.Value)); + } + + yield return new CollectionItemValidator(new ObjectValidator(fieldsValidators, false, "field", JValue.CreateNull())); } 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 CollectionValidator(field.Properties.IsRequired, field.Properties.MinItems, field.Properties.MaxItems); } yield return new AssetsValidator(field.Properties); @@ -107,7 +117,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent { 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 CollectionValidator(field.Properties.IsRequired, field.Properties.MinItems, field.Properties.MaxItems); } if (field.Properties.SchemaId != Guid.Empty) @@ -143,10 +153,10 @@ namespace Squidex.Domain.Apps.Core.ValidateContent { 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 CollectionValidator(field.Properties.IsRequired, field.Properties.MinItems, field.Properties.MaxItems); } - yield return new CollectionItemValidator(new RequiredStringValidator()); + yield return new CollectionItemValidator(new RequiredStringValidator()); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs index 2e3794ac8..99527b079 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs @@ -100,7 +100,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(null), errors, ctx); errors.ShouldBeEquivalentTo( - new[] { " is required." }); + new[] { "Field is required." }); } [Fact] @@ -111,7 +111,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(), errors, ctx); errors.ShouldBeEquivalentTo( - new[] { " is required." }); + new[] { "Field is required." }); } [Fact] @@ -122,7 +122,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync("invalid", errors); errors.ShouldBeEquivalentTo( - new[] { " is not a valid value." }); + new[] { "Not a valid value." }); } [Fact] @@ -133,7 +133,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(document.AssetId, document.AssetId), errors, ctx); errors.ShouldBeEquivalentTo( - new[] { " must have at least 3 item(s)." }); + new[] { "Must have at least 3 item(s)." }); } [Fact] @@ -144,7 +144,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(document.AssetId, document.AssetId), errors, ctx); errors.ShouldBeEquivalentTo( - new[] { " must have not more than 1 item(s)." }); + new[] { "Must have not more than 1 item(s)." }); } [Fact] @@ -157,7 +157,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(assetId), errors, ctx); errors.ShouldBeEquivalentTo( - new[] { $" has invalid asset #1: Id '{assetId}' not found." }); + new[] { $"[1]: Id '{assetId}' not found." }); } [Fact] @@ -168,7 +168,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx); errors.ShouldBeEquivalentTo( - new[] { $" has invalid asset #1: '4 kB' less than minimum of '5 kB'." }); + new[] { $"[1]: '4 kB' less than minimum of '5 kB'." }); } [Fact] @@ -179,7 +179,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx); errors.ShouldBeEquivalentTo( - new[] { $" has invalid asset #2: '8 kB' greater than maximum of '5 kB'." }); + new[] { $"[2]: '8 kB' greater than maximum of '5 kB'." }); } [Fact] @@ -190,7 +190,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx); errors.ShouldBeEquivalentTo( - new[] { $" has invalid asset #1: Not an image." }); + new[] { $"[1]: Not an image." }); } [Fact] @@ -201,7 +201,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx); errors.ShouldBeEquivalentTo( - new[] { $" has invalid asset #2: Width '800px' less than minimum of '1000px'." }); + new[] { $"[2]: Width '800px' less than minimum of '1000px'." }); } [Fact] @@ -212,7 +212,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx); errors.ShouldBeEquivalentTo( - new[] { $" has invalid asset #2: Width '800px' greater than maximum of '700px'." }); + new[] { $"[2]: Width '800px' greater than maximum of '700px'." }); } [Fact] @@ -223,7 +223,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx); errors.ShouldBeEquivalentTo( - new[] { $" has invalid asset #2: Height '600px' less than minimum of '800px'." }); + new[] { $"[2]: Height '600px' less than minimum of '800px'." }); } [Fact] @@ -234,7 +234,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx); errors.ShouldBeEquivalentTo( - new[] { $" has invalid asset #2: Height '600px' greater than maximum of '500px'." }); + new[] { $"[2]: Height '600px' greater than maximum of '500px'." }); } [Fact] @@ -245,7 +245,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx); errors.ShouldBeEquivalentTo( - new[] { " has invalid asset #2: Aspect ratio not '1:1'." }); + new[] { "[2]: Aspect ratio not '1:1'." }); } [Fact] @@ -258,8 +258,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent errors.ShouldBeEquivalentTo( new[] { - $" has invalid asset #1: Invalid file extension.", - $" has invalid asset #2: Invalid file extension." + $"[1]: Invalid file extension.", + $"[2]: Invalid file extension." }); } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs index 918e10f93..b5c110e06 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs @@ -54,7 +54,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(null), errors); errors.ShouldBeEquivalentTo( - new[] { " is required." }); + new[] { "Field is required." }); } [Fact] @@ -65,7 +65,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue("Invalid"), errors); errors.ShouldBeEquivalentTo( - new[] { " is not a valid value." }); + new[] { "Not a valid value." }); } private static JValue CreateValue(object v) 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 29d809c2b..1a2509771 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs @@ -37,7 +37,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent errors.ShouldBeEquivalentTo( new List { - new ValidationError("unknown is not a known field.", "unknown") + new ValidationError("unknown: Not a known field.", "unknown") }); } @@ -58,7 +58,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent errors.ShouldBeEquivalentTo( new List { - new ValidationError("my-field must be less or equals than '100'.", "my-field") + new ValidationError("my-field.iv: Must be less or equals than '100'.", "my-field.iv") }); } @@ -79,8 +79,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent errors.ShouldBeEquivalentTo( new List { - new ValidationError("my-field has an unsupported invariant value 'es'.", "my-field"), - new ValidationError("my-field has an unsupported invariant value 'it'.", "my-field") + new ValidationError("my-field.es: Not a known invariant value.", "my-field.es"), + new ValidationError("my-field.it: Not a known invariant value.", "my-field.it") }); } @@ -98,8 +98,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent errors.ShouldBeEquivalentTo( new List { - new ValidationError("my-field (de) is required.", "my-field"), - new ValidationError("my-field (en) is required.", "my-field") + new ValidationError("my-field.de: Field is required.", "my-field.de"), + new ValidationError("my-field.en: Field is required.", "my-field.en") }); } @@ -117,7 +117,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent errors.ShouldBeEquivalentTo( new List { - new ValidationError("my-field is required.", "my-field") + new ValidationError("my-field.iv: Field is required.", "my-field.iv") }); } @@ -138,7 +138,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent errors.ShouldBeEquivalentTo( new List { - new ValidationError("my-field has an unsupported language value 'xx'.", "my-field") + new ValidationError("my-field.xx: Not a known language.", "my-field.xx") }); } @@ -181,8 +181,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent errors.ShouldBeEquivalentTo( new List { - new ValidationError("my-field has an unsupported language value 'es'.", "my-field"), - new ValidationError("my-field has an unsupported language value 'it'.", "my-field") + new ValidationError("my-field.es: Not a known language.", "my-field.es"), + new ValidationError("my-field.it: Not a known language.", "my-field.it") }); } @@ -199,7 +199,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent errors.ShouldBeEquivalentTo( new List { - new ValidationError("unknown is not a known field.", "unknown") + new ValidationError("unknown: Not a known field.", "unknown") }); } @@ -220,7 +220,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent errors.ShouldBeEquivalentTo( new List { - new ValidationError("my-field must be less or equals than '100'.", "my-field") + new ValidationError("my-field.iv: Must be less or equals than '100'.", "my-field.iv") }); } @@ -241,8 +241,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent errors.ShouldBeEquivalentTo( new List { - new ValidationError("my-field has an unsupported invariant value 'es'.", "my-field"), - new ValidationError("my-field has an unsupported invariant value 'it'.", "my-field") + new ValidationError("my-field.es: Not a known invariant value.", "my-field.es"), + new ValidationError("my-field.it: Not a known invariant value.", "my-field.it") }); } @@ -291,7 +291,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent errors.ShouldBeEquivalentTo( new List { - new ValidationError("my-field has an unsupported language value 'xx'.", "my-field") + new ValidationError("my-field.xx: Not a known language.", "my-field.xx") }); } @@ -312,8 +312,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent errors.ShouldBeEquivalentTo( new List { - new ValidationError("my-field has an unsupported language value 'es'.", "my-field"), - new ValidationError("my-field has an unsupported language value 'it'.", "my-field") + new ValidationError("my-field.es: Not a known language.", "my-field.es"), + new ValidationError("my-field.it: Not a known language.", "my-field.it") }); } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs index bb2c88f3b..47e037228 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs @@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(null), errors); errors.ShouldBeEquivalentTo( - new[] { " is required." }); + new[] { "Field is required." }); } [Fact] @@ -57,7 +57,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(FutureDays(0)), errors); errors.ShouldBeEquivalentTo( - new[] { $" must be greater or equals than '{FutureDays(10)}'." }); + new[] { $"Must be greater or equals than '{FutureDays(10)}'." }); } [Fact] @@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(FutureDays(20)), errors); errors.ShouldBeEquivalentTo( - new[] { $" must be less or equals than '{FutureDays(10)}'." }); + new[] { $"Must be less or equals than '{FutureDays(10)}'." }); } [Fact] @@ -79,7 +79,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue("Invalid"), errors); errors.ShouldBeEquivalentTo( - new[] { " is not a valid value." }); + new[] { "Not a valid value." }); } [Fact] @@ -90,7 +90,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(123), errors); errors.ShouldBeEquivalentTo( - new[] { " is not a valid value." }); + new[] { "Not a valid value." }); } private static Instant FutureDays(int days) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs index 2e38c8dc2..e9093cc30 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs @@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(geolocation), errors); errors.ShouldBeEquivalentTo( - new[] { " is not a valid value." }); + new[] { "Not a valid value." }); } [Fact] @@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(geolocation), errors); errors.ShouldBeEquivalentTo( - new[] { " is not a valid value." }); + new[] { "Not a valid value." }); } [Fact] @@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(geolocation), errors); errors.ShouldBeEquivalentTo( - new[] { " is not a valid value." }); + new[] { "Not a valid value." }); } [Fact] @@ -104,7 +104,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(JValue.CreateNull()), errors); errors.ShouldBeEquivalentTo( - new[] { " is required." }); + new[] { "Field is required." }); } private static JToken CreateValue(JToken v) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs index 35f6d9e68..ca5a40c6d 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs @@ -44,7 +44,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(JValue.CreateNull()), errors); errors.ShouldBeEquivalentTo( - new[] { " is required." }); + new[] { "Field is required." }); } private static JValue CreateValue(JValue v) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs index 009f1c266..0312149b9 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs @@ -45,7 +45,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(null), errors); errors.ShouldBeEquivalentTo( - new[] { " is required." }); + new[] { "Field is required." }); } [Fact] @@ -56,7 +56,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(5), errors); errors.ShouldBeEquivalentTo( - new[] { " must be greater or equals than '10'." }); + new[] { "Must be greater or equals than '10'." }); } [Fact] @@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(20), errors); errors.ShouldBeEquivalentTo( - new[] { " must be less or equals than '10'." }); + new[] { "Must be less or equals than '10'." }); } [Fact] @@ -78,7 +78,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(20), errors); errors.ShouldBeEquivalentTo( - new[] { " is not an allowed value." }); + new[] { "Not an allowed value." }); } [Fact] @@ -89,7 +89,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue("Invalid"), errors); errors.ShouldBeEquivalentTo( - new[] { " is not a valid value." }); + new[] { "Not a valid value." }); } private static JValue CreateValue(object v) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs index ba6936e56..f4c56fbc8 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs @@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(null), errors); errors.ShouldBeEquivalentTo( - new[] { " is required." }); + new[] { "Field is required." }); } [Fact] @@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(), errors); errors.ShouldBeEquivalentTo( - new[] { " is required." }); + new[] { "Field is required." }); } [Fact] @@ -81,7 +81,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync("invalid", errors); errors.ShouldBeEquivalentTo( - new[] { " is not a valid value." }); + new[] { "Not a valid value." }); } [Fact] @@ -92,7 +92,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors); errors.ShouldBeEquivalentTo( - new[] { " must have at least 3 item(s)." }); + new[] { "Must have at least 3 item(s)." }); } [Fact] @@ -103,7 +103,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors); errors.ShouldBeEquivalentTo( - new[] { " must have not more than 1 item(s)." }); + new[] { "Must have not more than 1 item(s)." }); } [Fact] @@ -116,7 +116,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(referenceId), errors, ValidationTestExtensions.InvalidReferences(referenceId)); errors.ShouldBeEquivalentTo( - new[] { $" contains invalid reference '{referenceId}'." }); + new[] { $"Contains invalid reference '{referenceId}'." }); } private static JToken CreateValue(params Guid[] ids) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs index 0e4a41e4a..818384a5b 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs @@ -45,7 +45,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(null), errors); errors.ShouldBeEquivalentTo( - new[] { " is required." }); + new[] { "Field is required." }); } [Fact] @@ -56,7 +56,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue("123"), errors); errors.ShouldBeEquivalentTo( - new[] { " must have more than '10' characters." }); + new[] { "Must have more than '10' characters." }); } [Fact] @@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue("12345678"), errors); errors.ShouldBeEquivalentTo( - new[] { " must have less than '5' characters." }); + new[] { "Must have less than '5' characters." }); } [Fact] @@ -78,7 +78,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue("Bar"), errors); errors.ShouldBeEquivalentTo( - new[] { " is not an allowed value." }); + new[] { "Not an allowed value." }); } [Fact] @@ -89,7 +89,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue("abc"), errors); errors.ShouldBeEquivalentTo( - new[] { " is not valid." }); + new[] { "Not valid." }); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs index 18aaebc1b..3ea8b8138 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs @@ -58,7 +58,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(null), errors); errors.ShouldBeEquivalentTo( - new[] { " is required." }); + new[] { "Field is required." }); } [Fact] @@ -69,7 +69,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(), errors); errors.ShouldBeEquivalentTo( - new[] { " is required." }); + new[] { "Field is required." }); } [Fact] @@ -80,7 +80,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync("invalid", errors); errors.ShouldBeEquivalentTo( - new[] { " is not a valid value." }); + new[] { "Not a valid value." }); } [Fact] @@ -91,7 +91,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors); errors.ShouldBeEquivalentTo( - new[] { " must have at least 3 item(s)." }); + new[] { "Must have at least 3 item(s)." }); } [Fact] @@ -102,7 +102,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors); errors.ShouldBeEquivalentTo( - new[] { " must have not more than 1 item(s)." }); + new[] { "Must have not more than 1 item(s)." }); } private static JToken CreateValue(params Guid[] ids) 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 acbd2c373..d6e8c27ca 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs @@ -25,22 +25,50 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent public static Task ValidateAsync(this IValidator validator, object value, IList errors, ValidationContext context = null) { - return validator.ValidateAsync(value, context ?? ValidContext, errors.Add); + return validator.ValidateAsync(value, + CreateContext(context), + CreateFormatter(errors)); } public static Task ValidateOptionalAsync(this IValidator validator, object value, IList errors, ValidationContext context = null) { - return validator.ValidateAsync(value, (context ?? ValidContext).Optional(true), errors.Add); + return validator.ValidateAsync(value, + CreateContext(context).Optional(true), + CreateFormatter(errors)); } public static Task ValidateAsync(this IField field, JToken value, IList errors, ValidationContext context = null) { - return field.ValidateAsync(value, context ?? ValidContext, errors.Add); + return new FieldValidator(ValidatorsFactory.CreateValidators(field), field).ValidateAsync(value, + CreateContext(context), + CreateFormatter(errors)); } public static Task ValidateOptionalAsync(this IField field, JToken value, IList errors, ValidationContext context = null) { - return field.ValidateAsync(value, (context ?? ValidContext).Optional(true), errors.Add); + return new FieldValidator(ValidatorsFactory.CreateValidators(field), field).ValidateAsync(value, + CreateContext(context).Optional(true), + CreateFormatter(errors)); + } + + private static ErrorFormatter CreateFormatter(IList errors) + { + return (field, message) => + { + if (field == null) + { + errors.Add(message); + } + else + { + errors.Add($"{field}: {message}"); + } + }; + } + + private static ValidationContext CreateContext(ValidationContext context) + { + return context ?? ValidContext; } public static ValidationContext Assets(params IAssetInfo[] assets) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AllowedValuesValidatorTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AllowedValuesValidatorTests.cs index dfa62d5a5..e9165a47c 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AllowedValuesValidatorTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AllowedValuesValidatorTests.cs @@ -45,7 +45,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators await sut.ValidateAsync(50, errors); errors.ShouldBeEquivalentTo( - new[] { " is not an allowed value." }); + new[] { "Not an allowed value." }); } } } \ No newline at end of file diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionItemValidatorTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionItemValidatorTests.cs index 19e42b907..800ddc0c7 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionItemValidatorTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionItemValidatorTests.cs @@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators [Fact] public async Task Should_not_add_error_if_value_is_wrong_type() { - var sut = new CollectionItemValidator(new RangeValidator(2, 4)); + var sut = new CollectionItemValidator(new RangeValidator(2, 4)); await sut.ValidateAsync(true, errors); @@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators [Fact] public async Task Should_not_add_error_if_all_values_are_valid() { - var sut = new CollectionItemValidator(new RangeValidator(2, 4)); + var sut = new CollectionItemValidator(new RangeValidator(2, 4)); await sut.ValidateAsync(new List { 2, 3, 4 }, errors); @@ -40,15 +40,15 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators [Fact] public async Task Should_add_error_if_at_least_one_item_is_not_valid() { - var sut = new CollectionItemValidator(new RangeValidator(2, 4)); + var sut = new CollectionItemValidator(new RangeValidator(2, 4)); await sut.ValidateAsync(new List { 2, 1, 4, 5 }, errors); errors.ShouldBeEquivalentTo( new[] { - " item #2 must be greater or equals than '2'.", - " item #4 must be less or equals than '4'." + "[2]: Must be greater or equals than '2'.", + "[4]: Must be less or equals than '4'." }); } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionValidatorTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionValidatorTests.cs index cdb24dd14..4d31d0972 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionValidatorTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionValidatorTests.cs @@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators [Fact] public async Task Should_not_add_error_if_value_is_valid() { - var sut = new CollectionValidator(true, 1, 3); + var sut = new CollectionValidator(true, 1, 3); await sut.ValidateAsync(new List { 1, 2 }, errors); @@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators [Fact] public async Task Should_not_add_error_if_optional() { - var sut = new CollectionValidator(true, 1, 3); + var sut = new CollectionValidator(true, 1, 3); await sut.ValidateOptionalAsync(null, errors); @@ -40,34 +40,34 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators [Fact] public async Task Should_add_error_if_value_is_null() { - var sut = new CollectionValidator(true, 1, 3); + var sut = new CollectionValidator(true, 1, 3); await sut.ValidateAsync(null, errors); errors.ShouldBeEquivalentTo( - new[] { " is required." }); + new[] { "Field is required." }); } [Fact] public async Task Should_add_error_if_collection_has_too_few_items() { - var sut = new CollectionValidator(true, 2, 3); + var sut = new CollectionValidator(true, 2, 3); await sut.ValidateAsync(new List { 1 }, errors); errors.ShouldBeEquivalentTo( - new[] { " must have at least 2 item(s)." }); + new[] { "Must have at least 2 item(s)." }); } [Fact] public async Task Should_add_error_if_collection_has_too_many_items() { - var sut = new CollectionValidator(true, 2, 3); + var sut = new CollectionValidator(true, 2, 3); await sut.ValidateAsync(new List { 1, 2, 3, 4 }, errors); errors.ShouldBeEquivalentTo( - new[] { " must have not more than 3 item(s)." }); + new[] { "Must have not more than 3 item(s)." }); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/PatternValidatorTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/PatternValidatorTests.cs index 3b35d115d..a15f390e0 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/PatternValidatorTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/PatternValidatorTests.cs @@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators await sut.ValidateAsync("foo", errors); errors.ShouldBeEquivalentTo( - new[] { " is not valid." }); + new[] { "Not valid." }); } [Fact] @@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators await sut.ValidateAsync("https://archiverbx.blob.core.windows.net/static/C:/Users/USR/Documents/Projects/PROJ/static/images/full/1234567890.jpg", errors); errors.ShouldBeEquivalentTo( - new[] { " has a regex that is too slow." }); + new[] { "Regex is too slow." }); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RangeValidatorTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RangeValidatorTests.cs index 952dae61c..4d232d10d 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RangeValidatorTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RangeValidatorTests.cs @@ -58,7 +58,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators await sut.ValidateAsync(1500, errors); errors.ShouldBeEquivalentTo( - new[] { " must be greater or equals than '2000'." }); + new[] { "Must be greater or equals than '2000'." }); } [Fact] @@ -69,7 +69,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators await sut.ValidateAsync(1500, errors); errors.ShouldBeEquivalentTo( - new[] { " must be less or equals than '1000'." }); + new[] { "Must be less or equals than '1000'." }); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredStringValidatorTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredStringValidatorTests.cs index d0b81afec..63b065d59 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredStringValidatorTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredStringValidatorTests.cs @@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators await sut.ValidateAsync(string.Empty, errors); errors.ShouldBeEquivalentTo( - new[] { " is required." }); + new[] { "Field is required." }); } [Fact] @@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators await sut.ValidateAsync(null, errors); errors.ShouldBeEquivalentTo( - new[] { " is required." }); + new[] { "Field is required." }); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredValidatorTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredValidatorTests.cs index 42c10195c..e2a13ce67 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredValidatorTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredValidatorTests.cs @@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators await sut.ValidateAsync(null, errors); errors.ShouldBeEquivalentTo( - new[] { " is required." }); + new[] { "Field is required." }); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringLengthValidatorTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringLengthValidatorTests.cs index 8bbb88844..5e08b43a6 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringLengthValidatorTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringLengthValidatorTests.cs @@ -69,7 +69,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators await sut.ValidateAsync(CreateString(1500), errors); errors.ShouldBeEquivalentTo( - new[] { " must have more than '2000' characters." }); + new[] { "Must have more than '2000' characters." }); } [Fact] @@ -80,7 +80,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators await sut.ValidateAsync(CreateString(1500), errors); errors.ShouldBeEquivalentTo( - new[] { " must have less than '1000' characters." }); + new[] { "Must have less than '1000' characters." }); } private static string CreateString(int size)