use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Error, Fields, Lit, Meta};
mod migration_chain;
#[proc_macro]
pub fn migration_chain(input: TokenStream) -> TokenStream {
migration_chain::migration_chain(input)
}
#[proc_macro_derive(Linkable)]
pub fn derive_linkable(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let expanded = quote! {
impl ::aimdb_data_contracts::Linkable for #name {
fn from_bytes(
data: &[u8],
) -> Result<Self, ::aimdb_data_contracts::__private::alloc::string::String> {
::aimdb_data_contracts::__private::serde_json::from_slice(data).map_err(|e| {
::aimdb_data_contracts::__private::alloc::string::ToString::to_string(&e)
})
}
fn to_bytes(
&self,
) -> Result<
::aimdb_data_contracts::__private::alloc::vec::Vec<u8>,
::aimdb_data_contracts::__private::alloc::string::String,
> {
::aimdb_data_contracts::__private::serde_json::to_vec(self).map_err(|e| {
::aimdb_data_contracts::__private::alloc::string::ToString::to_string(&e)
})
}
}
};
expanded.into()
}
#[proc_macro_derive(RecordKey, attributes(key, key_prefix, link_address))]
pub fn derive_record_key(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match derive_record_key_impl(input) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
fn derive_record_key_impl(input: DeriveInput) -> Result<proc_macro2::TokenStream, Error> {
let name = &input.ident;
let data_enum = match &input.data {
Data::Enum(data) => data,
_ => {
return Err(Error::new_spanned(
&input,
"RecordKey can only be derived for enums",
));
}
};
let prefix = get_key_prefix(&input.attrs)?;
struct VariantData {
name: syn::Ident,
key: String,
link: Option<String>,
}
let mut variants = Vec::new();
let mut seen_keys = std::collections::HashSet::new();
for variant in &data_enum.variants {
match &variant.fields {
Fields::Unit => {}
_ => {
return Err(Error::new_spanned(
variant,
"RecordKey variants must be unit variants (no fields)",
));
}
}
let variant_name = &variant.ident;
let key = get_variant_key(&variant.attrs, variant_name)?;
let link = get_optional_attr(&variant.attrs, "link_address")?;
let full_key = if let Some(ref p) = prefix {
format!("{}{}", p, key)
} else {
key
};
if !seen_keys.insert(full_key.clone()) {
return Err(Error::new_spanned(
variant,
format!(
"Duplicate key \"{}\" - each variant must have a unique key",
full_key
),
));
}
variants.push(VariantData {
name: variant_name.clone(),
key: full_key,
link,
});
}
let has_link = variants.iter().any(|v| v.link.is_some());
let as_str_arms = variants.iter().map(|v| {
let variant_name = &v.name;
let key = &v.key;
quote! {
Self::#variant_name => #key
}
});
let link_impl = if has_link {
let arms = variants.iter().map(|v| {
let variant_name = &v.name;
match &v.link {
Some(url) => quote! { Self::#variant_name => Some(#url) },
None => quote! { Self::#variant_name => None },
}
});
quote! {
#[inline]
fn link_address(&self) -> Option<&str> {
match self {
#(#arms),*
}
}
}
} else {
quote! {}
};
let expanded = quote! {
impl aimdb_core::RecordKey for #name {
#[inline]
fn as_str(&self) -> &'static str {
match self {
#(#as_str_arms),*
}
}
#link_impl
}
impl core::borrow::Borrow<str> for #name {
#[inline]
fn borrow(&self) -> &str {
<Self as aimdb_core::RecordKey>::as_str(self)
}
}
impl core::hash::Hash for #name {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
<Self as aimdb_core::RecordKey>::as_str(self).hash(state);
}
}
};
Ok(expanded)
}
fn get_key_prefix(attrs: &[syn::Attribute]) -> Result<Option<String>, Error> {
get_optional_attr(attrs, "key_prefix")
}
fn get_optional_attr(attrs: &[syn::Attribute], attr_name: &str) -> Result<Option<String>, Error> {
for attr in attrs {
if attr.path().is_ident(attr_name) {
let meta = &attr.meta;
if let Meta::NameValue(nv) = meta {
if let syn::Expr::Lit(expr_lit) = &nv.value {
if let Lit::Str(lit_str) = &expr_lit.lit {
return Ok(Some(lit_str.value()));
}
}
}
return Err(Error::new_spanned(
attr,
format!("Expected #[{} = \"...\"]", attr_name),
));
}
}
Ok(None)
}
fn get_variant_key(attrs: &[syn::Attribute], variant_name: &syn::Ident) -> Result<String, Error> {
for attr in attrs {
if attr.path().is_ident("key") {
let meta = &attr.meta;
if let Meta::NameValue(nv) = meta {
if let syn::Expr::Lit(expr_lit) = &nv.value {
if let Lit::Str(lit_str) = &expr_lit.lit {
return Ok(lit_str.value());
}
}
}
return Err(Error::new_spanned(attr, "Expected #[key = \"...\"]"));
}
}
Err(Error::new_spanned(
variant_name,
format!(
"Missing #[key = \"...\"] attribute on variant `{}`",
variant_name
),
))
}
#[cfg(test)]
mod tests {
}