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};
|
use syn::{DeriveInput, parse_macro_input};
|
||||||
|
|
||||||
mod hashable;
|
mod hashable;
|
||||||
|
mod integrable;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
#[proc_macro_derive(Hashable)]
|
#[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);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
hashable::derive(&input).into()
|
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::Hashable as HASHABLE_TRAIT_PATH);
|
||||||
ensure_path!(::arbiter_crypto::hashing::Digest as HMAC_DIGEST_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