Skip to main content

powerio_dist/
error.rs

1use powerio_core::{DiagnosticInfo, ErrorCategory};
2use thiserror::Error;
3
4use crate::diagnostics::codes;
5
6pub type Result<T> = std::result::Result<T, Error>;
7
8#[derive(Debug, Error)]
9#[non_exhaustive]
10pub enum Error {
11    #[error("io error reading {path}: {source}")]
12    Io {
13        path: String,
14        #[source]
15        source: std::io::Error,
16    },
17
18    #[error("malformed {format} JSON: {message}")]
19    Json {
20        format: &'static str,
21        message: String,
22    },
23
24    #[error("{format} read error: {message}")]
25    FormatRead {
26        format: &'static str,
27        message: String,
28    },
29
30    #[error("unknown distribution format `{0}` (expected dss, bmopf, or pmd)")]
31    UnknownFormat(String),
32
33    #[error("neutral Kron reduction failed: {message}")]
34    KronReduction { message: String },
35}
36
37impl Error {
38    /// The registry entry for this error. The match is exhaustive over the
39    /// variant set, so a new variant must be coded here before it compiles.
40    #[must_use]
41    pub fn code(&self) -> &'static DiagnosticInfo {
42        match self {
43            Error::Io { .. } => &codes::READ_DIST_IO_FAILED,
44            Error::Json { .. } => &codes::PARSE_DIST_MALFORMED,
45            Error::FormatRead { .. } => &codes::PARSE_DIST_SOURCE_MALFORMED,
46            Error::UnknownFormat(_) => &codes::REQUEST_DIST_FORMAT_UNKNOWN,
47            Error::KronReduction { .. } => &codes::TRANSFORM_DIST_KRON_REDUCTION_FAILED,
48        }
49    }
50
51    /// Classify this error onto the five tokens every powerio surface uses.
52    #[must_use]
53    pub fn category(&self) -> ErrorCategory {
54        match self {
55            Error::Io { .. } => ErrorCategory::Io,
56            Error::Json { .. } | Error::FormatRead { .. } => ErrorCategory::Parse,
57            Error::UnknownFormat(_) => ErrorCategory::Request,
58            Error::KronReduction { .. } => ErrorCategory::Data,
59        }
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn every_error_code_publishes_the_category_the_variant_reports() {
69        let every = [
70            Error::Io {
71                path: "case.dss".into(),
72                source: std::io::Error::from(std::io::ErrorKind::NotFound),
73            },
74            Error::Json {
75                format: "BMOPF",
76                message: "top level is not an object".into(),
77            },
78            Error::FormatRead {
79                format: "case text",
80                message: "not valid UTF-8".into(),
81            },
82            Error::UnknownFormat("xyz".into()),
83            Error::KronReduction {
84                message: "ambiguous neutral".into(),
85            },
86        ];
87        for error in &every {
88            assert_eq!(
89                error.code().category,
90                Some(error.category()),
91                "{}",
92                error.code().code
93            );
94        }
95    }
96}