Input Validation¶
Pydantic schema validation on all API inputs with custom validators and react-admin-compatible error responses.
Validation Architecture¶
All API input goes through three validation layers:
- Pydantic schemas — Type checking and custom validators on request bodies
- Django ORM — Database constraints (unique, FK, check constraints)
- Service layer — Business rule validation (state transitions, balances)
Pydantic Schema Validation¶
Every API endpoint defines input schemas with typed fields and validators:
class BorrowerIn(Schema):
first_name: str = Field(description="Borrower's first name")
email: Annotated[
str,
Field(description="Email address"),
AfterValidator(validate_email_format),
]
ssn_last_four: Annotated[
str,
Field(description="Last four digits of SSN"),
AfterValidator(validate_ssn_last_four),
]
django-ninja-extra handles Pydantic validation automatically — invalid requests receive a 422 response before reaching the service layer.
Common Validators¶
common/validators.py provides reusable pure-function validators:
| Validator | Rule | Example |
|---|---|---|
validate_email_format |
Django email validator | user@example.com |
validate_phone_e164 |
Regex ^\+[1-9]\d{7,14}$ |
+12025551234 |
validate_positive_decimal |
Strictly > 0 | Decimal("100.00") |
validate_non_negative_decimal |
>= 0 | Decimal("0.00") |
validate_rate_decimal |
0 <= value <= 1 | Decimal("0.065") |
validate_ssn_last_four |
Regex ^\d{4}$ |
"1234" |
validate_timezone |
Valid IANA timezone | "America/New_York" |
validate_content_type_string |
app_label.model_name format |
"borrowers.borrower" |
validate_meta_data_dict |
Flat dict, max 50 keys, scalar values | {"key": "value"} |
Type-Annotated Aliases¶
Pre-built Annotated types for common patterns:
from common.validators import PositiveDecimal, NonNegativeDecimal, RateDecimal, TimezoneStr
class FeeIn(Schema):
amount: PositiveDecimal = Field(description="Fee amount")
rate: RateDecimal = Field(description="Fee rate (0.0 to 1.0)")
DualValidationError¶
Validators raise DualValidationError(ValueError, ValidationError) which is catchable by both Pydantic (as ValueError) and Django (as ValidationError).
Validation Error Format¶
Pydantic validation errors are converted to a react-admin-compatible format:
{
"message": "Validation failed",
"errors": {
"email": "Enter a valid email address.",
"ssn_last_four": "Must be exactly 4 digits.",
"parties[2].role": "Value is not a valid enumeration member."
}
}
HTTP 422 Unprocessable Entity
Error Message Mapping¶
CUSTOM_MESSAGES in config/api.py maps Pydantic error type codes to user-friendly messages:
| Pydantic Code | User Message |
|---|---|
string_type |
"This field is required." |
missing |
"This field is required." |
value_error |
Validator's message |
enum |
"Value is not a valid enumeration member." |
Field Path Extraction¶
Pydantic's loc tuple is converted to dot-notation paths:
("body", "payload", "email")→"email"(strips body prefix + parameter name)("body", "payload", "parties", 2, "role")→"parties[2].role"(bracket notation for indices)
Business Logic Errors¶
Service-layer exceptions are mapped to appropriate HTTP status codes:
| Exception | HTTP Status | When |
|---|---|---|
ObjectDoesNotExist |
404 | Resource not found |
ValidationError |
422 | Field-level validation failure |
InvalidStateTransitionError |
409 | Invalid status change |
ComplianceBlockError |
409 | Compliance check failed |
ConcurrencyConflictError |
409 | Optimistic locking conflict |
IntegrityError |
409 | Database constraint violation (duplicate) |
ResourceLockedError |
423 | Resource locked (with Retry-After header) |
ProcessorError |
502 | External payment processor error |
BusinessLogicError |
400 | General business rule violation |
SQL Injection Prevention¶
All database queries use Django's ORM with parameterized queries. Raw SQL is never constructed from user input:
- ORM methods (
filter(),get(),create()) automatically parameterize - The few
RunSQLmigrations use static SQL strings - No
raw()orextra()calls with user-provided values
See Also¶
- API Overview --- Error response format
- Code Conventions --- Schema conventions
- Data Protection --- SSN and secret handling