diff --git a/persmgr_derive/Cargo.lock b/persmgr_derive/Cargo.lock new file mode 100644 index 0000000..82a9bd6 --- /dev/null +++ b/persmgr_derive/Cargo.lock @@ -0,0 +1,101 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "persmgr_derive" +version = "0.1.0" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" diff --git a/persmgr_derive/Cargo.toml b/persmgr_derive/Cargo.toml new file mode 100644 index 0000000..4def38a --- /dev/null +++ b/persmgr_derive/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "persmgr_derive" +version = "0.1.0" +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +quote = "1" +proc-macro2 = "1" +syn = { version = "1", features = ["full"] } +darling = "0.13" diff --git a/persmgr_derive/src/lib.rs b/persmgr_derive/src/lib.rs new file mode 100644 index 0000000..7b6c9c7 --- /dev/null +++ b/persmgr_derive/src/lib.rs @@ -0,0 +1,109 @@ +use darling::FromDeriveInput; +use proc_macro::{self, TokenStream}; +use quote::quote; +use syn::{Data, DeriveInput, Expr, Fields, Type, parse_macro_input}; + +#[derive(FromDeriveInput, Default)] +#[darling(default, attributes(meta))] +struct Opts { + table: String, + key_t: Option, +} + +#[proc_macro_derive(TableMeta, attributes(meta))] +pub fn derive_table_meta(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input); + let opts = Opts::from_derive_input(&input) + .expect("Expected attribute #[meta(table=\"users\", key_t=i32)]"); + + let mut struct_items = Vec::new(); + + if let Data::Struct(data) = &input.data { + if let Fields::Named(fields) = &data.fields { + for field in &fields.named { + let ident = field.ident.as_ref().unwrap(); + let s = ident.to_string(); + if s.as_str() == "id" || s.as_str() == "created_at" { + continue; + } + let mut should_deref = false; + if let Type::Path(tp) = &field.ty { + if tp.path.segments.first().unwrap().ident == "ForeignKey" { + should_deref = true; + } + } + + struct_items.push((ident, should_deref)); + } + } + } + + let ident = input.ident; + let table_name = opts.table; + let key_t = opts + .key_t + .unwrap_or_else(|| syn::parse_str::("i64").unwrap()); + + // dbg!(&table_name); + //dbg!(&key_t); + + let mut query = format!("UPDATE {table_name} SET "); + let mut query_args = quote!(self.id); + let mut i = 2; + let mut modified_update = false; + for (item, should_deref) in &struct_items { + let s = item.to_string(); + //dbg!(&s); + + if s.as_str() == "modified_at" { + modified_update = true; + } + + if i != 2 { + query.push_str(", "); + } + query.push_str(s.as_str()); + query.push_str(&format!(" = ${i} ")); + i += 1; + + let deref = if *should_deref { quote!(*) } else { quote!() }; + + query_args = quote! { + #query_args, #deref self.#item + }; + } + query.push_str("WHERE id = $1 RETURNING *"); + + let mut modified_at = quote! {}; + + if modified_update { + modified_at = quote! { + self.modified_at = time::OffsetDateTime::now_utc().unix_timestamp(); + } + } + + let output = quote! { + impl TableMeta for #ident { + type PrimaryKey = #key_t; + const TABLE: &'static str = #table_name; + } + + impl #ident { + pub async fn update(&mut self, pool: &crate::db::CurrPool) -> anyhow::Result { + #modified_at + let session = sqlx::query_as!( + #ident, + #query, + #query_args + ) + .fetch_one(pool) + .await?; + Ok(session) + } + } + }; + + eprintln!("{}", output.to_string()); + + output.into() +}