rusql_alchemy_derive/
lib.rs1use 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}