durable_lambda_core/
response.rs1use serde_json::json;
20
21use crate::error::DurableError;
22
23pub fn wrap_handler_result(
67 result: Result<serde_json::Value, DurableError>,
68) -> Result<serde_json::Value, Box<dyn std::error::Error + Send + Sync>> {
69 match result {
70 Ok(value) => {
71 let result_str = serde_json::to_string(&value)
75 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;
76 Ok(json!({
77 "Status": "SUCCEEDED",
78 "Result": result_str,
79 }))
80 }
81
82 Err(ref err) if is_suspension(err) => {
83 Ok(json!({ "Status": "PENDING" }))
86 }
87
88 Err(err) => {
89 let error_type = err.code().to_string();
91 let error_message = err.to_string();
92 Ok(json!({
93 "Status": "FAILED",
94 "Error": {
95 "ErrorType": error_type,
96 "ErrorMessage": error_message,
97 },
98 }))
99 }
100 }
101}
102
103fn is_suspension(err: &DurableError) -> bool {
108 matches!(
109 err,
110 DurableError::StepRetryScheduled { .. }
111 | DurableError::WaitSuspended { .. }
112 | DurableError::CallbackSuspended { .. }
113 | DurableError::InvokeSuspended { .. }
114 )
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use crate::error::DurableError;
121
122 #[test]
123 fn success_wraps_in_succeeded_envelope() {
124 let value = serde_json::json!({"order_id": "123", "status": "ok"});
125 let output = wrap_handler_result(Ok(value.clone())).unwrap();
126
127 assert_eq!(output["Status"], "SUCCEEDED");
128 let result_str = output["Result"]
130 .as_str()
131 .expect("Result should be a string");
132 let parsed: serde_json::Value = serde_json::from_str(result_str).unwrap();
133 assert_eq!(parsed["order_id"], "123");
134 }
135
136 #[test]
137 fn step_retry_scheduled_returns_pending() {
138 let err = DurableError::step_retry_scheduled("charge_payment");
139 let output = wrap_handler_result(Err(err)).unwrap();
140 assert_eq!(output["Status"], "PENDING");
141 assert!(output.get("Error").is_none());
142 assert!(output.get("Result").is_none());
143 }
144
145 #[test]
146 fn wait_suspended_returns_pending() {
147 let err = DurableError::wait_suspended("cooldown");
148 let output = wrap_handler_result(Err(err)).unwrap();
149 assert_eq!(output["Status"], "PENDING");
150 }
151
152 #[test]
153 fn callback_suspended_returns_pending() {
154 let err = DurableError::callback_suspended("approval", "cb-123");
155 let output = wrap_handler_result(Err(err)).unwrap();
156 assert_eq!(output["Status"], "PENDING");
157 }
158
159 #[test]
160 fn invoke_suspended_returns_pending() {
161 let err = DurableError::invoke_suspended("call_processor");
162 let output = wrap_handler_result(Err(err)).unwrap();
163 assert_eq!(output["Status"], "PENDING");
164 }
165
166 #[test]
167 fn step_timeout_returns_failed() {
168 let err = DurableError::step_timeout("slow_op");
169 let output = wrap_handler_result(Err(err)).unwrap();
170 assert_eq!(output["Status"], "FAILED");
171 assert!(output.get("Error").is_some());
172 let error_obj = &output["Error"];
173 assert_eq!(error_obj["ErrorType"], "STEP_TIMEOUT");
174 assert!(
175 error_obj["ErrorMessage"]
176 .as_str()
177 .unwrap()
178 .contains("timed out"),
179 "ErrorMessage should contain 'timed out'"
180 );
181 }
182
183 #[test]
184 fn checkpoint_failed_returns_failed() {
185 let err = DurableError::checkpoint_failed(
186 "op",
187 std::io::Error::new(std::io::ErrorKind::Other, "network error"),
188 );
189 let output = wrap_handler_result(Err(err)).unwrap();
190 assert_eq!(output["Status"], "FAILED");
191 assert_eq!(output["Error"]["ErrorType"], "CHECKPOINT_FAILED");
192 }
193
194 #[test]
195 fn replay_mismatch_returns_failed() {
196 let err = DurableError::replay_mismatch("Step", "Wait", 3);
197 let output = wrap_handler_result(Err(err)).unwrap();
198 assert_eq!(output["Status"], "FAILED");
199 assert_eq!(output["Error"]["ErrorType"], "REPLAY_MISMATCH");
200 }
201
202 #[test]
203 fn failed_status_has_no_result_field() {
204 let err = DurableError::step_timeout("op");
205 let output = wrap_handler_result(Err(err)).unwrap();
206 assert!(output.get("Result").is_none());
207 }
208
209 #[test]
210 fn succeeded_status_result_is_json_string() {
211 let output = wrap_handler_result(Ok(serde_json::json!({"key": "value"}))).unwrap();
213 assert_eq!(output["Status"], "SUCCEEDED");
214 assert!(
215 output["Result"].is_string(),
216 "Result must be a JSON string, not an object"
217 );
218 }
219
220 #[test]
221 fn null_value_wraps_correctly() {
222 let output = wrap_handler_result(Ok(serde_json::Value::Null)).unwrap();
223 assert_eq!(output["Status"], "SUCCEEDED");
224 assert_eq!(output["Result"].as_str().unwrap(), "null");
225 }
226}