fletch-orm-macros 1.0.0

Procedural macros for the Fletch ORM database toolkit
Documentation
use fletch_orm::{ColumnType, Entity as EntityTrait, Value};
use fletch_orm_macros::Entity;

#[derive(Entity)]
struct User {
    #[fletch(id)]
    id: i64,
    name: String,
    active: bool,
}

#[test]
fn default_table_name_is_snake_case_plural() {
    assert_eq!(User::table_name(), "users");
}

#[test]
fn id_column_matches_marked_field() {
    assert_eq!(User::id_column(), "id");
}

#[test]
fn columns_include_all_non_skipped_fields() {
    let cols = User::columns();
    assert_eq!(cols.len(), 3);
    assert_eq!(cols[0].name(), "id");
    assert_eq!(cols[0].column_type(), ColumnType::Integer);
    assert_eq!(cols[1].name(), "name");
    assert_eq!(cols[1].column_type(), ColumnType::Text);
    assert_eq!(cols[2].name(), "active");
    assert_eq!(cols[2].column_type(), ColumnType::Boolean);
}

#[test]
fn id_returns_correct_value() {
    let u = User {
        id: 42,
        name: "Alice".into(),
        active: true,
    };
    assert_eq!(u.id(), 42);
}

#[test]
fn values_returns_all_field_values() {
    let u = User {
        id: 1,
        name: "Bob".into(),
        active: false,
    };
    let vals = u.values();
    assert_eq!(vals.len(), 3);
    assert_eq!(vals[0], Value::I64(1));
    assert_eq!(vals[1], Value::Text("Bob".into()));
    assert_eq!(vals[2], Value::Bool(false));
}

// --- Table name override ---

#[derive(Entity)]
#[fletch(table = "people")]
struct Person {
    #[fletch(id)]
    id: i64,
    full_name: String,
}

#[test]
fn table_name_override() {
    assert_eq!(Person::table_name(), "people");
}

// --- Skip attribute ---

#[derive(Entity)]
#[expect(dead_code, reason = "struct exists to test #[fletch(skip)] attribute")]
struct Post {
    #[fletch(id)]
    id: i64,
    title: String,
    #[fletch(skip)]
    cached_slug: String,
}

#[test]
fn skip_excludes_field_from_columns() {
    let cols = Post::columns();
    assert_eq!(cols.len(), 2);
    assert_eq!(cols[0].name(), "id");
    assert_eq!(cols[1].name(), "title");
}

#[test]
fn skip_excludes_field_from_values() {
    let p = Post {
        id: 1,
        title: "Hello".into(),
        cached_slug: "hello".into(),
    };
    let vals = p.values();
    assert_eq!(vals.len(), 2);
}

// --- Rename attribute ---

#[derive(Entity)]
struct Comment {
    #[fletch(id, rename = "comment_id")]
    id: i64,
    #[fletch(rename = "body_text")]
    body: String,
}

#[test]
fn rename_overrides_column_names() {
    let cols = Comment::columns();
    assert_eq!(cols[0].name(), "comment_id");
    assert_eq!(cols[1].name(), "body_text");
}

#[test]
fn rename_overrides_id_column() {
    assert_eq!(Comment::id_column(), "comment_id");
}

// --- Optional fields ---

#[derive(Entity)]
struct Profile {
    #[fletch(id)]
    id: i64,
    bio: Option<String>,
    age: Option<i32>,
}

#[test]
fn optional_fields_produce_correct_column_types() {
    let cols = Profile::columns();
    assert_eq!(cols.len(), 3);
    // Optional fields still map to Text/Integer column types
    assert_eq!(cols[1].name(), "bio");
    assert_eq!(cols[1].column_type(), ColumnType::Text);
    assert_eq!(cols[2].name(), "age");
    assert_eq!(cols[2].column_type(), ColumnType::Integer);
}

#[test]
fn optional_none_produces_null_value() {
    let p = Profile {
        id: 1,
        bio: None,
        age: None,
    };
    let vals = p.values();
    assert_eq!(vals[1], Value::Null);
    assert_eq!(vals[2], Value::Null);
}

#[test]
fn optional_some_produces_inner_value() {
    let p = Profile {
        id: 1,
        bio: Some("Hello".into()),
        age: Some(30),
    };
    let vals = p.values();
    assert_eq!(vals[1], Value::Text("Hello".into()));
    assert_eq!(vals[2], Value::I32(30));
}

// --- Pluralization cases ---

#[derive(Entity)]
struct Category {
    #[fletch(id)]
    id: i64,
}

#[test]
fn pluralizes_y_ending() {
    assert_eq!(Category::table_name(), "categories");
}

#[derive(Entity)]
struct Address {
    #[fletch(id)]
    id: i64,
}

#[test]
fn pluralizes_s_ending() {
    assert_eq!(Address::table_name(), "addresses");
}