Implement TableMeta proc_macro

This commit is contained in:
Gvidas Juknevičius 2025-09-15 21:40:08 +03:00
parent 0d3a89caeb
commit 771be89260
Signed by: MCorange
GPG Key ID: 5BE6B533CB76FE86
3 changed files with 223 additions and 0 deletions

101
persmgr_derive/Cargo.lock generated Normal file
View File

@ -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"

13
persmgr_derive/Cargo.toml Normal file
View File

@ -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"

109
persmgr_derive/src/lib.rs Normal file
View File

@ -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<Expr>,
}
#[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::<Expr>("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<Self> {
#modified_at
let session = sqlx::query_as!(
#ident,
#query,
#query_args
)
.fetch_one(pool)
.await?;
Ok(session)
}
}
};
eprintln!("{}", output.to_string());
output.into()
}