Validation and errors
Use validation to reject an input before it changes application state. Use an application error to explain which kind of failure occurred without making your service depend on HTTP status codes.
Define a rule on the input
This complete input type can live in a module shared by the HTTP adapter and the
application service. It requires iet-core and Serde with its derive feature.
use ;
use Deserialize;
The rule belongs to the input, not to an Axum extractor. A command-line adapter
or a test can use the same rule. Keep submitted values out of error codes and
field metadata: a field name such as text is useful; repeating a password or
private note in the response is not.
Validate at both boundaries
iet_http_axum::ValidatedJson<CreateNote> parses JSON and calls validate().
That protects the HTTP boundary. The service must still call input.validate()?
before mutation, because not every caller goes through HTTP.
This is deliberate duplication of the check, not duplication of the rule.
One implementation of Validate is called in both places.
JSON parsing errors are different from domain validation errors:
| Request problem | HTTP result |
|---|---|
| Malformed JSON | 400, code http.json |
JSON has the wrong data shape for T | 422, code http.json |
| Missing or unsupported JSON content type | 415, code http.content_type |
| JSON exceeds the configured limit | 413, code http.body_limit when rejected by the extractor |
JSON parses but Validate fails | 422, your validation code |
Outer body-limit middleware can reject a request before the extractor runs; do not assume every early middleware rejection has the extractor's problem body.
Convert errors only at the HTTP edge
Services return AppResult<T>. A handler returns a result whose error is
iet_http_axum::ApiError; its From<AppError> implementation allows ? to do the
conversion. The notes tutorial shows this in a handler.
ApiError produces application/problem+json with type, title, status,
code, and errors. It does not serialize the application's free-form message.
The validation error above becomes:
Choose an error kind
ErrorKind | Status | Use when |
|---|---|---|
Validation | 422 | A well-formed input violates an application rule |
Unauthenticated | 401 | The operation requires an authenticated caller |
Forbidden | 403 | The caller cannot perform the operation |
NotFound | 404 | The requested resource is not available to this lookup |
Conflict | 409 | The requested change conflicts with current state |
Unavailable | 503 | A required service cannot currently complete the operation |
DeadlineExceeded | 504 | The operation exceeded its deadline |
Internal | 500 | An unexpected internal condition prevented completion |
For server errors, ApiError returns the generic code service.error and no
field errors. Diagnostics are logged server-side instead of exposed to clients.
Do not put secrets in an error message merely because it is not sent over HTTP:
it may still be logged.
Source contract: crates/core/iet-core/src/lib.rs; HTTP behavior:
crates/features/iet-http-axum/src/extract.rs and
crates/features/iet-http-axum/src/error.rs.