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
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
76/// # #[cfg(feature = "nested-type")] {
77/// use struct_to_enum::FieldType;
78///
79/// #[derive(FieldType)]
80/// #[stem_type_derive(Debug, PartialEq)]
81/// struct Color {
82/// r: u8,
83/// g: u8,
84/// b: u8,
85/// }
86///
87/// #[derive(FieldType)]
88/// #[stem_type_derive(Debug, PartialEq)]
89/// struct Pixel {
90/// x: i32,
91/// y: i32,
92/// #[stem_type(nested)]
93/// color: Color,
94/// }
95///
96/// // PixelFieldType has variants: X(i32), Y(i32), R(u8), G(u8), B(u8)
97///
98/// let p = Pixel { x: 10, y: 20, color: Color { r: 255, g: 128, b: 0 } };
99/// let fields: [PixelFieldType; 5] = p.into();
100/// assert_eq!(fields[0], PixelFieldType::X(10));
101/// assert_eq!(fields[2], PixelFieldType::R(255));
102/// }
103/// ```
104///
105/// # Generics
106///
107/// Generic structs are supported. The generated enum carries the same type parameters:
108///
109/// ```rust
110/// use struct_to_enum::FieldType;
111///
112/// #[derive(FieldType)]
113/// #[stem_type_derive(Debug, PartialEq)]
114/// struct Pair<A, B> {
115/// first: A,
116/// second: B,
117/// }
118///
119/// // Generated: enum PairFieldType<A, B> { First(A), Second(B) }
120///
121/// let fields: [PairFieldType<i32, &str>; 2] = Pair { first: 42_i32, second: "hi" }.into();
122/// assert_eq!(fields[0], PairFieldType::First(42));
123/// ```
124///
125/// # Combining with other derives
126///
127/// Use `#[stem_type_derive]` and `#[stem_type_attr]` to pass anything to the generated enum.
128/// This works with crates like [`strum`](https://docs.rs/strum):
129///
130/// ```rust
131/// use struct_to_enum::FieldType;
132/// use strum::VariantNames;
133///
134/// #[derive(FieldType)]
135/// #[stem_type_derive(Debug, strum_macros::VariantNames)]
136/// #[stem_type_attr(strum(serialize_all = "SCREAMING-KEBAB-CASE"))]
137/// struct Request {
138/// user_id: u64,
139/// payload: Vec<u8>,
140/// }
141///
142/// assert_eq!(RequestFieldType::VARIANTS, ["USER-ID", "PAYLOAD"]);
143/// ```
144#[proc_macro_derive(
145 FieldType,
146 attributes(
147 stem_type,
148 ste_type,
149 stem_type_derive,
150 ste_type_derive,
151 stem_type_attr,
152 ste_type_attr,
153 )
154)]
155pub fn field_type(input: TokenStream) -> TokenStream {
156 let input = syn::parse_macro_input!(input as DeriveInput);
157 DeriveFieldType::new(input)
158 .and_then(|d| d.expand())
159 .unwrap_or_else(|e| e.to_compile_error())
160 .into()
161}
162
163/// Generates `{StructName}FieldName`, an enum whose variants represent struct field names.
164/// For enum variants with values use `FieldType` instead.
165///
166/// Each non-skipped field becomes a unit variant in `PascalCase`. The variants are ordered by
167/// field declaration order.
168///
169/// The generated enum derives `Debug`, `PartialEq`, `Eq`, `Clone`, and `Copy` by default.
170/// They can be removed by adding `no_defaults` to the `stem_name_derive` attribute.
171/// Use `#[stem_name_derive(...)]` to add more derives - the defaults are merged with whatever
172/// you specify, so you only need to list derives not already in the default set.
173///
174/// # Attributes
175/// | Attribute | Target | Description |
176/// |-----------|--------|-------------|
177/// | `#[stem_name(skip)]` | field | Exclude this field from the generated enum. |
178/// | `#[stem_name(nested)]` | field | Flatten the nested struct's `FieldName` variants into this enum. |
179/// | `#[stem_name_derive(...)]` | struct | Merge additional derives onto the generated enum (defaults are kept). |
180/// | `#[stem_name_attr(...)]` | struct | Extra attributes applied verbatim to the generated enum. |
181///
182/// All `stem_name*` attributes have short aliases: `ste_name`, `ste_name_derive`, `ste_name_attr`.
183///
184/// # Generated items
185///
186/// For a struct `Foo` with `N` non-skipped fields, the macro generates:
187///
188/// ```text
189/// enum FooFieldName { Field1, Field2, ... }
190///
191/// impl FieldNames<N> for Foo { ... }
192/// ```
193///
194/// # Example
195///
196/// ```rust
197/// use struct_to_enum::{FieldName, FieldNames};
198///
199/// #[derive(FieldName)]
200/// struct User {
201/// id: u64,
202/// user_name: String,
203/// #[stem_name(skip)]
204/// internal_token: String,
205/// }
206///
207/// // Generated: enum UserFieldName { Id, UserName } (Debug, PartialEq, Eq, Clone, Copy)
208///
209/// let names: [UserFieldName; 2] = User::field_names();
210/// assert_eq!(names, [UserFieldName::Id, UserFieldName::UserName]);
211/// ```
212///
213/// # Flattening nested structs
214///
215/// Mark a field with `#[stem_name(nested)]` to inline the variants of a nested struct
216/// (which must also derive `FieldName`) directly into the parent enum. Nesting can be
217/// arbitrarily deep.
218///
219/// ```rust
220/// # #[cfg(feature = "nested-name")] {
221/// use struct_to_enum::{FieldName, FieldNames};
222///
223/// #[derive(FieldName)]
224/// pub struct Address {
225/// pub street: String,
226/// pub city: String,
227/// }
228///
229/// #[derive(FieldName)]
230/// struct Person {
231/// name: String,
232/// #[stem_name(nested)]
233/// address: Address,
234/// }
235///
236/// // PersonFieldName: Name, Street, City (Address's variants are inlined)
237///
238/// let fields: [PersonFieldName; 3] = Person::field_names();
239/// assert_eq!(fields, [PersonFieldName::Name, PersonFieldName::Street, PersonFieldName::City]);
240/// # }
241/// ```
242///
243/// # Generics
244///
245/// Generic structs are supported. The `FieldNames` impl carries the same type parameters:
246///
247/// ```rust
248/// use struct_to_enum::{FieldName, FieldNames};
249///
250/// #[derive(FieldName)]
251/// struct Pair<A, B> {
252/// first: A,
253/// second: B,
254/// }
255///
256/// // Generated: enum PairFieldName { First, Second }
257/// assert_eq!(PairFieldName::First, PairFieldName::First);
258/// ```
259///
260/// # Combining with other derives
261///
262/// Use `#[stem_name_derive]` and `#[stem_name_attr]` to pass anything to the generated enum.
263/// This works with crates like [`strum`](https://docs.rs/strum):
264///
265/// ```rust
266/// use struct_to_enum::FieldName;
267/// use strum_macros::EnumString;
268///
269/// #[derive(FieldName)]
270/// #[stem_name_derive(EnumString)]
271/// #[stem_name_attr(strum(ascii_case_insensitive))]
272/// struct Query {
273/// user_id: u64,
274/// status: String,
275/// }
276///
277/// // Default derives (Debug, PartialEq, Eq, Clone, Copy) are merged with EnumString.
278/// let variant: QueryFieldName = "userid".parse().unwrap(); // case-insensitive
279/// assert_eq!(variant, QueryFieldName::UserId);
280/// ```
281#[proc_macro_derive(
282 FieldName,
283 attributes(
284 stem_name,
285 ste_name,
286 stem_name_derive,
287 ste_name_derive,
288 stem_name_attr,
289 ste_name_attr,
290 )
291)]
292pub fn field_name(input: TokenStream) -> TokenStream {
293 let input = syn::parse_macro_input!(input as DeriveInput);
294 DeriveFieldName::new(input)
295 .and_then(|d| d.expand())
296 .unwrap_or_else(|e| e.to_compile_error())
297 .into()
298}