sqlx-query-as-builder 0.1.2

Builder pattern macros for SQLx query_as! - populate builders instead of constructing structs directly
Documentation
use bon::Builder;
use sqlx::sqlite::SqlitePool;
use sqlx_query_as_builder::{query_as_builder, query_file_as_builder};

#[derive(Debug, PartialEq, Builder)]
struct User {
    id: i64,
    name: String,
    email: String,
    age: Option<i64>,
    role: Option<String>,
    #[builder(default = false)]
    verified: bool,
}

#[sqlx::test(fixtures("users"))]
async fn test_query_as_builder_fetch_one(pool: SqlitePool) -> sqlx::Result<()> {
    let user_id = 1_i64;
    let user = query_as_builder!(
        User::builder(),
        r#"SELECT id, name, email, age as "maybe_age" FROM users WHERE id = ?"#,
        user_id
    )
    .fetch_one(&pool)
    .await?
    .role("admin".to_string())
    .verified(true)
    .build();

    assert_eq!(user.id, 1);
    assert_eq!(user.name, "Alice");
    assert_eq!(user.email, "alice@example.com");
    assert_eq!(user.age, Some(30));
    assert_eq!(user.role, Some("admin".to_string()));
    assert!(user.verified);

    Ok(())
}

#[sqlx::test(fixtures("users"))]
async fn test_query_as_builder_fetch_all(pool: SqlitePool) -> sqlx::Result<()> {
    let users: Vec<User> = query_as_builder!(
        User::builder(),
        r#"SELECT id, name, email, age as "maybe_age" FROM users"#
    )
    .fetch_all(&pool)
    .await?
    .into_iter()
    .map(|builder| builder.role("user".to_string()).verified(false).build())
    .collect();

    assert_eq!(users.len(), 3);

    assert_eq!(users[0].name, "Alice");
    assert_eq!(users[0].role, Some("user".to_string()));
    assert!(!users[0].verified);

    assert_eq!(users[1].name, "Bob");
    assert_eq!(users[1].age, None);
    assert_eq!(users[1].role, Some("user".to_string()));

    assert_eq!(users[2].name, "Charlie");
    assert_eq!(users[2].age, Some(25));

    Ok(())
}

#[sqlx::test(fixtures("users"))]
async fn test_query_as_builder_with_filter(pool: SqlitePool) -> sqlx::Result<()> {
    let min_age = 25_i64;
    let users: Vec<User> = query_as_builder!(
        User::builder(),
        r#"SELECT id, name, email, age as "maybe_age" FROM users WHERE age >= ?"#,
        min_age
    )
    .fetch_all(&pool)
    .await?
    .into_iter()
    .map(|builder| builder.verified(true).build())
    .collect();

    assert_eq!(users.len(), 2);
    assert!(users.iter().all(|u| u.verified));
    assert!(users.iter().all(|u| u.age.is_some()));
    assert!(users.iter().all(|u| u.age.unwrap() >= 25));

    Ok(())
}

#[sqlx::test(fixtures("users"))]
async fn test_query_as_builder_partial_fields(pool: SqlitePool) -> sqlx::Result<()> {
    let user_id = 2_i64;
    let user = query_as_builder!(
        User::builder(),
        r#"SELECT id, name, email, age as "maybe_age" FROM users WHERE id = ?"#,
        user_id
    )
    .fetch_one(&pool)
    .await?
    .build();

    assert_eq!(user.id, 2);
    assert_eq!(user.name, "Bob");
    assert_eq!(user.age, None);
    assert_eq!(user.role, None);
    assert!(!user.verified);

    Ok(())
}

#[sqlx::test(fixtures("users"))]
async fn test_query_file_as_builder(pool: SqlitePool) -> sqlx::Result<()> {
    let user_id = 1_i64;
    let user = query_file_as_builder!(User::builder(), "tests/queries/get_user.sql", user_id)
        .fetch_one(&pool)
        .await?
        .role("moderator".to_string())
        .verified(true)
        .build();

    assert_eq!(user.id, 1);
    assert_eq!(user.name, "Alice");
    assert_eq!(user.role, Some("moderator".to_string()));
    assert!(user.verified);

    Ok(())
}