Browse Source

Validators improved.

pull/141/head
Sebastian Stehle 9 years ago
parent
commit
5b69447a5f
  1. 20
      src/Squidex.Domain.Apps.Core/Schemas/AssetsField.cs
  2. 25
      src/Squidex.Domain.Apps.Core/Schemas/AssetsValue.cs
  3. 20
      src/Squidex.Domain.Apps.Core/Schemas/ReferencesField.cs
  4. 25
      src/Squidex.Domain.Apps.Core/Schemas/ReferencesValue.cs
  5. 39
      src/Squidex.Domain.Apps.Core/Schemas/Validators/AssetsValidator.cs
  6. 46
      src/Squidex.Domain.Apps.Core/Schemas/Validators/CollectionItemValidator.cs
  7. 54
      src/Squidex.Domain.Apps.Core/Schemas/Validators/CollectionValidator.cs
  8. 4
      src/Squidex.Domain.Apps.Core/Schemas/Validators/RangeValidator.cs
  9. 36
      src/Squidex.Domain.Apps.Core/Schemas/Validators/ReferencesValidator.cs
  10. 4
      tests/Squidex.Domain.Apps.Core.Tests/ContentValidationTests.cs
  11. 12
      tests/Squidex.Domain.Apps.Core.Tests/Schemas/AssetsFieldTests.cs
  12. 4
      tests/Squidex.Domain.Apps.Core.Tests/Schemas/DateTimeFieldTests.cs
  13. 4
      tests/Squidex.Domain.Apps.Core.Tests/Schemas/NumberFieldTests.cs
  14. 12
      tests/Squidex.Domain.Apps.Core.Tests/Schemas/ReferencesFieldTests.cs
  15. 55
      tests/Squidex.Domain.Apps.Core.Tests/Schemas/Validators/CollectionItemValidatorTests.cs
  16. 73
      tests/Squidex.Domain.Apps.Core.Tests/Schemas/Validators/CollectionValidatorTests.cs
  17. 4
      tests/Squidex.Domain.Apps.Core.Tests/Schemas/Validators/RangeValidatorTests.cs

20
src/Squidex.Domain.Apps.Core/Schemas/AssetsField.cs

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas.Validators;
@ -16,7 +17,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class AssetsField : Field<AssetsFieldProperties>, IReferenceField
{
private static readonly Guid[] EmptyIds = new Guid[0];
private static readonly ImmutableList<Guid> EmptyIds = ImmutableList<Guid>.Empty;
public AssetsField(long id, string name, Partitioning partitioning)
: this(id, name, partitioning, new AssetsFieldProperties())
@ -30,22 +31,27 @@ namespace Squidex.Domain.Apps.Core.Schemas
protected override IEnumerable<IValidator> CreateValidators()
{
yield return new AssetsValidator(Properties.IsRequired, Properties.MinItems, Properties.MaxItems);
if (Properties.IsRequired || Properties.MinItems.HasValue || Properties.MaxItems.HasValue)
{
yield return new CollectionValidator<Guid>(Properties.IsRequired, Properties.MinItems, Properties.MaxItems);
}
yield return new AssetsValidator();
}
public IEnumerable<Guid> GetReferencedIds(JToken value)
{
Guid[] assetIds;
IEnumerable<Guid> result = null;
try
{
assetIds = value?.ToObject<Guid[]>() ?? EmptyIds;
result = value?.ToObject<List<Guid>>();
}
catch
{
assetIds = EmptyIds;
result = EmptyIds;
}
return assetIds;
return result ?? EmptyIds;
}
public JToken RemoveDeletedReferences(JToken value, ISet<Guid> deletedReferencedIds)
@ -63,7 +69,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
public override object ConvertValue(JToken value)
{
return new AssetsValue(value.ToObject<Guid[]>());
return value.ToObject<List<Guid>>();
}
public override T Visit<T>(IFieldVisitor<T> visitor)

25
src/Squidex.Domain.Apps.Core/Schemas/AssetsValue.cs

@ -1,25 +0,0 @@
// ==========================================================================
// AssetsValue.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class AssetsValue
{
private static readonly List<Guid> EmptyAssetIds = new List<Guid>();
public IReadOnlyList<Guid> AssetIds { get; }
public AssetsValue(IReadOnlyList<Guid> assetIds)
{
AssetIds = assetIds ?? EmptyAssetIds;
}
}
}

20
src/Squidex.Domain.Apps.Core/Schemas/ReferencesField.cs

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas.Validators;
@ -16,7 +17,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class ReferencesField : Field<ReferencesFieldProperties>, IReferenceField
{
private static readonly Guid[] EmptyIds = new Guid[0];
private static readonly ImmutableList<Guid> EmptyIds = ImmutableList<Guid>.Empty;
public ReferencesField(long id, string name, Partitioning partitioning)
: this(id, name, partitioning, new ReferencesFieldProperties())
@ -30,25 +31,30 @@ namespace Squidex.Domain.Apps.Core.Schemas
protected override IEnumerable<IValidator> CreateValidators()
{
if (Properties.IsRequired || Properties.MinItems.HasValue || Properties.MaxItems.HasValue)
{
yield return new CollectionValidator<Guid>(Properties.IsRequired, Properties.MinItems, Properties.MaxItems);
}
if (Properties.SchemaId != Guid.Empty)
{
yield return new ReferencesValidator(Properties.IsRequired, Properties.SchemaId, Properties.MinItems, Properties.MaxItems);
yield return new ReferencesValidator(Properties.SchemaId);
}
}
public IEnumerable<Guid> GetReferencedIds(JToken value)
{
Guid[] referenceIds;
IEnumerable<Guid> result = null;
try
{
referenceIds = value?.ToObject<Guid[]>() ?? EmptyIds;
result = value?.ToObject<List<Guid>>();
}
catch
{
referenceIds = EmptyIds;
result = EmptyIds;
}
return referenceIds.Union(new[] { Properties.SchemaId });
return (result ?? EmptyIds).Union(new[] { Properties.SchemaId });
}
public JToken RemoveDeletedReferences(JToken value, ISet<Guid> deletedReferencedIds)
@ -71,7 +77,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
public override object ConvertValue(JToken value)
{
return new ReferencesValue(value.ToObject<Guid[]>());
return value.ToObject<List<Guid>>();
}
public override T Visit<T>(IFieldVisitor<T> visitor)

25
src/Squidex.Domain.Apps.Core/Schemas/ReferencesValue.cs

@ -1,25 +0,0 @@
// ==========================================================================
// ReferencesValue.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class ReferencesValue
{
private static readonly List<Guid> EmptyReferencedIds = new List<Guid>();
public IReadOnlyList<Guid> ContentIds { get; }
public ReferencesValue(IReadOnlyList<Guid> assetIds)
{
ContentIds = assetIds ?? EmptyReferencedIds;
}
}
}

39
src/Squidex.Domain.Apps.Core/Schemas/Validators/AssetsValidator.cs

@ -7,50 +7,23 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Core.Schemas.Validators
{
public sealed class AssetsValidator : IValidator
{
private readonly bool isRequired;
private readonly int? minItems;
private readonly int? maxItems;
public AssetsValidator(bool isRequired, int? minItems = null, int? maxItems = null)
{
this.isRequired = isRequired;
this.minItems = minItems;
this.maxItems = maxItems;
}
public async Task ValidateAsync(object value, ValidationContext context, Action<string> addError)
{
if (!(value is AssetsValue assets) || assets.AssetIds.Count == 0)
if (value is ICollection<Guid> assetIds)
{
if (isRequired && !context.IsOptional)
var invalidIds = await context.GetInvalidAssetIdsAsync(assetIds);
foreach (var invalidId in invalidIds)
{
addError("<FIELD> is required.");
addError($"<FIELD> contains invalid asset '{invalidId}'.");
}
return;
}
if (minItems.HasValue && assets.AssetIds.Count < minItems.Value)
{
addError($"<FIELD> must have at least {minItems} asset(s).");
}
if (maxItems.HasValue && assets.AssetIds.Count > maxItems.Value)
{
addError($"<FIELD> must have not more than {maxItems} asset(s).");
}
var invalidIds = await context.GetInvalidAssetIdsAsync(assets.AssetIds);
foreach (var invalidId in invalidIds)
{
addError($"<FIELD> contains invalid asset '{invalidId}'.");
}
}
}

46
src/Squidex.Domain.Apps.Core/Schemas/Validators/CollectionItemValidator.cs

@ -0,0 +1,46 @@
// ==========================================================================
// CollectionItemValidator.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas.Validators
{
public sealed class CollectionItemValidator<T> : IValidator
{
private readonly IValidator[] itemValidators;
public CollectionItemValidator(params IValidator[] itemValidators)
{
Guard.NotNull(itemValidators, nameof(itemValidators));
Guard.NotEmpty(itemValidators, nameof(itemValidators));
this.itemValidators = itemValidators;
}
public async Task ValidateAsync(object value, ValidationContext context, Action<string> addError)
{
if (value is ICollection<T> items)
{
var index = 1;
foreach (var item in items)
{
foreach (var itemValidator in itemValidators)
{
await itemValidator.ValidateAsync(item, context, e => addError(e.Replace("<FIELD>", $"<FIELD> item #{index}")));
}
index++;
}
}
}
}
}

54
src/Squidex.Domain.Apps.Core/Schemas/Validators/CollectionValidator.cs

@ -0,0 +1,54 @@
// ==========================================================================
// CollectionValidator.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Core.Schemas.Validators
{
public sealed class CollectionValidator<T> : IValidator
{
private readonly bool isRequired;
private readonly int? minItems;
private readonly int? maxItems;
public CollectionValidator(bool isRequired, int? minItems = null, int? maxItems = null)
{
this.isRequired = isRequired;
this.minItems = minItems;
this.maxItems = maxItems;
}
public Task ValidateAsync(object value, ValidationContext context, Action<string> addError)
{
if (!(value is ICollection<T> items) || items.Count == 0)
{
if (isRequired && !context.IsOptional)
{
addError("<FIELD> is required.");
}
return TaskHelper.Done;
}
if (minItems.HasValue && items.Count < minItems.Value)
{
addError($"<FIELD> must have at least {minItems} item(s).");
}
if (maxItems.HasValue && items.Count > maxItems.Value)
{
addError($"<FIELD> must have not more than {maxItems} item(s).");
}
return TaskHelper.Done;
}
}
}

4
src/Squidex.Domain.Apps.Core/Schemas/Validators/RangeValidator.cs

@ -39,12 +39,12 @@ namespace Squidex.Domain.Apps.Core.Schemas.Validators
if (min.HasValue && typedValue.CompareTo(min.Value) < 0)
{
addError($"<FIELD> must be greater than '{min}'.");
addError($"<FIELD> must be greater or equals than '{min}'.");
}
if (max.HasValue && typedValue.CompareTo(max.Value) > 0)
{
addError($"<FIELD> must be less than '{max}'.");
addError($"<FIELD> must be less or equals than '{max}'.");
}
return TaskHelper.Done;

36
src/Squidex.Domain.Apps.Core/Schemas/Validators/ReferencesValidator.cs

@ -7,52 +7,30 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Core.Schemas.Validators
{
public sealed class ReferencesValidator : IValidator
{
private readonly bool isRequired;
private readonly Guid schemaId;
private readonly int? minItems;
private readonly int? maxItems;
public ReferencesValidator(bool isRequired, Guid schemaId, int? minItems = null, int? maxItems = null)
public ReferencesValidator(Guid schemaId)
{
this.isRequired = isRequired;
this.schemaId = schemaId;
this.minItems = minItems;
this.maxItems = maxItems;
}
public async Task ValidateAsync(object value, ValidationContext context, Action<string> addError)
{
if (!(value is ReferencesValue references) || references.ContentIds.Count == 0)
if (value is ICollection<Guid> contentIds)
{
if (isRequired && !context.IsOptional)
var invalidIds = await context.GetInvalidContentIdsAsync(contentIds, schemaId);
foreach (var invalidId in invalidIds)
{
addError("<FIELD> is required.");
addError($"<FIELD> contains invalid reference '{invalidId}'.");
}
return;
}
if (minItems.HasValue && references.ContentIds.Count < minItems.Value)
{
addError($"<FIELD> must have at least {minItems} reference(s).");
}
if (maxItems.HasValue && references.ContentIds.Count > maxItems.Value)
{
addError($"<FIELD> must have not more than {maxItems} reference(s).");
}
var invalidIds = await context.GetInvalidContentIdsAsync(references.ContentIds, schemaId);
foreach (var invalidId in invalidIds)
{
addError($"<FIELD> contains invalid reference '{invalidId}'.");
}
}
}

4
tests/Squidex.Domain.Apps.Core.Tests/ContentValidationTests.cs

@ -56,7 +56,7 @@ namespace Squidex.Domain.Apps.Core
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field must be less than '100'.", "my-field")
new ValidationError("my-field must be less or equals than '100'.", "my-field")
});
}
@ -212,7 +212,7 @@ namespace Squidex.Domain.Apps.Core
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field must be less than '100'.", "my-field")
new ValidationError("my-field must be less or equals than '100'.", "my-field")
});
}

12
tests/Squidex.Domain.Apps.Core.Tests/Schemas/AssetsFieldTests.cs

@ -99,7 +99,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> must have at least 3 asset(s)." });
new[] { "<FIELD> must have at least 3 item(s)." });
}
[Fact]
@ -110,7 +110,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> must have not more than 1 asset(s)." });
new[] { "<FIELD> must have not more than 1 item(s)." });
}
[Fact]
@ -206,14 +206,6 @@ namespace Squidex.Domain.Apps.Core.Schemas
Assert.Same(token, result);
}
[Fact]
public void Should_create_assets_value_with_empty_list_when_null()
{
var value = new AssetsValue(null);
Assert.Equal(new List<Guid>(), value.AssetIds);
}
private static JToken CreateValue(params Guid[] ids)
{
return ids == null ? JValue.CreateNull() : (JToken)new JArray(ids.OfType<object>().ToArray());

4
tests/Squidex.Domain.Apps.Core.Tests/Schemas/DateTimeFieldTests.cs

@ -65,7 +65,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
await sut.ValidateAsync(CreateValue(FutureDays(0)), errors);
errors.ShouldBeEquivalentTo(
new[] { $"<FIELD> must be greater than '{FutureDays(10)}'." });
new[] { $"<FIELD> must be greater or equals than '{FutureDays(10)}'." });
}
[Fact]
@ -76,7 +76,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
await sut.ValidateAsync(CreateValue(FutureDays(20)), errors);
errors.ShouldBeEquivalentTo(
new[] { $"<FIELD> must be less than '{FutureDays(10)}'." });
new[] { $"<FIELD> must be less or equals than '{FutureDays(10)}'." });
}
[Fact]

4
tests/Squidex.Domain.Apps.Core.Tests/Schemas/NumberFieldTests.cs

@ -64,7 +64,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
await sut.ValidateAsync(CreateValue(5), errors);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> must be greater than '10'." });
new[] { "<FIELD> must be greater or equals than '10'." });
}
[Fact]
@ -75,7 +75,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
await sut.ValidateAsync(CreateValue(20), errors);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> must be less than '10'." });
new[] { "<FIELD> must be less or equals than '10'." });
}
[Fact]

12
tests/Squidex.Domain.Apps.Core.Tests/Schemas/ReferencesFieldTests.cs

@ -100,7 +100,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> must have at least 3 reference(s)." });
new[] { "<FIELD> must have at least 3 item(s)." });
}
[Fact]
@ -111,7 +111,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> must have not more than 1 reference(s)." });
new[] { "<FIELD> must have not more than 1 item(s)." });
}
[Fact]
@ -220,14 +220,6 @@ namespace Squidex.Domain.Apps.Core.Schemas
Assert.Same(token, result);
}
[Fact]
public void Should_create_references_value_with_empty_list_when_null()
{
var value = new ReferencesValue(null);
Assert.Equal(new List<Guid>(), value.ContentIds);
}
private static JToken CreateValue(params Guid[] ids)
{
return ids == null ? JValue.CreateNull() : (JToken)new JArray(ids.OfType<object>().ToArray());

55
tests/Squidex.Domain.Apps.Core.Tests/Schemas/Validators/CollectionItemValidatorTests.cs

@ -0,0 +1,55 @@
// ==========================================================================
// CollectionValidatorTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Xunit;
namespace Squidex.Domain.Apps.Core.Schemas.Validators
{
public class CollectionItemValidatorTests
{
private readonly List<string> errors = new List<string>();
[Fact]
public async Task Should_not_add_error_if_value_is_wrong_type()
{
var sut = new CollectionItemValidator<int>(new RangeValidator<int>(2, 4));
await sut.ValidateAsync(true, errors);
Assert.Empty(errors);
}
[Fact]
public async Task Should_not_add_error_if_all_values_are_valid()
{
var sut = new CollectionItemValidator<int>(new RangeValidator<int>(2, 4));
await sut.ValidateAsync(new List<int> { 2, 3, 4 }, errors);
Assert.Empty(errors);
}
[Fact]
public async Task Should_add_error_if_at_least_one_item_is_not_valid()
{
var sut = new CollectionItemValidator<int>(new RangeValidator<int>(2, 4));
await sut.ValidateAsync(new List<int> { 2, 1, 4, 5 }, errors);
errors.ShouldBeEquivalentTo(
new[]
{
"<FIELD> item #2 must be greater or equals than '2'.",
"<FIELD> item #4 must be less or equals than '4'."
});
}
}
}

73
tests/Squidex.Domain.Apps.Core.Tests/Schemas/Validators/CollectionValidatorTests.cs

@ -0,0 +1,73 @@
// ==========================================================================
// CollectionValidatorTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Xunit;
namespace Squidex.Domain.Apps.Core.Schemas.Validators
{
public class CollectionValidatorTests
{
private readonly List<string> errors = new List<string>();
[Fact]
public async Task Should_not_add_error_if_value_is_valid()
{
var sut = new CollectionValidator<int>(true, 1, 3);
await sut.ValidateAsync(new List<int> { 1, 2 }, errors);
Assert.Empty(errors);
}
[Fact]
public async Task Should_not_add_error_if_optional()
{
var sut = new CollectionValidator<int>(true, 1, 3);
await sut.ValidateOptionalAsync(null, errors);
Assert.Empty(errors);
}
[Fact]
public async Task Should_add_error_if_value_is_null()
{
var sut = new CollectionValidator<int>(true, 1, 3);
await sut.ValidateAsync(null, errors);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> is required." });
}
[Fact]
public async Task Should_add_error_if_collection_has_too_few_items()
{
var sut = new CollectionValidator<int>(true, 2, 3);
await sut.ValidateAsync(new List<int> { 1 }, errors);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> must have at least 2 item(s)." });
}
[Fact]
public async Task Should_add_error_if_collection_has_too_many_items()
{
var sut = new CollectionValidator<int>(true, 2, 3);
await sut.ValidateAsync(new List<int> { 1, 2, 3, 4 }, errors);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> must have not more than 3 item(s)." });
}
}
}

4
tests/Squidex.Domain.Apps.Core.Tests/Schemas/Validators/RangeValidatorTests.cs

@ -58,7 +58,7 @@ namespace Squidex.Domain.Apps.Core.Schemas.Validators
await sut.ValidateAsync(1500, errors);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> must be greater than '2000'." });
new[] { "<FIELD> must be greater or equals than '2000'." });
}
[Fact]
@ -69,7 +69,7 @@ namespace Squidex.Domain.Apps.Core.Schemas.Validators
await sut.ValidateAsync(1500, errors);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> must be less than '1000'." });
new[] { "<FIELD> must be less or equals than '1000'." });
}
}
}

Loading…
Cancel
Save