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            path_segments: vec![],
82            raw_path: "/".to_string(),
83            raw_query: String::new(),
84            method: http::Method::POST,
85            is_query_protocol: true,
86            access_key_id: None,
87            principal: None,
88        }
89    }
90
91    #[test]
92    fn query_response_xml_format() {
93        let xml = query_response_xml("Foo", "http://example.com/", "<Bar/>", "req-1");
94        assert!(xml.contains("<FooResponse"));
95        assert!(xml.contains("<FooResult><Bar/></FooResult>"));
96        assert!(xml.contains("<RequestId>req-1</RequestId>"));
97    }
98
99    #[test]
100    fn query_metadata_only_xml_omits_result() {
101        let xml = query_metadata_only_xml("Foo", "http://example.com/", "req-1");
102        assert!(xml.contains("<FooResponse"));
103        assert!(!xml.contains("<FooResult"));
104        assert!(xml.contains("<RequestId>req-1</RequestId>"));
105    }
106
107    #[test]
108    fn optional_query_param_returns_value() {
109        let req = make_req(&[("key", "value")]);
110        assert_eq!(optional_query_param(&req, "key").as_deref(), Some("value"));
111    }
112
113    #[test]
114    fn optional_query_param_missing_returns_none() {
115        let req = make_req(&[]);
116        assert!(optional_query_param(&req, "key").is_none());
117    }
118
119    #[test]
120    fn optional_query_param_empty_returns_none() {
121        let req = make_req(&[("key", "")]);
122        assert!(optional_query_param(&req, "key").is_none());
123    }
124
125    #[test]
126    fn required_query_param_returns_value() {
127        let req = make_req(&[("k", "v")]);
128        assert_eq!(required_query_param(&req, "k").unwrap(), "v");
129    }
130
131    #[test]
132    fn required_query_param_missing_errors() {
133        let req = make_req(&[]);
134        assert!(required_query_param(&req, "k").is_err());
135    }
136}