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(&registry);
103///     MyConfig::register(&registry);
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}