Skip to content

Validation

The validation system lets you define reusable validation rules and apply them to database function parameters automatically. It is strategy-agnostic (FluentValidation, Manual, DataAnnotations, Assert) and language-agnostic — templates control the emitted syntax.

Key features

  • Reusable rules — define once, apply to many parameters.
  • Custom executors — point to existing functions or generate inline code.
  • Multi-error collection — return all errors at once or fail-fast.
  • Function-specific overrides — global rules plus per-function customization.

Basic setup

{
  "Validation": {
    "ValidationStrategy": "Manual",
    "ValidationLocations": ["Controller", "Provider"],
    "ValidationBehavior": {
      "CollectAllErrors": true,
      "ReturnType": "ValidationResult"
    }
  }
}

Rule definitions

Define rules that can be reused across parameters. Each rule carries per-strategy templates:

{
  "ValidationRuleDefinitions": [
    {
      "Name": "CheckBusinessCode",
      "Type": "Custom",
      "ExecutorType": "ExistingFunction",
      "ExecutorReference": "ValidationHelpers.ValidateBusinessCode",
      "ErrorMessage": "Invalid business unit code",
      "ErrorCode": "INVALID_BUSINESS_CODE",
      "Templates": {
        "FluentValidation": ".Must(ValidationHelpers.ValidateBusinessCode).WithMessage(\"Invalid business unit code\").WithErrorCode(\"INVALID_BUSINESS_CODE\")",
        "Manual": {
          "Check": "ValidationHelpers.ValidateBusinessCode({{.ParamName}})",
          "ErrorBuilder": "new ValidationError(\"{{.ParamName}}\", \"Invalid business unit code\", \"INVALID_BUSINESS_CODE\")"
        }
      }
    }
  ]
}

Rule types

ExecutorType Behavior
ExistingFunction Calls a validation function already in your codebase (ExecutorReference).
GenerateCode Emits inline validation code (GeneratedCode).

Type is Built-in, Custom, or Generated.

Applying rules to parameters

Global mappings

Map rules to parameter names across all functions:

{
  "ParameterValidationMappings": [
    { "ParameterNames": ["businessunitcode", "businesscode"], "Rules": ["CheckBusinessCode"] },
    { "ParameterNames": ["email", "emailaddress"], "Rules": ["Required", "EmailFormat", {"Name": "MaxLength", "Value": 255}] },
    { "ParameterNames": ["userid", "customerid"], "Rules": ["Required", "PositiveNumber"] }
  ]
}

A rule reference is either a string (rule name) or an object with parameters ({"Name": "MaxLength", "Value": 255}).

Function-specific overrides

Extend or override global rules for a single function:

{
  "FunctionSpecificValidations": {
    "CreateUser": { "age": [{"Name": "Range", "Min": 18, "Max": 120}] },
    "UpdateUserEmail": { "newemail": ["Required", "EmailFormat"] }
  }
}

Strategies

var errors = new List<ValidationError>();
if (email == null || string.IsNullOrEmpty(email))
    errors.Add(new ValidationError("email", "Email is required", "REQUIRED_FIELD"));
if (!ValidationHelpers.ValidateBusinessCode(businessCode))
    errors.Add(new ValidationError("businessCode", "Invalid business unit code", "INVALID_BUSINESS_CODE"));
if (errors.Any())
    return BadRequest(new ValidationResult { Success = false, Errors = errors });
public class CreateUserValidator : AbstractValidator<CreateUserRequest>
{
    public CreateUserValidator()
    {
        RuleFor(x => x.Email)
            .NotNull().NotEmpty().WithErrorCode("REQUIRED_FIELD")
            .EmailAddress().WithErrorCode("INVALID_EMAIL")
            .MaximumLength(255).WithErrorCode("MAX_LENGTH_EXCEEDED");
    }
}

Behavior options

  • CollectAllErrorstrue returns all validation errors at once; false fails on the first.
  • ReturnTypeValidationResult (simple error array) or ProblemDetails (RFC 7807).

Template placeholders

Available in validation rule templates:

Placeholder Meaning
{{.ParamName}} Parameter name
{{.ParamDisplayName}} Display name
{{.Min}} / {{.Max}} Range bounds
{{.Value}} Generic value (e.g. MaxLength)

Works with context mapping

Validation composes with context mapping — context parameters are validated before being extracted from the context object:

{
  "UseUserContext": true,
  "ContextParameterMappings": [ /* ... */ ],
  "Validation": {
    "ParameterValidationMappings": [
      { "ParameterNames": ["userid"], "Rules": ["Required", "PositiveNumber"] }
    ]
  }
}

Adopt gradually

Start by adding rules for critical parameters (email, ids), define custom rules for business logic, then enable CollectAllErrors for better UX. Existing code keeps working as you go.