Skip to main content

powerio_pkg/
error.rs

1//! Failures reading, writing, and transforming a `.pio.json` package.
2//!
3//! Every fallible entry point used to return `serde_json::Result`, so a
4//! version rejection, a `model_kind` inconsistency, and a genuine JSON syntax
5//! failure all arrived as one opaque `serde_json::Error` that a caller could
6//! only tell apart by matching on the message text. Each is its own variant
7//! here.
8
9use thiserror::Error as ThisError;
10
11/// A `.pio.json` failure.
12#[derive(Debug, ThisError)]
13#[non_exhaustive]
14pub enum Error {
15    /// The document is not well formed JSON, or does not have the shape the
16    /// document requires.
17    #[error("invalid .pio.json package: {0}")]
18    Malformed(#[source] serde_json::Error),
19
20    /// The document comes from a powerio lineage this build does not read.
21    #[error("{0}")]
22    UnsupportedVersion(String),
23
24    /// The document's `model_kind` disagrees with the payload it carries.
25    #[error("model_kind does not match model.kind")]
26    ModelKindMismatch,
27
28    /// An operating point or study index that the document does not contain.
29    #[error("{0}")]
30    NoSuchIndex(String),
31
32    /// The payload could not be built, applied, or serialized.
33    #[error("{0}")]
34    Payload(String),
35
36    /// A failure from the balanced model, its readers, or its writers.
37    #[error(transparent)]
38    Core(#[from] powerio::Error),
39
40    /// A failure from the multiconductor model.
41    #[error(transparent)]
42    Multiconductor(#[from] powerio_dist::Error),
43
44    /// Serializing the package to JSON failed.
45    #[error("serializing .pio.json: {0}")]
46    Serialize(#[source] serde_json::Error),
47}
48
49impl From<serde_json::Error> for Error {
50    /// A `serde_json` failure raised inside this crate is a serialization
51    /// step, never a document the caller handed us: `from_json` names its own
52    /// failures through [`Error::Malformed`] before any of these can fire.
53    fn from(error: serde_json::Error) -> Self {
54        Error::Serialize(error)
55    }
56}
57
58impl Error {
59    /// Classify this error, using the hub's taxonomy.
60    #[must_use]
61    pub fn category(&self) -> powerio::ErrorCategory {
62        use powerio::ErrorCategory as C;
63        match self {
64            Error::Core(inner) => inner.category(),
65            Error::Malformed(_) | Error::UnsupportedVersion(_) | Error::Multiconductor(_) => {
66                C::Parse
67            }
68            Error::ModelKindMismatch | Error::NoSuchIndex(_) | Error::Payload(_) => C::Data,
69            Error::Serialize(_) => C::Output,
70        }
71    }
72}
73
74/// The result type every fallible entry point in this crate returns.
75pub type Result<T> = std::result::Result<T, Error>;
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use powerio::ErrorCategory::{Data, Parse};
81
82    #[test]
83    fn a_version_rejection_is_not_a_syntax_failure() {
84        // The whole point of the split: both used to be one `serde_json::Error`
85        // that a caller could only tell apart by matching on message text.
86        let version = Error::UnsupportedVersion("stated 0.2.1".into());
87        assert_eq!(version.category(), Parse);
88        assert!(matches!(version, Error::UnsupportedVersion(_)));
89        assert!(matches!(Error::ModelKindMismatch, Error::ModelKindMismatch));
90        assert_eq!(Error::ModelKindMismatch.category(), Data);
91    }
92
93    #[test]
94    fn a_wrapped_hub_error_keeps_its_own_message() {
95        let wrapped: Error = powerio::Error::MissingField("bus").into();
96        assert_eq!(
97            wrapped.to_string(),
98            powerio::Error::MissingField("bus").to_string()
99        );
100    }
101}