redact_composer_derive/lib.rs
1#![deny(missing_docs)]
2//! Derive macros for `redact_composer`. Not needed as a direct dependency.
3
4use darling::FromDeriveInput;
5use proc_macro::{self, TokenStream};
6use quote::quote;
7use syn::{parse_macro_input, DeriveInput, Expr};
8
9#[derive(FromDeriveInput, Default)]
10#[darling(default, attributes(element))]
11struct Opts {
12 name: Option<String>,
13 wrapped_element: Option<Expr>,
14 wrapped_element_doc: Option<String>,
15}
16
17/// Derives a `redact-composer` `Element` impl for this type.
18///
19/// The default implementation (which likely satisfies the majority of cases) is nothing more than:
20/// ```ignore
21/// # #[derive(Debug)]
22/// # struct MyElement;
23/// #[typetag::serde] // If "serde" feature enabled
24/// impl Element for MyElement {}
25/// ```
26///
27/// > *Important!: At the current time, if using the `serde` feature (enabled by default), in order
28/// > to use this derive macro you need to have [`typetag`] added as a dependency to your
29/// > crate.*
30///
31/// Additional options (if needed) are specified via the `#[element(params)]` attribute which accepts
32/// any of the following params:
33/// * `feature: serde`
34///
35/// **`name: String`:** Provides a different serialization name if you need to avoid naming collisions
36/// or just prefer something different. In either case, this name is just passed along to
37/// `#[typetag::serde(name = name)]`.
38///
39/// **Default:** the type's name.
40///
41/// * **`wrapped_element: Expr`:** If you are creating an Element that wraps
42/// another you can specify the expression to access it (e.g. `Some(self.wrapped_item())`). The
43/// expression should return an `Option<&dyn Element>`.
44///
45/// **Default:** `None`.
46///
47/// * **`wrapped_element_doc: String`:** Use this to provide a doc comment (no /// necessary) for the
48/// wrapped element. Only has an effect if `wrapped_element` is also present.
49#[proc_macro_derive(Element, attributes(element))]
50pub fn derive(input: TokenStream) -> TokenStream {
51 derive_impl(quote! { ::redact_composer }, input)
52}
53
54/// See [`Element`]. This version is used if only depending on `redact_composer_core` (i.e. for
55/// lib development).
56#[proc_macro_derive(ElementCore, attributes(element))]
57pub fn core_derive(input: TokenStream) -> TokenStream {
58 derive_impl(quote! { ::redact_composer_core }, input)
59}
60
61fn derive_impl(crate_path: proc_macro2::TokenStream, input: TokenStream) -> TokenStream {
62 let input = parse_macro_input!(input);
63 let opts = Opts::from_derive_input(&input).expect("Invalid element option");
64 let DeriveInput { ident, .. } = input;
65
66 let wrapped_element_comment = if let Some(comment) = opts.wrapped_element_doc {
67 quote! { #[doc= #comment ] }
68 } else {
69 quote! { #[doc= "Wrapped element." ] }
70 };
71
72 let wrapped_element_accessor = match opts.wrapped_element {
73 Some(accessor) => quote! {
74 #wrapped_element_comment
75 fn wrapped_element(&self) -> Option<&dyn Element> {
76 #accessor
77 }
78 },
79 None => quote! {},
80 };
81
82 let typetag_attr = if cfg!(feature = "serde") {
83 let type_tag_opts = match opts.name {
84 Some(name_opt) => quote! { (name = #name_opt) },
85 None => quote! {},
86 };
87
88 quote! { #[typetag::serde #type_tag_opts] }
89 } else {
90 quote! {}
91 };
92
93 let output = quote! {
94 #typetag_attr
95 impl #crate_path::Element for #ident {
96 #wrapped_element_accessor
97 }
98 };
99
100 output.into()
101}