1use bytes::Bytes;
2use http::StatusCode;
3use quick_xml::se::Serializer as XmlSerializer;
4use serde::Serialize;
5
6pub fn xml_error_response(
8 status: StatusCode,
9 code: &str,
10 message: &str,
11 request_id: &str,
12) -> (StatusCode, String, Bytes) {
13 #[derive(Serialize)]
14 #[serde(rename = "ErrorResponse")]
15 struct ErrorResponse<'a> {
16 #[serde(rename = "Error")]
17 error: ErrorBody<'a>,
18 #[serde(rename = "RequestId")]
19 request_id: &'a str,
20 }
21
22 #[derive(Serialize)]
23 struct ErrorBody<'a> {
24 #[serde(rename = "Type")]
25 error_type: &'a str,
26 #[serde(rename = "Code")]
27 code: &'a str,
28 #[serde(rename = "Message")]
29 message: &'a str,
30 }
31
32 let error_type = if status.is_server_error() {
33 "Receiver"
34 } else {
35 "Sender"
36 };
37
38 let resp = ErrorResponse {
39 error: ErrorBody {
40 error_type,
41 code,
42 message,
43 },
44 request_id,
45 };
46
47 let mut buffer = String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
48 let mut ser = XmlSerializer::new(&mut buffer);
49 ser.indent(' ', 2);
50 resp.serialize(ser)
51 .expect("XML serialization should not fail");
52
53 (status, "text/xml".to_string(), Bytes::from(buffer))
54}
55
56pub fn json_error_response(
58 status: StatusCode,
59 code: &str,
60 message: &str,
61) -> (StatusCode, String, Bytes) {
62 json_error_response_with_fields(status, code, message, &[])
63}
64
65pub fn json_error_response_with_fields(
70 status: StatusCode,
71 code: &str,
72 message: &str,
73 extra_fields: &[(String, String)],
74) -> (StatusCode, String, Bytes) {
75 let mut body = serde_json::Map::new();
76 body.insert("__type".to_string(), serde_json::json!(code));
77 body.insert("message".to_string(), serde_json::json!(message));
78 for (key, value) in extra_fields {
79 let parsed = serde_json::from_str(value)
80 .unwrap_or_else(|_| serde_json::Value::String(value.clone()));
81 body.insert(key.clone(), parsed);
82 }
83
84 (
85 status,
86 "application/x-amz-json-1.1".to_string(),
87 Bytes::from(serde_json::Value::Object(body).to_string()),
88 )
89}
90
91pub fn s3_xml_error_response(
94 status: StatusCode,
95 code: &str,
96 message: &str,
97 request_id: &str,
98) -> (StatusCode, String, Bytes) {
99 s3_xml_error_response_with_fields(status, code, message, request_id, &[])
100}
101
102pub fn s3_xml_error_response_with_fields(
104 status: StatusCode,
105 code: &str,
106 message: &str,
107 request_id: &str,
108 extra_fields: &[(String, String)],
109) -> (StatusCode, String, Bytes) {
110 let mut buffer = String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Error>\n");
111 buffer.push_str(&format!(" <Code>{}</Code>\n", xml_escape(code)));
112 buffer.push_str(&format!(" <Message>{}</Message>\n", xml_escape(message)));
113 for (key, value) in extra_fields {
114 buffer.push_str(&format!(" <{}>{}</{}>\n", key, xml_escape(value), key));
115 }
116 buffer.push_str(&format!(
117 " <RequestId>{}</RequestId>\n",
118 xml_escape(request_id)
119 ));
120 buffer.push_str("</Error>");
121
122 (status, "application/xml".to_string(), Bytes::from(buffer))
123}
124
125use crate::xml::xml_escape;
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn xml_error_has_correct_structure() {
133 let (status, content_type, body) = xml_error_response(
134 StatusCode::BAD_REQUEST,
135 "InvalidAction",
136 "not found",
137 "req-1",
138 );
139 assert_eq!(status, StatusCode::BAD_REQUEST);
140 assert_eq!(content_type, "text/xml");
141 let body_str = String::from_utf8(body.to_vec()).unwrap();
142 assert!(body_str.contains("<Code>InvalidAction</Code>"));
143 assert!(body_str.contains("<RequestId>req-1</RequestId>"));
144 }
145
146 #[test]
147 fn json_error_has_correct_structure() {
148 let (status, content_type, body) =
149 json_error_response(StatusCode::BAD_REQUEST, "ValidationException", "bad input");
150 assert_eq!(status, StatusCode::BAD_REQUEST);
151 assert!(content_type.contains("json"));
152 let v: serde_json::Value = serde_json::from_slice(&body).unwrap();
153 assert_eq!(v["__type"], "ValidationException");
154 assert_eq!(v["message"], "bad input");
155 }
156
157 #[test]
158 fn s3_xml_error_basic() {
159 let (status, content_type, body) =
160 s3_xml_error_response(StatusCode::NOT_FOUND, "NoSuchKey", "not found", "req-x");
161 assert_eq!(status, StatusCode::NOT_FOUND);
162 assert_eq!(content_type, "application/xml");
163 let body_str = String::from_utf8(body.to_vec()).unwrap();
164 assert!(body_str.contains("<Code>NoSuchKey</Code>"));
165 assert!(body_str.contains("<Message>not found</Message>"));
166 assert!(body_str.contains("<RequestId>req-x</RequestId>"));
167 }
168
169 #[test]
170 fn s3_xml_error_with_fields_includes_extra() {
171 let extras = vec![("BucketName".to_string(), "my-bucket".to_string())];
172 let (_, _, body) = s3_xml_error_response_with_fields(
173 StatusCode::CONFLICT,
174 "BucketAlreadyOwnedByYou",
175 "already owns",
176 "req-1",
177 &extras,
178 );
179 let body_str = String::from_utf8(body.to_vec()).unwrap();
180 assert!(body_str.contains("<BucketName>my-bucket</BucketName>"));
181 }
182
183 #[test]
184 fn xml_error_escapes_special_chars() {
185 let (_, _, body) = xml_error_response(StatusCode::BAD_REQUEST, "E", "a<b>c", "req-1");
186 let body_str = String::from_utf8(body.to_vec()).unwrap();
187 assert!(body_str.contains("a<b>c"));
188 }
189}