1use std::fmt;
2
3#[derive(Debug)]
5pub enum SdkError {
6 Engine(hypen_engine::EngineError),
8
9 ModuleNotFound(String),
11
12 ActionPayload { action: String, message: String },
14
15 StateSerde(String),
17
18 Route(String),
20
21 Component(String),
23
24 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}