struct_to_enum_macros/lib.rs
1//! Derive macros that generate enums from struct fields.
2//!
3//! | Macro | Generated type | Conversion |
4//! |-------|---------------|------------|
5//! | [`FieldName`] | Unit enum — one variant per field name | `From<&Struct>` → `[FieldName; N]` |
6//! | [`FieldType`] | Tuple enum — one variant per field value | `From<Struct>` → `[FieldType; N]` |
7
8mod common;
9mod field_name;
10mod field_type;
11
12use field_name::DeriveFieldName;
13use field_type::DeriveFieldType;
14use proc_macro::TokenStream;
15use syn::DeriveInput;
16
17/// Generates `{StructName}FieldType`, an enum whose variants wrap struct field values.
18/// For enum variants without values use `FieldName` instead.
19///
20/// Each non-skipped field becomes a variant `VariantName(FieldType)` where the variant name is
21/// the field name in `PascalCase`. The variants are ordered by field declaration order.
22///
23/// **No derives are added by default.** Add derives with `#[stem_type_derive(...)]`.
24///
25/// # Attributes
26/// | Attribute | Target | Description |
27/// |-----------|--------|-------------|
28/// | `#[stem_type(skip)]` | field | Exclude this field from the generated enum. |
29/// | `#[stem_type(nested)]` | field | Flatten the nested struct's `FieldType` variants into this enum. |
30/// | `#[stem_type_derive(...)]` | struct | Derives for the generated enum. None are added by default. |
31/// | `#[stem_type_attr(...)]` | struct | Extra attributes applied verbatim to the generated enum. |
32///
33/// All `stem_type*` attributes have short aliases: `ste_type`, `ste_type_derive`, `ste_type_attr`.
34///
35/// # Generated items
36///
37/// For a struct `Foo` with `N` non-skipped fields, this macro generates:
38///
39/// ```text
40/// enum FooFieldType { Field1(T1), Field2(T2), ... }
41///
42/// impl From<Foo> for [FooFieldType; N] { ... }
43/// ```
44///
45/// # Example
46///
47/// ```rust,no_run
48/// use struct_to_enum::FieldType;
49///
50/// #[derive(Clone)]
51/// #[derive(FieldType)]
52/// #[stem_type_derive(Debug, PartialEq, Clone)]
53/// struct Config {
54/// width: u32,
55/// height: u32,
56/// #[stem_type(skip)]
57/// name: String,
58/// }
59///
60/// // Generated: enum ConfigFieldType { Width(u32), Height(u32) }
61///
62/// let cfg = Config { width: 1920, height: 1080, name: "hd".into() };
63/// let fields: [ConfigFieldType; 2] = cfg.into();
64///
65/// assert_eq!(fields[0], ConfigFieldType::Width(1920));
66/// assert_eq!(fields[1], ConfigFieldType::Height(1080));
67/// ```
68///
69/// # Flattening nested structs
70///
71/// Mark a field with `#[stem_type(nested)]` to inline the variants of a nested struct
72/// (which must also derive `FieldType`) directly into the parent enum. Nesting can be
73/// arbitrarily deep.
74///
75/// ```rust,no_run
76/// use struct_to_enum::FieldType;
77///
78/// #[derive(FieldType)]
79/// #[stem_type_derive(Debug, PartialEq)]
80/// struct Color {
81/// r: u8,
82/// g: u8,
83/// b: u8,
84/// }
85///
86/// #[derive(FieldType)]
87/// #[stem_type_derive(Debug, PartialEq)]
88/// struct Pixel {
89/// x: i32,
90/// y: i32,
91/// #[stem_type(nested)]
92/// color: Color,
93/// }
94///
95/// // PixelFieldType has variants: X(i32), Y(i32), R(u8), G(u8), B(u8)
96///
97/// let p = Pixel { x: 10, y: 20, color: Color { r: 255, g: 128, b: 0 } };
98/// let fields: [PixelFieldType; 5] = p.into();
99/// assert_eq!(fields[0], PixelFieldType::X(10));
100/// assert_eq!(fields[2], PixelFieldType::R(255));
101/// ```
102///
103/// # Generics
104///
105/// Generic structs are supported. The generated enum carries the same type parameters:
106///
107/// ```rust,no_run
108/// use struct_to_enum::FieldType;
109///
110/// #[derive(FieldType)]
111/// #[stem_type_derive(Debug, PartialEq)]
112/// struct Pair<A, B> {
113/// first: A,
114/// second: B,
115/// }
116///
117/// // Generated: enum PairFieldType<A, B> { First(A), Second(B) }
118///
119/// let fields: [PairFieldType<i32, &str>; 2] = Pair { first: 42_i32, second: "hi" }.into();
120/// assert_eq!(fields[0], PairFieldType::First(42));
121/// ```
122///
123/// # Combining with other derives
124///
125/// Use `#[stem_type_derive]` and `#[stem_type_attr]` to pass anything to the generated enum.
126/// This works with crates like [`strum`](https://docs.rs/strum):
127///
128/// ```rust,no_run
129/// use struct_to_enum::FieldType;
130/// use strum::VariantNames;
131///
132/// #[derive(FieldType)]
133/// #[stem_type_derive(Debug, strum_macros::VariantNames)]
134/// #[stem_type_attr(strum(serialize_all = "SCREAMING-KEBAB-CASE"))]
135/// struct Request {
136/// user_id: u64,
137/// payload: Vec<u8>,
138/// }
139///
140/// assert_eq!(RequestFieldType::VARIANTS, ["USER-ID", "PAYLOAD"]);
141/// ```
142#[proc_macro_derive(
143 FieldType,
144 attributes(
145 stem_type,
146 ste_type,
147 stem_type_derive,
148 ste_type_derive,
149 stem_type_attr,
150 ste_type_attr,
151 )
152)]
153pub fn field_type(input: TokenStream) -> TokenStream {
154 let input = syn::parse_macro_input!(input as DeriveInput);
155 DeriveFieldType::new(input)
156 .and_then(|d| d.expand())
157 .unwrap_or_else(|e| e.to_compile_error())
158 .into()
159}
160
161/// Generates `{StructName}FieldName`, an enum whose variants represent struct field names.
162/// For enum variants with values use `FieldType` instead.
163///
164/// Each non-skipped field becomes a unit variant in `PascalCase`. The variants are ordered by
165/// field declaration order.
166///
167/// The generated enum derives `Debug`, `PartialEq`, `Eq`, `Clone`, and `Copy` by default.
168/// Use `#[stem_name_derive(...)]` to add more derives - the defaults are merged with whatever
169/// you specify, so you only need to list derives not already in the default set.
170///
171/// # Attributes
172/// | Attribute | Target | Description |
173/// |-----------|--------|-------------|
174/// | `#[stem_name(skip)]` | field | Exclude this field from the generated enum. |
175/// | `#[stem_name(nested)]` | field | Flatten the nested struct's `FieldName` variants into this enum. |
176/// | `#[stem_name_derive(...)]` | struct | Merge additional derives onto the generated enum (defaults are kept). |
177/// | `#[stem_name_attr(...)]` | struct | Extra attributes applied verbatim to the generated enum. |
178///
179/// All `stem_name*` attributes have short aliases: `ste_name`, `ste_name_derive`, `ste_name_attr`.
180///
181/// # Generated items
182///
183/// For a struct `Foo` with `N` non-skipped fields, the macro generates:
184///
185/// ```text
186/// enum FooFieldName { Field1, Field2, ... }
187///
188/// impl FieldNames<N> for Foo { ... }
189/// ```
190///
191/// # Example
192///
193/// ```rust
194/// use struct_to_enum::{FieldName, FieldNames};
195///
196/// #[derive(FieldName)]
197/// struct User {
198/// id: u64,
199/// user_name: String,
200/// #[stem_name(skip)]
201/// internal_token: String,
202/// }
203///
204/// // Generated: enum UserFieldName { Id, UserName } (Debug, PartialEq, Eq, Clone, Copy)
205///
206/// let names: [UserFieldName; 2] = User::field_names();
207/// assert_eq!(names, [UserFieldName::Id, UserFieldName::UserName]);
208/// ```
209///
210/// # Flattening nested structs
211///
212/// Mark a field with `#[stem_name(nested)]` to inline the variants of a nested struct
213/// (which must also derive `FieldName`) directly into the parent enum. Nesting can be
214/// arbitrarily deep.
215///
216/// ```rust
217/// use struct_to_enum::{FieldName, FieldNames};
218///
219/// #[derive(FieldName)]
220/// pub struct Address {
221/// pub street: String,
222/// pub city: String,
223/// }
224///
225/// #[derive(FieldName)]
226/// struct Person {
227/// name: String,
228/// #[stem_name(nested)]
229/// address: Address,
230/// }
231///
232/// // PersonFieldName: Name, Street, City (Address's variants are inlined)
233///
234/// let fields: [PersonFieldName; 3] = Person::field_names();
235/// assert_eq!(fields, [PersonFieldName::Name, PersonFieldName::Street, PersonFieldName::City]);
236/// ```
237///
238/// # Generics
239///
240/// Generic structs are supported. The `FieldNames` impl carries the same type parameters:
241///
242/// ```rust
243/// use struct_to_enum::{FieldName, FieldNames};
244///
245/// #[derive(FieldName)]
246/// struct Pair<A, B> {
247/// first: A,
248/// second: B,
249/// }
250///
251/// // Generated: enum PairFieldName { First, Second }
252/// assert_eq!(PairFieldName::First, PairFieldName::First);
253/// ```
254///
255/// # Combining with other derives
256///
257/// Use `#[stem_name_derive]` and `#[stem_name_attr]` to pass anything to the generated enum.
258/// This works with crates like [`strum`](https://docs.rs/strum):
259///
260/// ```rust
261/// use struct_to_enum::FieldName;
262/// use strum_macros::EnumString;
263///
264/// #[derive(FieldName)]
265/// #[stem_name_derive(EnumString)]
266/// #[stem_name_attr(strum(ascii_case_insensitive))]
267/// struct Query {
268/// user_id: u64,
269/// status: String,
270/// }
271///
272/// // Default derives (Debug, PartialEq, Eq, Clone, Copy) are merged with EnumString.
273/// let variant: QueryFieldName = "userid".parse().unwrap(); // case-insensitive
274/// assert_eq!(variant, QueryFieldName::UserId);
275/// ```
276#[proc_macro_derive(
277 FieldName,
278 attributes(
279 stem_name,
280 ste_name,
281 stem_name_derive,
282 ste_name_derive,
283 stem_name_attr,
284 ste_name_attr,
285 )
286)]
287pub fn field_name(input: TokenStream) -> TokenStream {
288 let input = syn::parse_macro_input!(input as DeriveInput);
289 DeriveFieldName::new(input)
290 .and_then(|d| d.expand())
291 .unwrap_or_else(|e| e.to_compile_error())
292 .into()
293}