1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
use proc_macro::TokenStream;
use quote::quote;
use syn::parse_macro_input;
use syn::DeriveInput;
/// The derive macro to add `agdb` compatibility
/// to user defined types. It implements [`agdb::DbUserValue`]
/// for the type automatically to allow your type to be read and
/// stored from/to the database. If your type contains a field
/// `db_id: Option<agdb::DbId>` it will be treated specially
/// and will additionally allow shorthand inserts/updates
/// of the elements directly.
///
/// # Examples
///
/// ```ignore
/// #[derive(agdb_derive::UserValue)]
/// struct MyValue {
/// db_id: Option<agdb::DbId>, //this field is useful but not mandatory
/// num_value: i64,
/// string_value: String,
/// vec_value: Vec<u64>,
/// }
/// ```
#[proc_macro_derive(UserValue)]
pub fn db_user_value_derive(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let name = input.ident;
let syn::Data::Struct(data) = input.data else {
unimplemented!()
};
let db_id = data
.fields
.iter()
.find_map(|f| {
if let Some(name) = &f.ident {
if name == "db_id" {
return Some(quote! {
self.db_id
});
}
}
None
})
.unwrap_or(quote! {
None
});
let mut counter: usize = 0;
let from_db_element = data.fields.iter().filter_map(|f| {
if let Some(name) = &f.ident {
if name == "db_id" {
return Some(quote! {
#name: Some(element.id)
});
} else {
let i = counter;
counter += 1;
return Some(quote! {
#name: element.values[#i].value.clone().try_into()?
});
}
}
None
});
let db_values = data.fields.iter().filter_map(|f| {
if let Some(name) = &f.ident {
if name != "db_id" {
let key = name.to_string();
return Some(quote! {
(#key, self.#name.clone()).into()
});
}
}
None
});
let db_keys = data.fields.iter().filter_map(|f| {
if let Some(name) = &f.ident {
if name != "db_id" {
return Some(name.to_string());
}
}
None
});
let tokens = quote! {
impl agdb::DbUserValue for #name {
fn db_id(&self) -> Option<agdb::DbId> {
#db_id
}
fn db_keys() -> Vec<agdb::DbKey> {
vec![#(#db_keys.into()),*]
}
fn from_db_element(element: &agdb::DbElement) -> Result<Self, agdb::DbError> {
Ok(Self {
#(#from_db_element),*
})
}
fn to_db_values(&self) -> Vec<agdb::DbKeyValue> {
vec![#(#db_values),*]
}
}
impl TryFrom<agdb::QueryResult> for #name {
type Error = agdb::DbError;
fn try_from(value: agdb::QueryResult) -> Result<Self, Self::Error> {
use agdb::DbUserValue;
#name::from_db_element(
value
.elements
.get(0)
.ok_or(Self::Error::from("No element found"))?
)
}
}
};
tokens.into()
}