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 126 127 128 129 130 131 132 133 134 135 136 137
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! {
if let Some(id) = self.db_id.clone() {
return Some(id.into());
} else {
return None;
}
});
}
}
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.into())
});
} else {
let i = counter;
counter += 1;
return Some(quote! {
#name: element.values.get(#i).ok_or(agdb::DbError::from("Not enough keys"))?.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::QueryId> {
#db_id
}
fn db_keys() -> Vec<agdb::DbValue> {
vec![#(#db_keys.into()),*]
}
fn from_db_element(element: &agdb::DbElement) -> std::result::Result<Self, agdb::DbError> {
Ok(Self {
#(#from_db_element),*
})
}
fn to_db_values(&self) -> Vec<agdb::DbKeyValue> {
vec![#(#db_values),*]
}
}
impl TryFrom<&agdb::DbElement> for #name {
type Error = agdb::DbError;
fn try_from(value: &agdb::DbElement) -> std::result::Result<Self, Self::Error> {
use agdb::DbUserValue;
#name::from_db_element(value)
}
}
impl TryFrom<agdb::QueryResult> for #name {
type Error = agdb::DbError;
fn try_from(value: agdb::QueryResult) -> std::result::Result<Self, Self::Error> {
use agdb::DbUserValue;
value
.elements
.first()
.ok_or(Self::Error::from("No element found"))?
.try_into()
}
}
};
tokens.into()
}