arcly-http 0.2.1

Enterprise-grade NestJS-inspired web framework on axum: zero-lock DI, declarative controllers, multi-tenant data routing, transactional outbox, ABAC, and a self-documenting OpenAPI surface
Documentation
//! `Validated<T>` -- type-branded, deserialized-and-validated request payload.
//!
//! Unlike the `#[Body] dto: T` shorthand (which validates silently), `Validated<T>`
//! makes the validation contract visible at the type level. Functions that only
//! accept pre-validated inputs declare `Validated<T>` in their signature;
//! the type system enforces the invariant across the call stack.
//!
//! ## Usage
//!
//! ```rust,ignore
//! async fn create_user(ctx: RequestContext) -> Result<Json<User>, HttpException> {
//!     let dto = Validated::<CreateUserDto>::from_body(&ctx)?;
//!     service.create(dto.into_inner()).await
//! }
//! ```
//!
//! ## RFC 7807 Error Shape
//!
//! Deserialize failure  -> `400 Bad Request`  (`bad-request` problem type)
//! Constraint violation -> `422 Unprocessable Entity` (`validation` problem type)
//!                         with per-field `errors` array

use std::ops::Deref;

use serde::de::DeserializeOwned;
use validator::Validate;

use crate::web::context::RequestContext;
use crate::web::error::{HttpException, Validation};
use crate::web::extract::{extract_body_json, extract_query};

// ─── The wrapper type ────────────────────────────────────────────────────────

/// A payload `T` that has been deserialized **and** validated.
///
/// Construction is possible only through [`from_body`](Self::from_body) or
/// [`from_query`](Self::from_query) (plus [`assume_valid`](Self::assume_valid)
/// for test fixtures). All other callers receive a `T` provably free of
/// constraint violations.
#[derive(Debug, Clone)]
pub struct Validated<T>(T);

impl<T> Validated<T> {
    /// Consume the wrapper, returning the validated inner value.
    #[inline]
    pub fn into_inner(self) -> T {
        self.0
    }

    /// Borrow the validated inner value.
    #[inline]
    pub fn inner(&self) -> &T {
        &self.0
    }

    /// Construct without validation -- use **only** in unit-test fixtures where
    /// the value is already known good. Never call in handler code.
    #[inline]
    pub fn assume_valid(v: T) -> Self {
        Self(v)
    }
}

impl<T> Deref for Validated<T> {
    type Target = T;
    #[inline]
    fn deref(&self) -> &T {
        &self.0
    }
}

// ─── Extraction from RequestContext ─────────────────────────────────────────

impl<T: DeserializeOwned + Validate> Validated<T> {
    /// Deserialize `T` from the JSON request body, then run `T::validate()`.
    ///
    /// Returns `Err(HttpException)` on either step, converting automatically
    /// into the RFC 7807 problem shape:
    ///
    /// | Failure             | Status | `type` field     |
    /// |---------------------|--------|------------------|
    /// | Malformed JSON      | 400    | `bad-request`    |
    /// | Constraint violated | 422    | `validation`     |
    pub fn from_body(ctx: &RequestContext) -> Result<Self, HttpException> {
        let v: T = extract_body_json(ctx)?;
        v.validate()
            .map_err(|e| HttpException::from(Validation::from(e)))?;
        Ok(Self(v))
    }

    /// Deserialize `T` from the URL query string, then validate.
    ///
    /// Same error shape as [`from_body`](Self::from_body).
    pub fn from_query(ctx: &RequestContext) -> Result<Self, HttpException> {
        let v: T = extract_query(ctx)?;
        v.validate()
            .map_err(|e| HttpException::from(Validation::from(e)))?;
        Ok(Self(v))
    }
}