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