Skip to main content

Crate enum_path

Crate enum_path 

Source
Expand description

Derive core::str::FromStr and core::fmt::Display for enums whose variants serialize to hierarchical, delimiter-separated paths.

use enum_path::EnumPath;

#[derive(EnumPath, Clone, Debug, PartialEq, Eq)]
#[enum_path(FromStr, Display, rename_all = "snake_case")]
enum Action
{
    Exit,
    SendMessage(String),
    SetState(State),
}

#[derive(EnumPath, Clone, Debug, PartialEq, Eq)]
#[enum_path(FromStr, Display)]
enum State
{
    Idle,
    Ready,
}

let parsed: Action = "set_state.Idle".parse().unwrap();
assert_eq!(parsed.to_string(), "set_state.Idle");

§Enum-level attributes

Attached as #[enum_path(...)] on the enum definition. Multiple flags may appear in the same attribute, comma-separated.

AttributeMeaning
FromStrDerive core::str::FromStr for the enum.
DisplayDerive core::fmt::Display for the enum.
rename_all = "..."Apply a case convention to every variant; see Rename casing.
delimiter = "..."Separator between a variant name and its inner type; defaults to ".".
case_insensitiveMatch variant names with ASCII case-insensitive comparison.
error = MyErrorUse a custom error type from generated FromStr impls; see Custom error types.
crate = pathOverride the runtime crate path when enum_path is re-exported.

§Per-variant attributes

AttributeMeaning
rename = "..."Override the serialized name for this variant.

§Variant shape requirements

Every variant must be either a unit variant (e.g. Exit) or a single-field tuple variant (e.g. SendMessage(String)). Multi-field tuple variants and named-field variants are rejected at compile time.

The inner type of a single-field tuple variant must implement core::fmt::Display when the enum derives Display, and core::str::FromStr when the enum derives FromStr. There is no Self::Item: FromStr bound on the generated impl; instead the macro generates a <T as FromStr>::from_str(rest) call inline, which produces a type error at the call site if the bound is not satisfied.

§Custom error types

Pass #[enum_path(FromStr, error = MyError)] to override the error type used by the generated FromStr impl. The supplied type must implement core::convert::From<enum_path::Error> so the macro can construct it from a parse failure:

use enum_path::EnumPath;

#[derive(Debug)]
struct MyError(enum_path::Error);

impl From<enum_path::Error> for MyError
{
    fn from(e: enum_path::Error) -> Self
    {
        Self(e)
    }
}

#[derive(EnumPath, Clone, Debug, PartialEq, Eq)]
#[enum_path(FromStr, error = MyError)]
enum Thing
{
    Foo,
}

The generated FromStr::Err type becomes MyError; the bound is MyError: From<enum_path::Error>. The struct field Error::expected always holds the original enum’s name regardless of any wrapping conversion.

§Rename casing

rename_all accepts the usual serde-style values, but the algorithm is built around ASCII case detection and may diverge from serde for some inputs:

  • "lowercase" / "UPPERCASE" call str::to_lowercase / to_uppercase on the entire identifier with no word splitting.
  • "snake_case", "SCREAMING_SNAKE_CASE", "kebab-case", "SCREAMING-KEBAB-CASE", "PascalCase", "camelCase" first split the identifier into words by walking ASCII case boundaries and digits, then join the lowercased words with the appropriate separator.

Caveats of the word splitter:

  • Word boundaries are detected per-character via char::is_lowercase, char::is_ascii_digit, and char::is_alphanumeric. Identifiers containing non-ASCII letters are split on uppercase / lowercase transitions of those letters (so ƑōőƂɑρ splits as ƒōő + ƃɑρ).
  • The splitter treats any non-alphanumeric ASCII character as a word boundary and drops it (Foo_Bar -> ["Foo", "Bar"]).
  • A digit followed by a non-digit alphabetic character starts a new word (foo23bar -> foo23.bar), and a non-digit character followed by a digit does not (so Foo2 -> ["Foo2"]).
  • Unicode width casing (e.g. the ligature ) goes through char::to_lowercase/to_uppercase, which can produce multiple output characters from one input character.

If you need behavior the rules above don’t capture, set the variant name explicitly with #[enum_path(rename = "...")].

§Uniqueness

The macro rejects (at compile time) two variants that resolve to the same serialized name, since FromStr would otherwise become order-dependent. It also rejects a serialized name that is a prefix of another serialized name followed by the configured delimiter (e.g. variants named foo and foo.bar would alias under the default . delimiter).

Structs§

Error
Error returned by generated FromStr implementations.

Derive Macros§

EnumPath