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}