sashite-feen 0.1.0

Field Expression Encoding Notation (FEEN): a compact, ASCII-only, no_std, zero-allocation validator and encoder for board-game positions in abstract strategy games, built on EPIN and SIN.
Documentation
//! Errors produced when parsing or validating a FEEN string.

/// The reason a string is not a valid, canonical FEEN position.
///
/// Returned by [`crate::Feen::parse`]. The taxonomy mirrors the specification's
/// validation requirements (§11) and the reference implementations: each
/// variant pins down one class of failure so the error mapping cannot drift.
///
/// This enum is `#[non_exhaustive]`: future revisions may add variants without
/// a breaking change, so downstream `match` expressions should include a
/// wildcard arm.
///
/// # Examples
///
/// ```
/// use sashite_feen::{Feen, ParseError};
///
/// assert_eq!(Feen::parse("nope").unwrap_err(), ParseError::FieldCount);
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ParseError {
    // --- Input-level ---
    /// The input exceeds [`crate::MAX_STRING_LENGTH`] bytes.
    InputTooLong,
    /// The input contains a non-ASCII byte (FEEN is ASCII-only).
    NonAscii,
    /// The input is not exactly three space-separated fields.
    FieldCount,

    // --- Field 1: piece placement ---
    /// The piece-placement field is empty.
    PlacementEmpty,
    /// The piece-placement field starts with a `/` separator.
    PlacementStartsWithSeparator,
    /// The piece-placement field ends with a `/` separator.
    PlacementEndsWithSeparator,
    /// A segment between separators is empty.
    EmptySegment,
    /// An empty-count token is zero or has a leading zero.
    InvalidEmptyCount,
    /// A piece token is not a valid EPIN identifier.
    InvalidPieceToken,

    // --- Field 1: board geometry ---
    /// The board is not regular: ranks differ in size within a dimension.
    BoardNotRegular,
    /// A separator group of depth `N` encloses a segment lacking depth `N-1`.
    DimensionalCoherence,
    /// The board has more than [`crate::MAX_DIMENSIONS`] dimensions.
    TooManyDimensions,
    /// A dimension has more than [`crate::MAX_DIMENSION_SIZE`] cells.
    DimensionTooLarge,

    // --- Field 2: hands ---
    /// The hands field does not contain exactly one `/` delimiter.
    InvalidHandsDelimiter,
    /// A hand multiplicity is `0`, `1`, or has a leading zero.
    InvalidHandCount,
    /// Identical EPIN tokens in a hand are not aggregated into one item.
    HandNotAggregated,
    /// Hand items are not in canonical order.
    HandNotCanonical,

    // --- Field 3: style–turn ---
    /// The style–turn field does not contain exactly one `/` delimiter.
    InvalidStyleTurnDelimiter,
    /// A style token is not a valid SIN identifier.
    InvalidStyleToken,
    /// The two style tokens are not of opposite case.
    StylesSameCase,

    // --- Cross-field cardinality ---
    /// The board has more than [`crate::MAX_SQUARE_COUNT`] squares.
    TooManySquares,
    /// The position has more pieces than the board has squares.
    TooManyPieces,
}

impl core::fmt::Display for ParseError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        let message = match self {
            Self::InputTooLong => "FEEN string exceeds the maximum length",
            Self::NonAscii => "FEEN string contains a non-ASCII byte",
            Self::FieldCount => "FEEN string must have exactly three space-separated fields",
            Self::PlacementEmpty => "the piece-placement field is empty",
            Self::PlacementStartsWithSeparator => {
                "the piece-placement field must not start with '/'"
            }
            Self::PlacementEndsWithSeparator => "the piece-placement field must not end with '/'",
            Self::EmptySegment => "the piece-placement field has an empty segment",
            Self::InvalidEmptyCount => "an empty-count is zero or has a leading zero",
            Self::InvalidPieceToken => "the piece-placement field contains an invalid piece token",
            Self::BoardNotRegular => "the board is not regular (ranks of differing sizes)",
            Self::DimensionalCoherence => "the board violates dimensional coherence",
            Self::TooManyDimensions => "the board has too many dimensions",
            Self::DimensionTooLarge => "a board dimension exceeds the maximum size",
            Self::InvalidHandsDelimiter => "the hands field must contain exactly one '/'",
            Self::InvalidHandCount => "a hand multiplicity is 0, 1, or has a leading zero",
            Self::HandNotAggregated => "identical hand pieces are not aggregated",
            Self::HandNotCanonical => "hand items are not in canonical order",
            Self::InvalidStyleTurnDelimiter => "the style-turn field must contain exactly one '/'",
            Self::InvalidStyleToken => "the style-turn field contains an invalid style token",
            Self::StylesSameCase => "the two style tokens must be of opposite case",
            Self::TooManySquares => "the board has more squares than the maximum",
            Self::TooManyPieces => "the position has more pieces than squares",
        };
        f.write_str(message)
    }
}

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