awsim_core/protocol/
query.rs1use axum::http::{HeaderMap, StatusCode};
2use bytes::Bytes;
3use serde_json::Value;
4
5use crate::error::AwsError;
6
7use super::ParsedRequest;
8
9pub fn parse_request(body: &Bytes) -> Result<ParsedRequest, AwsError> {
14 let body_str = std::str::from_utf8(body)
15 .map_err(|_| AwsError::bad_request("InvalidRequest", "Request body is not valid UTF-8"))?;
16
17 let params: Vec<(String, String)> = serde_urlencoded::from_str(body_str)
18 .map_err(|e| AwsError::bad_request("InvalidRequest", format!("Invalid form body: {e}")))?;
19
20 let operation = params
21 .iter()
22 .find(|(k, _)| k == "Action")
23 .map(|(_, v)| v.clone())
24 .ok_or_else(|| AwsError::bad_request("MissingAction", "Missing 'Action' parameter"))?;
25
26 let input = flatten_to_json(¶ms);
28
29 Ok(ParsedRequest { operation, input })
30}
31
32fn flatten_to_json(params: &[(String, String)]) -> Value {
39 let mut map = serde_json::Map::new();
40 for (key, value) in params {
41 if key == "Action" || key == "Version" {
42 continue;
43 }
44 set_nested(&mut map, key, value);
45 }
46 Value::Object(map)
47}
48
49fn set_nested(map: &mut serde_json::Map<String, Value>, key: &str, value: &str) {
50 let parts: Vec<&str> = key.split('.').collect();
51 set_nested_recursive(map, &parts, value);
52}
53
54fn set_nested_recursive(map: &mut serde_json::Map<String, Value>, parts: &[&str], value: &str) {
55 if parts.is_empty() {
56 return;
57 }
58 if parts.len() == 1 {
59 map.insert(parts[0].to_string(), Value::String(value.to_string()));
60 return;
61 }
62
63 let key = parts[0];
64 let rest = &parts[1..];
65
66 if let Some(next) = rest.first() {
68 if next.parse::<usize>().is_ok() {
69 let entry = map
71 .entry(key.to_string())
72 .or_insert_with(|| Value::Array(Vec::new()));
73 if let Value::Array(arr) = entry {
74 let idx: usize = next.parse::<usize>().unwrap() - 1; while arr.len() <= idx {
76 arr.push(Value::Object(serde_json::Map::new()));
77 }
78 if rest.len() > 1 {
79 if let Value::Object(ref mut inner) = arr[idx] {
80 set_nested_recursive(inner, &rest[1..], value);
81 }
82 } else {
83 arr[idx] = Value::String(value.to_string());
84 }
85 }
86 return;
87 }
88 }
89
90 let entry = map
91 .entry(key.to_string())
92 .or_insert_with(|| Value::Object(serde_json::Map::new()));
93 if let Value::Object(inner) = entry {
94 set_nested_recursive(inner, rest, value);
95 }
96}
97
98pub fn serialize_response(
112 operation: &str,
113 output: &Value,
114 request_id: &str,
115) -> (StatusCode, HeaderMap, Bytes) {
116 let result_xml = json_to_xml_fields(output);
117
118 let xml = format!(
119 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
120 <{operation}Response xmlns=\"https://iam.amazonaws.com/doc/2010-05-08/\">\n\
121 <{operation}Result>\n\
122 {result_xml}\
123 </{operation}Result>\n\
124 <ResponseMetadata>\n\
125 <RequestId>{request_id}</RequestId>\n\
126 </ResponseMetadata>\n\
127 </{operation}Response>"
128 );
129
130 let mut headers = HeaderMap::new();
131 headers.insert("content-type", "text/xml".parse().unwrap());
132 headers.insert("x-amzn-requestid", request_id.parse().unwrap());
133 (StatusCode::OK, headers, Bytes::from(xml))
134}
135
136pub fn serialize_error(
138 error: &AwsError,
139 request_id: &str,
140) -> (StatusCode, HeaderMap, Bytes) {
141 let error_type = match error.error_type {
142 crate::error::ErrorType::Sender => "Sender",
143 crate::error::ErrorType::Receiver => "Receiver",
144 };
145
146 let xml = format!(
147 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
148 <ErrorResponse xmlns=\"http://iam.amazonaws.com/doc/2010-05-08/\">\n\
149 <Error>\n\
150 <Type>{error_type}</Type>\n\
151 <Code>{code}</Code>\n\
152 <Message>{message}</Message>\n\
153 </Error>\n\
154 <RequestId>{request_id}</RequestId>\n\
155 </ErrorResponse>",
156 code = error.code,
157 message = error.message,
158 );
159
160 let mut headers = HeaderMap::new();
161 headers.insert("content-type", "text/xml".parse().unwrap());
162 headers.insert("x-amzn-requestid", request_id.parse().unwrap());
163 (error.status, headers, Bytes::from(xml))
164}
165
166pub fn json_to_xml_fields(value: &Value) -> String {
168 match value {
169 Value::Object(map) => {
170 let mut xml = String::new();
171 for (key, val) in map {
172 match val {
173 Value::Object(_) => {
174 xml.push_str(&format!(
175 "<{key}>\n{}</{key}>\n",
176 json_to_xml_fields(val)
177 ));
178 }
179 Value::Array(arr) => {
180 for item in arr {
181 xml.push_str(&format!(
182 "<{key}>\n{}</{key}>\n",
183 json_to_xml_fields(item)
184 ));
185 }
186 }
187 Value::String(s) => {
188 xml.push_str(&format!("<{key}>{s}</{key}>\n"));
189 }
190 Value::Number(n) => {
191 xml.push_str(&format!("<{key}>{n}</{key}>\n"));
192 }
193 Value::Bool(b) => {
194 xml.push_str(&format!("<{key}>{b}</{key}>\n"));
195 }
196 Value::Null => {
197 xml.push_str(&format!("<{key}/>\n"));
198 }
199 }
200 }
201 xml
202 }
203 Value::String(s) => s.clone(),
204 Value::Number(n) => n.to_string(),
205 Value::Bool(b) => b.to_string(),
206 _ => String::new(),
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_parse_simple_query() {
216 let body = Bytes::from("Action=GetCallerIdentity&Version=2011-06-15");
217 let result = parse_request(&body).unwrap();
218 assert_eq!(result.operation, "GetCallerIdentity");
219 assert_eq!(result.input, Value::Object(serde_json::Map::new()));
220 }
221
222 #[test]
223 fn test_parse_query_with_params() {
224 let body = Bytes::from("Action=CreateUser&UserName=testuser&Path=/engineering/");
225 let result = parse_request(&body).unwrap();
226 assert_eq!(result.operation, "CreateUser");
227 assert_eq!(result.input["UserName"], "testuser");
228 assert_eq!(result.input["Path"], "/engineering/");
229 }
230
231 #[test]
232 fn test_flatten_dot_notation() {
233 let params = vec![
234 ("Action".to_string(), "TagResource".to_string()),
235 ("Tags.member.1.Key".to_string(), "Env".to_string()),
236 ("Tags.member.1.Value".to_string(), "prod".to_string()),
237 ("Tags.member.2.Key".to_string(), "Team".to_string()),
238 ("Tags.member.2.Value".to_string(), "eng".to_string()),
239 ];
240 let result = flatten_to_json(¶ms);
241 let tags = result["Tags"]["member"].as_array().unwrap();
242 assert_eq!(tags.len(), 2);
243 assert_eq!(tags[0]["Key"], "Env");
244 assert_eq!(tags[0]["Value"], "prod");
245 assert_eq!(tags[1]["Key"], "Team");
246 assert_eq!(tags[1]["Value"], "eng");
247 }
248}