feat(macros): add #[derive(Integrable)] proc-macro
This commit is contained in:
62
server/crates/arbiter-macros/src/integrable.rs
Normal file
62
server/crates/arbiter-macros/src/integrable.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use crate::utils::INTEGRABLE_TRAIT_PATH;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{DeriveInput, LitInt, LitStr};
|
||||
|
||||
struct IntegrableAttr {
|
||||
kind: String,
|
||||
version: i32,
|
||||
}
|
||||
|
||||
impl IntegrableAttr {
|
||||
fn from_attrs(attrs: &[syn::Attribute], span: proc_macro2::Span) -> Result<Self, syn::Error> {
|
||||
let mut kind: Option<String> = None;
|
||||
let mut version: i32 = 1;
|
||||
|
||||
for attr in attrs {
|
||||
if !attr.path().is_ident("integrable") {
|
||||
continue;
|
||||
}
|
||||
attr.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("kind") {
|
||||
let lit: LitStr = meta.value()?.parse()?;
|
||||
kind = Some(lit.value());
|
||||
} else if meta.path.is_ident("version") {
|
||||
let lit: LitInt = meta.value()?.parse()?;
|
||||
version = lit.base10_parse()?;
|
||||
} else {
|
||||
return Err(meta.error("unknown key; expected `kind` or `version`"));
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
|
||||
let kind = kind.ok_or_else(|| {
|
||||
syn::Error::new(span, "#[integrable(kind = \"...\")] is required")
|
||||
})?;
|
||||
|
||||
Ok(Self { kind, version })
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn derive(input: &DeriveInput) -> TokenStream {
|
||||
let integrable_trait = INTEGRABLE_TRAIT_PATH.to_path();
|
||||
let ident = &input.ident;
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
|
||||
let attr = match IntegrableAttr::from_attrs(&input.attrs, proc_macro2::Span::call_site()) {
|
||||
Ok(a) => a,
|
||||
Err(e) => return e.to_compile_error(),
|
||||
};
|
||||
|
||||
let kind = attr.kind;
|
||||
let version = attr.version;
|
||||
|
||||
quote! {
|
||||
#[automatically_derived]
|
||||
impl #impl_generics #integrable_trait for #ident #ty_generics #where_clause {
|
||||
const KIND: &'static str = #kind;
|
||||
const VERSION: i32 = #version;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use syn::{DeriveInput, parse_macro_input};
|
||||
|
||||
mod hashable;
|
||||
mod integrable;
|
||||
mod utils;
|
||||
|
||||
#[proc_macro_derive(Hashable)]
|
||||
@@ -8,3 +9,9 @@ pub fn derive_hashable(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
hashable::derive(&input).into()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Integrable, attributes(integrable))]
|
||||
pub fn derive_integrable(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
integrable::derive(&input).into()
|
||||
}
|
||||
|
||||
@@ -22,3 +22,4 @@ macro_rules! ensure_path {
|
||||
|
||||
ensure_path!(::arbiter_crypto::hashing::Hashable as HASHABLE_TRAIT_PATH);
|
||||
ensure_path!(::arbiter_crypto::hashing::Digest as HMAC_DIGEST_PATH);
|
||||
ensure_path!(::arbiter_crypto::integrity::Integrable as INTEGRABLE_TRAIT_PATH);
|
||||
|
||||
25
server/crates/arbiter-macros/tests/integrable.rs
Normal file
25
server/crates/arbiter-macros/tests/integrable.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use arbiter_crypto::integrity::Integrable;
|
||||
|
||||
#[derive(arbiter_macros::Hashable, arbiter_macros::Integrable)]
|
||||
#[integrable(kind = "test_entity")]
|
||||
struct TestEntity {
|
||||
value: i32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_version_is_one() {
|
||||
assert_eq!(<TestEntity as Integrable>::VERSION, 1, "default version must be 1");
|
||||
assert_eq!(<TestEntity as Integrable>::KIND, "test_entity");
|
||||
}
|
||||
|
||||
#[derive(arbiter_macros::Hashable, arbiter_macros::Integrable)]
|
||||
#[integrable(kind = "versioned_entity", version = 3)]
|
||||
struct VersionedEntity {
|
||||
data: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_version_attribute() {
|
||||
assert_eq!(<VersionedEntity as Integrable>::VERSION, 3);
|
||||
assert_eq!(<VersionedEntity as Integrable>::KIND, "versioned_entity");
|
||||
}
|
||||
Reference in New Issue
Block a user