structural_typing_macros/
lib.rs

1use proc_macro::TokenStream;
2use syn::{DeriveInput, parse_macro_input};
3
4mod codegen;
5mod parsing;
6
7/// Generates type-safe structural typing support for a struct.
8///
9/// This macro transforms a struct to track which fields are present/absent at the type level,
10/// enabling compile-time enforcement of field requirements.
11///
12/// **Note**: If you use `#[derive(...)]` on your struct, you must add `derive-where` to your dependencies:
13/// ```bash
14/// cargo add derive-where
15/// ```
16///
17/// # Generated Code
18///
19/// For a struct `MyStruct`, this macro generates:
20///
21/// - A `my_struct` module with `Fields` trait, `FieldSet<...>` type, and `with::` aliases
22/// - Builder methods: `.field(value)`
23/// - Getter methods: `.get_field()`, `.get_field_mut()`
24/// - `.merge()`, `.extract()`, `.try_extract()` operations
25///
26/// # Field States
27///
28/// - **Present**: Value of type `T`
29/// - **Optional**: `Option<T>`
30/// - **Absent**: `PhantomData<T>`
31///
32/// # Example
33///
34/// ```ignore
35/// use structural_typing::{structural, presence::Present};
36///
37/// #[structural]
38/// struct User {
39///     name: String,
40///     email: String,
41/// }
42///
43/// // Build incrementally
44/// let user = User::empty().name("Alice".to_owned());
45///
46/// // Methods can require specific fields
47/// impl<F: user::Fields<name = Present>> User<F> {
48///     fn greet(&self) -> String {
49///         format!("Hello, {}!", self.name)
50///     }
51/// }
52///
53/// user.greet(); // ✓ Compiles
54/// ```
55///
56/// # Serde Support
57///
58/// Deserialization uses a helper struct where all fields are `Option<T>` with `#[serde(default)]`,
59/// then converts via `TryFrom`. Present fields error if missing, Optional fields become `Some`/`None`,
60/// Absent fields are ignored.
61///
62/// **Limitations:**
63/// - Present fields cannot use the field type's `Default` impl during deserialization
64/// - Optional fields cannot distinguish between missing and explicit `null` in JSON
65/// - Absent fields silently ignore data if present in input
66/// - All field types must implement `Deserialize` even if marked Absent
67///
68/// **Custom deserializers:** If using `#[serde(deserialize_with)]`, the function must produce `Option<T>`.
69///
70/// **Incompatible:** `#[serde(default)]`, `#[serde(skip)]`, `#[serde(skip_deserializing)]`, `#[serde(flatten)]`.
71/// **Compatible:** `rename`, `alias`, `rename_all`, `deserialize_with` (with `Option<T>` output).
72///
73/// # Restrictions
74///
75/// - Named structs only
76/// - No generics
77/// - At least one field
78#[proc_macro_attribute]
79pub fn structural(_attr: TokenStream, item: TokenStream) -> TokenStream {
80    let input = parse_macro_input!(item as DeriveInput);
81
82    match codegen::generate(input) {
83        Ok(tokens) => tokens.into(),
84        Err(err) => err.to_compile_error().into(),
85    }
86}