DocsServer features
Validation
Apply readable business rules after deserialization and before side effects, with deterministic field-aware failures.
Shape first, business rules second
Serde and Rust types reject malformed or mistyped payloads before the handler runs. Validator handles structurally valid input that violates application rules. Validate before database queries, external calls, cache writes, or mutations.
#[rpc]
pub async fn register(
email: String,
password: String,
confirmation: String,
) -> rahti::Result<Account> {
let email = email.trim().to_lowercase();
let mut validator = rahti::Validator::new();
validator
.required("email", &email)
.email("email", &email)
.max_chars("email", &email, 254)
.min_chars("password", &password, 10)
.same("confirmation", &confirmation, "password", &password);
validator.finish()?;
create_account(email, password).await
}Built-in rules
| Rule | Checks |
|---|---|
| required / required_option | Blank text or a missing optional value. |
| min_chars / max_chars | Unicode character length, not UTF-8 bytes. |
| email / same | Plausible email syntax or matching text fields. |
| one_of | Membership in an allowed typed slice. |
| min_value / max_value | Comparable numeric or value bounds. |
| check | An application-defined synchronous condition. |
Rules collect all failures in call order and group messages by field. Content rules skip empty strings, so combine them with required when blank input is invalid. Add async rule results through errors_mut().add(...).
HTTP error contract
A validation failure converts directly into rahti::Error and returns HTTP 422 with an error summary plus an errors field map. Malformed JSON and values that cannot deserialize remain HTTP 400 because business validation never ran.
