diff --git a/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs b/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs index b9abcee99..57e135981 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs @@ -115,11 +115,11 @@ namespace Squidex.Domain.Apps.Core.Scripting } catch (ParserException ex) { - throw new ValidationException("Failed to execute script with javascript syntax error.", new ValidationError(ex.Message)); + throw new ValidationException($"Failed to execute script with javascript syntax error: {ex.Message}", new ValidationError(ex.Message)); } catch (JavaScriptException ex) { - throw new ValidationException("Failed to execute script with javascript error.", new ValidationError(ex.Message)); + throw new ValidationException($"Failed to execute script with javascript error: {ex.Message}", new ValidationError(ex.Message)); } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs index 604284090..b46cf9240 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs @@ -21,7 +21,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (string.IsNullOrWhiteSpace(command.Id)) { - error(new ValidationError("Client id must be defined.", nameof(command.Id))); + error(new ValidationError("Client id is required.", nameof(command.Id))); } else if (clients.ContainsKey(command.Id)) { @@ -40,7 +40,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (string.IsNullOrWhiteSpace(command.Id)) { - error(new ValidationError("Client id must be defined.", nameof(command.Id))); + error(new ValidationError("Client id is required.", nameof(command.Id))); } }); } @@ -55,12 +55,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (string.IsNullOrWhiteSpace(command.Id)) { - error(new ValidationError("Client id must be defined.", nameof(command.Id))); + error(new ValidationError("Client id is required.", nameof(command.Id))); } if (string.IsNullOrWhiteSpace(command.Name) && command.Permission == null) { - error(new ValidationError("Either name or permission must be defined.", nameof(command.Name), nameof(command.Permission))); + error(new ValidationError("Either name or permission is required.", nameof(command.Name), nameof(command.Permission))); } if (command.Permission.HasValue && !command.Permission.Value.IsEnumValue()) diff --git a/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs b/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs index 756441da2..757959a6a 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs @@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards { if (string.IsNullOrWhiteSpace(command.FileName)) { - error(new ValidationError("Name must be defined.", nameof(command.FileName))); + error(new ValidationError("Name is required.", nameof(command.FileName))); } if (string.Equals(command.FileName, oldName)) diff --git a/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs b/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs index 86707be2f..249553206 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs @@ -22,7 +22,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { if (command.Trigger == null) { - error(new ValidationError("Trigger must be defined.", nameof(command.Trigger))); + error(new ValidationError("Trigger is required.", nameof(command.Trigger))); } else { @@ -33,7 +33,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards if (command.Action == null) { - error(new ValidationError("Trigger must be defined.", nameof(command.Action))); + error(new ValidationError("Trigger is required.", nameof(command.Action))); } else { @@ -52,7 +52,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { if (command.Trigger == null && command.Action == null) { - error(new ValidationError("Either trigger or action must be defined.", nameof(command.Trigger), nameof(command.Action))); + error(new ValidationError("Either trigger or action is required.", nameof(command.Trigger), nameof(command.Action))); } if (command.Trigger != null) diff --git a/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs b/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs index 2d36a13c5..3b794922d 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs @@ -31,17 +31,17 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards if (string.IsNullOrWhiteSpace(action.ApiKey)) { - errors.Add(new ValidationError("Api key must be defined.", nameof(action.ApiKey))); + errors.Add(new ValidationError("Api key is required.", nameof(action.ApiKey))); } if (string.IsNullOrWhiteSpace(action.AppId)) { - errors.Add(new ValidationError("Application ID key must be defined.", nameof(action.AppId))); + errors.Add(new ValidationError("Application ID key is required.", nameof(action.AppId))); } if (string.IsNullOrWhiteSpace(action.IndexName)) { - errors.Add(new ValidationError("Index name must be defined.", nameof(action.ApiKey))); + errors.Add(new ValidationError("Index name is required.", nameof(action.IndexName))); } return Task.FromResult>(errors); @@ -53,12 +53,12 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards if (string.IsNullOrWhiteSpace(action.ConnectionString)) { - errors.Add(new ValidationError("Connection string must be defined.", nameof(action.ConnectionString))); + errors.Add(new ValidationError("Connection string is required.", nameof(action.ConnectionString))); } if (string.IsNullOrWhiteSpace(action.Queue)) { - errors.Add(new ValidationError("Queue must be defined.", nameof(action.Queue))); + errors.Add(new ValidationError("Queue is required.", nameof(action.Queue))); } else if (!Regex.IsMatch(action.Queue, "^[a-z][a-z0-9]{2,}(\\-[a-z0-9]+)*$")) { @@ -74,12 +74,12 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards if (string.IsNullOrWhiteSpace(action.ApiKey)) { - errors.Add(new ValidationError("Api key must be defined.", nameof(action.ApiKey))); + errors.Add(new ValidationError("Api key is required.", nameof(action.ApiKey))); } if (string.IsNullOrWhiteSpace(action.ServiceId)) { - errors.Add(new ValidationError("Service name must be defined.", nameof(action.ServiceId))); + errors.Add(new ValidationError("Service ID is required.", nameof(action.ServiceId))); } return Task.FromResult>(errors); @@ -91,7 +91,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards if (action.WebhookUrl == null || !action.WebhookUrl.IsAbsoluteUri) { - errors.Add(new ValidationError("Webhook Url must be specified and absolute.", nameof(action.WebhookUrl))); + errors.Add(new ValidationError("Webhook Url is required and must be an absolute URL.", nameof(action.WebhookUrl))); } return Task.FromResult>(errors); @@ -103,7 +103,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards if (action.Url == null || !action.Url.IsAbsoluteUri) { - errors.Add(new ValidationError("Url must be specified and absolute.", nameof(action.Url))); + errors.Add(new ValidationError("Url is required and must be an absolute URL.", nameof(action.Url))); } return Task.FromResult>(errors); diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs index 730a8083a..44becd941 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs @@ -57,7 +57,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (properties.AspectWidth.HasValue != properties.AspectHeight.HasValue) { - yield return new ValidationError("Aspect width and height must be defined.", + yield return new ValidationError("Aspect width and height is required.", nameof(properties.AspectWidth), nameof(properties.AspectHeight)); } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs index a7cd406c5..c95bac2f6 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs @@ -52,7 +52,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (field.Properties == null) { - error(new ValidationError("Properties must be defined.", $"{prefix}.{nameof(field.Properties)}")); + error(new ValidationError("Properties is required.", $"{prefix}.{nameof(field.Properties)}")); } var propertyErrors = FieldPropertiesValidator.Validate(field.Properties); @@ -79,7 +79,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (command.FieldIds == null) { - error(new ValidationError("Field ids must be specified.", nameof(command.FieldIds))); + error(new ValidationError("Field ids is required.", nameof(command.FieldIds))); } if (command.FieldIds.Count != schema.Fields.Count || command.FieldIds.Any(x => !schema.FieldsById.ContainsKey(x))) diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs index 58ec7dc5b..73463a710 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs @@ -32,7 +32,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (command.Properties == null) { - error(new ValidationError("Properties must be defined.", nameof(command.Properties))); + error(new ValidationError("Properties is required.", nameof(command.Properties))); } var propertyErrors = FieldPropertiesValidator.Validate(command.Properties); @@ -57,7 +57,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (command.Properties == null) { - error(new ValidationError("Properties must be defined.", nameof(command.Properties))); + error(new ValidationError("Properties is required.", nameof(command.Properties))); } var propertyErrors = FieldPropertiesValidator.Validate(command.Properties); diff --git a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index 386e84edc..327a28e8f 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs @@ -146,7 +146,7 @@ namespace Squidex.Areas.Api.Controllers.Assets /// 400 => Asset exceeds the maximum size. /// /// - /// You can only upload one file at a time. The mime type of the file is not calculated by Squidex and must be defined correctly. + /// You can only upload one file at a time. The mime type of the file is not calculated by Squidex and is required correctly. /// [MustBeAppEditor] [HttpPost] @@ -248,7 +248,7 @@ namespace Squidex.Areas.Api.Controllers.Assets { if (file.Count != 1) { - var error = new ValidationError($"Can only upload one file, found {file.Count}."); + var error = new ValidationError($"Can only upload one file, found {file.Count} files."); throw new ValidationException("Cannot create asset.", error); } diff --git a/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs b/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs index 92fec91b3..d77397666 100644 --- a/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs +++ b/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs @@ -24,6 +24,7 @@ namespace Squidex.Areas.Api.Controllers.Users { [ApiAuthorize] [ApiExceptionFilter] + [ApiModelValidation] [MustBeAdministrator] [SwaggerIgnore] public sealed class UserManagementController : ApiController diff --git a/src/Squidex/Pipeline/ApiModelValidationAttribute.cs b/src/Squidex/Pipeline/ApiModelValidationAttribute.cs new file mode 100644 index 000000000..8b62de32f --- /dev/null +++ b/src/Squidex/Pipeline/ApiModelValidationAttribute.cs @@ -0,0 +1,37 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Filters; +using Squidex.Infrastructure; + +namespace Squidex.Pipeline +{ + public sealed class ApiModelValidationAttribute : ActionFilterAttribute + { + public override void OnActionExecuting(ActionExecutingContext context) + { + if (!context.ModelState.IsValid) + { + var errors = new List(); + + foreach (var m in context.ModelState) + { + foreach (var e in m.Value.Errors) + { + if (!string.IsNullOrWhiteSpace(e.ErrorMessage)) + { + errors.Add(new ValidationError(e.ErrorMessage, m.Key)); + } + } + } + + throw new ValidationException("The model is not valid.", errors); + } + } + } +} diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/AssetsFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/AssetsFieldPropertiesTests.cs index 1c0f2c564..f031cd5eb 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/AssetsFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/AssetsFieldPropertiesTests.cs @@ -82,7 +82,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties errors.ShouldBeEquivalentTo( new List { - new ValidationError("Aspect width and height must be defined.", "AspectWidth", "AspectHeight") + new ValidationError("Aspect width and height is required.", "AspectWidth", "AspectHeight") }); } @@ -96,7 +96,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties errors.ShouldBeEquivalentTo( new List { - new ValidationError("Aspect width and height must be defined.", "AspectWidth", "AspectHeight") + new ValidationError("Aspect width and height is required.", "AspectWidth", "AspectHeight") }); } }