VALIDATION_ENGINE.md

RowGuard Validation Engine

Purpose

The Validation Engine is responsible for transforming adapted row mappings into validated Pydantic models.

It is the heart of RowGuard’s value proposition: every accepted row satisfies the target Pydantic model, and every rejected row produces structured, observable diagnostics.

The Validation Engine does not execute SQL, adapt rows, or determine how validation failures are handled. Those responsibilities belong to the Query Engine, Row Adapter, and Reject Handler respectively.


Responsibilities

The Validation Engine:

  • Accepts adapted row mappings.

  • Invokes Pydantic validation.

  • Produces strongly typed models.

  • Captures validation failures.

  • Preserves validation context.

  • Emits validation diagnostics.

  • Records validation metrics.

The Validation Engine does not:

  • Compile SQLRules constraints.

  • Perform SQL filtering.

  • Build SQL statements.

  • Execute queries.

  • Decide rejection policy.

  • Modify application data.


Position in the Pipeline

Database
    │
    ▼
Row Adapter
    │
    ▼
Validation Engine
    │
    ├── Valid Model
    └── Validation Failure
          │
          ▼
     Reject Handler

Inputs

Each validation operation receives:

  • Target Pydantic model

  • Adapted row mapping

  • Validation configuration

  • Validation context (optional)

Example:

validated = UserRead.model_validate(mapping)

The engine should rely on Pydantic’s public APIs rather than reimplementing validation behavior.


Outputs

Successful validation returns a model instance.

Failed validation returns a structured internal object that contains:

  • original mapping

  • ValidationError

  • target model

  • validation duration

  • diagnostics

The Query Engine decides whether that failure becomes a rejection, exception, or callback.


Validation Contract

The engine guarantees:

  • Every accepted object is an instance of the requested model.

  • Every validation failure preserves the original Pydantic error.

  • Validation behavior is deterministic.

  • No implicit coercion beyond Pydantic’s configured behavior.


Validation Modes

Strict

Honor strict field behavior exactly as defined by the model.

Default

Use normal Pydantic validation semantics.

Contextual (Future)

Allow callers to pass validation context.

model.model_validate(
    mapping,
    context={"request_id": "..."},
)

RowGuard should pass context through unchanged.


Nested Models

Nested models should work naturally because validation is delegated to Pydantic.

Example:

class Address(BaseModel):
    city: str

class User(BaseModel):
    name: str
    address: Address

If the Row Adapter produces the expected nested mapping, the Validation Engine requires no special logic.


Validation Errors

Validation failures should preserve:

  • full ValidationError

  • error locations

  • error messages

  • input values (subject to application privacy policies)

The engine should never flatten or simplify errors unless explicitly requested.


ValidationResult

Internally, validation may return a lightweight result object.

@dataclass(frozen=True, slots=True)
class ValidationResult[T]:
    success: bool
    model: T | None
    error: ValidationError | None
    duration_ns: int

This keeps orchestration code independent from Pydantic exceptions.


Statistics

Suggested metrics:

  • rows_validated

  • rows_valid

  • rows_invalid

  • validation_time

  • average_validation_time

Future:

  • validation throughput

  • per-model metrics


Performance

Validation is expected to be one of the most expensive stages after database I/O.

Guidelines:

  • Validate exactly once per row.

  • Avoid copying adapted mappings.

  • Reuse immutable validation configuration.

  • Measure before optimizing.

Future optimizations may include batch orchestration, but validation semantics must remain identical.


Error Handling

Unexpected failures should be distinguished from ordinary validation failures.

Suggested hierarchy:

RowGuardError
└── ValidationEngineError
    ├── ValidationConfigurationError
    └── UnexpectedValidationError

Ordinary Pydantic ValidationError objects are expected outcomes and should not be wrapped unless additional execution context is needed.


Extension Points

Future plugins may provide:

  • validation observers

  • metrics exporters

  • tracing hooks

  • alternate validation engines (experimental)

Any alternate engine must preserve RowGuard’s validation contract.


Testing Requirements

Tests should cover:

  • successful validation

  • failed validation

  • nested models

  • aliases

  • optional fields

  • extra fields

  • strict mode

  • context propagation

  • deterministic behavior

  • performance regressions


MVP Scope

The initial release supports:

  • Pydantic v2

  • model_validate()

  • deterministic validation

  • structured validation failures

  • validation timing

  • nested model support through adapted mappings

Deferred:

  • alternate validation engines

  • distributed validation

  • speculative validation


Design Principles

  • Pydantic is the source of truth.

  • Validate every adapted row.

  • Never suppress validation errors.

  • Preserve complete error information.

  • Keep validation independent from rejection policy.

  • Optimize orchestration, not semantics.