ferrunix_macros/lib.rs
1//! Proc-macro crate for [`ferrunix`].
2//!
3//! See the [`derive_inject`] macro for documentation.
4//!
5//! [`ferrunix`]: https://crates.io/crates/ferrunix
6#![allow(
7 clippy::panic,
8 clippy::min_ident_chars,
9 clippy::module_name_repetitions,
10 clippy::missing_docs_in_private_items
11)]
12
13use darling::FromDeriveInput;
14use syn::{parse_macro_input, DeriveInput};
15
16use self::attr::DeriveAttrInput;
17use self::inject::derive_macro_impl;
18
19mod attr;
20mod inject;
21mod utils;
22
23/// `#[derive(Inject)]` proc-macro for [`ferrunix`].
24///
25/// The `Inject` derive macro supports the two following attributes:
26///
27/// - `#[provides]`: Customizing the object registration.
28/// - `#[inject]`: Customizing how an injected member is created.
29///
30/// ```rust,ignore
31/// #[derive(Inject)]
32/// #[provides(PROPERTY...)]
33/// struct Transient {
34/// #[inject(PROPERTY...)]
35/// field: UserType,
36/// }
37/// ```
38///
39/// ## `provides` Properties
40///
41/// - `transient [= "<TYPE-SIGNATURE>"]`
42/// - The object is provided as a transient registered with `<TYPE-SIGNATURE>`
43/// as key. If the signature is omitted, the concrete type is used as a key.
44/// - `singleton [= "<TYPE-SIGNATURE>"]`
45/// - The object is provided as a singleton registered with `<TYPE-SIGNATURE>`
46/// as key. If the signature is omitted, the concrete type is used as a key.
47/// - `ctor = <IDENTIFIER>`
48/// - The object isn't constructed using member-wise construction, but it's
49/// constructed using a custom constructor (e.g., `new`). The constructor
50/// will be passed the members in order of declaration as parameters.
51/// - `no_registration`
52/// - The type isn't registered automatically and the generated
53/// `Self::register(&ferrunix::Registry)` function needs to be called
54/// manually to register the type.
55///
56/// ## `inject` Properties
57///
58/// - `default`
59/// - Construct the field using the `Default` implementation.
60/// - `ctor = "<RUST-CODE>"`
61/// - Construct the field using the provided Rust code.
62/// - `transient [= true]`
63/// - Construct the field as a transient by retrieving it from the `Registry`.
64/// - `singleton [= true]`
65/// - Construct the field as a singleton by retrieving it from the `Registry`.
66///
67/// ```rust,ignore,no_run
68/// # #![allow(unused)]
69/// use ferrunix::Inject;
70///
71/// pub trait Logger {}
72///
73/// #[derive(Inject)]
74/// #[provides(transient = "dyn Logger", no_registration)]
75/// // ^^^^^^^^^^^^^^
76/// // The explicit type can be omitted, if it matches the concrete type.
77/// pub struct MyLogger {}
78///
79/// impl Logger for MyLogger {}
80///
81/// #[derive(Inject)]
82/// #[provides(singleton, no_registration)]
83/// pub struct MyConfig {
84/// #[inject(default)]
85/// // Use the `Default::default` impl for construction.
86/// counter: u32,
87///
88/// #[inject(ctor = r#""log-prefix: ".to_owned()"#)]
89/// // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
90/// // The constructor must be valid Rust code. For strings, two sets of quotes
91/// // are required.
92/// prefix: String,
93///
94/// #[inject(transient /* = true */)]
95/// // ^^^^^^^^^^^^
96/// // The long form with `= true` is optional.
97/// logger: Box<dyn Logger>,
98/// }
99///
100/// fn main() {
101/// let registry = ferrunix::Registry::empty();
102/// MyLogger::register(®istry);
103/// MyConfig::register(®istry);
104/// }
105/// ```
106///
107/// [`ferrunix`]: https://crates.io/crates/ferrunix
108#[proc_macro_derive(Inject, attributes(provides, inject))]
109#[allow(clippy::missing_panics_doc)]
110pub fn derive_inject(
111 input: proc_macro::TokenStream,
112) -> proc_macro::TokenStream {
113 // Parse the input tokens into a syntax tree
114 let input = parse_macro_input!(input as DeriveInput);
115 // eprintln!("input: {input:#?}");
116
117 let attr_input =
118 DeriveAttrInput::from_derive_input(&input).map_err(syn::Error::from);
119 if let Err(err) = attr_input {
120 return err.into_compile_error().into();
121 }
122 let attr_input = attr_input.expect("error is returned above");
123 // eprintln!("attr_input: {attr_input:#?}");
124
125 derive_macro_impl(&input, &attr_input)
126 .unwrap_or_else(syn::Error::into_compile_error)
127 .into()
128}