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<(), sqlx::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<(), sqlx::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        quote! {
82            async fn delete(&self, conn: &Connection) -> Result<(), sqlx::Error> {
83                let placeholder = rusql_alchemy::PLACEHOLDER.to_string();
84                sqlx::query(&#query.replace("?", &placeholder))
85                    .bind(self.#the_primary_key.clone())
86                    .execute(conn)
87                    .await?;
88                Ok(())
89            }
90        }
91    };
92
93    let expanded = quote! {
94        #[async_trait]
95        impl Model for #name {
96            const NAME: &'static str = stringify!(#name);
97            #schema
98            #primary_key
99            #save
100            #update
101            #delete
102        }
103
104        impl Default for #name {
105            fn default() -> Self {
106                Self {
107                    #(#default_fields),*
108                }
109            }
110        }
111
112        rusql_alchemy::prelude::inventory::submit! {
113            MigrationRegistrar {
114                migrate_fn: #name::migrate
115            }
116        }
117    };
118
119    expanded.into()
120}