1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4string_constants! {
5 ErrorTypes {
7 CONFIGURATION => "https://serverlessworkflow.io/spec/1.0.0/errors/configuration",
8 VALIDATION => "https://serverlessworkflow.io/spec/1.0.0/errors/validation",
9 EXPRESSION => "https://serverlessworkflow.io/spec/1.0.0/errors/expression",
10 AUTHENTICATION => "https://serverlessworkflow.io/spec/1.0.0/errors/authentication",
11 AUTHORIZATION => "https://serverlessworkflow.io/spec/1.0.0/errors/authorization",
12 TIMEOUT => "https://serverlessworkflow.io/spec/1.0.0/errors/timeout",
13 COMMUNICATION => "https://serverlessworkflow.io/spec/1.0.0/errors/communication",
14 RUNTIME => "https://serverlessworkflow.io/spec/1.0.0/errors/runtime",
15 }
16}
17
18#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(transparent)]
25pub struct ErrorType(String);
26
27impl ErrorType {
28 pub fn uri_template(template: &str) -> Self {
30 ErrorType(template.to_string())
31 }
32
33 pub fn runtime_expression(expression: &str) -> Self {
35 ErrorType(expression.to_string())
36 }
37
38 pub fn is_runtime_expression(&self) -> bool {
40 self.0.starts_with("${")
41 }
42
43 pub fn as_str(&self) -> &str {
45 &self.0
46 }
47}
48
49impl From<String> for ErrorType {
50 fn from(s: String) -> Self {
51 ErrorType(s)
52 }
53}
54
55impl From<&str> for ErrorType {
56 fn from(s: &str) -> Self {
57 ErrorType(s.to_string())
58 }
59}
60
61#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
63pub struct ErrorDefinition {
64 #[serde(rename = "type")]
66 pub type_: ErrorType,
67
68 #[serde(skip_serializing_if = "Option::is_none", default)]
70 pub title: Option<String>,
71
72 pub status: Value,
74
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub detail: Option<String>,
78
79 #[serde(skip_serializing_if = "Option::is_none")]
82 pub instance: Option<String>,
83}
84macro_rules! define_error_type {
85 ($factory:ident, $is:ident, $const:ident, $title:literal, $status:expr) => {
86 #[doc = concat!("Creates a new ", stringify!($factory))]
87 pub fn $factory(detail: Option<String>, instance: Option<String>) -> Self {
88 Self::new(
89 ErrorTypes::$const,
90 $title,
91 serde_json::json!($status),
92 detail,
93 instance,
94 )
95 }
96
97 #[doc = concat!("Checks if this error is a ", stringify!($factory))]
98 pub fn $is(&self) -> bool {
99 self.type_.as_str() == ErrorTypes::$const
100 }
101 };
102}
103
104impl ErrorDefinition {
105 pub fn new(
107 type_: &str,
108 title: &str,
109 status: Value,
110 detail: Option<String>,
111 instance: Option<String>,
112 ) -> Self {
113 Self {
114 type_: ErrorType::uri_template(type_),
115 title: Some(title.to_string()),
116 status,
117 detail,
118 instance,
119 }
120 }
121
122 define_error_type!(
123 configuration_error,
124 is_configuration_error,
125 CONFIGURATION,
126 "Configuration Error",
127 400
128 );
129 define_error_type!(
130 validation_error,
131 is_validation_error,
132 VALIDATION,
133 "Validation Error",
134 400
135 );
136 define_error_type!(
137 expression_error,
138 is_expression_error,
139 EXPRESSION,
140 "Expression Error",
141 400
142 );
143 define_error_type!(
144 authentication_error,
145 is_authentication_error,
146 AUTHENTICATION,
147 "Authentication Error",
148 401
149 );
150 define_error_type!(
151 authorization_error,
152 is_authorization_error,
153 AUTHORIZATION,
154 "Authorization Error",
155 403
156 );
157 define_error_type!(
158 timeout_error,
159 is_timeout_error,
160 TIMEOUT,
161 "Timeout Error",
162 408
163 );
164 define_error_type!(
165 communication_error,
166 is_communication_error,
167 COMMUNICATION,
168 "Communication Error",
169 500
170 );
171 define_error_type!(
172 runtime_error,
173 is_runtime_error,
174 RUNTIME,
175 "Runtime Error",
176 500
177 );
178}
179
180define_one_of_or_reference!(
181 OneOfErrorDefinitionOrReference, Error(ErrorDefinition)
183);
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use serde_json::json;
189
190 #[test]
191 fn test_error_definition_new() {
192 let err = ErrorDefinition::new(
193 ErrorTypes::RUNTIME,
194 "Runtime Error",
195 json!(500),
196 Some("Something went wrong".to_string()),
197 Some("/task/1".to_string()),
198 );
199 assert_eq!(err.type_.as_str(), ErrorTypes::RUNTIME);
200 assert_eq!(err.title, Some("Runtime Error".to_string()));
201 assert_eq!(err.status, json!(500));
202 assert_eq!(err.detail, Some("Something went wrong".to_string()));
203 assert_eq!(err.instance, Some("/task/1".to_string()));
204 }
205
206 #[test]
207 fn test_error_type_check_methods() {
208 let err = ErrorDefinition::validation_error(None, None);
209 assert!(err.is_validation_error());
210 assert!(!err.is_runtime_error());
211
212 let err = ErrorDefinition::runtime_error(None, None);
213 assert!(err.is_runtime_error());
214 assert!(!err.is_communication_error());
215
216 let err = ErrorDefinition::authentication_error(None, None);
217 assert!(err.is_authentication_error());
218
219 let err = ErrorDefinition::authorization_error(None, None);
220 assert!(err.is_authorization_error());
221
222 let err = ErrorDefinition::timeout_error(None, None);
223 assert!(err.is_timeout_error());
224
225 let err = ErrorDefinition::communication_error(None, None);
226 assert!(err.is_communication_error());
227
228 let err = ErrorDefinition::configuration_error(None, None);
229 assert!(err.is_configuration_error());
230
231 let err = ErrorDefinition::expression_error(None, None);
232 assert!(err.is_expression_error());
233 }
234
235 #[test]
236 fn test_error_type_enum() {
237 let uri =
238 ErrorType::uri_template("https://serverlessworkflow.io/spec/1.0.0/errors/runtime");
239 assert_eq!(
240 uri.as_str(),
241 "https://serverlessworkflow.io/spec/1.0.0/errors/runtime"
242 );
243 assert!(!uri.is_runtime_expression());
244
245 let expr = ErrorType::runtime_expression("${ .errorType }");
246 assert_eq!(expr.as_str(), "${ .errorType }");
247 assert!(expr.is_runtime_expression());
248 }
249
250 #[test]
251 fn test_error_definition_serialize() {
252 let err = ErrorDefinition::new(
253 ErrorTypes::COMMUNICATION,
254 "Communication Error",
255 json!(500),
256 None,
257 None,
258 );
259 let json_str = serde_json::to_string(&err).unwrap();
260 assert!(json_str.contains(
261 "\"type\":\"https://serverlessworkflow.io/spec/1.0.0/errors/communication\""
262 ));
263 assert!(json_str.contains("\"title\":\"Communication Error\""));
264 assert!(json_str.contains("\"status\":500"));
265 assert!(!json_str.contains("detail"));
266 assert!(!json_str.contains("instance"));
267 }
268
269 #[test]
270 fn test_error_definition_deserialize() {
271 let json = r#"{
272 "type": "https://serverlessworkflow.io/spec/1.0.0/errors/runtime",
273 "title": "Runtime Error",
274 "status": 500,
275 "detail": "Something failed",
276 "instance": "/task/step1"
277 }"#;
278 let err: ErrorDefinition = serde_json::from_str(json).unwrap();
279 assert_eq!(
280 err.type_.as_str(),
281 "https://serverlessworkflow.io/spec/1.0.0/errors/runtime"
282 );
283 assert_eq!(err.title, Some("Runtime Error".to_string()));
284 assert_eq!(err.detail, Some("Something failed".to_string()));
285 }
286
287 #[test]
288 fn test_oneof_error_reference_deserialize() {
289 let json = r#""someErrorRef""#;
290 let oneof: OneOfErrorDefinitionOrReference = serde_json::from_str(json).unwrap();
291 match oneof {
292 OneOfErrorDefinitionOrReference::Reference(name) => {
293 assert_eq!(name, "someErrorRef");
294 }
295 _ => panic!("Expected Reference variant"),
296 }
297 }
298
299 #[test]
300 fn test_oneof_error_inline_deserialize() {
301 let json = r#"{
302 "type": "https://serverlessworkflow.io/spec/1.0.0/errors/timeout",
303 "title": "Timeout Error",
304 "status": 408
305 }"#;
306 let oneof: OneOfErrorDefinitionOrReference = serde_json::from_str(json).unwrap();
307 match oneof {
308 OneOfErrorDefinitionOrReference::Error(err) => {
309 assert_eq!(
310 err.type_.as_str(),
311 "https://serverlessworkflow.io/spec/1.0.0/errors/timeout"
312 );
313 }
314 _ => panic!("Expected Error variant"),
315 }
316 }
317
318 #[test]
321 fn test_error_definition_roundtrip() {
322 let json = r#"{
323 "type": "https://serverlessworkflow.io/spec/1.0.0/errors/communication",
324 "title": "Communication Error",
325 "status": 500,
326 "detail": "Connection refused",
327 "instance": "/task/step2"
328 }"#;
329 let err: ErrorDefinition = serde_json::from_str(json).unwrap();
330 let serialized = serde_json::to_string(&err).unwrap();
331 let deserialized: ErrorDefinition = serde_json::from_str(&serialized).unwrap();
332 assert_eq!(err, deserialized);
333 }
334
335 #[test]
336 fn test_oneof_error_reference_roundtrip() {
337 let oneof = OneOfErrorDefinitionOrReference::Reference("myErrorRef".to_string());
338 let serialized = serde_json::to_string(&oneof).unwrap();
339 assert_eq!(serialized, r#""myErrorRef""#);
340 let deserialized: OneOfErrorDefinitionOrReference =
341 serde_json::from_str(&serialized).unwrap();
342 assert_eq!(oneof, deserialized);
343 }
344
345 #[test]
346 fn test_oneof_error_inline_roundtrip() {
347 let json = r#"{
348 "type": "https://serverlessworkflow.io/spec/1.0.0/errors/authentication",
349 "title": "Auth Error",
350 "status": 401
351 }"#;
352 let oneof: OneOfErrorDefinitionOrReference = serde_json::from_str(json).unwrap();
353 let serialized = serde_json::to_string(&oneof).unwrap();
354 let deserialized: OneOfErrorDefinitionOrReference =
355 serde_json::from_str(&serialized).unwrap();
356 assert_eq!(oneof, deserialized);
357 }
358
359 #[test]
360 fn test_error_type_runtime_expression_detection() {
361 let uri_type =
363 ErrorType::uri_template("https://serverlessworkflow.io/spec/1.0.0/errors/runtime");
364 assert!(!uri_type.is_runtime_expression());
365
366 let expr_type = ErrorType::runtime_expression("${ .errorType }");
368 assert!(expr_type.is_runtime_expression());
369
370 let uri_with_expr = ErrorType::uri_template("${ .dynamicError }");
372 assert!(uri_with_expr.is_runtime_expression());
373 }
374
375 #[test]
376 fn test_error_definition_with_runtime_type() {
377 let json = r#"{
379 "type": "${ .error.type }",
380 "title": "Dynamic Error",
381 "status": 500
382 }"#;
383 let err: ErrorDefinition = serde_json::from_str(json).unwrap();
384 assert!(err.type_.is_runtime_expression());
385 }
386
387 #[test]
388 fn test_standard_error_factory_methods() {
389 let config = ErrorDefinition::configuration_error(Some("bad config".to_string()), None);
391 assert!(config.is_configuration_error());
392 assert_eq!(config.status, json!(400));
393
394 let validation = ErrorDefinition::validation_error(None, None);
395 assert!(validation.is_validation_error());
396 assert_eq!(validation.status, json!(400));
397
398 let expr = ErrorDefinition::expression_error(None, None);
399 assert!(expr.is_expression_error());
400 assert_eq!(expr.status, json!(400));
401
402 let authn = ErrorDefinition::authentication_error(None, None);
403 assert!(authn.is_authentication_error());
404 assert_eq!(authn.status, json!(401));
405
406 let authz = ErrorDefinition::authorization_error(None, None);
407 assert!(authz.is_authorization_error());
408 assert_eq!(authz.status, json!(403));
409
410 let timeout = ErrorDefinition::timeout_error(None, None);
411 assert!(timeout.is_timeout_error());
412 assert_eq!(timeout.status, json!(408));
413
414 let comm = ErrorDefinition::communication_error(None, None);
415 assert!(comm.is_communication_error());
416 assert_eq!(comm.status, json!(500));
417
418 let runtime = ErrorDefinition::runtime_error(None, None);
419 assert!(runtime.is_runtime_error());
420 assert_eq!(runtime.status, json!(500));
421 }
422
423 #[test]
424 fn test_error_definition_without_optional_title() {
425 let json = r#"{
427 "type": "https://serverlessworkflow.io/spec/1.0.0/errors/timeout",
428 "status": 408,
429 "detail": "Request took too long"
430 }"#;
431 let err: ErrorDefinition = serde_json::from_str(json).unwrap();
432 assert_eq!(
433 err.type_.as_str(),
434 "https://serverlessworkflow.io/spec/1.0.0/errors/timeout"
435 );
436 assert_eq!(err.title, None);
437 assert_eq!(err.status, json!(408));
438 assert_eq!(err.detail, Some("Request took too long".to_string()));
439 }
440
441 #[test]
442 fn test_error_definition_serialize_skips_none_title() {
443 let err = ErrorDefinition {
444 type_: ErrorType::uri_template(
445 "https://serverlessworkflow.io/spec/1.0.0/errors/timeout",
446 ),
447 title: None,
448 status: json!(408),
449 detail: Some("Timed out".to_string()),
450 instance: None,
451 };
452 let json_str = serde_json::to_string(&err).unwrap();
453 assert!(!json_str.contains("title"));
454 assert!(json_str.contains("\"detail\":\"Timed out\""));
455 }
456}