rusql_alchemy_derive/
lib.rs

1use codegen::{process_fields, ModelData};
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{parse_macro_input, Data, DeriveInput, Fields};
5
6mod codegen;
7
8#[proc_macro_derive(Model, attributes(field))]
9pub fn model_derive(input: TokenStream) -> TokenStream {
10    let input = parse_macro_input!(input as DeriveInput);
11    let name = input.ident;
12
13    let fields = match input.data {
14        Data::Struct(ref data) => match data.fields {
15            Fields::Named(ref fields) => &fields.named,
16            _ => panic!("Model derive macro only supports structs with named fields"),
17        },
18        _ => panic!("Model derive macro only supports structs"),
19    };
20
21    let ModelData {
22        schema_fields,
23        create_args,
24        update_args,
25        the_primary_key,
26        default_fields,
27    } = process_fields(fields);
28
29    let primary_key = {
30        let pk = the_primary_key.to_string();
31        quote! {
32            const PK: &'static str = #pk;
33        }
34    };
35
36    let schema = {
37        let fields = schema_fields
38            .iter()
39            .map(|f| f.to_string())
40            .collect::<Vec<_>>()
41            .join(", ");
42
43        let schema = format!("create table if not exists {name} ({fields});").replace('"', "");
44
45        quote! {
46            const SCHEMA: &'static str = #schema;
47        }
48    };
49
50    let save = {
51        quote! {
52            async fn save(&self, conn: &Connection) -> Result<(), rusql_alchemy::Error> {
53                Self::create(
54                    kwargs!(
55                        #(#create_args = self.#create_args),*
56                    ),
57                    conn,
58                )
59                .await
60            }
61        }
62    };
63
64    let update = {
65        quote! {
66            async fn update(&self, conn: &Connection) -> Result<(), rusql_alchemy::Error> {
67                Self::set(
68                    self.#the_primary_key.clone(),
69                    kwargs!(
70                        #(#update_args = self.#update_args),*
71                    ),
72                    conn,
73                )
74                .await
75            }
76        }
77    };
78
79    let delete = {
80        let query = format!("delete from {name} where {the_primary_key}=?1;");
81        #[cfg(not(feature = "turso"))]
82        quote! {
83            async fn delete(&self, conn: &Connection) -> Result<(), rusql_alchemy::Error> {
84                sqlx::query(&#query.replace("?", rusql_alchemy::PLACEHOLDER))
85                    .bind(self.#the_primary_key.clone())
86                    .execute(conn)
87                    .await?;
88                Ok(())
89            }
90        }
91
92        #[cfg(feature = "turso")]
93        quote! {
94            async fn delete(&self, conn: &Connection) -> Result<(), rusql_alchemy::Error> {
95                conn.execute(&#query.replace("?", rusql_alchemy::PLACEHOLDER), rusql_alchemy::params![self.#the_primary_key.clone()]).await?;
96                Ok(())
97            }
98        }
99    };
100
101    let expanded = quote! {
102        #[async_trait]
103        impl Model for #name {
104            const NAME: &'static str = stringify!(#name);
105            #schema
106            #primary_key
107            #save
108            #update
109            #delete
110        }
111
112        impl Default for #name {
113            fn default() -> Self {
114                Self {
115                    #(#default_fields),*
116                }
117            }
118        }
119
120        rusql_alchemy::prelude::inventory::submit! {
121            MigrationRegistrar {
122                migrate_fn: #name::migrate
123            }
124        }
125    };
126
127    expanded.into()
128}