strut_factory/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(missing_docs)]
3#![cfg_attr(test, deny(warnings))]
4
5use proc_macro::TokenStream;
6
7/// Common helper code re-used across macros.
8#[allow(dead_code)]
9pub(crate) mod common {
10    pub mod error;
11    pub mod parse;
12}
13
14/// Implementation related to the Strut application’s entrypoint function.
15mod entry;
16
17/// Implementation related to the application configuration.
18mod config {
19    /// Implementation related to the configuration struct fields.
20    pub mod field {
21        /// Output generator.
22        pub mod generator;
23        /// Input representation.
24        pub mod input;
25    }
26
27    /// Implementation related to the configuration enums.
28    pub mod choice;
29}
30
31/// Marks an `async` main function as the main function of this Strut
32/// application. This macro helps boot the Strut application without having to
33/// use [`App`](../strut/struct.App.html) or
34/// [`Launchpad`](../strut/struct.Launchpad.html) directly.
35///
36/// This macro is designed as familiar syntactic sugar for applications that
37/// rely on the default Strut startup behavior. For more complex cases, where
38/// customizing the startup wiring is required, use the
39/// [`Launchpad`](../strut/struct.Launchpad.html) instead.
40///
41/// This macro can only be used on an `async fn main() { /* ... */ }` definition.
42///
43/// ## Example
44///
45/// ```
46/// #[strut::main]
47/// async fn main() {
48///     println!("Hello, world!");
49/// }
50/// ```
51///
52/// The invocation above is equivalent to:
53///
54/// ```
55/// fn main() {
56///     strut::App::boot(async {
57///         println!("Hello, world!");
58///     });
59/// }
60/// ```
61///
62/// ## Negative examples
63///
64/// The following invocations do not compile.
65///
66/// The function must be called `main`:
67///
68/// ```compile_fail
69/// #[strut::main]
70/// async fn some_function() {}
71/// ```
72///
73/// The function must have no arguments defined:
74///
75/// ```compile_fail
76/// #[strut::main]
77/// async fn main(input: bool) {}
78/// ```
79///
80/// The function must be `async`:
81///
82/// ```compile_fail
83/// #[strut::main]
84/// fn main() {}
85/// ```
86///
87/// The function must define no return type (not even a union type):
88///
89/// ```compile_fail
90/// #[strut::main]
91/// fn main() -> () {}
92/// ```
93#[proc_macro_attribute]
94pub fn main(attr: TokenStream, item: TokenStream) -> TokenStream {
95    entry::main(attr.into(), item.into())
96        .unwrap_or_else(|error| error)
97        .into()
98}
99
100/// Provides an in-house deriving of the `serde` crate’s `Deserialize` trait,
101/// focusing on flexibly interpreting human-provided inputs.
102///
103/// The usage is best explained with the example below. The logic of comparing
104/// the input string with all the aliases may be provided by referencing a
105/// function with the signature `(a: &str, b: &str) -> bool`.
106///
107/// Every variant gets an implicit alias generated by taking the alias’s name
108/// and converting it to `snake_case`.
109///
110/// ## Example
111///
112/// ```rust
113/// use strut_factory::Deserialize as StrutDeserialize;
114/// use serde_json::from_str;
115///
116/// #[derive(StrutDeserialize)]
117/// #[strut(eq_fn = compare)]
118/// enum SomeValue {
119///     #[strut(alias = "alpha", alias = "first")]
120///     AlphaFirst,
121///     #[strut(alias = "other")]
122///     BravoSecond,
123///     CharlieThird,
124/// }
125///
126/// /// The function used to compare the input string against all aliases.
127/// fn compare(a: &str, b: &str) -> bool {
128///     a.eq_ignore_ascii_case(b)
129/// }
130///
131/// assert!(matches!(from_str("\"alpha_first\"").unwrap(), SomeValue::AlphaFirst));
132/// assert!(matches!(from_str("\"alpha\"").unwrap(), SomeValue::AlphaFirst));
133/// assert!(matches!(from_str("\"FIRST\"").unwrap(), SomeValue::AlphaFirst));
134///
135/// assert!(matches!(from_str("\"BRAVO_SECOND\"").unwrap(), SomeValue::BravoSecond));
136/// assert!(matches!(from_str("\"Other\"").unwrap(), SomeValue::BravoSecond));
137///
138/// assert!(matches!(from_str("\"Charlie_Third\"").unwrap(), SomeValue::CharlieThird));
139/// ```
140#[proc_macro_derive(Deserialize, attributes(strut))]
141pub fn config_choice(attr: TokenStream) -> TokenStream {
142    // Parse input
143    let input = syn::parse_macro_input!(attr as syn::DeriveInput);
144
145    // Delegate
146    config::choice::config_choice(input)
147        .unwrap_or_else(syn::Error::into_compile_error)
148        .into()
149}
150
151/// The Strut ecosystem often forgoes deriving the
152/// [`Deserialize`](serde::Deserialize) implementation, and instead implements
153/// that trait manually in order to provide enhance flexibility and developer
154/// convenience when deserializing things like application configuration. Manual
155/// implementation of `Deserialize` involves a fair amount of boilerplate code,
156/// while only a small portion of that boilerplate is really worth customizing.
157///
158/// The “usual” implementation of `Deserialize` (from a map-style input) goes
159/// through the following beats:
160///
161/// 1. Define an `Option` for every field in the target struct/enum.
162/// 2. Iterate over all key-value pairs in the input map.
163/// 3. If a key is recognized, put its value into the corresponding `Option`.
164/// 4. Instantiate the target struct/enum with the collected values, or fail.
165///
166/// This macro generates a helper enum that abstracts away most of the code that is
167/// generally not worth customizing. This helper enum has the following features:
168///
169/// - Each variant represents a field on the target struct/enum.
170/// - The helper enum itself also implements `Deserialize` from a string input.
171///     - This implementation allows to recognize the string keys in the input
172///         map.
173///     - This deserialization uses the given **key matcher function** to match
174///         input keys onto expected keys.
175///     - The key matcher function may be as strict or as relaxed as necessary.
176///     - The Strut ecosystem provides the
177///         [`eq_as_slugs`](strut_deserialize::Slug::eq_as_slugs) function as
178///         a suggested key matcher function for application config keys.
179/// - Each variant may have any number of string aliases, making the input key
180///     matching even more flexible.
181/// - Variants have the `poll` function, which takes mutable references to the
182///     input [`MapAccess`](serde::de::MapAccess) and to a target `Option`,
183///     and populates that `Option` with whatever
184///     [`next_value`](serde::de::MapAccess::next_value) returns.
185///     - If the given `Option` reference is already `Some`, this function
186///         generates an appropriate “duplicate value” error.
187/// - Variants have the `take` function, which takes the target `Option`, and
188///     returns its wrapped value.
189///     - If the given `Option` is `None`, this function generates an appropriate
190///         “required value missing” error.
191///
192/// Thanks to Rust’s robust type inference mechanisms, this helper enum can be
193/// used cleanly (see below), almost like with dynamically typed languages.
194///
195/// ## Example
196///
197/// The usage of this macro is likely best described with an example:
198///
199/// ```
200/// /// Here is a struct, for which we want to implement custom deserialization
201/// struct SomeStruct {
202///     field_a: i64,
203///     field_b: String,
204/// }
205///
206/// /// When matching keys from the input, we want to compare case-insensitively,
207/// /// e.g., `FIELD_A` should match `field_a`. We need to define a matcher function
208/// /// for that. The signature of this function must be `(a: &str, b: &str) -> bool`.
209/// fn eq_fn(a: &str, b: &str) -> bool {
210///     a.eq_ignore_ascii_case(b)
211/// }
212///
213/// /// Implement the helper enum named `SomeStructField` to help write
214/// /// deserialization logic for `SomeStruct`.
215/// strut_factory::impl_deserialize_field!(
216///     SomeStructField,
217///     eq_fn,
218///     field_a,
219///     field_b | alias_b | another_b,
220/// );
221/// ```
222///
223/// Our goal is to take a map-like input (e.g., a JSON or YAML object) and produce
224/// a new instance of `SomeStruct`.
225///
226/// We pass the name of the helper enum as the first argument to the macro.
227///
228/// We will use the `eq_fn` function to match keys in the input map to the
229/// struct fields. We pass the path to this function as the second argument to
230/// the macro.
231///
232/// The helper enum will have two variants:
233///
234/// First variant is named `field_a`, and it is matched from any string for
235/// which `eq_fn` returns `true`, when compared against the string `"field_a"`.
236///
237/// Second variant is named `field_b`, with similar matching rules. However,
238/// this variant will also be matched against the strings `"alias_b"` and
239/// `"another_b"`.
240///
241/// The call-site above would expand to something like this:
242///
243/// ```rust,ignore
244/// #[allow(non_camel_case_types)]
245/// enum SomeStructField {
246///     field_a,
247///     field_b,
248///     __ignore, // fall-back case for unrecognized input
249/// }
250///
251/// impl SomeStructField {
252///     // ... convenience methods
253/// }
254///
255/// impl<'de> serde::de::Deserialize<'de> for SomeStructField {
256///     // ... make helper enum deserializable
257/// }
258/// ```
259///
260/// The field names passed into the macro (e.g., `field_a`, `field_b`, etc.) will
261/// be used verbatim in the error messages that guide the user to providing correct
262/// data for deserialization. In this example, the field names match the struct
263/// fields exactly, but this is not a requirement.
264///
265/// Here’s the expanded example, showing the intended usage:
266///
267/// ```
268/// use std::fmt::Formatter;
269/// use serde::{Deserialize, Deserializer};
270/// use serde::de::{MapAccess, Visitor};
271/// use strut_factory::impl_deserialize_field;
272///
273/// struct SomeStruct {
274///     field_a: i64,
275///     field_b: String,
276/// }
277///
278/// fn eq_fn(a: &str, b: &str) -> bool {
279///     a.eq_ignore_ascii_case(b)
280/// }
281///
282/// impl_deserialize_field!(
283///     SomeStructField,
284///     eq_fn,
285///     field_a,
286///     field_b | alias_b | another_b,
287/// );
288///
289/// /// Implement `Deserialize` via a custom `Visitor`
290/// impl<'de> Deserialize<'de> for SomeStruct {
291///     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
292///     where
293///         D: Deserializer<'de>,
294///     {
295///         deserializer.deserialize_map(SomeStructVisitor)
296///     }
297/// }
298///
299/// struct SomeStructVisitor;
300///
301/// impl<'de> Visitor<'de> for SomeStructVisitor {
302///     type Value = SomeStruct;
303///
304///     fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
305///         formatter.write_str("a map of SomeStruct")
306///     }
307///
308///     fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
309///     where
310///         A: MapAccess<'de>,
311///     {
312///         // Define an `Option` for every deserialized field
313///         let mut field_a = None; // Rust can infer type `Option<i64>` from code below
314///         let mut field_b = None; // here inferred type is `Option<String>`
315///
316///         while let Some(key) = map.next_key()? { // How does Rust know what is the type of `key`? See next line.
317///             match key { // We match against `SomeStructField` variants, so compiler infers that key is of type `SomeStructField`.
318///                 SomeStructField::field_a => key.poll(&mut map, &mut field_a)?, // takes next map value, with readable error handling
319///                 //                              ^^^^ convenience method
320///                 SomeStructField::field_b => key.poll(&mut map, &mut field_b)?,
321///                 SomeStructField::__ignore => map.next_value()?,
322///             };
323///         }
324///
325///         Ok(SomeStruct{
326///             field_a: SomeStructField::field_a.take(field_a)?, // takes required value out of the `Option`, with readable error handling
327///             //                                ^^^^ convenience method
328///             field_b: field_b.unwrap_or_else(default_field_b), // alternative error-less approach, when a default exists
329///         })
330///     }
331/// }
332///
333/// fn default_field_b() -> String {
334///     "some_default".to_string()
335/// }
336/// ```
337///
338/// With the help of this macro, we mostly just need to implement the `visit_map`
339/// method on a custom [`Visitor`](serde::de::Visitor). This happens to be exactly
340/// the method where most deserialization customizations would happen. Even without
341/// such customizations, the helper enum alone, with its flexible key matching and
342/// key aliasing, provides significant improvement (in terms of usage convenience)
343/// on the standard derived implementation of [`Deserialize`](serde::de::Deserialize).
344#[proc_macro]
345pub fn impl_deserialize_field(input: TokenStream) -> TokenStream {
346    config::field::generator::impl_deserialize_field(input)
347}