Skip to main content

flowdb_derive/
lib.rs

1//! FlowDB derive macros.
2//!
3//! Provides `#[derive(ObjectStore)]` for generating a [`StoreDef`] from a
4//! struct definition with field-level index annotations.
5
6use proc_macro::TokenStream;
7use quote::quote;
8use syn::{parse_macro_input, DeriveInput, Lit};
9
10/// Derive the [`ObjectStore`] trait for a struct.
11///
12/// Generates an implementation of `ObjectStore::store_def()` based on the
13/// struct's `#[store(...)]` container attribute and `#[index(...)]` field
14/// attributes.
15///
16/// # Container Attribute
17///
18/// `#[store(key_path = "...")]` — required. Specifies the primary key field
19/// path. Options:
20/// - `name = "..."` — override the store name (defaults to the struct name)
21/// - `auto_increment` — enable auto-increment primary keys
22///
23/// ```ignore
24/// #[derive(ObjectStore)]
25/// #[store(key_path = "id", auto_increment)]
26/// struct Log { ... }
27/// ```
28///
29/// # Field Attributes
30///
31/// `#[index(...)]` on a field creates a secondary index. Accepted options:
32/// - `unique` — create a unique index
33/// - `name = "..."` — custom index name (defaults to the field name)
34///
35/// ```ignore
36/// #[derive(ObjectStore)]
37/// #[store(key_path = "id")]
38/// struct User {
39///     #[index(unique)]
40///     email: String,
41///     #[index]
42///     age: u32,
43///     city: String,          // no index
44/// }
45/// ```
46#[proc_macro_derive(ObjectStore, attributes(store, index))]
47pub fn derive_object_store(input: TokenStream) -> TokenStream {
48    let input = parse_macro_input!(input as DeriveInput);
49    let name = &input.ident;
50    let store_name = name.to_string();
51
52    let mut key_path: Option<String> = None;
53    let mut auto_increment = false;
54    let mut store_name = store_name; // override via #[store(name = "...")]
55
56    for attr in &input.attrs {
57        if attr.path().is_ident("store") {
58            let _ = attr.parse_nested_meta(|meta| {
59                if meta.path.is_ident("key_path") {
60                    let value: Lit = meta.value()?.parse()?;
61                    if let Lit::Str(s) = value {
62                        key_path = Some(s.value());
63                    }
64                } else if meta.path.is_ident("auto_increment") {
65                    auto_increment = true;
66                } else if meta.path.is_ident("name") {
67                    let value: Lit = meta.value()?.parse()?;
68                    if let Lit::Str(s) = value {
69                        store_name = s.value();
70                    }
71                }
72                Ok(())
73            });
74        }
75    }
76
77    let key_path = key_path.expect("#[store(key_path = \"...\")] is required");
78
79    let mut indexes = Vec::new();
80
81    if let syn::Data::Struct(data) = &input.data {
82        for field in &data.fields {
83            let field_name = field
84                .ident
85                .as_ref()
86                .map(|i| i.to_string())
87                .expect("named fields only");
88
89            for attr in &field.attrs {
90                if attr.path().is_ident("index") {
91                    let mut unique = false;
92                    let mut index_name = field_name.clone();
93
94                    let _ = attr.parse_nested_meta(|meta| {
95                        if meta.path.is_ident("unique") {
96                            unique = true;
97                        } else if meta.path.is_ident("name") {
98                            let value: Lit = meta.value()?.parse()?;
99                            if let Lit::Str(s) = value {
100                                index_name = s.value();
101                            }
102                        }
103                        Ok(())
104                    });
105
106                    indexes.push((index_name, field_name.clone(), unique));
107                }
108            }
109        }
110    }
111
112    let index_calls: Vec<_> = indexes
113        .iter()
114        .map(|(idx_name, field_name, unique)| {
115            let u = *unique;
116            quote! {
117                .with_index(#idx_name, &[#field_name], #u)
118            }
119        })
120        .collect();
121
122    let auto_inc = if auto_increment {
123        quote! { .with_auto_increment() }
124    } else {
125        quote! {}
126    };
127
128    let expanded = quote! {
129        impl flowdb::jsondb::ObjectStore for #name {
130            fn store_def() -> flowdb::jsondb::StoreSchema {
131                flowdb::jsondb::StoreSchema::new(#store_name, #key_path)
132                    #(#index_calls)*
133                    #auto_inc
134            }
135        }
136    };
137
138    expanded.into()
139}