rustapi-rs 0.1.450

A FastAPI-like web framework for Rust - DX-first, type-safe, batteries included
Documentation
use rustapi_rs::prelude::*;
use rustapi_validate::v2::ValidationContext;
use serde::{Deserialize, Serialize};

// ============================================================================
// Sync Validation Tests (Legacy validator crate compatibility)
// ============================================================================

#[derive(Debug, Deserialize, Serialize, validator::Validate)]
#[cfg(feature = "legacy-validator")]
struct LegacyUser {
    #[validate(length(min = 3))]
    name: String,
}

#[test]
#[cfg(feature = "legacy-validator")]
fn test_legacy_validator_compat() {
    let valid_user = LegacyUser {
        name: "Bob".to_string(),
    };
    let invalid_user = LegacyUser {
        name: "Bo".to_string(),
    };

    // Test direct Validatable implementation
    assert!(valid_user.do_validate().is_ok());

    let err = invalid_user.do_validate().unwrap_err();
    assert_eq!(err.error_type, "validation_error");
    assert!(err.fields.is_some());
    let fields = err.fields.unwrap();
    assert_eq!(fields[0].field, "name");
}

// ============================================================================
// V2 Validation Tests (New engine)
// ============================================================================

#[derive(Debug, Deserialize, Serialize, rustapi_macros::Validate)]
struct V2User {
    #[validate(length(min = 3))]
    name: String,
}

#[test]
fn test_v2_validate_macro() {
    let valid_user = V2User {
        name: "Alice".to_string(),
    };
    let invalid_user = V2User {
        name: "Al".to_string(),
    };

    // Test direct Validatable implementation generated by macro
    assert!(valid_user.do_validate().is_ok());

    let err = invalid_user.do_validate().unwrap_err();
    assert_eq!(err.error_type, "validation_error");
    assert!(err.fields.is_some());
    let fields = err.fields.unwrap();
    assert_eq!(fields[0].field, "name");
}

// ============================================================================
// Async Validation Tests
// ============================================================================

#[derive(Debug, Deserialize, Serialize, rustapi_macros::Validate)]
struct AsyncUser {
    #[validate(custom_async = "check_custom")]
    name: String,
}

async fn check_custom(
    val: &String,
    _ctx: &ValidationContext,
) -> Result<(), rustapi_validate::v2::RuleError> {
    if val == "taken" {
        Err(rustapi_validate::v2::RuleError::new(
            "taken",
            "Name is taken",
        ))
    } else {
        Ok(())
    }
}

#[tokio::test]
async fn test_async_validation() {
    // We can't easily test AsyncValidatedJson extractor without constructing a full Request
    // and setting up the app, but we can test the trait and manual usage if needed.
    // However, since we updated AsyncValidatedJson to use validate_full, we should trust
    // unit tests in rustapi-core if we had them.
    // Here we verify that the macro generated the AsyncValidate impl correctly.

    let user = AsyncUser {
        name: "available".to_string(),
    };
    let ctx = ValidationContext::default();

    assert!(user.validate_async(&ctx).await.is_ok());

    let invalid_user = AsyncUser {
        name: "taken".to_string(),
    };
    let err = invalid_user.validate_async(&ctx).await.unwrap_err();
    // err here is ValidationErrors
    assert!(!err.is_empty());
    let name_errors = err.get("name").expect("Should have errors for name");
    assert!(name_errors.iter().any(|e| e.code == "taken"));
}