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    #[error("LinDist3Flow preparation failed: {message}")]
37    LinDist3FlowPreparation { message: String },
38}
39
40impl Error {
41    /// The registry entry for this error. The match is exhaustive over the
42    /// variant set, so a new variant must be coded here before it compiles.
43    #[must_use]
44    pub fn code(&self) -> &'static DiagnosticInfo {
45        match self {
46            Error::Io { .. } => &codes::READ_DIST_IO_FAILED,
47            Error::Json { .. } => &codes::PARSE_DIST_MALFORMED,
48            Error::FormatRead { .. } => &codes::PARSE_DIST_SOURCE_MALFORMED,
49            Error::UnknownFormat(_) => &codes::REQUEST_DIST_FORMAT_UNKNOWN,
50            Error::KronReduction { .. } => &codes::TRANSFORM_DIST_KRON_REDUCTION_FAILED,
51            Error::LinDist3FlowPreparation { .. } => {
52                &codes::TRANSFORM_DIST_LINDIST3FLOW_PREPARATION_FAILED
53            }
54        }
55    }
56
57    /// Classify this error onto the five tokens every powerio surface uses.
58    #[must_use]
59    pub fn category(&self) -> ErrorCategory {
60        match self {
61            Error::Io { .. } => ErrorCategory::Io,
62            Error::Json { .. } | Error::FormatRead { .. } => ErrorCategory::Parse,
63            Error::UnknownFormat(_) => ErrorCategory::Request,
64            Error::KronReduction { .. } | Error::LinDist3FlowPreparation { .. } => {
65                ErrorCategory::Data
66            }
67        }
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn every_error_code_publishes_the_category_the_variant_reports() {
77        let every = [
78            Error::Io {
79                path: "case.dss".into(),
80                source: std::io::Error::from(std::io::ErrorKind::NotFound),
81            },
82            Error::Json {
83                format: "BMOPF",
84                message: "top level is not an object".into(),
85            },
86            Error::FormatRead {
87                format: "case text",
88                message: "not valid UTF-8".into(),
89            },
90            Error::UnknownFormat("xyz".into()),
91            Error::KronReduction {
92                message: "ambiguous neutral".into(),
93            },
94            Error::LinDist3FlowPreparation {
95                message: "unsupported transformer".into(),
96            },
97        ];
98        for error in &every {
99            assert_eq!(
100                error.code().category,
101                Some(error.category()),
102                "{}",
103                error.code().code
104            );
105        }
106    }
107}