Crate derive_attribute
source ·Expand description
Derive-Attribute
A set of macros to automatically deserialize standard attributes
Compatible with all major versions of Syn
Supports custom deserialization
Can return multiple errors at once
Allows for flexible attribute syntax
Syn Compatibility
This crate is meant to be used in conjunction with Syn within a procedural macro crate.
A major version of Syn can be selected as a feature like: features = ["syn_2"]
.
Note: A Syn version must be selected
Flexible Attribute Syntax
Implicit Booleans
#[some_attr(is_bool)]
can also be written as #[some_attr(is_bool = true)]
Seperated Lists
#[some_attr(list(key_a = "value", key_b = 123))]
can also be written as
#[some_attr(list(key_a = "value"))]
#[some_attr(list(key_b = 123))]
Multiple Errors
Most macros will only return one attribute error at a time. This crate’s macros can return multiple errors at once resulting in a better developer experience.
Custom Deserialization
Any type that implements TryFromMeta
can be used as a valid attribute type.
Although Its recommended that you use CustomArgFromMeta
instead in order to simplify the implementation.
See example
Attr Arguments
The #[attr()]
attribute can be added to the attribute struct or its fields to add additional options.
The full list of arguments are:
name [str] - Renames the field.
default [bool/str] - Uses a default value if the argument isn’t found.
If its a boolean, the type’s implementation of Default::default will be used.
If its a string, it must be a path to a function that returns the type.
Usage
Our attribute type is declared in a procedural macro crate:
#[derive(Attribute)]
#[attr(name = "my_attr")] // We set the attribute name to 'my_attr'
struct MyAttribute { // Note: The attribute name will be the struct name in snake_case by default
name: String,
// wrapping a type in an option will make it optional
list: Option<NestedList>, // deserializes a meta list named list i.e. list(num = 1)
// booleans are always optional
is_selected: bool,
}
#[derive(List)]
pub struct NestedList {
num: Option<u8>
}
It can then be used to parse the following attribute using the from_attrs method:
#[my_attr(name = "some_name", is_selected)]
lets look at the same attribute used in a derive macro
Basic derive
procedural macro crate:
use derive_attribute::{Attribute, List};
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[derive(Attribute)]
#[attr(name = "my_attr")]
struct MyAttribute {
name: String,
// wrapping a type in an option will make it optional
// deserializes a meta list named list i.e. list(num = 1)
list: Option<NestedList>,
// booleans are always optional
is_selected: bool,
}
#[derive(List)]
pub struct NestedList {
num: Option<u8>
}
#[proc_macro_derive(YOUR_MACRO_NAME, attributes(my_attr))]
pub fn derive_my_trait(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = parse_macro_input!(tokens as DeriveInput);
fn attempt_derive(ast: DeriveInput) -> Result<TokenStream2, Vec<syn::Error>> {
// Wrapping an attribute in an option makes it optional
// A missing error won't be returnwd
let maybe_attribute = <Option<MyAttribute>>::from_attrs(ast.ident.span(), &ast.attrs)?;
let output: TokenStream2 = {
// Your Macro Generation Code
};
Ok(output)
}
let generated_tokens =
match attempt_derive(ast) {
Ok(tokens) => tokens,
Err(errors) => {
let compile_errors = errors.into_iter().map(|e| e.to_compile_error());
quote!(#(#compile_errors)*)
}
};
generated_tokens.into()
}
Another crate using our macro
#[derive(YOUR_MACRO_NAME)]
#[my_attr(name = "some_name", is_selected)]
struct SomeStruct;
Now lets add our own argument type
Custom Deserialization
proc-macro crate:
use derive_attribute::{CustomArg, CustomArgFromMeta};
struct ErrorType {
Warning,
Severe
}
// Any type that implements 'TryFromMeta' can be deserialized however its a bit verbose
// In order to simplify the implementation we can implement 'CustomArgFromMeta' instead and wrap our type in the 'CustomArg' struct
impl<V: SynVersion> CustomArgFromMeta<V> for ErrorType {
fn try_from_meta(meta: Self::Metadata) -> Result<Self, ErrorMsg> {
let maybe_error_kind =
match V::deserialize_string(meta) {
Some(string) => {
match string.to_string().as_str() {
"warning" => Some(Self::Warning),
"severe" => Some(Self::Severe),
_ => None
}
}
None => None
};
match maybe_error_kind {
Some(error_kind) => Ok(error_kind),
None => Err(InvalidType { expected: r#" "warning" or "severe" "# })
}
}
}
Our attribute struct now looks like this:
#[derive(Attribute)]
#[attr(name = "my_attr")]
struct MyAttribute {
// In order to use the simplified trait(CustomArgFromMeta) we need to wrap our struct in 'CustomArg'
error_type: CustomArg<ErrorType>,
name: String,
list: Option<u32>,
is_selected: bool,
}
Another crate using our macro:
#[derive(YOUR_MACRO_NAME)]
#[error(error_type = "warning", name = "some_name", is_selected)]
struct Test;
Modules
Structs
- A result type that can contain multiple errors and a value at the same time.
- Allows a type to implement
CustomArgFromMeta
, a simplified version ofTryFromMeta
. - Deserialization functions & types for Syn version 1
- Deserialization functions & types for Syn version 1
Enums
Traits
- Represents a struct that can be deserialized from Syn attributes.
- Combines an argument with a previously stored instance.
- A simplified version of the
TryFromMeta
trait. Types that implement this must be wrapped in theCustomArg
struct. - Gets the Span of Syn metadata
- Represents a Syn version and how it can parse attribute data into values
- A trait for deserializing Syn metadata.
Its recommended that you use theCustomArgFromMeta
trait for deserializing simple arguments.
Functions
- Validates a simple required type.