Skip to main content

bible_io_references/
error.rs

1//! Typed parsing errors and stable machine-readable error codes.
2
3use core::fmt;
4
5/// Stable classification for failures produced by the reference and passage parsers.
6#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
7#[non_exhaustive]
8pub enum ParseErrorKind {
9    /// A fallback classification for a parse failure without a more specific kind.
10    Unknown,
11    /// The input contains no reference text.
12    EmptyReference,
13    /// The input does not match the requested grammar.
14    PatternMismatch,
15    /// No known alias matched the book token.
16    UnknownBook,
17    /// A numeric token was not an integer.
18    InvalidNumericToken,
19    /// A chapter or verse number was zero.
20    NonPositiveNumericToken,
21    /// A chapter or verse number exceeded the package sanity limit.
22    NumericTokenOutOfRange,
23    /// No text remained in a required book token.
24    EmptyBookToken,
25    /// More than one distinct book matched under a rejecting policy.
26    AmbiguousBook,
27    /// The requested language has no built-in or custom aliases.
28    UnsupportedLanguage,
29    /// A same-book range does not move forward.
30    SameBookRangeNotAscending,
31    /// A cross-book range does not move forward in this crate's book order.
32    CrossBookRangeNotAscending,
33    /// A required chapter or verse number is absent.
34    MissingNumericToken,
35}
36
37impl ParseErrorKind {
38    /// Return the stable snake-case code used by the Dart package.
39    #[must_use]
40    pub const fn code(self) -> &'static str {
41        match self {
42            Self::Unknown => "unknown",
43            Self::EmptyReference => "empty_reference",
44            Self::PatternMismatch => "pattern_mismatch",
45            Self::UnknownBook => "unknown_book",
46            Self::InvalidNumericToken => "invalid_numeric_token",
47            Self::NonPositiveNumericToken => "non_positive_numeric_token",
48            Self::NumericTokenOutOfRange => "numeric_token_out_of_range",
49            Self::EmptyBookToken => "empty_book_token",
50            Self::AmbiguousBook => "ambiguous_book",
51            Self::UnsupportedLanguage => "unsupported_language",
52            Self::SameBookRangeNotAscending => "same_book_range_not_ascending",
53            Self::CrossBookRangeNotAscending => "cross_book_range_not_ascending",
54            Self::MissingNumericToken => "missing_numeric_token",
55        }
56    }
57}
58
59/// An owned, structured parse failure.
60#[derive(Clone, Debug, Eq, PartialEq)]
61pub struct ParseError {
62    kind: ParseErrorKind,
63    details: String,
64}
65
66impl ParseError {
67    /// Construct a parse error with a stable classification and diagnostic.
68    #[must_use]
69    pub fn new(kind: ParseErrorKind, details: impl Into<String>) -> Self {
70        Self {
71            kind,
72            details: details.into(),
73        }
74    }
75
76    /// Return the typed error classification.
77    #[must_use]
78    pub const fn kind(&self) -> ParseErrorKind {
79        self.kind
80    }
81
82    /// Return the stable machine-readable error code.
83    #[must_use]
84    pub const fn code(&self) -> &'static str {
85        self.kind.code()
86    }
87
88    /// Return a human-readable diagnostic with input-specific context.
89    #[must_use]
90    pub fn details(&self) -> &str {
91        &self.details
92    }
93}
94
95impl fmt::Display for ParseError {
96    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
97        write!(formatter, "{}: {}", self.code(), self.details)
98    }
99}
100
101impl std::error::Error for ParseError {}