Skip to main content

rustack_sqs_http/
response.rs

1//! SQS response serialization and error formatting.
2
3use rustack_sqs_model::error::SqsError;
4
5use crate::body::SqsResponseBody;
6
7/// Content type for SQS JSON responses.
8pub const CONTENT_TYPE: &str = "application/x-amz-json-1.0";
9
10/// Serialize an SQS error into a JSON response body.
11///
12/// The error format follows the AWS SQS JSON protocol:
13///
14/// ```json
15/// {
16///   "__type": "AWS.SimpleQueueService.NonExistentQueue",
17///   "message": "The specified queue does not exist."
18/// }
19/// ```
20#[must_use]
21pub fn error_to_json(error: &SqsError) -> Vec<u8> {
22    let obj = serde_json::json!({
23        "__type": error.code.error_type(),
24        "message": error.message,
25    });
26    serde_json::to_vec(&obj).expect("JSON serialization of error cannot fail")
27}
28
29/// Convert an `SqsError` into a complete HTTP error response.
30///
31/// Includes the `x-amzn-query-error` header for `awsQueryCompatible` support.
32#[must_use]
33pub fn error_to_response(error: &SqsError, request_id: &str) -> http::Response<SqsResponseBody> {
34    let json = error_to_json(error);
35    let crc = crc32fast::hash(&json);
36    let body = SqsResponseBody::from_json(json);
37
38    let mut response = http::Response::builder()
39        .status(error.code.status_code())
40        .header("content-type", CONTENT_TYPE)
41        .header("x-amzn-requestid", request_id)
42        .header("x-amzn-query-error", error.code.query_error_header())
43        .body(body)
44        .expect("valid error response");
45
46    if let Ok(hv) = http::HeaderValue::from_str(&crc.to_string()) {
47        response.headers_mut().insert("x-amz-crc32", hv);
48    }
49
50    response
51}
52
53/// Build a success response from JSON bytes.
54#[must_use]
55pub fn json_response(json: Vec<u8>, request_id: &str) -> http::Response<SqsResponseBody> {
56    let crc = crc32fast::hash(&json);
57    let body = SqsResponseBody::from_json(json);
58
59    let mut response = http::Response::builder()
60        .status(http::StatusCode::OK)
61        .header("content-type", CONTENT_TYPE)
62        .header("x-amzn-requestid", request_id)
63        .body(body)
64        .expect("valid JSON response");
65
66    if let Ok(hv) = http::HeaderValue::from_str(&crc.to_string()) {
67        response.headers_mut().insert("x-amz-crc32", hv);
68    }
69
70    response
71}
72
73#[cfg(test)]
74mod tests {
75    use rustack_sqs_model::error::SqsErrorCode;
76
77    use super::*;
78
79    #[test]
80    fn test_should_format_error_json() {
81        let err = SqsError::new(
82            SqsErrorCode::NonExistentQueue,
83            "The specified queue does not exist.",
84        );
85        let json = error_to_json(&err);
86        let parsed: serde_json::Value = serde_json::from_slice(&json).unwrap();
87        assert_eq!(parsed["__type"], "AWS.SimpleQueueService.NonExistentQueue");
88        assert_eq!(parsed["message"], "The specified queue does not exist.");
89    }
90
91    #[test]
92    fn test_should_build_error_response_with_query_error_header() {
93        let err = SqsError::new(
94            SqsErrorCode::NonExistentQueue,
95            "The specified queue does not exist.",
96        );
97        let resp = error_to_response(&err, "test-req-123");
98        assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST);
99        assert_eq!(resp.headers().get("content-type").unwrap(), CONTENT_TYPE);
100        assert_eq!(
101            resp.headers().get("x-amzn-query-error").unwrap(),
102            "AWS.SimpleQueueService.NonExistentQueue;Sender",
103        );
104        assert_eq!(
105            resp.headers().get("x-amzn-requestid").unwrap(),
106            "test-req-123",
107        );
108        assert!(resp.headers().get("x-amz-crc32").is_some());
109    }
110
111    #[test]
112    fn test_should_build_json_success_response() {
113        let json = serde_json::to_vec(
114            &serde_json::json!({"QueueUrl": "http://localhost:4566/000000000000/test"}),
115        )
116        .unwrap();
117        let resp = json_response(json, "req-456");
118        assert_eq!(resp.status(), http::StatusCode::OK);
119        assert_eq!(resp.headers().get("content-type").unwrap(), CONTENT_TYPE);
120        assert!(resp.headers().get("x-amz-crc32").is_some());
121    }
122}