Validation Severity Filtering
Validation severity filtering allows you to control which validation results block command execution based on their severity level. This enables user-friendly workflows where warnings and informational messages can be shown to users for confirmation before allowing execution to proceed.
Overview
Section titled “Overview”When validating commands, validation results can have different severity levels:
- Error (
ValidationResultSeverity.Error = 3): Critical issues that must be fixed - Warning (
ValidationResultSeverity.Warning = 2): Potential issues that should be reviewed - Information (
ValidationResultSeverity.Information = 1): Informational messages that don’t prevent execution - Unknown (
ValidationResultSeverity.Unknown = 0): Unclassified validation results
By default, only Error severity results block command execution. Warnings and Information results are filtered out and don’t prevent the command from executing.
Purpose
Section titled “Purpose”Severity filtering enables better user experiences by allowing you to:
- Show Warnings to Users: Display validation warnings without blocking execution
- Require User Confirmation: Let users review and acknowledge warnings before proceeding
- Progressive Execution: First validate with strict rules, then allow users to override warnings
- Flexible Validation: Apply different validation strictness based on user roles or contexts
How It Works
Section titled “How It Works”Default Behavior
Section titled “Default Behavior”Without specifying an allowed severity, only Error level validation results block execution:
const command = new CreateOrder();command.orderNumber = 'ORD-12345';
// Default: only errors block executionconst result = await command.execute();
if (!result.isSuccess) { // Only errors are present - warnings and info were filtered out console.log('Errors:', result.validationResults);}Allowing Warnings
Section titled “Allowing Warnings”To allow warnings (only errors block execution):
const command = new CreateOrder();command.orderNumber = 'ORD-12345';
// Allow warnings - only errors block executionconst result = await command.execute(ValidationResultSeverity.Warning);
if (!result.isSuccess) { // Only errors are present console.log('Errors:', result.validationResults);}Allowing Information
Section titled “Allowing Information”To allow both warnings and information (only errors block):
const command = new CreateOrder();command.orderNumber = 'ORD-12345';
// Allow information - only errors block executionconst result = await command.execute(ValidationResultSeverity.Information);
if (!result.isSuccess) { // Only errors are present console.log('Errors:', result.validationResults);}Common Patterns
Section titled “Common Patterns”Warning Confirmation Workflow
Section titled “Warning Confirmation Workflow”A typical pattern is to first execute without allowing warnings, show them to the user, and then re-execute with warnings allowed if the user confirms:
import { CreateOrder } from './generated/commands';import { ValidationResultSeverity } from '@cratis/arc/validation';
async function createOrderWithWarningConfirmation() { const command = new CreateOrder(); command.orderNumber = 'ORD-12345'; command.customerId = '550e8400-e29b-41d4-a716-446655440000';
// First attempt: default behavior (only errors block) let result = await command.execute();
if (!result.isSuccess) { // Check if we have only warnings (no errors) const hasOnlyWarnings = result.validationResults.every( v => v.severity === ValidationResultSeverity.Warning );
if (hasOnlyWarnings) { // Show warnings to user const warnings = result.validationResults.map(v => v.message).join('\n'); const userConfirmed = await showWarningDialog( 'Warning', `The following warnings were found:\n\n${warnings}\n\nDo you want to proceed anyway?` );
if (userConfirmed) { // User confirmed - re-execute allowing warnings result = await command.execute(ValidationResultSeverity.Warning); } else { // User cancelled return; } } else { // We have errors - show them to user const errors = result.validationResults.map(v => v.message).join('\n'); showErrorDialog('Validation Errors', errors); return; } }
if (result.isSuccess) { console.log('Order created successfully!'); }}
function showWarningDialog(title: string, message: string): Promise<boolean> { // Implementation depends on your UI framework // Return true if user confirms, false if user cancels}
function showErrorDialog(title: string, message: string): void { // Implementation depends on your UI framework}Helper Function
Section titled “Helper Function”You can create a reusable helper function for the warning confirmation pattern:
import { ICommand, CommandResult } from '@cratis/arc/commands';import { ValidationResultSeverity } from '@cratis/arc/validation';
interface ConfirmationOptions { title?: string; allowInformation?: boolean;}
async function executeWithConfirmation<TResponse>( command: ICommand<any, TResponse>, confirmCallback: (messages: string[]) => Promise<boolean>, options?: ConfirmationOptions): Promise<CommandResult<TResponse>> { const allowedSeverity = options?.allowInformation ? ValidationResultSeverity.Information : ValidationResultSeverity.Warning;
// First attempt with default behavior let result = await command.execute();
if (!result.isSuccess && !result.hasExceptions) { // Check if all validation results are warnings/info const maxSeverity = Math.max(...result.validationResults.map(v => v.severity));
if (maxSeverity <= allowedSeverity) { // Only warnings/info present const messages = result.validationResults.map(v => v.message); const confirmed = await confirmCallback(messages);
if (confirmed) { // Re-execute with allowed severity result = await command.execute(allowedSeverity); } } }
return result;}
// Usageconst command = new CreateOrder();const result = await executeWithConfirmation( command, async (messages) => { return confirm(`Warnings:\n${messages.join('\n')}\n\nProceed anyway?`); });React Integration
Section titled “React Integration”For React applications, you can create a custom hook:
import { useState } from 'react';import { ICommand, CommandResult } from '@cratis/arc/commands';import { ValidationResultSeverity } from '@cratis/arc/validation';
interface UseCommandWithConfirmationResult<TResponse> { execute: () => Promise<void>; result?: CommandResult<TResponse>; warnings?: string[]; isLoading: boolean; confirmAndExecute: () => Promise<void>;}
export function useCommandWithConfirmation<TResponse>( command: ICommand<any, TResponse>): UseCommandWithConfirmationResult<TResponse> { const [result, setResult] = useState<CommandResult<TResponse>>(); const [warnings, setWarnings] = useState<string[]>(); const [isLoading, setIsLoading] = useState(false);
const execute = async () => { setIsLoading(true); try { const cmdResult = await command.execute(); setResult(cmdResult);
if (!cmdResult.isSuccess && !cmdResult.hasExceptions) { const hasOnlyWarnings = cmdResult.validationResults.every( v => v.severity === ValidationResultSeverity.Warning );
if (hasOnlyWarnings) { setWarnings(cmdResult.validationResults.map(v => v.message)); } } } finally { setIsLoading(false); } };
const confirmAndExecute = async () => { setIsLoading(true); try { const cmdResult = await command.execute(ValidationResultSeverity.Warning); setResult(cmdResult); setWarnings(undefined); } finally { setIsLoading(false); } };
return { execute, result, warnings, isLoading, confirmAndExecute };}
// Usage in a componentfunction CreateOrderForm() { const command = new CreateOrder(); const { execute, result, warnings, isLoading, confirmAndExecute } = useCommandWithConfirmation(command);
const handleSubmit = async () => { await execute(); };
return ( <div> <button onClick={handleSubmit} disabled={isLoading}> Create Order </button>
{warnings && ( <div className="warning-dialog"> <h3>Warnings</h3> <ul> {warnings.map((w, i) => <li key={i}>{w}</li>)} </ul> <button onClick={confirmAndExecute}>Proceed Anyway</button> <button onClick={() => setWarnings(undefined)}>Cancel</button> </div> )}
{result?.isSuccess && <div>Order created successfully!</div>} </div> );}Backend Implementation
Section titled “Backend Implementation”The severity filtering happens both on the client and server:
Client-Side Filtering
Section titled “Client-Side Filtering”Before sending the request, the client filters validation results to determine if execution should proceed:
- Client-side validators run (if configured)
- Validation results are filtered based on
allowedSeverity - If only allowed severities remain, the request proceeds to the server
- The
X-Allowed-Severityheader is sent with the request
Server-Side Filtering
Section titled “Server-Side Filtering”The backend also filters validation results:
- The
CommandEndpointMapperreads theX-Allowed-Severityheader - The
CommandPipelineruns all validation filters - Validation results are filtered based on the allowed severity
- Only validation results with severity >
allowedSeverityblock execution
For backend implementation details, see Backend Validation Severity Filtering.
API Reference
Section titled “API Reference”ValidationResultSeverity Enum
Section titled “ValidationResultSeverity Enum”enum ValidationResultSeverity { Unknown = 0, Information = 1, Warning = 2, Error = 3}ICommand.execute()
Section titled “ICommand.execute()”interface ICommand<TCommandContent, TCommandResponse> { /** * Execute the command. * @param allowedSeverity Optional maximum severity level to allow. * Validation results with severity higher than this will cause the command to fail. * If not specified, only Error severity blocks execution. */ execute( allowedSeverity?: ValidationResultSeverity, ignoreWarnings?: boolean ): Promise<CommandResult<TCommandResponse>>;}Filtering Logic
Section titled “Filtering Logic”The filtering logic works as follows:
allowedSeveritynot specified (default): OnlyErrorseverity results block executionallowedSeverity = ValidationResultSeverity.Warning: OnlyErrorseverity results block execution (warnings allowed)allowedSeverity = ValidationResultSeverity.Information: OnlyErrorandWarningseverity results block execution (information allowed)
Results with severity greater than the allowedSeverity will block execution.
Best Practices
Section titled “Best Practices”When to Use Severity Filtering
Section titled “When to Use Severity Filtering”✅ Good Use Cases:
- Orders with non-critical warnings (e.g., “Low stock” warning)
- Forms with recommendations that users can override
- Operations with soft validation rules
- Multi-step wizards where some warnings can be deferred
❌ Avoid:
- Critical business rules (always use Error severity)
- Security-related validations
- Data integrity checks
- Regulatory compliance requirements
Security Considerations
Section titled “Security Considerations”- Never use severity filtering to bypass security validations
- Always use
Errorseverity for authorization failures - Critical business rules should always be
Errorseverity - Don’t rely on client-side severity filtering for security (server validates too)
Performance Tips
Section titled “Performance Tips”- Severity filtering doesn’t add significant overhead
- The same validation runs regardless of allowed severity
- Use appropriate severity levels in your validators
- Consider caching validation results if executing multiple times
Troubleshooting
Section titled “Troubleshooting”Warnings Still Block Execution
Section titled “Warnings Still Block Execution”Cause: Not specifying allowedSeverity parameter or validators returning Error severity.
Solution:
- Pass
ValidationResultSeverity.Warningtoexecute() - Check that validators are using
Warningseverity for non-critical issues - Verify that custom validators set the correct severity level
Errors Are Allowed Through
Section titled “Errors Are Allowed Through”Cause: Using too high of an allowedSeverity value or validators incorrectly assigning severity.
Solution:
- Never use
ValidationResultSeverity.ErrorasallowedSeverity - Verify validators are using
Errorseverity for critical issues - Check backend validator implementations
Server Returns Different Results
Section titled “Server Returns Different Results”Cause: Server-side and client-side validators may have different rules.
Solution:
- Server always has final authority on validation
- Ensure client-side validators match server-side rules
- Use FluentValidation with proxy generation for consistency
Related Documentation
Section titled “Related Documentation”- Validation - Pre-flight validation without execution
- Validation - Client-side validation rules
- Backend Validation Severity Filtering - Server implementation
- Command Filters - Backend validation pipeline