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}