domainstack-derive 1.1.1

Derive macros for domainstack: #[derive(Validate, ToSchema, ToJsonSchema)] - validation + auto-generate OpenAPI/JSON Schema
Documentation
use domainstack::prelude::*;
use domainstack_derive::Validate;

#[derive(Debug, Validate)]
struct Room {
    #[validate(range(min = 1, max = 4))]
    adults: u8,

    #[validate(range(min = 0, max = 3))]
    children: u8,
}

#[derive(Debug, Validate)]
struct HotelBooking {
    #[validate(length(min = 1))]
    guest_name: String,

    #[validate(each(nested))]
    rooms: Vec<Room>,
}

#[test]
fn test_valid_collection() {
    let booking = HotelBooking {
        guest_name: "John Doe".to_string(),
        rooms: vec![
            Room {
                adults: 2,
                children: 1,
            },
            Room {
                adults: 1,
                children: 0,
            },
        ],
    };

    assert!(booking.validate().is_ok());
}

#[test]
fn test_collection_with_invalid_items() {
    let booking = HotelBooking {
        guest_name: "John Doe".to_string(),
        rooms: vec![
            Room {
                adults: 2,
                children: 1,
            },
            Room {
                adults: 5,
                children: 0,
            },
            Room {
                adults: 1,
                children: 4,
            },
        ],
    };

    let result = booking.validate();
    assert!(result.is_err());

    let err = result.unwrap_err();
    assert_eq!(err.violations.len(), 2);

    assert_eq!(err.violations[0].path.to_string(), "rooms[1].adults");
    assert_eq!(err.violations[0].code, "out_of_range");

    assert_eq!(err.violations[1].path.to_string(), "rooms[2].children");
    assert_eq!(err.violations[1].code, "out_of_range");
}

#[test]
fn test_each_with_primitive_rules() {
    #[derive(Debug, Validate)]
    struct TagList {
        #[validate(each(length(min = 1, max = 20)))]
        tags: Vec<String>,
    }

    let tags = TagList {
        tags: vec![
            "rust".to_string(),
            "validation".to_string(),
            "domain".to_string(),
        ],
    };

    assert!(tags.validate().is_ok());
}

#[test]
fn test_each_length_violation() {
    #[derive(Debug, Validate)]
    struct TagList {
        #[validate(each(length(min = 3, max = 10)))]
        tags: Vec<String>,
    }

    let tags = TagList {
        tags: vec![
            "valid".to_string(),
            "x".to_string(),
            "toolongstring".to_string(),
        ],
    };

    let result = tags.validate();
    assert!(result.is_err());

    let err = result.unwrap_err();
    assert_eq!(err.violations.len(), 2);

    assert_eq!(err.violations[0].path.to_string(), "tags[1]");
    assert_eq!(err.violations[0].code, "min_length");

    assert_eq!(err.violations[1].path.to_string(), "tags[2]");
    assert_eq!(err.violations[1].code, "max_length");
}

#[test]
fn test_each_with_rich_syntax() {
    #[derive(Debug, Validate)]
    struct UrlList {
        #[validate(each(url))]
        urls: Vec<String>,
    }

    let urls = UrlList {
        urls: vec![
            "https://example.com".to_string(),
            "https://rust-lang.org".to_string(),
        ],
    };

    assert!(urls.validate().is_ok());
}

#[test]
fn test_each_email() {
    #[derive(Debug, Validate)]
    struct EmailList {
        #[validate(each(email))]
        emails: Vec<String>,
    }

    let emails = EmailList {
        emails: vec![
            "alice@example.com".to_string(),
            "bob@example.com".to_string(),
        ],
    };

    assert!(emails.validate().is_ok());

    let invalid = EmailList {
        emails: vec!["alice@example.com".to_string(), "not-an-email".to_string()],
    };

    let result = invalid.validate();
    assert!(result.is_err());
    let err = result.unwrap_err();
    assert_eq!(err.violations.len(), 1);
    assert_eq!(err.violations[0].path.to_string(), "emails[1]");
}