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 && next.parse::<usize>().is_ok()
69 {
70 let entry = map
72 .entry(key.to_string())
73 .or_insert_with(|| Value::Array(Vec::new()));
74 if let Value::Array(arr) = entry {
75 let idx: usize = next.parse::<usize>().unwrap() - 1; while arr.len() <= idx {
77 arr.push(Value::Object(serde_json::Map::new()));
78 }
79 if rest.len() > 1 {
80 if let Value::Object(ref mut inner) = arr[idx] {
81 set_nested_recursive(inner, &rest[1..], value);
82 }
83 } else {
84 arr[idx] = Value::String(value.to_string());
85 }
86 }
87 return;
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(error: &AwsError, request_id: &str) -> (StatusCode, HeaderMap, Bytes) {
138 let error_type = match error.error_type {
139 crate::error::ErrorType::Sender => "Sender",
140 crate::error::ErrorType::Receiver => "Receiver",
141 };
142
143 let xml = format!(
144 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
145 <ErrorResponse xmlns=\"http://iam.amazonaws.com/doc/2010-05-08/\">\n\
146 <Error>\n\
147 <Type>{error_type}</Type>\n\
148 <Code>{code}</Code>\n\
149 <Message>{message}</Message>\n\
150 </Error>\n\
151 <RequestId>{request_id}</RequestId>\n\
152 </ErrorResponse>",
153 code = error.code,
154 message = error.message,
155 );
156
157 let mut headers = HeaderMap::new();
158 headers.insert("content-type", "text/xml".parse().unwrap());
159 headers.insert("x-amzn-requestid", request_id.parse().unwrap());
160 (error.status, headers, Bytes::from(xml))
161}
162
163pub fn json_to_xml_fields(value: &Value) -> String {
165 match value {
166 Value::Object(map) => {
167 let mut xml = String::new();
168 for (key, val) in map {
169 match val {
170 Value::Object(_) => {
171 xml.push_str(&format!("<{key}>\n{}</{key}>\n", json_to_xml_fields(val)));
172 }
173 Value::Array(arr) => {
174 for item in arr {
175 xml.push_str(&format!(
176 "<{key}>\n{}</{key}>\n",
177 json_to_xml_fields(item)
178 ));
179 }
180 }
181 Value::String(s) => {
182 xml.push_str(&format!("<{key}>{s}</{key}>\n"));
183 }
184 Value::Number(n) => {
185 xml.push_str(&format!("<{key}>{n}</{key}>\n"));
186 }
187 Value::Bool(b) => {
188 xml.push_str(&format!("<{key}>{b}</{key}>\n"));
189 }
190 Value::Null => {
191 xml.push_str(&format!("<{key}/>\n"));
192 }
193 }
194 }
195 xml
196 }
197 Value::String(s) => s.clone(),
198 Value::Number(n) => n.to_string(),
199 Value::Bool(b) => b.to_string(),
200 _ => String::new(),
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn test_parse_simple_query() {
210 let body = Bytes::from("Action=GetCallerIdentity&Version=2011-06-15");
211 let result = parse_request(&body).unwrap();
212 assert_eq!(result.operation, "GetCallerIdentity");
213 assert_eq!(result.input, Value::Object(serde_json::Map::new()));
214 }
215
216 #[test]
217 fn test_parse_query_with_params() {
218 let body = Bytes::from("Action=CreateUser&UserName=testuser&Path=/engineering/");
219 let result = parse_request(&body).unwrap();
220 assert_eq!(result.operation, "CreateUser");
221 assert_eq!(result.input["UserName"], "testuser");
222 assert_eq!(result.input["Path"], "/engineering/");
223 }
224
225 #[test]
226 fn test_flatten_dot_notation() {
227 let params = vec![
228 ("Action".to_string(), "TagResource".to_string()),
229 ("Tags.member.1.Key".to_string(), "Env".to_string()),
230 ("Tags.member.1.Value".to_string(), "prod".to_string()),
231 ("Tags.member.2.Key".to_string(), "Team".to_string()),
232 ("Tags.member.2.Value".to_string(), "eng".to_string()),
233 ];
234 let result = flatten_to_json(¶ms);
235 let tags = result["Tags"]["member"].as_array().unwrap();
236 assert_eq!(tags.len(), 2);
237 assert_eq!(tags[0]["Key"], "Env");
238 assert_eq!(tags[0]["Value"], "prod");
239 assert_eq!(tags[1]["Key"], "Team");
240 assert_eq!(tags[1]["Value"], "eng");
241 }
242}