Form requests authorise, then validate, then hand you a struct.

A form request is the rules for an input plus who may send it. The controller receives a typed value that has passed both — and the same rules describe the endpoint in the generated OpenAPI document.

What you get

  • authorize runs first: an unauthorised caller gets a 403 without the input being read.
  • Failures answer 422 in one documented shape, with per-rule messages you can override.
  • Only the fields the rules name reach the struct.
  • The OpenAPI document is generated from the validator’s rules, so the schema cannot drift from what is enforced.

In the docs

src/app/http/requests/store_post.rs
use rainier_framework::auth::AuthenticatedUser;
use rainier_framework::prelude::*;
use serde::Deserialize;

#[derive(Debug, Deserialize)]
pub struct StorePostRequest {
    pub title: String,
    pub body: String,
}

#[async_trait]
impl FormRequest for StorePostRequest {
    fn rules() -> RuleSet {
        vec![
            field("title", [Rule::Required, Rule::String, Rule::Between(3.0, 120.0)]),
            field("body", [Rule::Required, Rule::String, Rule::Min(10.0)]),
        ]
    }

    async fn authorize(request: &Request) -> bool {
        request.extension::<AuthenticatedUser<User>>().is_some()
    }
}

Up close

Select an image to see it full size.

Invalid input becomes a 422 with an error list per field, using the messages the FormRequest declared; an anonymous caller is refused before validation runs.
The OpenAPI schema comes from the validator rules: Between(3, 120) becomes minLength and maxLength, so the published contract cannot drift from the code.
All features

Type to search 2.0.0 docs.