Skip to main content

fakecloud_core/
query.rs

1//! Shared helpers for AWS Query protocol services (SQS, SNS, ElastiCache, RDS, SES v1, IAM).
2
3use http::StatusCode;
4
5use crate::service::{AwsRequest, AwsServiceError};
6
7/// Wrap an action result in the standard AWS Query protocol XML envelope.
8///
9/// Produces the canonical response shape:
10/// ```xml
11/// <?xml version="1.0" encoding="UTF-8"?>
12/// <{Action}Response xmlns="{namespace}">
13///   <{Action}Result>{inner}</{Action}Result>
14///   <ResponseMetadata><RequestId>{request_id}</RequestId></ResponseMetadata>
15/// </{Action}Response>
16/// ```
17pub fn query_response_xml(action: &str, namespace: &str, inner: &str, request_id: &str) -> String {
18    format!(
19        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
20         <{action}Response xmlns=\"{namespace}\">\
21         <{action}Result>{inner}</{action}Result>\
22         <ResponseMetadata><RequestId>{request_id}</RequestId></ResponseMetadata>\
23         </{action}Response>"
24    )
25}
26
27/// Produce a Query protocol XML response with only metadata (no result body).
28pub fn query_metadata_only_xml(action: &str, namespace: &str, request_id: &str) -> String {
29    format!(
30        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
31         <{action}Response xmlns=\"{namespace}\">\
32         <ResponseMetadata><RequestId>{request_id}</RequestId></ResponseMetadata>\
33         </{action}Response>"
34    )
35}
36
37/// Extract an optional query parameter from an `AwsRequest`.
38///
39/// Returns `None` if the parameter is missing or empty.
40pub fn optional_query_param(req: &AwsRequest, name: &str) -> Option<String> {
41    req.query_params
42        .get(name)
43        .cloned()
44        .filter(|value| !value.is_empty())
45}
46
47/// Extract a required query parameter from an `AwsRequest`.
48///
49/// Returns `MissingParameter` error if the parameter is missing or empty.
50pub fn required_query_param(req: &AwsRequest, name: &str) -> Result<String, AwsServiceError> {
51    optional_query_param(req, name).ok_or_else(|| {
52        AwsServiceError::aws_error(
53            StatusCode::BAD_REQUEST,
54            "MissingParameter",
55            format!("The request must contain the parameter {name}."),
56        )
57    })
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use bytes::Bytes;
64    use http::HeaderMap;
65    use std::collections::HashMap;
66
67    fn make_req(params: &[(&str, &str)]) -> AwsRequest {
68        let mut query_params = HashMap::new();
69        for (k, v) in params {
70            query_params.insert((*k).to_string(), (*v).to_string());
71        }
72        AwsRequest {
73            service: "x".to_string(),
74            action: "A".to_string(),
75            region: "us-east-1".to_string(),
76            account_id: "123".to_string(),
77            request_id: "r".to_string(),
78            headers: HeaderMap::new(),
79            query_params,
80            body: Bytes::new(),
81            body_stream: parking_lot::Mutex::new(None),
82            path_segments: vec![],
83            raw_path: "/".to_string(),
84            raw_query: String::new(),
85            method: http::Method::POST,
86            is_query_protocol: true,
87            access_key_id: None,
88            principal: None,
89        }
90    }
91
92    #[test]
93    fn query_response_xml_format() {
94        let xml = query_response_xml("Foo", "http://example.com/", "<Bar/>", "req-1");
95        assert!(xml.contains("<FooResponse"));
96        assert!(xml.contains("<FooResult><Bar/></FooResult>"));
97        assert!(xml.contains("<RequestId>req-1</RequestId>"));
98    }
99
100    #[test]
101    fn query_metadata_only_xml_omits_result() {
102        let xml = query_metadata_only_xml("Foo", "http://example.com/", "req-1");
103        assert!(xml.contains("<FooResponse"));
104        assert!(!xml.contains("<FooResult"));
105        assert!(xml.contains("<RequestId>req-1</RequestId>"));
106    }
107
108    #[test]
109    fn optional_query_param_returns_value() {
110        let req = make_req(&[("key", "value")]);
111        assert_eq!(optional_query_param(&req, "key").as_deref(), Some("value"));
112    }
113
114    #[test]
115    fn optional_query_param_missing_returns_none() {
116        let req = make_req(&[]);
117        assert!(optional_query_param(&req, "key").is_none());
118    }
119
120    #[test]
121    fn optional_query_param_empty_returns_none() {
122        let req = make_req(&[("key", "")]);
123        assert!(optional_query_param(&req, "key").is_none());
124    }
125
126    #[test]
127    fn required_query_param_returns_value() {
128        let req = make_req(&[("k", "v")]);
129        assert_eq!(required_query_param(&req, "k").unwrap(), "v");
130    }
131
132    #[test]
133    fn required_query_param_missing_errors() {
134        let req = make_req(&[]);
135        assert!(required_query_param(&req, "k").is_err());
136    }
137}