Skip to main content

hypen_server/
error.rs

1use std::fmt;
2
3/// Error type for the Hypen SDK.
4#[derive(Debug)]
5pub enum SdkError {
6    /// Error from the underlying engine.
7    Engine(hypen_engine::EngineError),
8
9    /// A module was not found in the registry.
10    ModuleNotFound(String),
11
12    /// Failed to deserialize an action payload.
13    ActionPayload { action: String, message: String },
14
15    /// State serialization/deserialization error.
16    StateSerde(String),
17
18    /// Route matching or navigation error.
19    Route(String),
20
21    /// Component file I/O or resolution error.
22    Component(String),
23
24    /// Generic SDK error.
25    Other(String),
26}
27
28impl fmt::Display for SdkError {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        match self {
31            SdkError::Engine(e) => write!(f, "Engine error: {e}"),
32            SdkError::ModuleNotFound(name) => write!(f, "Module not found: {name}"),
33            SdkError::ActionPayload { action, message } => {
34                write!(f, "Invalid payload for action '{action}': {message}")
35            }
36            SdkError::StateSerde(msg) => write!(f, "State serialization error: {msg}"),
37            SdkError::Route(msg) => write!(f, "Route error: {msg}"),
38            SdkError::Component(msg) => write!(f, "Component error: {msg}"),
39            SdkError::Other(msg) => write!(f, "{msg}"),
40        }
41    }
42}
43
44impl std::error::Error for SdkError {
45    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
46        match self {
47            SdkError::Engine(e) => Some(e),
48            _ => None,
49        }
50    }
51}
52
53impl From<hypen_engine::EngineError> for SdkError {
54    fn from(e: hypen_engine::EngineError) -> Self {
55        SdkError::Engine(e)
56    }
57}
58
59impl From<serde_json::Error> for SdkError {
60    fn from(e: serde_json::Error) -> Self {
61        SdkError::StateSerde(e.to_string())
62    }
63}
64
65pub type Result<T> = std::result::Result<T, SdkError>;
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use std::error::Error;
71
72    #[test]
73    fn test_display_all_variants() {
74        let cases: Vec<(SdkError, &str)> = vec![
75            (
76                SdkError::ModuleNotFound("Counter".into()),
77                "Module not found: Counter",
78            ),
79            (
80                SdkError::ActionPayload {
81                    action: "add".into(),
82                    message: "missing field".into(),
83                },
84                "Invalid payload for action 'add': missing field",
85            ),
86            (
87                SdkError::StateSerde("bad json".into()),
88                "State serialization error: bad json",
89            ),
90            (SdkError::Route("no match".into()), "Route error: no match"),
91            (
92                SdkError::Component("not found".into()),
93                "Component error: not found",
94            ),
95            (SdkError::Other("something".into()), "something"),
96        ];
97
98        for (err, expected) in cases {
99            assert_eq!(err.to_string(), expected);
100        }
101    }
102
103    #[test]
104    fn test_display_engine_variant() {
105        let err = SdkError::Engine(hypen_engine::EngineError::ActionNotFound("missing".into()));
106        let msg = err.to_string();
107        assert!(msg.starts_with("Engine error:"));
108    }
109
110    #[test]
111    fn test_source_engine_returns_some() {
112        let engine_err = hypen_engine::EngineError::ActionNotFound("x".into());
113        let err = SdkError::Engine(engine_err);
114        assert!(err.source().is_some());
115    }
116
117    #[test]
118    fn test_source_non_engine_returns_none() {
119        let err = SdkError::Other("hello".into());
120        assert!(err.source().is_none());
121    }
122
123    #[test]
124    fn test_from_engine_error() {
125        let engine_err = hypen_engine::EngineError::ActionNotFound("x".into());
126        let sdk_err: SdkError = engine_err.into();
127        assert!(matches!(sdk_err, SdkError::Engine(_)));
128    }
129
130    #[test]
131    fn test_from_serde_error() {
132        let serde_err = serde_json::from_str::<i32>("not json").unwrap_err();
133        let sdk_err: SdkError = serde_err.into();
134        assert!(matches!(sdk_err, SdkError::StateSerde(_)));
135    }
136}