Browse Source

Multiple references in core model.

pull/422/head
Sebastian 6 years ago
parent
commit
5c81242e1a
  1. 19
      src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs
  2. 6
      src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs
  3. 2
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs
  4. 7
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs
  5. 5
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/FieldValueValidatorsFactory.cs
  6. 44
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs
  7. 22
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs
  8. 2
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs
  9. 1
      src/Squidex.Domain.Users/UserManagerExtensions.cs
  10. 2
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs
  11. 51
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs
  12. 2
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs
  13. 1
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/UIFieldTests.cs
  14. 15
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs
  15. 6
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValidatorTests.cs
  16. 1
      tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj
  17. 11
      tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs

19
src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs

@ -6,6 +6,8 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Squidex.Domain.Apps.Core.Schemas
{
@ -21,7 +23,22 @@ namespace Squidex.Domain.Apps.Core.Schemas
public ReferencesFieldEditor Editor { get; set; }
public Guid SchemaId { get; set; }
public ReadOnlyCollection<Guid> SchemaIds { get; set; }
public Guid SchemaId
{
set
{
if (value != default)
{
SchemaIds = new ReadOnlyCollection<Guid>(new List<Guid> { value });
}
else
{
SchemaIds = null;
}
}
}
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{

6
src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs

@ -54,11 +54,15 @@ namespace Squidex.Domain.Apps.Core.Schemas
return schema.Properties.Label.WithFallback(schema.TypeName());
}
public static Guid SingleId(this ReferencesFieldProperties properties)
{
return properties.SchemaIds?.Count == 1 ? properties.SchemaIds[0] : Guid.Empty;
}
public static IEnumerable<IField<ReferencesFieldProperties>> ResolvingReferences(this Schema schema)
{
return schema.Fields.OfType<IField<ReferencesFieldProperties>>()
.Where(x =>
x.Properties.SchemaId != Guid.Empty &&
x.Properties.ResolveReference &&
x.Properties.MaxItems == 1 &&
(x.Properties.IsListField || schema.Fields.Count == 1));

2
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs

@ -36,7 +36,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
public IJsonValue Visit(IField<ReferencesFieldProperties> field)
{
if (oldReferences.Contains(field.Properties.SchemaId))
if (oldReferences.Contains(field.Properties.SingleId()))
{
return JsonValue.Array();
}

7
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs

@ -62,9 +62,12 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
var ids = value.ToGuidSet();
if (strategy == Ids.All && field.Properties.SchemaId != Guid.Empty)
if (strategy == Ids.All && field.Properties.SchemaIds != null)
{
ids.Add(field.Properties.SchemaId);
foreach (var schemaId in field.Properties.SchemaIds)
{
ids.Add(schemaId);
}
}
return ids;

5
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/FieldValueValidatorsFactory.cs

@ -134,10 +134,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
yield return new UniqueValuesValidator<Guid>();
}
if (field.Properties.SchemaId != Guid.Empty)
{
yield return new ReferencesValidator(field.Properties.SchemaId);
}
yield return new ReferencesValidator(field.Properties.SchemaIds);
}
public IEnumerable<IValidator> Visit(IField<StringFieldProperties> field)

44
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs

@ -14,7 +14,9 @@ using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Core.ValidateContent
{
public delegate Task<IReadOnlyList<Guid>> CheckContents(Guid schemaId, FilterNode<ClrValue> filter);
public delegate Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> CheckContents(Guid schemaId, FilterNode<ClrValue> filter);
public delegate Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> CheckContentsByIds(HashSet<Guid> ids);
public delegate Task<IReadOnlyList<IAssetInfo>> CheckAssets(IEnumerable<Guid> ids);
@ -23,6 +25,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
private readonly Guid contentId;
private readonly Guid schemaId;
private readonly CheckContents checkContent;
private readonly CheckContentsByIds checkContentByIds;
private readonly CheckAssets checkAsset;
private readonly ImmutableQueue<string> propertyPath;
@ -47,8 +50,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
Guid contentId,
Guid schemaId,
CheckContents checkContent,
CheckContentsByIds checkContentsByIds,
CheckAssets checkAsset)
: this(contentId, schemaId, checkContent, checkAsset, ImmutableQueue<string>.Empty, false)
: this(contentId, schemaId, checkContent, checkContentsByIds, checkAsset, ImmutableQueue<string>.Empty, false)
{
}
@ -56,16 +60,19 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
Guid contentId,
Guid schemaId,
CheckContents checkContent,
CheckContentsByIds checkContentByIds,
CheckAssets checkAsset,
ImmutableQueue<string> propertyPath,
bool isOptional)
{
Guard.NotNull(checkAsset, nameof(checkAsset));
Guard.NotNull(checkContent, nameof(checkAsset));
Guard.NotNull(checkContent, nameof(checkContent));
Guard.NotNull(checkContentByIds, nameof(checkContentByIds));
this.propertyPath = propertyPath;
this.checkContent = checkContent;
this.checkContentByIds = checkContentByIds;
this.checkAsset = checkAsset;
this.contentId = contentId;
@ -76,17 +83,40 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public ValidationContext Optional(bool isOptional)
{
return isOptional == IsOptional ? this : new ValidationContext(contentId, schemaId, checkContent, checkAsset, propertyPath, isOptional);
return isOptional == IsOptional ? this : OptionalCore(isOptional);
}
private ValidationContext OptionalCore(bool isOptional)
{
return new ValidationContext(
contentId,
schemaId,
checkContent,
checkContentByIds,
checkAsset,
propertyPath,
isOptional);
}
public ValidationContext Nested(string property)
{
return new ValidationContext(contentId, schemaId, checkContent, checkAsset, propertyPath.Enqueue(property), IsOptional);
return new ValidationContext(
contentId, schemaId,
checkContent,
checkContentByIds,
checkAsset,
propertyPath.Enqueue(property),
IsOptional);
}
public Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> GetContentIdsAsync(HashSet<Guid> ids)
{
return checkContentByIds(ids);
}
public Task<IReadOnlyList<Guid>> GetContentIdsAsync(Guid validatedSchemaId, FilterNode<ClrValue> filter)
public Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> GetContentIdsAsync(Guid schemaId, FilterNode<ClrValue> filter)
{
return checkContent(validatedSchemaId, filter);
return checkContent(schemaId, filter);
}
public Task<IReadOnlyList<IAssetInfo>> GetAssetInfosAsync(IEnumerable<Guid> assetId)

22
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs

@ -9,35 +9,37 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
public sealed class ReferencesValidator : IValidator
{
private static readonly PropertyPath Path = "Id";
private readonly IEnumerable<Guid> schemaIds;
private readonly Guid schemaId;
public ReferencesValidator(Guid schemaId)
public ReferencesValidator(IEnumerable<Guid> schemaIds)
{
this.schemaId = schemaId;
this.schemaIds = schemaIds;
}
public async Task ValidateAsync(object value, ValidationContext context, AddError addError)
{
if (value is ICollection<Guid> contentIds)
{
var filter = ClrFilter.In(Path, contentIds.ToList());
var foundIds = await context.GetContentIdsAsync(schemaId, filter);
var foundIds = await context.GetContentIdsAsync(contentIds.ToHashSet());
foreach (var id in contentIds)
{
if (!foundIds.Contains(id))
var (schemaId, _) = foundIds.FirstOrDefault(x => x.Id == id);
if (schemaId == Guid.Empty)
{
addError(context.Path, $"Contains invalid reference '{id}'.");
}
else if (schemaIds?.Any() == true && !schemaIds.Contains(schemaId))
{
addError(context.Path, $"Contains reference '{id}' to invalid schema.");
}
}
}
}

2
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs

@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
var found = await context.GetContentIdsAsync(context.SchemaId, filter);
if (found.Any(x => x != context.ContentId))
if (found.Any(x => x.Id != context.ContentId))
{
addError(context.Path, "Another content with the same value exists.");
}

1
src/Squidex.Domain.Users/UserManagerExtensions.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;

2
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs

@ -98,7 +98,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new NumberFieldProperties { IsUnique = true });
await sut.ValidateAsync(CreateValue(12.5), errors, ValidationTestExtensions.References(Guid.NewGuid()));
await sut.ValidateAsync(CreateValue(12.5), errors, ValidationTestExtensions.References((Guid.NewGuid(), Guid.NewGuid())));
errors.Should().BeEquivalentTo(
new[] { "Another content with the same value exists." });

51
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs

@ -11,6 +11,7 @@ using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Infrastructure.Json.Objects;
using Xunit;
@ -36,7 +37,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new ReferencesFieldProperties());
await sut.ValidateAsync(CreateValue(ref1), errors, ValidationTestExtensions.References(ref1));
await sut.ValidateAsync(CreateValue(ref1), errors, Context());
Assert.Empty(errors);
}
@ -46,7 +47,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new ReferencesFieldProperties());
await sut.ValidateAsync(CreateValue(null), errors);
await sut.ValidateAsync(CreateValue(null), errors, Context());
Assert.Empty(errors);
}
@ -56,7 +57,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new ReferencesFieldProperties { MinItems = 2, MaxItems = 2 });
await sut.ValidateAsync(CreateValue(ref1, ref2), errors);
await sut.ValidateAsync(CreateValue(ref1, ref2), errors, Context());
Assert.Empty(errors);
}
@ -66,7 +67,17 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new ReferencesFieldProperties { MinItems = 2, MaxItems = 2, AllowDuplicates = true });
await sut.ValidateAsync(CreateValue(ref1, ref1), errors);
await sut.ValidateAsync(CreateValue(ref1, ref1), errors, Context());
Assert.Empty(errors);
}
[Fact]
public async Task Should_not_add_error_if_schemas_not_defined()
{
var sut = Field(new ReferencesFieldProperties());
await sut.ValidateAsync(CreateValue(ref1), errors, ValidationTestExtensions.References((Guid.NewGuid(), ref1)));
Assert.Empty(errors);
}
@ -76,7 +87,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId, IsRequired = true });
await sut.ValidateAsync(CreateValue(null), errors);
await sut.ValidateAsync(CreateValue(null), errors, Context());
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
@ -87,7 +98,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId, IsRequired = true });
await sut.ValidateAsync(CreateValue(), errors);
await sut.ValidateAsync(CreateValue(), errors, Context());
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
@ -98,7 +109,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new ReferencesFieldProperties());
await sut.ValidateAsync(JsonValue.Create("invalid"), errors);
await sut.ValidateAsync(JsonValue.Create("invalid"), errors, Context());
errors.Should().BeEquivalentTo(
new[] { "Not a valid value." });
@ -109,7 +120,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId, MinItems = 3 });
await sut.ValidateAsync(CreateValue(ref1, ref2), errors, ValidationTestExtensions.References(ref1, ref2));
await sut.ValidateAsync(CreateValue(ref1, ref2), errors, Context());
errors.Should().BeEquivalentTo(
new[] { "Must have at least 3 item(s)." });
@ -120,7 +131,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId, MaxItems = 1 });
await sut.ValidateAsync(CreateValue(ref1, ref2), errors, ValidationTestExtensions.References(ref1, ref2));
await sut.ValidateAsync(CreateValue(ref1, ref2), errors, Context());
errors.Should().BeEquivalentTo(
new[] { "Must not have more than 1 item(s)." });
@ -137,12 +148,25 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
new[] { $"Contains invalid reference '{ref1}'." });
}
[Fact]
public async Task Should_add_error_if_reference_schema_is_not_valid()
{
var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId });
await sut.ValidateAsync(CreateValue(ref1), errors, ValidationTestExtensions.References((Guid.NewGuid(), ref1)));
errors.Should().BeEquivalentTo(
new[] { $"Contains reference '{ref1}' to invalid schema." });
}
[Fact]
public async Task Should_add_error_if_reference_contains_duplicate_values()
{
var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId });
await sut.ValidateAsync(CreateValue(ref1, ref1), errors, ValidationTestExtensions.References(ref1));
await sut.ValidateAsync(CreateValue(ref1, ref1), errors,
ValidationTestExtensions.References(
(schemaId, ref1)));
errors.Should().BeEquivalentTo(
new[] { "Must not contain duplicate values." });
@ -153,6 +177,13 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
return ids == null ? JsonValue.Null : JsonValue.Array(ids.Select(x => (object)x.ToString()).ToArray());
}
private ValidationContext Context()
{
return ValidationTestExtensions.References(
(schemaId, ref1),
(schemaId, ref2));
}
private static RootField<ReferencesFieldProperties> Field(ReferencesFieldProperties properties)
{
return Fields.References(1, "my-refs", Partitioning.Invariant, properties);

2
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs

@ -120,7 +120,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new StringFieldProperties { IsUnique = true });
await sut.ValidateAsync(CreateValue("abc"), errors, ValidationTestExtensions.References(Guid.NewGuid()));
await sut.ValidateAsync(CreateValue("abc"), errors, ValidationTestExtensions.References((Guid.NewGuid(), Guid.NewGuid())));
errors.Should().BeEquivalentTo(
new[] { "Another content with the same value exists." });

1
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/UIFieldTests.cs

@ -110,6 +110,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
Guid.NewGuid(),
Guid.NewGuid(),
(c, s) => null,
(s) => null,
(c) => null);
var validator = new ContentValidator(schema, x => InvariantPartitioning.Instance, validationContext);

15
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs

@ -17,10 +17,13 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
public static class ValidationTestExtensions
{
private static readonly Task<IReadOnlyList<Guid>> EmptyReferences = Task.FromResult<IReadOnlyList<Guid>>(new List<Guid>());
private static readonly Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> EmptyReferences = Task.FromResult<IReadOnlyList<(Guid SchemaId, Guid Id)>>(new List<(Guid SchemaId, Guid Id)>());
private static readonly Task<IReadOnlyList<IAssetInfo>> EmptyAssets = Task.FromResult<IReadOnlyList<IAssetInfo>>(new List<IAssetInfo>());
public static readonly ValidationContext ValidContext = new ValidationContext(Guid.NewGuid(), Guid.NewGuid(), (x, y) => EmptyReferences, x => EmptyAssets);
public static readonly ValidationContext ValidContext = new ValidationContext(Guid.NewGuid(), Guid.NewGuid(),
(x, y) => EmptyReferences,
(x) => EmptyReferences,
(x) => EmptyAssets);
public static Task ValidateAsync(this IValidator validator, object value, IList<string> errors, ValidationContext context = null)
{
@ -70,14 +73,14 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var actual = Task.FromResult<IReadOnlyList<IAssetInfo>>(assets.ToList());
return new ValidationContext(Guid.NewGuid(), Guid.NewGuid(), (x, y) => EmptyReferences, x => actual);
return new ValidationContext(Guid.NewGuid(), Guid.NewGuid(), (x, y) => EmptyReferences, x => EmptyReferences, x => actual);
}
public static ValidationContext References(params Guid[] referencesIds)
public static ValidationContext References(params (Guid Id, Guid SchemaId)[] referencesIds)
{
var actual = Task.FromResult<IReadOnlyList<Guid>>(referencesIds.ToList());
var actual = Task.FromResult<IReadOnlyList<(Guid Id, Guid SchemaId)>>(referencesIds.ToList());
return new ValidationContext(Guid.NewGuid(), Guid.NewGuid(), (x, y) => actual, x => EmptyAssets);
return new ValidationContext(Guid.NewGuid(), Guid.NewGuid(), (x, y) => actual, x => actual, x => EmptyAssets);
}
}
}

6
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValidatorTests.cs

@ -82,7 +82,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators
{
filter(filterNode.ToString());
return Task.FromResult<IReadOnlyList<Guid>>(new List<Guid> { id });
return Task.FromResult<IReadOnlyList<(Guid, Guid)>>(new List<(Guid, Guid)> { (schemaId, id) });
},
(ids) =>
{
return Task.FromResult<IReadOnlyList<(Guid, Guid)>>(new List<(Guid, Guid)> { (schemaId, id) });
},
ids =>
{

1
tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj

@ -9,7 +9,6 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Squidex.Domain.Apps.Core.Model\Squidex.Domain.Apps.Core.Model.csproj" />
<ProjectReference Include="..\..\src\Squidex.Domain.Apps.Core.Operations\Squidex.Domain.Apps.Core.Operations.csproj" />
<ProjectReference Include="..\..\src\Squidex.Domain.Apps.Entities\Squidex.Domain.Apps.Entities.csproj" />
<ProjectReference Include="..\..\src\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>

11
tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs

@ -129,7 +129,14 @@ namespace Squidex.Domain.Apps.Core
public static void TestFreeze(IFreezable sut)
{
foreach (var property in sut.GetType().GetRuntimeProperties().Where(x => x.Name != "IsFrozen"))
var properties =
sut.GetType().GetRuntimeProperties()
.Where(x =>
x.CanWrite &&
x.CanRead &&
x.Name != "IsFrozen");
foreach (var property in properties)
{
var value =
property.PropertyType.IsValueType ?
@ -145,7 +152,7 @@ namespace Squidex.Domain.Apps.Core
sut.Freeze();
foreach (var property in sut.GetType().GetRuntimeProperties().Where(x => x.Name != "IsFrozen"))
foreach (var property in properties)
{
var value =
property.PropertyType.IsValueType ?

Loading…
Cancel
Save