nestforge 1.9.0

NestJS-inspired modular backend framework for Rust
Documentation
use axum::{routing::get, Json, Router};
use nestforge::{dto, Query, Validate};
use serde::Deserialize;
use tower::ServiceExt;

#[dto]
struct CreateUserDto {
    #[validate(required, email)]
    email: String,
    #[validate(min_length = 3, max_length = 12)]
    username: String,
    #[validate(min = 18, max = 120)]
    age: u32,
}

#[derive(Debug, Deserialize)]
struct SearchQuery {
    page: u32,
}

#[tokio::test]
async fn validate_supports_length_and_numeric_rules() {
    let dto = CreateUserDto {
        email: "bad-email".to_string(),
        username: "ab".to_string(),
        age: 17,
    };

    let errors = dto.validate().expect_err("dto should fail validation");
    let messages = errors
        .errors
        .iter()
        .map(|error| error.message.clone())
        .collect::<Vec<_>>();

    assert!(messages
        .iter()
        .any(|message| message.contains("valid email")));
    assert!(messages
        .iter()
        .any(|message| message.contains("at least 3 characters")));
    assert!(messages
        .iter()
        .any(|message| message.contains("at least 18")));
}

#[tokio::test]
async fn query_extractor_parses_typed_query_parameters() {
    let app = Router::new().route(
        "/search",
        get(|query: Query<SearchQuery>| async move { Json(query.value().page) }),
    );

    let response = app
        .oneshot(
            axum::http::Request::builder()
                .uri("/search?page=7")
                .body(axum::body::Body::empty())
                .expect("request should build"),
        )
        .await
        .expect("request should succeed");

    assert_eq!(response.status(), axum::http::StatusCode::OK);
}