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 let body = serde_json::json!({
63 "__type": code,
64 "message": message,
65 });
66
67 (
68 status,
69 "application/x-amz-json-1.1".to_string(),
70 Bytes::from(body.to_string()),
71 )
72}
73
74pub fn s3_xml_error_response(
77 status: StatusCode,
78 code: &str,
79 message: &str,
80 request_id: &str,
81) -> (StatusCode, String, Bytes) {
82 s3_xml_error_response_with_fields(status, code, message, request_id, &[])
83}
84
85pub fn s3_xml_error_response_with_fields(
87 status: StatusCode,
88 code: &str,
89 message: &str,
90 request_id: &str,
91 extra_fields: &[(String, String)],
92) -> (StatusCode, String, Bytes) {
93 let mut buffer = String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Error>\n");
94 buffer.push_str(&format!(" <Code>{}</Code>\n", xml_escape(code)));
95 buffer.push_str(&format!(" <Message>{}</Message>\n", xml_escape(message)));
96 for (key, value) in extra_fields {
97 buffer.push_str(&format!(" <{}>{}</{}>\n", key, xml_escape(value), key));
98 }
99 buffer.push_str(&format!(
100 " <RequestId>{}</RequestId>\n",
101 xml_escape(request_id)
102 ));
103 buffer.push_str("</Error>");
104
105 (status, "application/xml".to_string(), Bytes::from(buffer))
106}
107
108use crate::xml::xml_escape;
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn xml_error_has_correct_structure() {
116 let (status, content_type, body) = xml_error_response(
117 StatusCode::BAD_REQUEST,
118 "InvalidAction",
119 "not found",
120 "req-1",
121 );
122 assert_eq!(status, StatusCode::BAD_REQUEST);
123 assert_eq!(content_type, "text/xml");
124 let body_str = String::from_utf8(body.to_vec()).unwrap();
125 assert!(body_str.contains("<Code>InvalidAction</Code>"));
126 assert!(body_str.contains("<RequestId>req-1</RequestId>"));
127 }
128
129 #[test]
130 fn json_error_has_correct_structure() {
131 let (status, content_type, body) =
132 json_error_response(StatusCode::BAD_REQUEST, "ValidationException", "bad input");
133 assert_eq!(status, StatusCode::BAD_REQUEST);
134 assert!(content_type.contains("json"));
135 let v: serde_json::Value = serde_json::from_slice(&body).unwrap();
136 assert_eq!(v["__type"], "ValidationException");
137 assert_eq!(v["message"], "bad input");
138 }
139}