Skip to main content

awsim_core/protocol/
json.rs

1use axum::http::{HeaderMap, StatusCode};
2use bytes::Bytes;
3use serde_json::Value;
4
5use crate::error::AwsError;
6
7use super::ParsedRequest;
8
9/// Parse an awsJson (1.0/1.1) request.
10///
11/// Operation is extracted from the `X-Amz-Target` header:
12/// Format: `ServicePrefix_Version.OperationName` (e.g., `DynamoDB_20120810.GetItem`)
13pub fn parse_request(headers: &HeaderMap, body: &Bytes) -> Result<ParsedRequest, AwsError> {
14    let target = headers
15        .get("x-amz-target")
16        .and_then(|v| v.to_str().ok())
17        .ok_or_else(|| AwsError::bad_request("MissingHeader", "Missing X-Amz-Target header"))?;
18
19    let operation = target
20        .split('.')
21        .last()
22        .ok_or_else(|| {
23            AwsError::bad_request(
24                "InvalidHeader",
25                format!("Invalid X-Amz-Target format: {target}"),
26            )
27        })?
28        .to_string();
29
30    let input = if body.is_empty() {
31        Value::Object(serde_json::Map::new())
32    } else {
33        serde_json::from_slice(body).map_err(|e| {
34            AwsError::bad_request("SerializationException", format!("Invalid JSON body: {e}"))
35        })?
36    };
37
38    Ok(ParsedRequest { operation, input })
39}
40
41/// Serialize a successful JSON response.
42pub fn serialize_response(
43    output: &Value,
44    request_id: &str,
45) -> (StatusCode, HeaderMap, Bytes) {
46    if let Some(raw_b64) = output.get("__raw_body").and_then(Value::as_str) {
47        use base64::Engine;
48        let data = base64::engine::general_purpose::STANDARD
49            .decode(raw_b64)
50            .unwrap_or_default();
51        let content_type = output
52            .get("__content_type")
53            .and_then(Value::as_str)
54            .unwrap_or("application/octet-stream");
55        let mut headers = HeaderMap::new();
56        headers.insert("content-type", content_type.parse().unwrap());
57        headers.insert("x-amzn-requestid", request_id.parse().unwrap());
58        return (StatusCode::OK, headers, Bytes::from(data));
59    }
60
61    let body = serde_json::to_vec(output).unwrap_or_default();
62    let mut headers = HeaderMap::new();
63    headers.insert("content-type", "application/x-amz-json-1.0".parse().unwrap());
64    headers.insert("x-amzn-requestid", request_id.parse().unwrap());
65    (StatusCode::OK, headers, Bytes::from(body))
66}
67
68/// Serialize a JSON error response.
69pub fn serialize_error(
70    error: &AwsError,
71    request_id: &str,
72) -> (StatusCode, HeaderMap, Bytes) {
73    let body = serde_json::json!({
74        "__type": error.code,
75        "message": error.message,
76    });
77    let body = serde_json::to_vec(&body).unwrap_or_default();
78    let mut headers = HeaderMap::new();
79    headers.insert("content-type", "application/x-amz-json-1.0".parse().unwrap());
80    headers.insert("x-amzn-requestid", request_id.parse().unwrap());
81    headers.insert(
82        "x-amzn-errortype",
83        error.code.parse().unwrap(),
84    );
85    (error.status, headers, Bytes::from(body))
86}