Skip to main content

modo/qrcode/
error.rs

1use std::fmt;
2
3/// Errors that can occur during QR code generation or rendering.
4///
5/// Converts into [`modo::Error`](crate::Error) with HTTP 400 (Bad Request)
6/// status. Use [`QrError::code`] to get a stable string identifier for
7/// each variant.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum QrError {
10    /// Input data exceeds QR code capacity for the chosen error correction level.
11    DataTooLong,
12    /// Invalid hex color string (missing `#`, wrong length, or non-hex characters).
13    InvalidColor(String),
14}
15
16impl QrError {
17    /// Returns a stable, namespaced string code for this error
18    /// (e.g. `"qrcode:data_too_long"`).
19    pub fn code(&self) -> &'static str {
20        match self {
21            Self::DataTooLong => "qrcode:data_too_long",
22            Self::InvalidColor(_) => "qrcode:invalid_color",
23        }
24    }
25}
26
27impl fmt::Display for QrError {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        match self {
30            Self::DataTooLong => write!(f, "input data exceeds QR code capacity"),
31            Self::InvalidColor(c) => write!(f, "invalid hex color: {c}"),
32        }
33    }
34}
35
36impl std::error::Error for QrError {}
37
38impl From<QrError> for crate::Error {
39    fn from(err: QrError) -> Self {
40        let code = err.code();
41        crate::Error::bad_request(err.to_string())
42            .chain(err)
43            .with_code(code)
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn all_variants_have_unique_codes() {
53        let variants = [QrError::DataTooLong, QrError::InvalidColor("#bad".into())];
54        let mut codes: Vec<&str> = variants.iter().map(|v| v.code()).collect();
55        let len_before = codes.len();
56        codes.sort();
57        codes.dedup();
58        assert_eq!(codes.len(), len_before, "duplicate error codes found");
59    }
60
61    #[test]
62    fn all_codes_start_with_qrcode_prefix() {
63        let variants = [QrError::DataTooLong, QrError::InvalidColor("x".into())];
64        for v in &variants {
65            assert!(
66                v.code().starts_with("qrcode:"),
67                "code {} missing prefix",
68                v.code()
69            );
70        }
71    }
72
73    #[test]
74    fn display_is_human_readable() {
75        assert_eq!(
76            QrError::DataTooLong.to_string(),
77            "input data exceeds QR code capacity"
78        );
79        assert_eq!(
80            QrError::InvalidColor("#xyz".into()).to_string(),
81            "invalid hex color: #xyz"
82        );
83    }
84
85    #[test]
86    fn converts_to_modo_error() {
87        let err: crate::Error = QrError::DataTooLong.into();
88        assert_eq!(err.status(), http::StatusCode::BAD_REQUEST);
89        assert_eq!(err.error_code(), Some("qrcode:data_too_long"));
90    }
91
92    #[test]
93    fn recoverable_via_source_as() {
94        let err: crate::Error = QrError::InvalidColor("#bad".into()).into();
95        let qr_err = err.source_as::<QrError>();
96        assert_eq!(qr_err, Some(&QrError::InvalidColor("#bad".into())));
97    }
98}