{"record":{"id":"9a0722898d3688be","repo":"kgrzybek/modular-monolith-with-ddd","slug":"validator-error-messages","errorCode":null,"errorMessage":"{validator error messages}","messagePattern":"\\{validator error messages\\}","errorType":"validation","errorClass":"InvalidCommandException","httpStatus":400,"severity":"warning","filePath":"src/Modules/Payments/Infrastructure/Configuration/Processing/ValidationCommandHandlerDecorator.cs","lineNumber":32,"sourceCode":"        public ValidationCommandHandlerDecorator(\n            IList<IValidator<T>> validators,\n            ICommandHandler<T> decorated)\n        {\n            this._validators = validators;\n            _decorated = decorated;\n        }\n\n        public async Task Handle(T command, CancellationToken cancellationToken)\n        {\n            var errors = _validators\n                .Select(v => v.Validate(command))\n                .SelectMany(result => result.Errors)\n                .Where(error => error != null)\n                .ToList();\n\n            if (errors.Any())\n            {\n                throw new InvalidCommandException(errors.Select(x => x.ErrorMessage).ToList());\n            }\n\n            await _decorated.Handle(command, cancellationToken);\n        }\n    }\n}","sourceCodeStart":14,"sourceCodeEnd":38,"githubUrl":"https://github.com/kgrzybek/modular-monolith-with-ddd/blob/91c8ef24b4cb6ef558c95d8267fa07d68c7059f8/src/Modules/Payments/Infrastructure/Configuration/Processing/ValidationCommandHandlerDecorator.cs#L14-L38","documentation":"Thrown by the Payments module ValidationCommandHandlerDecorator, which wraps every command handler. It runs all registered FluentValidation validators for the command T, collects every ValidationFailure.ErrorMessage, and if any exist throws InvalidCommandException(errors). The literal message '{validator error messages}' is a placeholder: the real messages come from the command's validators.","triggerScenarios":"Sending any Payments command whose properties fail a registered FluentValidation validator: null/empty required fields, strings exceeding length, invalid enum codes, negative money values, malformed Guids, etc.","commonSituations":"Client omits a required field; enum code not in the allowed catalog; money currency empty; integration test constructs a command by hand and skips required values.","solutions":["Inspect ex.Errors to see exactly which validators failed and which properties.","Fix the offending command property to satisfy the validator (length, format, required-ness, allowed values).","Run the same FluentValidation validators on the client/API layer before dispatching.","Register/adjust validators if a legitimate value is being rejected."],"exampleFix":"// before\nawait _commandDispatcher.SendAsync(new ChangePriceListItemAttributesCommand(itemId, \"\", \"\", \"\", -1, \"\"));\n\n// after\nawait _commandDispatcher.SendAsync(new ChangePriceListItemAttributesCommand(itemId, \"PL\", \"MONTHLY\", \"PRO\", 19.99m, \"PLN\"));","handlingStrategy":"validation","validationCode":"var validator = new ChangePriceListItemAttributesCommandValidator();\nvar result = await validator.ValidateAsync(cmd);\nif (!result.IsValid) return BadRequest(result.Errors.Select(e => e.ErrorMessage));\nawait _commandDispatcher.SendAsync(cmd);","typeGuard":null,"tryCatchPattern":"try { await _commandDispatcher.SendAsync(cmd); }\ncatch (InvalidCommandException ex)\n{ return BadRequest(new { errors = ex.Errors }); }","preventionTips":["Run the same FluentValidation validators client-side before dispatch.","Read ex.Errors to find which property/validator failed.","Keep command DTOs and validators in sync when adding fields."],"tags":["payments","validation","fluentvalidation","command-handler","invalid-command-exception","decorator"],"backgroundTag":null,"analyzedSha":"91c8ef24b4cb6ef558c95d8267fa07d68c7059f8","analyzedAt":"2026-08-13T17:18:16.571Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}