enum-path 0.1.0

Derive FromStr and Display impls for enums that follow a hierarchical path-like serialization scheme
Documentation
//! 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.
//!
//! | Attribute            | Meaning                                                            |
//! |----------------------|--------------------------------------------------------------------|
//! | `FromStr`            | Derive `core::str::FromStr` for the enum.                          |
//! | `Display`            | Derive `core::fmt::Display` for the enum.                          |
//! | `rename_all = "..."` | Apply a case convention to every variant; see [Rename casing](#rename-casing). |
//! | `delimiter = "..."`  | Separator between a variant name and its inner type; defaults to `"."`. |
//! | `case_insensitive`   | Match variant names with ASCII case-insensitive comparison.        |
//! | `error = MyError`    | Use a custom error type from generated `FromStr` impls; see [Custom error types](#custom-error-types). |
//! | `crate = path`       | Override the runtime crate path when `enum_path` is re-exported.    |
//!
//! # Per-variant attributes
//!
//! | Attribute            | Meaning                                          |
//! |----------------------|--------------------------------------------------|
//! | `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 `ffl`) 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).

#![no_std]

extern crate alloc;

use alloc::string::String;
use core::fmt;

pub use enum_path_derive::EnumPath;

/// Error returned by generated `FromStr` implementations.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Error
{
    /// The input that failed to parse.
    pub input:    String,
    /// The type name that was being parsed.
    pub expected: &'static str,
}

impl fmt::Display for Error
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
    {
        write!(f, "invalid {} value: {:?}", self.expected, self.input)
    }
}

impl core::error::Error for Error {}

/// Implementation details used by generated code. Not part of the public API.
#[doc(hidden)]
pub mod __private
{
    pub use alloc::borrow::ToOwned;
}