enum_display/
lib.rs

1//!
2//! enum-display is a crate for implementing [`core::fmt::Display`] on enum variants with macros.
3//!
4//! This crate supports both `std` and `no_std` environments. In `no_std` mode, it works
5//! without allocation by writing directly to the formatter.
6//!
7//! # Simple Example
8//!
9//! ```rust
10//! use enum_display::EnumDisplay;
11//!
12//! #[derive(EnumDisplay)]
13//! enum Color {
14//!    Red,
15//!    Green,
16//!    Blue,
17//! }
18//!
19//! assert_eq!(Color::Red.to_string(), "Red");
20//! assert_eq!(Color::Green.to_string(), "Green");
21//! assert_eq!(Color::Blue.to_string(), "Blue");
22//! ```
23//!
24//! # Example With Custom Case Transform
25//!
26//! Any case from [convert_case](https://docs.rs/convert_case/latest/convert_case/) is supported.
27//!
28//! ```rust
29//! use enum_display::EnumDisplay;
30//!
31//! #[derive(EnumDisplay)]
32//! #[enum_display(case = "Kebab")]
33//! enum Message {
34//!     HelloGreeting { name: String },
35//! }
36//!
37//! # #[cfg(feature = "std")]
38//! assert_eq!(Message::HelloGreeting { name: "Alice".to_string() }.to_string(), "hello-greeting");
39//! ```
40//!
41//! # No-std Usage
42//!
43//! This crate works in `no_std` environments:
44//!
45//! ```rust
46//! # #![cfg_attr(not(feature = "std"), no_std)]
47//! use enum_display::EnumDisplay;
48//!
49//! #[derive(EnumDisplay)]
50//! enum Status {
51//!     Ready,
52//!
53//!     #[display("Error: {code}")]
54//!     Error { code: u32 },
55//! }
56//! ```
57//!
58//! # Example With Custom Variant Formatting
59//!
60//! Display output can be customised using a format string passed to the `display` enum
61//! variant attribute.
62//!
63//! The case-converted variant name is always available via the `{variant}` named parameter.
64//!
65//! Additional parameters depend on the type of enum variant:
66//!
67//! | Variant Type  | Format String Field Access                                                        | Example                             |
68//! |---------------|-----------------------------------------------------------------------------------|-------------------------------------|
69//! | Named {...}   | [Named Parameters](https://doc.rust-lang.org/std/fmt/#named-parameters)           | `"{variant} name field is: {name}"` |
70//! | Unnamed (...) | [Positional Parameters](https://doc.rust-lang.org/std/fmt/#positional-parameters) | `"{variant} age field is: {0}"`     |
71//! | Unit          | No additional fields available                                                    | `"{variant} has no fields"`         |
72//!
73//! ```rust
74//! use enum_display::EnumDisplay;
75//!
76//! #[derive(EnumDisplay)]
77//! enum Conversation {
78//!     #[display("{variant} {name}!")]
79//!     Hello { name: String },
80//!
81//!     #[display("{variant}? {0}")]
82//!     HowOld(usize),
83//!
84//!     #[display("{variant}!")]
85//!     Wow,
86//! }
87//!
88//! assert_eq!(Conversation::Hello { name: "Alice".to_string() }.to_string(), "Hello Alice!");
89//! assert_eq!(Conversation::HowOld(123).to_string(), "HowOld? 123");
90//! assert_eq!(Conversation::Wow.to_string(), "Wow!");
91//! ```
92
93#![cfg_attr(not(feature = "std"), no_std)]
94
95pub use enum_display_macro::*;
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[cfg(feature = "std")]
102    use std::string::{String, ToString};
103
104    #[cfg(not(feature = "std"))]
105    extern crate alloc;
106
107    #[cfg(not(feature = "std"))]
108    use alloc::string::{String, ToString};
109
110    #[allow(dead_code)]
111    #[derive(EnumDisplay)]
112    enum TestEnum {
113        Name,
114
115        #[display("Overridden Name")]
116        OverriddenName,
117
118        #[display("Unit: {variant}")]
119        NameFullFormat,
120
121        Address {
122            street: String,
123            city: String,
124            state: String,
125            zip: String,
126        },
127
128        #[display("Named: {variant} {{{street}, {zip}}}")]
129        AddressPartialFormat {
130            street: String,
131            city: String,
132            state: String,
133            zip: String,
134        },
135
136        #[display("Named: {variant} {{{street}, {city}, {state}, {zip}}}")]
137        AddressFullFormat {
138            street: String,
139            city: String,
140            state: String,
141            zip: String,
142        },
143
144        DateOfBirth(u32, u32, u32),
145
146        #[display("Unnamed: {variant}({2})")]
147        DateOfBirthPartialFormat(u32, u32, u32),
148
149        #[display("Unnamed: {variant}({0}, {1}, {2})")]
150        DateOfBirthFullFormat(u32, u32, u32),
151    }
152
153    #[allow(dead_code)]
154    #[derive(EnumDisplay)]
155    #[enum_display(case = "Kebab")]
156    enum TestEnumWithAttribute {
157        Name,
158        Address {
159            street: String,
160            city: String,
161            state: String,
162            zip: String,
163        },
164        DateOfBirth(u32, u32, u32),
165    }
166
167    #[allow(dead_code)]
168    #[derive(EnumDisplay)]
169    enum TestEnumWithLifetimeAndGenerics<'a, T: Clone>
170    where
171        T: core::fmt::Display,
172    {
173        Name,
174        Address {
175            street: &'a T,
176            city: &'a T,
177            state: &'a T,
178            zip: &'a T,
179        },
180        DateOfBirth(u32, u32, u32),
181    }
182
183    #[test]
184    fn test_unit_field_variant() {
185        assert_eq!(TestEnum::Name.to_string(), "Name");
186        assert_eq!(TestEnum::OverriddenName.to_string(), "Overridden Name");
187        assert_eq!(TestEnum::NameFullFormat.to_string(), "Unit: NameFullFormat");
188    }
189
190    #[test]
191    fn test_named_fields_variant() {
192        assert_eq!(
193            TestEnum::Address {
194                street: "123 Main St".to_string(),
195                city: "Any Town".to_string(),
196                state: "CA".to_string(),
197                zip: "12345".to_string()
198            }
199            .to_string(),
200            "Address"
201        );
202        assert_eq!(
203            TestEnum::AddressPartialFormat {
204                street: "123 Main St".to_string(),
205                city: "Any Town".to_string(),
206                state: "CA".to_string(),
207                zip: "12345".to_string()
208            }
209            .to_string(),
210            "Named: AddressPartialFormat {123 Main St, 12345}"
211        );
212        assert_eq!(
213            TestEnum::AddressFullFormat {
214                street: "123 Main St".to_string(),
215                city: "Any Town".to_string(),
216                state: "CA".to_string(),
217                zip: "12345".to_string()
218            }
219            .to_string(),
220            "Named: AddressFullFormat {123 Main St, Any Town, CA, 12345}"
221        );
222    }
223
224    #[test]
225    fn test_unnamed_fields_variant() {
226        assert_eq!(TestEnum::DateOfBirth(1, 2, 1999).to_string(), "DateOfBirth");
227        assert_eq!(
228            TestEnum::DateOfBirthPartialFormat(1, 2, 1999).to_string(),
229            "Unnamed: DateOfBirthPartialFormat(1999)"
230        );
231        assert_eq!(
232            TestEnum::DateOfBirthFullFormat(1, 2, 1999).to_string(),
233            "Unnamed: DateOfBirthFullFormat(1, 2, 1999)"
234        );
235    }
236
237    #[test]
238    fn test_unit_field_variant_case_transform() {
239        assert_eq!(TestEnumWithAttribute::Name.to_string(), "name");
240    }
241
242    #[test]
243    fn test_named_fields_variant_case_transform() {
244        assert_eq!(
245            TestEnumWithAttribute::Address {
246                street: "123 Main St".to_string(),
247                city: "Any Town".to_string(),
248                state: "CA".to_string(),
249                zip: "12345".to_string()
250            }
251            .to_string(),
252            "address"
253        );
254    }
255
256    #[test]
257    fn test_unnamed_fields_variant_case_transform() {
258        assert_eq!(
259            TestEnumWithAttribute::DateOfBirth(1, 1, 2000).to_string(),
260            "date-of-birth"
261        );
262    }
263
264    #[test]
265    fn test_unit_field_variant_with_lifetime_and_generics() {
266        assert_eq!(
267            TestEnumWithLifetimeAndGenerics::<'_, String>::Name.to_string(),
268            "Name"
269        );
270    }
271
272    #[test]
273    fn test_named_fields_variant_with_lifetime_and_generics() {
274        assert_eq!(
275            TestEnumWithLifetimeAndGenerics::Address {
276                street: &"123 Main St".to_string(),
277                city: &"Any Town".to_string(),
278                state: &"CA".to_string(),
279                zip: &"12345".to_string()
280            }
281            .to_string(),
282            "Address"
283        );
284    }
285
286    #[test]
287    fn test_unnamed_fields_variant_with_lifetime_and_generics() {
288        assert_eq!(
289            TestEnumWithLifetimeAndGenerics::<'_, String>::DateOfBirth(1, 1, 2000).to_string(),
290            "DateOfBirth"
291        );
292    }
293}