1use serde::{Serialize, Serializer};
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
4#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
5pub enum OwsErrorCode {
6 WalletNotFound,
7 ChainNotSupported,
8 InvalidPassphrase,
9 InvalidInput,
10 CaipParseError,
11 PolicyDenied,
12 ApiKeyNotFound,
13 ApiKeyExpired,
14}
15
16#[derive(Debug, Clone, thiserror::Error)]
17pub enum OwsError {
18 #[error("wallet not found: {id}")]
19 WalletNotFound { id: String },
20
21 #[error("chain not supported: {chain}")]
22 ChainNotSupported { chain: String },
23
24 #[error("invalid passphrase")]
25 InvalidPassphrase,
26
27 #[error("invalid input: {message}")]
28 InvalidInput { message: String },
29
30 #[error("CAIP parse error: {message}")]
31 CaipParseError { message: String },
32
33 #[error("policy denied: {reason}")]
34 PolicyDenied { policy_id: String, reason: String },
35
36 #[error("API key not found")]
37 ApiKeyNotFound,
38
39 #[error("API key expired: {id}")]
40 ApiKeyExpired { id: String },
41}
42
43impl OwsError {
44 pub fn code(&self) -> OwsErrorCode {
45 match self {
46 OwsError::WalletNotFound { .. } => OwsErrorCode::WalletNotFound,
47 OwsError::ChainNotSupported { .. } => OwsErrorCode::ChainNotSupported,
48 OwsError::InvalidPassphrase => OwsErrorCode::InvalidPassphrase,
49 OwsError::InvalidInput { .. } => OwsErrorCode::InvalidInput,
50 OwsError::CaipParseError { .. } => OwsErrorCode::CaipParseError,
51 OwsError::PolicyDenied { .. } => OwsErrorCode::PolicyDenied,
52 OwsError::ApiKeyNotFound => OwsErrorCode::ApiKeyNotFound,
53 OwsError::ApiKeyExpired { .. } => OwsErrorCode::ApiKeyExpired,
54 }
55 }
56}
57
58#[derive(Serialize)]
59struct ErrorPayload {
60 code: OwsErrorCode,
61 message: String,
62}
63
64impl Serialize for OwsError {
65 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
66 let payload = ErrorPayload {
67 code: self.code(),
68 message: self.to_string(),
69 };
70 payload.serialize(serializer)
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn test_code_mapping_wallet_not_found() {
80 let err = OwsError::WalletNotFound {
81 id: "abc".to_string(),
82 };
83 assert_eq!(err.code(), OwsErrorCode::WalletNotFound);
84 }
85
86 #[test]
87 fn test_code_mapping_all_variants() {
88 assert_eq!(
89 OwsError::ChainNotSupported { chain: "x".into() }.code(),
90 OwsErrorCode::ChainNotSupported
91 );
92 assert_eq!(
93 OwsError::InvalidPassphrase.code(),
94 OwsErrorCode::InvalidPassphrase
95 );
96 assert_eq!(
97 OwsError::InvalidInput {
98 message: "x".into()
99 }
100 .code(),
101 OwsErrorCode::InvalidInput
102 );
103 assert_eq!(
104 OwsError::CaipParseError {
105 message: "x".into()
106 }
107 .code(),
108 OwsErrorCode::CaipParseError
109 );
110 assert_eq!(
111 OwsError::PolicyDenied {
112 policy_id: "x".into(),
113 reason: "x".into()
114 }
115 .code(),
116 OwsErrorCode::PolicyDenied
117 );
118 assert_eq!(
119 OwsError::ApiKeyNotFound.code(),
120 OwsErrorCode::ApiKeyNotFound
121 );
122 assert_eq!(
123 OwsError::ApiKeyExpired { id: "x".into() }.code(),
124 OwsErrorCode::ApiKeyExpired
125 );
126 }
127
128 #[test]
129 fn test_display_output() {
130 let err = OwsError::WalletNotFound {
131 id: "abc-123".to_string(),
132 };
133 assert_eq!(err.to_string(), "wallet not found: abc-123");
134 }
135
136 #[test]
137 fn test_json_serialization_shape() {
138 let err = OwsError::WalletNotFound {
139 id: "abc-123".to_string(),
140 };
141 let json = serde_json::to_value(&err).unwrap();
142 assert_eq!(json["code"], "WALLET_NOT_FOUND");
143 assert_eq!(json["message"], "wallet not found: abc-123");
144 }
145
146 #[test]
147 fn test_caip_parse_error_serialization() {
148 let err = OwsError::CaipParseError {
149 message: "bad format".to_string(),
150 };
151 let json = serde_json::to_value(&err).unwrap();
152 assert_eq!(json["code"], "CAIP_PARSE_ERROR");
153 assert!(json["message"].as_str().unwrap().contains("bad format"));
154 }
155
156 #[test]
157 fn test_policy_denied_serialization() {
158 let err = OwsError::PolicyDenied {
159 policy_id: "spending-limit".into(),
160 reason: "exceeded daily limit".into(),
161 };
162 let json = serde_json::to_value(&err).unwrap();
163 assert_eq!(json["code"], "POLICY_DENIED");
164 assert!(json["message"]
165 .as_str()
166 .unwrap()
167 .contains("exceeded daily limit"));
168 }
169
170 #[test]
171 fn test_api_key_not_found_serialization() {
172 let err = OwsError::ApiKeyNotFound;
173 let json = serde_json::to_value(&err).unwrap();
174 assert_eq!(json["code"], "API_KEY_NOT_FOUND");
175 }
176
177 #[test]
178 fn test_api_key_expired_serialization() {
179 let err = OwsError::ApiKeyExpired {
180 id: "key-123".into(),
181 };
182 let json = serde_json::to_value(&err).unwrap();
183 assert_eq!(json["code"], "API_KEY_EXPIRED");
184 assert!(json["message"].as_str().unwrap().contains("key-123"));
185 }
186}