// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.ValidateContent.Validators; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Validation; using Squidex.Log; #pragma warning disable SA1028, IDE0004 // Code must not contain trailing whitespace namespace Squidex.Domain.Apps.Core.ValidateContent { public sealed class ContentValidator { private readonly PartitionResolver partitionResolver; private readonly ValidationContext context; private readonly IEnumerable factories; private readonly ISemanticLog log; private readonly ConcurrentBag errors = new ConcurrentBag(); public IReadOnlyCollection Errors { get { return errors; } } public ContentValidator(PartitionResolver partitionResolver, ValidationContext context, IEnumerable factories, ISemanticLog log) { Guard.NotNull(context, nameof(context)); Guard.NotNull(factories, nameof(factories)); Guard.NotNull(partitionResolver, nameof(partitionResolver)); Guard.NotNull(log, nameof(log)); this.context = context; this.factories = factories; this.partitionResolver = partitionResolver; this.log = log; } private void AddError(IEnumerable path, string message) { var pathString = path.ToPathString(); errors.Add(new ValidationError(message, pathString)); } public Task ValidateInputPartialAsync(NamedContentData data) { Guard.NotNull(data, nameof(data)); var validator = CreateSchemaValidator(true); return validator.ValidateAsync(data, context, AddError); } public Task ValidateInputAsync(NamedContentData data) { Guard.NotNull(data, nameof(data)); var validator = CreateSchemaValidator(false); return validator.ValidateAsync(data, context, AddError); } public Task ValidateContentAsync(NamedContentData data) { Guard.NotNull(data, nameof(data)); var validator = new AggregateValidator(CreateContentValidators(), log); return validator.ValidateAsync(data, context, AddError); } private IValidator CreateSchemaValidator(bool isPartial) { var fieldValidators = new Dictionary(context.Schema.Fields.Count); foreach (var field in context.Schema.Fields) { fieldValidators[field.Name] = (!field.RawProperties.IsRequired, CreateFieldValidator(field, isPartial)); } return new ObjectValidator(fieldValidators, isPartial, "field"); } private IValidator CreateFieldValidator(IRootField field, bool isPartial) { var valueValidator = CreateValueValidator(field); var partitioning = partitionResolver(field.Partitioning); var partitioningValidators = new Dictionary(); foreach (var partitionKey in partitioning.AllKeys) { var optional = partitioning.IsOptional(partitionKey); partitioningValidators[partitionKey] = (optional, valueValidator); } var typeName = partitioning.ToString()!; return new AggregateValidator( CreateFieldValidators(field) .Union(Enumerable.Repeat( new ObjectValidator(partitioningValidators, isPartial, typeName), 1)), log); } private IValidator CreateValueValidator(IField field) { return new FieldValidator(new AggregateValidator(CreateValueValidators(field), log), field); } private IEnumerable CreateContentValidators() { return factories.SelectMany(x => x.CreateContentValidators(context, CreateValueValidator)); } private IEnumerable CreateValueValidators(IField field) { return factories.SelectMany(x => x.CreateValueValidators(context, field, CreateValueValidator)); } private IEnumerable CreateFieldValidators(IField field) { return factories.SelectMany(x => x.CreateFieldValidators(context, field, CreateValueValidator)); } } }