diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs index 94979e7a3..02f32a8e6 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs @@ -5,66 +5,21 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; -using System.Collections; using System.Threading.Tasks; -using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { - public sealed class CollectionValidator : IValidator + public sealed class CollectionValidator : CollectionValidatorBase, IValidator { - private readonly bool isRequired; - private readonly int? minItems; - private readonly int? maxItems; - public CollectionValidator(bool isRequired, int? minItems = null, int? maxItems = null) + : base(isRequired, minItems, maxItems) { - if (minItems.HasValue && maxItems.HasValue && minItems > maxItems) - { - throw new ArgumentException("Min length must be greater than max length.", nameof(minItems)); - } - - this.isRequired = isRequired; - this.minItems = minItems; - this.maxItems = maxItems; } public Task ValidateAsync(object? value, ValidationContext context, AddError addError) { - if (!(value is ICollection items) || items.Count == 0) - { - if (isRequired && !context.IsOptional) - { - addError(context.Path, T.Get("contents.validation.required")); - } - - return Task.CompletedTask; - } - - if (minItems.HasValue && maxItems.HasValue) - { - if (minItems == maxItems && minItems != items.Count) - { - addError(context.Path, T.Get("contents.validation.itemCount", new { count = minItems })); - } - else if (items.Count < minItems || items.Count > maxItems) - { - addError(context.Path, T.Get("contents.validation.itemCountBetween", new { min = minItems, max = maxItems })); - } - } - else - { - if (minItems.HasValue && items.Count < minItems) - { - addError(context.Path, T.Get("contents.validation.minItems", new { min = minItems })); - } - - if (maxItems.HasValue && items.Count > maxItems) - { - addError(context.Path, T.Get("contents.validation.maxItems", new { max = maxItems })); - } - } + ValidateRequired(value, context, addError); + ValidateSize(value, context, addError); return Task.CompletedTask; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidatorBase.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidatorBase.cs new file mode 100644 index 000000000..c2d531fd8 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidatorBase.cs @@ -0,0 +1,86 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections; +using Squidex.Infrastructure.Translations; + +namespace Squidex.Domain.Apps.Core.ValidateContent.Validators +{ + public abstract class CollectionValidatorBase + { + private readonly bool isRequired; + private readonly int? minItems; + private readonly int? maxItems; + + protected CollectionValidatorBase(bool isRequired, int? minItems = null, int? maxItems = null) + { + if (minItems.HasValue && maxItems.HasValue && minItems > maxItems) + { + throw new ArgumentException("Min length must be greater than max length.", nameof(minItems)); + } + + this.isRequired = isRequired; + this.minItems = minItems; + this.maxItems = maxItems; + } + + protected void ValidateRequired(object? value, ValidationContext context, AddError addError) + { + var size = 0; + + if (value is ICollection items) + { + size = items.Count; + } + + if (size == 0 && isRequired && !context.IsOptional) + { + addError(context.Path, T.Get("contents.validation.required")); + } + } + + protected void ValidateSize(object? value, ValidationContext context, AddError addError) + { + var size = 0; + + if (value is ICollection items) + { + size = items.Count; + } + + if (size == 0) + { + return; + } + + if (minItems.HasValue && maxItems.HasValue) + { + if (minItems == maxItems && minItems != size) + { + addError(context.Path, T.Get("contents.validation.itemCount", new { count = minItems })); + } + else if (size < minItems || size > maxItems) + { + addError(context.Path, T.Get("contents.validation.itemCountBetween", new { min = minItems, max = maxItems })); + } + } + else + { + if (minItems.HasValue && size < minItems) + { + addError(context.Path, T.Get("contents.validation.minItems", new { min = minItems })); + } + + if (maxItems.HasValue && size > maxItems) + { + addError(context.Path, T.Get("contents.validation.maxItems", new { max = maxItems })); + } + } + } + } +} \ No newline at end of file diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs index 95b5aafd1..dcb3d73a3 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs @@ -31,7 +31,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators public async Task ValidateAsync(object? value, ValidationContext context, AddError addError) { - if (value is ICollection contentIds) + if (value is ICollection contentIds && contentIds.Count > 0) { var foundIds = await checkReferences(contentIds.ToHashSet()); diff --git a/frontend/app/shared/services/schemas.service.spec.ts b/frontend/app/shared/services/schemas.service.spec.ts index cd62f6f03..ca8b09ea6 100644 --- a/frontend/app/shared/services/schemas.service.spec.ts +++ b/frontend/app/shared/services/schemas.service.spec.ts @@ -600,6 +600,19 @@ describe('SchemasService', () => { req.flush({}); })); + function schemaPropertiesResponse(id: number, suffix = '') { + return { + label: `label${id}${suffix}`, + contentsSidebarUrl: `url/to/contents/${id}${suffix}`, + contentSidebarUrl: `url/to/content/${id}${suffix}`, + tags: [ + `tags${id}${suffix}` + ], + validateOnPublish: id % 2 === 1, + hints: `hints${id}${suffix}` + }; + } + function schemaResponse(id: number, suffix = '') { return { id: `schema-id${id}${suffix}`, @@ -611,15 +624,7 @@ describe('SchemasService', () => { createdBy: `creator${id}`, lastModified: `${id % 1000 + 2000}-11-11T10:10:00Z`, lastModifiedBy: `modifier${id}`, - properties: { - label: `label${id}${suffix}`, - contentsSidebarUrl: `url/to/contents/${id}${suffix}`, - contentSidebarUrl: `url/to/content/${id}${suffix}`, - tags: [ - `tags${id}${suffix}` - ], - hints: `hints${id}${suffix}` - }, + properties: schemaPropertiesResponse(id, suffix), version: `${id}`, _links: { update: { method: 'PUT', href: `/schemas/${id}` } @@ -639,16 +644,7 @@ describe('SchemasService', () => { lastModified: `${id % 1000 + 2000}-11-11T10:10:00Z`, lastModifiedBy: `modifier${id}`, version: `${id}`, - properties: { - label: `label${id}${suffix}`, - contentsSidebarUrl: `url/to/contents/${id}${suffix}`, - contentSidebarUrl: `url/to/content/${id}${suffix}`, - validateOnPublish: id % 2 === 1, - tags: [ - `tags${id}${suffix}` - ], - hints: `hints${id}${suffix}` - }, + properties: schemaPropertiesResponse(id, suffix), previewUrls: { Default: 'url' }, @@ -824,7 +820,7 @@ function createSchemaProperties(id: number, suffix = '') { `hints${id}${suffix}`, `url/to/contents/${id}${suffix}`, `url/to/content/${id}${suffix}`, - id % 2 === 0, + id % 2 === 1, [ `tags${id}${suffix}` ]