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<(), 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}