Browse Source

Improvement for validation.

pull/359/head
Sebastian Stehle 7 years ago
parent
commit
9b4ece1e0a
  1. 16
      src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs
  2. 2
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs
  3. 2
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs
  4. 2
      src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmSchemaExtensions.cs
  5. 3
      src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonSchemaExtensions.cs
  6. 3
      src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs
  7. 22
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs
  8. 31
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Extensions.cs
  9. 85
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/FieldBagValidatorsFactory.cs
  10. 16
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/FieldValueValidatorsFactory.cs
  11. 24
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Undefined.cs
  12. 33
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AggregateValidator.cs
  13. 41
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs
  14. 2
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/NoValueValidator.cs
  15. 12
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs
  16. 7
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs
  17. 2
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs
  18. 3
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs
  19. 2
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedGraphType.cs
  20. 6
      src/Squidex/app/features/content/pages/content/content-field.component.html
  21. 2
      src/Squidex/app/features/content/shared/array-item.component.ts
  22. 4
      src/Squidex/app/features/content/shared/field-editor.component.html
  23. 2
      src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html
  24. 32
      src/Squidex/app/features/schemas/pages/schema/field.component.html
  25. 2
      src/Squidex/app/features/schemas/pages/schema/forms/field-form-common.component.html
  26. 2
      src/Squidex/app/shared/services/schemas.service.ts
  27. 4
      src/Squidex/app/shared/services/schemas.types.ts
  28. 2
      src/Squidex/app/shared/state/schemas.forms.ts
  29. 2
      tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateJsonSchema/JsonSchemaTests.cs
  30. 18
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs
  31. 74
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/UIFieldTests.cs
  32. 9
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs
  33. 5
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/NoValueValidatorTests.cs

16
src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs

@ -7,6 +7,7 @@
using Squidex.Infrastructure;
using System.Collections.Generic;
using System.Linq;
using NamedIdStatic = Squidex.Infrastructure.NamedId;
namespace Squidex.Domain.Apps.Core.Schemas
@ -18,6 +19,21 @@ namespace Squidex.Domain.Apps.Core.Schemas
return NamedIdStatic.Of(field.Id, field.Name);
}
public static IEnumerable<T> NonHidden<T>(this FieldCollection<T> fields, bool withHidden = false) where T : IField
{
return fields.Ordered.ForApi(withHidden);
}
public static IEnumerable<T> ForApi<T>(this IEnumerable<T> fields, bool withHidden = false) where T : IField
{
return fields.Where(x => IsForApi(x, withHidden));
}
public static bool IsForApi<T>(this T field, bool withHidden = false) where T : IField
{
return (withHidden || !field.IsHidden) && !(field is IField<UIFieldProperties>);
}
public static Schema ReorderFields(this Schema schema, List<long> ids, long? parentId = null)
{
if (parentId != null)

2
src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs

@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
public static FieldConverter ExcludeHidden()
{
return (data, field) => field.IsHidden ? null : data;
return (data, field) => !field.IsForApi() ? null : data;
}
public static FieldConverter ExcludeChangedTypes()

2
src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs

@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
public static ValueConverter ExcludeHidden()
{
return (value, field) => field.IsHidden ? Value.Unset : value;
return (value, field) => !field.IsForApi() ? Value.Unset : value;
}
public static ValueConverter ExcludeChangedTypes()

2
src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmSchemaExtensions.cs

@ -36,7 +36,7 @@ namespace Squidex.Domain.Apps.Core.GenerateEdmSchema
foreach (var field in schema.FieldsByName.Values)
{
if (!withHidden && field.IsHidden)
if (!field.IsForApi(withHidden))
{
continue;
}

3
src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonSchemaExtensions.cs

@ -6,7 +6,6 @@
// ==========================================================================
using System;
using System.Linq;
using NJsonSchema;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
@ -25,7 +24,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
var jsonTypeVisitor = new JsonTypeVisitor(schemaResolver);
var jsonSchema = new JsonSchema4 { Type = JsonObjectType.Object };
foreach (var field in schema.Fields.Where(x => !x.IsHidden))
foreach (var field in schema.Fields.ForApi())
{
var partitionObject = Builder.Object();
var partition = partitionResolver(field.Partitioning);

3
src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs

@ -7,7 +7,6 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using NJsonSchema;
using Squidex.Domain.Apps.Core.Schemas;
@ -26,7 +25,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
{
var item = Builder.Object();
foreach (var nestedField in field.Fields.Where(x => !x.IsHidden))
foreach (var nestedField in field.Fields.ForApi())
{
var childProperty = nestedField.Accept(this);

22
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs

@ -21,7 +21,6 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
{
public sealed class ContentValidator
{
private static readonly ContentFieldData DefaultFieldData = new ContentFieldData();
private readonly Schema schema;
private readonly PartitionResolver partitionResolver;
private readonly ValidationContext context;
@ -35,6 +34,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public ContentValidator(Schema schema, PartitionResolver partitionResolver, ValidationContext context)
{
Guard.NotNull(schema, nameof(schema));
Guard.NotNull(context, nameof(context));
Guard.NotNull(partitionResolver, nameof(partitionResolver));
this.schema = schema;
@ -71,19 +71,19 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
{
var fieldsValidators = new Dictionary<string, (bool IsOptional, IValidator Validator)>(schema.Fields.Count);
foreach (var field in schema.FieldsByName)
foreach (var field in schema.Fields)
{
fieldsValidators[field.Key] = (!field.Value.RawProperties.IsRequired, CreateFieldValidator(field.Value, isPartial));
fieldsValidators[field.Name] = (!field.RawProperties.IsRequired, CreateFieldValidator(field, isPartial));
}
return new ObjectValidator<ContentFieldData>(fieldsValidators, isPartial, "field", DefaultFieldData);
return new ObjectValidator<ContentFieldData>(fieldsValidators, isPartial, "field");
}
private IValidator CreateFieldValidator(IRootField field, bool isPartial)
{
var partitioning = partitionResolver(field.Partitioning);
var fieldValidator = new FieldValidator(ValidatorsFactory.CreateValidators(field).ToArray(), field);
var fieldValidator = field.CreateValidator();
var fieldsValidators = new Dictionary<string, (bool IsOptional, IValidator Validator)>();
foreach (var partition in partitioning)
@ -91,11 +91,17 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
fieldsValidators[partition.Key] = (partition.IsOptional, fieldValidator);
}
var isLanguage = field.Partitioning.Equals(Partitioning.Language);
return new AggregateValidator(
field.CreateBagValidator()
.Union(Enumerable.Repeat(
new ObjectValidator<IJsonValue>(fieldsValidators, isPartial, TypeName(field)), 1)));
}
var type = isLanguage ? "language" : "invariant value";
private static string TypeName(IRootField field)
{
var isLanguage = field.Partitioning.Equals(Partitioning.Language);
return new ObjectValidator<IJsonValue>(fieldsValidators, isPartial, type, JsonValue.Null);
return isLanguage ? "language" : "invariant value";
}
}
}

31
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Extensions.cs

@ -0,0 +1,31 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent.Validators;
namespace Squidex.Domain.Apps.Core.ValidateContent
{
public static class Extensions
{
public static FieldValidator CreateValidator(this IField field)
{
return new FieldValidator(CreateValueValidators(field), field);
}
private static IEnumerable<IValidator> CreateValueValidators(IField field)
{
return FieldValueValidatorsFactory.CreateValidators(field);
}
public static IEnumerable<IValidator> CreateBagValidator(this IField field)
{
return FieldBagValidatorsFactory.CreateValidators(field);
}
}
}

85
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/FieldBagValidatorsFactory.cs

@ -0,0 +1,85 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent.Validators;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.ValidateContent
{
public sealed class FieldBagValidatorsFactory : IFieldVisitor<IEnumerable<IValidator>>
{
private static readonly FieldBagValidatorsFactory Instance = new FieldBagValidatorsFactory();
private FieldBagValidatorsFactory()
{
}
public static IEnumerable<IValidator> CreateValidators(IField field)
{
Guard.NotNull(field, nameof(field));
return field.Accept(Instance);
}
public IEnumerable<IValidator> Visit(IArrayField field)
{
yield break;
}
public IEnumerable<IValidator> Visit(IField<AssetsFieldProperties> field)
{
yield break;
}
public IEnumerable<IValidator> Visit(IField<BooleanFieldProperties> field)
{
yield break;
}
public IEnumerable<IValidator> Visit(IField<DateTimeFieldProperties> field)
{
yield break;
}
public IEnumerable<IValidator> Visit(IField<GeolocationFieldProperties> field)
{
yield break;
}
public IEnumerable<IValidator> Visit(IField<JsonFieldProperties> field)
{
yield break;
}
public IEnumerable<IValidator> Visit(IField<NumberFieldProperties> field)
{
yield break;
}
public IEnumerable<IValidator> Visit(IField<ReferencesFieldProperties> field)
{
yield break;
}
public IEnumerable<IValidator> Visit(IField<StringFieldProperties> field)
{
yield break;
}
public IEnumerable<IValidator> Visit(IField<TagsFieldProperties> field)
{
yield break;
}
public IEnumerable<IValidator> Visit(IField<UIFieldProperties> field)
{
yield return NoValueValidator.Instance;
}
}
}

16
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs → src/Squidex.Domain.Apps.Core.Operations/ValidateContent/FieldValueValidatorsFactory.cs

@ -7,7 +7,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NodaTime;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent.Validators;
@ -16,11 +15,11 @@ using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ValidateContent
{
public sealed class ValidatorsFactory : IFieldVisitor<IEnumerable<IValidator>>
public sealed class FieldValueValidatorsFactory : IFieldVisitor<IEnumerable<IValidator>>
{
private static readonly ValidatorsFactory Instance = new ValidatorsFactory();
private static readonly FieldValueValidatorsFactory Instance = new FieldValueValidatorsFactory();
private ValidatorsFactory()
private FieldValueValidatorsFactory()
{
}
@ -42,10 +41,10 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
foreach (var nestedField in field.Fields)
{
nestedSchema[nestedField.Name] = (false, new FieldValidator(nestedField.Accept(this).ToArray(), nestedField));
nestedSchema[nestedField.Name] = (false, nestedField.CreateValidator());
}
yield return new CollectionItemValidator(new ObjectValidator<IJsonValue>(nestedSchema, false, "field", JsonValue.Null));
yield return new CollectionItemValidator(new ObjectValidator<IJsonValue>(nestedSchema, false, "field"));
}
public IEnumerable<IValidator> Visit(IField<AssetsFieldProperties> field)
@ -186,7 +185,10 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public IEnumerable<IValidator> Visit(IField<UIFieldProperties> field)
{
yield return NoValueValidator.Instance;
if (field is INestedField)
{
yield return NoValueValidator.Instance;
}
}
}
}

24
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Undefined.cs

@ -0,0 +1,24 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.ValidateContent
{
public static class Undefined
{
public static readonly object Value = new object();
public static bool IsUndefined(this object other)
{
return ReferenceEquals(other, Value);
}
public static bool IsNullOrUndefined(this object other)
{
return other == null || other.IsUndefined();
}
}
}

33
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AggregateValidator.cs

@ -0,0 +1,33 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
public sealed class AggregateValidator : IValidator
{
private readonly IValidator[] validators;
public AggregateValidator(IEnumerable<IValidator> validators)
{
this.validators = validators?.ToArray();
}
public Task ValidateAsync(object value, ValidationContext context, AddError addError)
{
if (validators?.Length > 0)
{
return Task.WhenAll(validators.Select(x => x.ValidateAsync(value, context, addError)));
}
return Task.CompletedTask;
}
}
}

41
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs

@ -6,8 +6,10 @@
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
@ -17,9 +19,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
private readonly IValidator[] validators;
private readonly IField field;
public FieldValidator(IValidator[] validators, IField field)
public FieldValidator(IEnumerable<IValidator> validators, IField field)
{
this.validators = validators;
Guard.NotNull(field, nameof(field));
this.validators = validators.ToArray();
this.field = field;
}
@ -27,21 +32,35 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
try
{
object typedValue = null;
var typedValue = value;
if (value is IJsonValue jsonValue)
if (value == null)
{
typedValue = jsonValue.Type == JsonValueType.Null ? null : JsonValueConverter.ConvertValue(field, jsonValue);
typedValue = Undefined.Value;
}
var tasks = new List<Task>();
foreach (var validator in validators)
else if (value is IJsonValue jsonValue)
{
tasks.Add(validator.ValidateAsync(typedValue, context, addError));
if (jsonValue.Type == JsonValueType.Null)
{
typedValue = null;
}
else
{
typedValue = JsonValueConverter.ConvertValue(field, jsonValue);
}
}
await Task.WhenAll(tasks);
if (validators?.Length > 0)
{
var tasks = new List<Task>();
foreach (var validator in validators)
{
tasks.Add(validator.ValidateAsync(typedValue, context, addError));
}
await Task.WhenAll(tasks);
}
}
catch
{

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

@ -19,7 +19,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
public Task ValidateAsync(object value, ValidationContext context, AddError addError)
{
if (value != null)
if (!value.IsUndefined())
{
addError(context.Path, "Value must not be defined.");
}

12
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs

@ -12,21 +12,25 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
public sealed class ObjectValidator<TValue> : IValidator
{
private static readonly IReadOnlyDictionary<string, TValue> DefaultValue = new Dictionary<string, TValue>();
private readonly IDictionary<string, (bool IsOptional, IValidator Validator)> schema;
private readonly bool isPartial;
private readonly string fieldType;
private readonly TValue fieldDefault;
public ObjectValidator(IDictionary<string, (bool IsOptional, IValidator Validator)> schema, bool isPartial, string fieldType, TValue fieldDefault)
public ObjectValidator(IDictionary<string, (bool IsOptional, IValidator Validator)> schema, bool isPartial, string fieldType)
{
this.schema = schema;
this.fieldDefault = fieldDefault;
this.fieldType = fieldType;
this.isPartial = isPartial;
}
public async Task ValidateAsync(object value, ValidationContext context, AddError addError)
{
if (value == null)
{
value = DefaultValue;
}
if (value is IReadOnlyDictionary<string, TValue> values)
{
foreach (var fieldData in values)
@ -52,7 +56,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
continue;
}
fieldValue = fieldDefault;
fieldValue = default;
}
var (isOptional, validator) = field.Value;

7
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs

@ -26,12 +26,17 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
return TaskHelper.Done;
}
if (value == null || (value is string stringValue && validateEmptyStrings && string.IsNullOrWhiteSpace(stringValue)))
if (value.IsNullOrUndefined() || IsEmptyString(value))
{
addError(context.Path, "Field is required.");
}
return TaskHelper.Done;
}
private bool IsEmptyString(object value)
{
return value is string typed && validateEmptyStrings && string.IsNullOrWhiteSpace(typed);
}
}
}

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

@ -14,7 +14,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
public Task ValidateAsync(object value, ValidationContext context, AddError addError)
{
if (value == null && !context.IsOptional)
if (value.IsNullOrUndefined() && !context.IsOptional)
{
addError(context.Path, "Field is required.");
}

3
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs

@ -6,7 +6,6 @@
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using GraphQL.Resolvers;
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents;
@ -26,7 +25,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
Name = $"{schemaType}DataDto";
foreach (var field in schema.SchemaDef.Fields.Where(x => !x.IsHidden))
foreach (var field in schema.SchemaDef.Fields.ForApi())
{
var (resolvedType, valueResolver) = model.GetGraphType(schema, field);

2
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedGraphType.cs

@ -26,7 +26,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
Name = $"{schemaType}{fieldName}ChildDto";
foreach (var nestedField in field.Fields.Where(x => !x.IsHidden))
foreach (var nestedField in field.Fields.ForApi())
{
var fieldInfo = model.GetGraphType(schema, nestedField);

6
src/Squidex/app/features/content/pages/content/content-field.component.html

@ -1,6 +1,6 @@
<div class="row no-gutters" [class.compare]="fieldFormCompare">
<div [class.col-12]="!fieldFormCompare" [class.col-6]="fieldFormCompare">
<div class="table-items-row" [class.field-invalid]="isInvalid | async" *ngIf="field.properties.isNormalField; else uiField">
<div class="table-items-row" [class.field-invalid]="isInvalid | async" *ngIf="field.properties.isContentField; else uiField">
<div class="languages-buttons">
<button *ngIf="isTranslatable" type="button" class="btn btn-text-secondary btn-sm mr-1" (click)="translate()" title="Autotranslate from master language">
<i class="icon-translate"></i>
@ -55,11 +55,11 @@
</div>
<div class="col-6 col-right" *ngIf="fieldFormCompare">
<button type="button" class="btn btn-primary btn-sm field-copy" (click)="copy()" *ngIf="field.properties.isNormalField && (isDifferent | async)">
<button type="button" class="btn btn-primary btn-sm field-copy" (click)="copy()" *ngIf="field.properties.isContentField && (isDifferent | async)">
<i class="icon-arrow_back"></i>
</button>
<div class="table-items-row" *ngIf="field.properties.isNormalField; else uiField">
<div class="table-items-row" *ngIf="field.properties.isContentField; else uiField">
<div class="languages-buttons">
<sqx-field-languages
[field]="field"

2
src/Squidex/app/features/content/shared/array-item.component.ts

@ -76,7 +76,7 @@ export class ArrayItemComponent implements OnChanges {
}
if (changes['itemForm'] || changes['field']) {
this.fieldControls = this.field.nested.map(field => ({ field, control: this.itemForm.get(field.name)! })).filter(x => !x.field.properties.isNormalField || !!x.control);
this.fieldControls = this.field.nested.map(field => ({ field, control: this.itemForm.get(field.name)! })).filter(x => !x.field.properties.isContentField || !!x.control);
}
}

4
src/Squidex/app/features/content/shared/field-editor.component.html

@ -1,5 +1,5 @@
<div [class.ui]="!field.properties.isNormalField" *ngIf="field">
<ng-container *ngIf="field.properties.isNormalField">
<div [class.ui]="!field.properties.isContentField" *ngIf="field">
<ng-container *ngIf="field.properties.isContentField">
<label>
{{field.displayName}} {{displaySuffix}} <span class="field-required" [class.hidden]="!field.properties.isRequired">*</span>
</label>

2
src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html

@ -44,7 +44,7 @@
placeholder="Enter field name" sqxFocusOnInit />
</div>
<div class="form-group" *ngIf="!parent && (addFieldForm.isNormalField | async)">
<div class="form-group" *ngIf="!parent && (addFieldForm.isContentField | async)">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="isLocalizable" formControlName="isLocalizable" />
<label class="form-check-label" for="isLocalizable">

32
src/Squidex/app/features/schemas/pages/schema/field.component.html

@ -29,21 +29,23 @@
<i class="icon-dots"></i>
</button>
<div class="dropdown-menu" *sqxModalView="dropdown;closeAlways:true" [sqxModalTarget]="optionsButton" @fade>
<a class="dropdown-item" (click)="enableField()" *ngIf="field.isDisabled">
Enable in UI
</a>
<a class="dropdown-item" (click)="disableField()" *ngIf="!field.isDisabled">
Disable in UI
</a>
<a class="dropdown-item" (click)="hideField()" *ngIf="!field.isHidden && !field.isLocked">
Hide in API
</a>
<a class="dropdown-item" (click)="showField()" *ngIf="field.isHidden && !field.isLocked">
Show in API
</a>
<ng-container *ngIf="field.properties.isContentField">
<a class="dropdown-item" (click)="enableField()" *ngIf="field.isDisabled">
Enable in UI
</a>
<a class="dropdown-item" (click)="disableField()" *ngIf="!field.isDisabled">
Disable in UI
</a>
<a class="dropdown-item" (click)="hideField()" *ngIf="!field.isHidden && !field.isLocked">
Hide in API
</a>
<a class="dropdown-item" (click)="showField()" *ngIf="field.isHidden && !field.isLocked">
Show in API
</a>
</ng-container>
<ng-container *ngIf="!field.isLocked">
<div class="dropdown-divider"></div>
<div class="dropdown-divider" *ngIf="field.properties.isContentField"></div>
<a class="dropdown-item"
(sqxConfirmClick)="lockField()"
@ -75,10 +77,10 @@
<li class="nav-item">
<a class="nav-link" (click)="selectTab(0)" [class.active]="selectedTab === 0">Common</a>
</li>
<li class="nav-item" [class.hidden]="!field.properties.isNormalField">
<li class="nav-item" [class.hidden]="!field.properties.isContentField">
<a class="nav-link" (click)="selectTab(1)" [class.active]="selectedTab === 1">Validation</a>
</li>
<li class="nav-item" [class.hidden]="!field.properties.isNormalField || field.properties.fieldType === 'Assets'">
<li class="nav-item" [class.hidden]="!field.properties.isContentField || field.properties.fieldType === 'Assets'">
<a class="nav-link" (click)="selectTab(2)" [class.active]="selectedTab === 2">Editing</a>
</li>
</ul>

2
src/Squidex/app/features/schemas/pages/schema/forms/field-form-common.component.html

@ -39,7 +39,7 @@
</div>
</div>
<div class="form-group row" *ngIf="field.properties.isNormalField">
<div class="form-group row" *ngIf="field.properties.isContentField">
<div class="col-6 offset-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldListfield" formControlName="isListField" />

2
src/Squidex/app/shared/services/schemas.service.ts

@ -71,7 +71,7 @@ export class SchemaDetailsDto extends SchemaDto {
protected onCloned() {
if (this.fields) {
let fields = this.fields.filter(x => x.properties.isListField && x.properties.isNormalField);
let fields = this.fields.filter(x => x.properties.isListField && x.properties.isContentField);
if (fields.length === 0 && this.fields.length > 0) {
fields = [this.fields[0]];

4
src/Squidex/app/shared/services/schemas.types.ts

@ -158,7 +158,7 @@ export abstract class FieldPropertiesDto {
return true;
}
public get isNormalField() {
public get isContentField() {
return true;
}
@ -407,7 +407,7 @@ export class UIFieldPropertiesDto extends FieldPropertiesDto {
return false;
}
public get isNormalField() {
public get isContentField() {
return false;
}

2
src/Squidex/app/shared/state/schemas.forms.ts

@ -182,7 +182,7 @@ export class EditSchemaForm extends Form<FormGroup, { label?: string, hints?: st
}
export class AddFieldForm extends Form<FormGroup, AddFieldDto> {
public isNormalField = value$(this.form.get('type')!).pipe(map(x => x !== 'UI'));
public isContentField = value$(this.form.get('type')!).pipe(map(x => x !== 'UI'));
constructor(formBuilder: FormBuilder) {
super(formBuilder.group({

2
tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateJsonSchema/JsonSchemaTests.cs

@ -29,7 +29,7 @@ namespace Squidex.Domain.Apps.Core.Operations.GenerateJsonSchema
void CheckField(IField field)
{
if (field is IField<UIFieldProperties> || field.IsHidden)
if (!field.IsForApi())
{
Assert.DoesNotContain(field.Name, jsonProperties);
}

18
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs

@ -122,6 +122,24 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
});
}
[Fact]
public async Task Should_add_error_if_required_data_string_field_is_not_in_bag()
{
schema = schema.AddString(1, "my-field", Partitioning.Invariant,
new StringFieldProperties { IsRequired = true });
var data =
new NamedContentData();
await data.ValidateAsync(context, schema, languagesConfig.ToResolver(), errors);
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Field is required.", "my-field")
});
}
[Fact]
public async Task Should_add_error_if_data_contains_invalid_language()
{

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

@ -5,10 +5,14 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Xunit;
@ -27,23 +31,24 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_not_add_error_if_value_is_null()
public async Task Should_not_add_error_if_value_is_undefined()
{
var sut = Field(new UIFieldProperties());
await sut.ValidateAsync(null, errors, ValidationTestExtensions.ValidContext);
await sut.ValidateAsync(Undefined.Value, errors, ValidationTestExtensions.ValidContext);
Assert.Empty(errors);
}
[Fact]
public async Task Should_not_add_error_if_value_is_json_null()
public async Task Should_add_error_if_value_is_json_null()
{
var sut = Field(new UIFieldProperties());
await sut.ValidateAsync(JsonValue.Null, errors);
Assert.Empty(errors);
errors.Should().BeEquivalentTo(
new[] { "Value must not be defined." });
}
[Fact]
@ -57,9 +62,66 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
new[] { "Value must not be defined." });
}
private static RootField<UIFieldProperties> Field(UIFieldProperties properties)
[Fact]
public async Task Should_add_error_if_field_object_is_defined()
{
var schema =
new Schema("my-schema")
.AddUI(1, "my-ui1", Partitioning.Invariant)
.AddUI(2, "my-ui2", Partitioning.Invariant);
var data =
new NamedContentData()
.AddField("my-ui1", new ContentFieldData())
.AddField("my-ui2", new ContentFieldData()
.AddValue("iv", null));
var validationContext = ValidationTestExtensions.ValidContext;
var validator = new ContentValidator(schema, new PartitionResolver(x => InvariantPartitioning.Instance), validationContext);
await validator.ValidateAsync(data);
validator.Errors.Should().BeEquivalentTo(
new[] {
new ValidationError("Value must not be defined.", "my-ui1"),
new ValidationError("Value must not be defined.", "my-ui2")
});
}
[Fact]
public async Task Should_add_error_if_array_item_field_is_defined()
{
var schema =
new Schema("my-schema")
.AddArray(1, "my-array", Partitioning.Invariant, array => array
.AddUI(101, "my-ui"));
var data =
new NamedContentData()
.AddField("my-array", new ContentFieldData()
.AddValue("iv",
JsonValue.Array(
JsonValue.Object()
.Add("my-ui", null))));
var validationContext =
new ValidationContext(
Guid.NewGuid(),
Guid.NewGuid(),
(c, s) => null,
(c) => null);
var validator = new ContentValidator(schema, new PartitionResolver(x => InvariantPartitioning.Instance), validationContext);
await validator.ValidateAsync(data);
validator.Errors.Should().BeEquivalentTo(
new[] { new ValidationError("Value must not be defined.", "my-array[1].my-ui") });
}
private static NestedField<UIFieldProperties> Field(UIFieldProperties properties)
{
return Fields.UI(1, "my-ui", Partitioning.Invariant, properties);
return new NestedField<UIFieldProperties>(1, "my-ui", properties);
}
}
}

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

@ -12,7 +12,6 @@ using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Domain.Apps.Core.ValidateContent.Validators;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
@ -38,18 +37,18 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
CreateFormatter(errors));
}
public static Task ValidateAsync(this IField field, IJsonValue value, IList<string> errors, ValidationContext context = null)
public static Task ValidateAsync(this IField field, object value, IList<string> errors, ValidationContext context = null)
{
return new FieldValidator(ValidatorsFactory.CreateValidators(field).ToArray(), field)
return new FieldValidator(FieldValueValidatorsFactory.CreateValidators(field).ToArray(), field)
.ValidateAsync(
value,
CreateContext(context),
CreateFormatter(errors));
}
public static Task ValidateOptionalAsync(this IField field, IJsonValue value, IList<string> errors, ValidationContext context = null)
public static Task ValidateOptionalAsync(this IField field, object value, IList<string> errors, ValidationContext context = null)
{
return new FieldValidator(ValidatorsFactory.CreateValidators(field).ToArray(), field)
return new FieldValidator(FieldValueValidatorsFactory.CreateValidators(field).ToArray(), field)
.ValidateAsync(
value,
CreateContext(context).Optional(true),

5
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/NoValueValidatorTests.cs

@ -8,6 +8,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Domain.Apps.Core.ValidateContent.Validators;
using Squidex.Infrastructure.Json.Objects;
using Xunit;
@ -19,11 +20,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators
private readonly List<string> errors = new List<string>();
[Fact]
public async Task Should_not_add_error_if_value_is_null()
public async Task Should_not_add_error_if_value_is_undefined()
{
var sut = NoValueValidator.Instance;
await sut.ValidateAsync(null, errors);
await sut.ValidateAsync(Undefined.Value, errors);
Assert.Empty(errors);
}

Loading…
Cancel
Save