Validation

June 10, 2026 · View on GitHub

DataSurface provides comprehensive built-in validation driven by the ResourceContract. Validation rules are declared via [CrudField] attributes and enforced automatically on create and update operations.


Validation Rules

RuleAttribute PropertyApplies ToDescription
Required on createRequiredOnCreate = truePOSTField must be present in the request body
ImmutableImmutable = truePATCHField is rejected on update — can only be set on create
Min lengthMinLength = NPOST, PATCHMinimum string length
Max lengthMaxLength = NPOST, PATCHMaximum string length
Min valueMin = NPOST, PATCHMinimum numeric value
Max valueMax = NPOST, PATCHMaximum numeric value
Regex patternRegex = "..."POST, PATCHValue must match the regular expression
Allowed valuesAllowedValues = "a|b|c"POST, PATCHValue must be one of the pipe-separated options
JSON type check(automatic)POST, PATCHWrong JSON type for a field is rejected with 400 — e.g. a number sent for a string field returns "Value must be a string.", a non-numeric value for a numeric field returns "Value must be a valid number."
Unknown field rejection(automatic)POST, PATCHFields not in the contract are rejected

Example

[CrudResource("users")]
public class User
{
    [CrudKey]
    public int Id { get; set; }

    // Required on create, with regex pattern
    [CrudField(CrudDto.Read | CrudDto.Create | CrudDto.Update,
        RequiredOnCreate = true,
        Regex = @"^[\w.+-]+@[\w-]+\.[\w.]+$")]
    public string Email { get; set; } = default!;

    // Immutable — set once on create, rejected on PATCH
    [CrudField(CrudDto.Read | CrudDto.Create, Immutable = true)]
    public string Username { get; set; } = default!;

    // String length validation
    [CrudField(CrudDto.Read | CrudDto.Create | CrudDto.Update,
        MinLength = 8, MaxLength = 100)]
    public string Password { get; set; } = default!;

    // Numeric range validation
    [CrudField(CrudDto.Read | CrudDto.Create | CrudDto.Update,
        Min = 0, Max = 150)]
    public int Age { get; set; }

    // Regex pattern validation
    [CrudField(CrudDto.Read | CrudDto.Create | CrudDto.Update,
        Regex = @"^\+?[1-9]\d{1,14}$")]
    public string? PhoneNumber { get; set; }

    // Allowed values — enum-like restriction
    [CrudField(CrudDto.Read | CrudDto.Create | CrudDto.Update,
        AllowedValues = "Active|Inactive|Pending")]
    public string Status { get; set; } = default!;
}

Validation Behavior

On Create (POST)

  1. Unknown fields → rejected
  2. RequiredOnCreate fields → must be present
  3. String length checks → MinLength, MaxLength
  4. Numeric range checks → Min, Max
  5. Pattern matching → Regex
  6. Value restriction → AllowedValues

On Update (PATCH)

  1. Unknown fields → rejected
  2. Immutable fields → rejected (cannot be changed)
  3. Concurrency token → required if RequiredOnUpdate = true
  4. String length checks → on provided fields
  5. Numeric range checks → on provided fields
  6. Pattern matching → on provided fields
  7. Value restriction → on provided fields

Validation only runs on fields actually present in the request body. Omitted fields on PATCH are left unchanged and not validated.


Error Response

Validation errors return HTTP 400 with RFC 7807 Problem Details:

{
  "title": "Validation failed",
  "status": 400,
  "detail": "One or more validation errors occurred.",
  "traceId": "00-abc123...",
  "errors": {
    "email": ["Field is required."],
    "password": ["Minimum length is 8."],
    "age": ["Maximum value is 150."],
    "status": ["Value must be one of: Active, Inactive, Pending."]
  }
}

Multiple validation errors are collected and returned together — the response includes all failures, not just the first one.


Feature Flag

Validation can be disabled via feature flags:

opt.Features = new DataSurfaceFeatures
{
    EnableFieldValidation = false  // Disables MinLength, MaxLength, Min, Max, Regex, AllowedValues and the JSON type check
};

Note: RequiredOnCreate, Immutable, and unknown field rejection are always enforced regardless of the feature flag.