typeshift 0.5.1

Zod-like parse, validation, and JSON Schema flow for Rust types
Documentation
use typeshift::typeshift;

#[typeshift]
struct User {
    #[validate(length(min = 3))]
    name: String,
}

#[typeshift]
enum Role {
    Admin,
    Member,
    Guest,
}

#[typeshift]
struct Envelope<T> {
    payload: T,
}

#[typeshift]
#[serde(tag = "kind", content = "data")]
enum Event {
    #[serde(rename = "user.created")]
    UserCreated {
        #[serde(rename = "user_id")]
        user_id: u64,
    },
}

#[typeshift]
enum CheckedEvent {
    UserName(#[validate(length(min = 3))] String),
    Nothing,
}

#[test]
fn parse_json_and_validate() {
    let user: User = match typeshift::parse_str(r#"{"name":"Ada"}"#) {
        Ok(user) => user,
        Err(err) => panic!("valid user json should parse: {err}"),
    };
    assert_eq!(user.name, "Ada");

    let err = match typeshift::parse_str::<User>(r#"{"name":"Al"}"#) {
        Ok(_) => panic!("short name should fail validation"),
        Err(err) => err,
    };
    match err {
        typeshift::TypeShiftError::Validation(_) => {}
        typeshift::TypeShiftError::Json(_) => panic!("expected validation error"),
    }
}

#[test]
fn to_json_and_schema_work() {
    let user = User {
        name: "Ada".to_string(),
    };
    let json = match typeshift::to_json(&user) {
        Ok(json) => json,
        Err(err) => panic!("serialization should succeed: {err}"),
    };
    assert_eq!(json, r#"{"name":"Ada"}"#);

    let schema = typeshift::schema_json::<User>();
    assert_eq!(schema["type"], "object");
}

#[test]
fn enum_support_works() {
    let role: Role = match typeshift::parse_str(r#""Admin""#) {
        Ok(role) => role,
        Err(err) => panic!("enum json should parse: {err}"),
    };

    match role {
        Role::Admin => {}
        Role::Member | Role::Guest => panic!("expected admin variant"),
    }

    let schema = typeshift::schema_json::<Role>();
    assert_eq!(schema["type"], "string");
}

#[test]
fn generic_support_works() {
    let env: Envelope<User> = match typeshift::parse_str(r#"{"payload":{"name":"Ada"}}"#) {
        Ok(value) => value,
        Err(err) => panic!("generic payload should parse: {err}"),
    };
    assert_eq!(env.payload.name, "Ada");

    let schema = typeshift::schema_json::<Envelope<User>>();
    assert_eq!(schema["type"], "object");
}

#[test]
fn enum_serde_and_schema_attrs_work() {
    let event = Event::UserCreated { user_id: 7 };
    let json = match typeshift::to_json(&event) {
        Ok(json) => json,
        Err(err) => panic!("enum serialization should succeed: {err}"),
    };
    assert_eq!(json, r#"{"kind":"user.created","data":{"user_id":7}}"#);

    let parsed: Event = match typeshift::parse_str(&json) {
        Ok(value) => value,
        Err(err) => panic!("enum deserialization should succeed: {err}"),
    };

    match parsed {
        Event::UserCreated { user_id } => assert_eq!(user_id, 7),
    }

    let schema = typeshift::schema_json::<Event>();
    assert!(schema["oneOf"].is_array());
}

#[test]
fn enum_variant_field_validation_works() {
    let valid: CheckedEvent = match typeshift::parse_str(r#"{"UserName":"Ada"}"#) {
        Ok(value) => value,
        Err(err) => panic!("valid enum payload should parse: {err}"),
    };

    match valid {
        CheckedEvent::UserName(name) => assert_eq!(name, "Ada"),
        CheckedEvent::Nothing => panic!("expected UserName variant"),
    }

    let err = match typeshift::parse_str::<CheckedEvent>(r#"{"UserName":"Al"}"#) {
        Ok(_) => panic!("short enum value should fail validation"),
        Err(err) => err,
    };

    match err {
        typeshift::TypeShiftError::Validation(_) => {}
        typeshift::TypeShiftError::Json(_) => panic!("expected validation error"),
    }
}