Skip to main content

enum_path/
lib.rs

1//! Derive `core::str::FromStr` and `core::fmt::Display` for enums whose
2//! variants serialize to hierarchical, delimiter-separated paths.
3//!
4//! ```
5//! use enum_path::EnumPath;
6//!
7//! #[derive(EnumPath, Clone, Debug, PartialEq, Eq)]
8//! #[enum_path(FromStr, Display, rename_all = "snake_case")]
9//! enum Action
10//! {
11//!     Exit,
12//!     SendMessage(String),
13//!     SetState(State),
14//! }
15//!
16//! #[derive(EnumPath, Clone, Debug, PartialEq, Eq)]
17//! #[enum_path(FromStr, Display)]
18//! enum State
19//! {
20//!     Idle,
21//!     Ready,
22//! }
23//!
24//! let parsed: Action = "set_state.Idle".parse().unwrap();
25//! assert_eq!(parsed.to_string(), "set_state.Idle");
26//! ```
27//!
28//! # Enum-level attributes
29//!
30//! Attached as `#[enum_path(...)]` on the enum definition. Multiple flags
31//! may appear in the same attribute, comma-separated.
32//!
33//! | Attribute            | Meaning                                                            |
34//! |----------------------|--------------------------------------------------------------------|
35//! | `FromStr`            | Derive `core::str::FromStr` for the enum.                          |
36//! | `Display`            | Derive `core::fmt::Display` for the enum.                          |
37//! | `rename_all = "..."` | Apply a case convention to every variant; see [Rename casing](#rename-casing). |
38//! | `delimiter = "..."`  | Separator between a variant name and its inner type; defaults to `"."`. |
39//! | `case_insensitive`   | Match variant names with ASCII case-insensitive comparison.        |
40//! | `error = MyError`    | Use a custom error type from generated `FromStr` impls; see [Custom error types](#custom-error-types). |
41//! | `crate = path`       | Override the runtime crate path when `enum_path` is re-exported.    |
42//!
43//! # Per-variant attributes
44//!
45//! | Attribute            | Meaning                                          |
46//! |----------------------|--------------------------------------------------|
47//! | `rename = "..."`     | Override the serialized name for this variant.   |
48//!
49//! # Variant shape requirements
50//!
51//! Every variant must be either a unit variant (e.g. `Exit`) or a single-field
52//! tuple variant (e.g. `SendMessage(String)`). Multi-field tuple variants
53//! and named-field variants are rejected at compile time.
54//!
55//! The inner type of a single-field tuple variant must implement
56//! `core::fmt::Display` when the enum derives `Display`, and
57//! `core::str::FromStr` when the enum derives `FromStr`. There is no
58//! `Self::Item: FromStr` bound on the generated impl; instead the macro
59//! generates a `<T as FromStr>::from_str(rest)` call inline, which
60//! produces a type error at the call site if the bound is not satisfied.
61//!
62//! # Custom error types
63//!
64//! Pass `#[enum_path(FromStr, error = MyError)]` to override the error
65//! type used by the generated `FromStr` impl. The supplied type must
66//! implement `core::convert::From<enum_path::Error>` so the macro can
67//! construct it from a parse failure:
68//!
69//! ```
70//! use enum_path::EnumPath;
71//!
72//! #[derive(Debug)]
73//! struct MyError(enum_path::Error);
74//!
75//! impl From<enum_path::Error> for MyError
76//! {
77//!     fn from(e: enum_path::Error) -> Self
78//!     {
79//!         Self(e)
80//!     }
81//! }
82//!
83//! #[derive(EnumPath, Clone, Debug, PartialEq, Eq)]
84//! #[enum_path(FromStr, error = MyError)]
85//! enum Thing
86//! {
87//!     Foo,
88//! }
89//! ```
90//!
91//! The generated `FromStr::Err` type becomes `MyError`; the bound is
92//! `MyError: From<enum_path::Error>`. The struct field
93//! `Error::expected` always holds the *original* enum's name regardless
94//! of any wrapping conversion.
95//!
96//! # Rename casing
97//!
98//! `rename_all` accepts the usual serde-style values, but the algorithm
99//! is built around ASCII case detection and may diverge from serde for
100//! some inputs:
101//!
102//! - **`"lowercase"`** / **`"UPPERCASE"`** call `str::to_lowercase` /
103//!   `to_uppercase` on the entire identifier with no word splitting.
104//! - **`"snake_case"`**, **`"SCREAMING_SNAKE_CASE"`**, **`"kebab-case"`**,
105//!   **`"SCREAMING-KEBAB-CASE"`**, **`"PascalCase"`**, **`"camelCase"`**
106//!   first split the identifier into words by walking ASCII case
107//!   boundaries and digits, then join the lowercased words with the
108//!   appropriate separator.
109//!
110//! Caveats of the word splitter:
111//!
112//! - Word boundaries are detected per-character via
113//!   `char::is_lowercase`, `char::is_ascii_digit`, and
114//!   `char::is_alphanumeric`. Identifiers containing non-ASCII letters
115//!   are split on uppercase / lowercase transitions of those letters
116//!   (so `ƑōőƂɑρ` splits as `ƒōő` + `ƃɑρ`).
117//! - The splitter treats any non-alphanumeric ASCII character as a word
118//!   boundary and drops it (`Foo_Bar` -> `["Foo", "Bar"]`).
119//! - A digit followed by a non-digit alphabetic character starts a new
120//!   word (`foo23bar` -> `foo23.bar`), and a non-digit character
121//!   followed by a digit does not (so `Foo2` -> `["Foo2"]`).
122//! - Unicode width casing (e.g. the ligature `ffl`) goes through
123//!   `char::to_lowercase`/`to_uppercase`, which can produce multiple
124//!   output characters from one input character.
125//!
126//! If you need behavior the rules above don't capture, set the variant
127//! name explicitly with `#[enum_path(rename = "...")]`.
128//!
129//! # Uniqueness
130//!
131//! The macro rejects (at compile time) two variants that resolve to the
132//! same serialized name, since `FromStr` would otherwise become
133//! order-dependent. It also rejects a serialized name that is a prefix
134//! of another serialized name followed by the configured delimiter
135//! (e.g. variants named `foo` and `foo.bar` would alias under the
136//! default `.` delimiter).
137
138#![no_std]
139
140extern crate alloc;
141
142use alloc::string::String;
143use core::fmt;
144
145pub use enum_path_derive::EnumPath;
146
147/// Error returned by generated `FromStr` implementations.
148#[derive(Clone, Debug, PartialEq, Eq, Hash)]
149pub struct Error
150{
151    /// The input that failed to parse.
152    pub input:    String,
153    /// The type name that was being parsed.
154    pub expected: &'static str,
155}
156
157impl fmt::Display for Error
158{
159    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
160    {
161        write!(f, "invalid {} value: {:?}", self.expected, self.input)
162    }
163}
164
165impl core::error::Error for Error {}
166
167/// Implementation details used by generated code. Not part of the public API.
168#[doc(hidden)]
169pub mod __private
170{
171    pub use alloc::borrow::ToOwned;
172}