Skip to main content

bwipp/
error.rs

1//! Error type.
2
3use std::fmt;
4
5/// Errors that can be returned by the encoder or renderer.
6///
7/// `Error` implements `std::error::Error` and `std::fmt::Display`, so it
8/// integrates with `?`, `anyhow`, and `thiserror` like any other library
9/// error.
10///
11/// # Example
12///
13/// ```
14/// use bwipp::{render_svg, Options, Symbology, Error};
15///
16/// let err = render_svg(Symbology::Ean13, "abc", &Options::default()).unwrap_err();
17/// // The Display impl prepends a kind-specific prefix:
18/// assert!(err.to_string().starts_with("invalid data:"));
19/// // Pattern-match for programmatic recovery:
20/// assert!(matches!(err, Error::InvalidData(_)));
21/// ```
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum Error {
24    /// The input data is not valid for the requested symbology
25    /// (e.g. wrong length, illegal character, bad check digit).
26    InvalidData(String),
27    /// An option value is out of range or refers to an unknown setting.
28    InvalidOption(String),
29    /// The symbology requested is recognized but the requested
30    /// option / variant lies outside this Rust port's scope (e.g.
31    /// BWIPP feature that the port has consciously deferred).
32    ///
33    /// **Status note.** As of the current release, no code path in the
34    /// crate actually returns this variant — the catalog is
35    /// 169 verified / 0 partial (see
36    /// [`PORT_STATUS.md`](https://github.com/erdzan12/bwipp-rs/blob/main/rust/PORT_STATUS.md)),
37    /// and the BWIPP opt-in knobs that are deferred (e.g. POSICODE
38    /// `parsefnc`, Code 16K `sam`, Code One `eci`/`version`,
39    /// Ultracode `rev=1`/`raw=true`/`link1`) are surfaced via
40    /// [`Error::InvalidData`] / [`Error::InvalidOption`] with a tagged
41    /// message rather than via this variant. The variant is kept in
42    /// the public API as a reserved slot for future semantic
43    /// migration (callers who want to distinguish "input invalid" from
44    /// "input valid but encoder lacks support") and removing it would
45    /// be a breaking change. Match on it defensively if you care, but
46    /// don't expect to see it fire today.
47    Unimplemented(&'static str),
48    /// A backing crate (e.g. `qrcode`, `datamatrix`) reported an error.
49    Backend(String),
50}
51
52impl fmt::Display for Error {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        match self {
55            Error::InvalidData(s) => write!(f, "invalid data: {s}"),
56            Error::InvalidOption(s) => write!(f, "invalid option: {s}"),
57            Error::Unimplemented(s) => write!(f, "{s} is outside this port's scope"),
58            Error::Backend(s) => write!(f, "backend error: {s}"),
59        }
60    }
61}
62
63impl std::error::Error for Error {}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    /// Stage 11.A8c — pin every Display format for the Error enum.
70    /// Kills `delete match arm` mutations on each arm and any
71    /// format-string swap (e.g. "invalid data" → "invalid option").
72    #[test]
73    fn display_format_per_variant() {
74        let e = Error::InvalidData("bad input".into());
75        assert_eq!(e.to_string(), "invalid data: bad input");
76
77        let e = Error::InvalidOption("eclevel=Z".into());
78        assert_eq!(e.to_string(), "invalid option: eclevel=Z");
79
80        let e = Error::Unimplemented("foo");
81        assert_eq!(e.to_string(), "foo is outside this port's scope");
82
83        let e = Error::Backend("qrcode crate failed".into());
84        assert_eq!(e.to_string(), "backend error: qrcode crate failed");
85    }
86
87    /// Stage 11.A8c — pin PartialEq behavior across variants.
88    /// Kills any `derive(PartialEq)` accidental removal mutation.
89    #[test]
90    fn equality_across_variants() {
91        assert_eq!(
92            Error::InvalidData("x".into()),
93            Error::InvalidData("x".into())
94        );
95        assert_ne!(
96            Error::InvalidData("x".into()),
97            Error::InvalidData("y".into())
98        );
99        // Different variants are never equal.
100        assert_ne!(
101            Error::InvalidData("x".into()),
102            Error::InvalidOption("x".into())
103        );
104        assert_ne!(Error::Unimplemented("foo"), Error::Backend("foo".into()));
105    }
106}