1use std::collections::HashMap;
7
8use rustack_ses_model::error::{SesError, SesErrorCode};
9
10#[must_use]
12pub fn parse_form_params(body: &[u8]) -> Vec<(String, String)> {
13 form_urlencoded::parse(body)
14 .map(|(k, v)| (k.into_owned(), v.into_owned()))
15 .collect()
16}
17
18pub fn get_required_param<'a>(
22 params: &'a [(String, String)],
23 key: &str,
24) -> Result<&'a str, SesError> {
25 params
26 .iter()
27 .find(|(k, _)| k == key)
28 .map(|(_, v)| v.as_str())
29 .ok_or_else(|| {
30 SesError::with_message(
31 SesErrorCode::InvalidParameterValue,
32 format!("Missing required parameter: {key}"),
33 )
34 })
35}
36
37#[must_use]
39pub fn get_optional_param<'a>(params: &'a [(String, String)], key: &str) -> Option<&'a str> {
40 params
41 .iter()
42 .find(|(k, _)| k == key)
43 .map(|(_, v)| v.as_str())
44}
45
46#[must_use]
51pub fn get_optional_bool(params: &[(String, String)], key: &str) -> Option<bool> {
52 get_optional_param(params, key).map(|v| v.eq_ignore_ascii_case("true"))
53}
54
55#[must_use]
57pub fn get_optional_i32(params: &[(String, String)], key: &str) -> Option<i32> {
58 get_optional_param(params, key).and_then(|v| v.parse().ok())
59}
60
61#[must_use]
66pub fn parse_member_list(params: &[(String, String)], prefix: &str) -> Vec<String> {
67 let member_prefix = format!("{prefix}.member.");
68 let mut items: Vec<(u32, String)> = Vec::new();
69 for (k, v) in params {
70 if let Some(rest) = k.strip_prefix(&member_prefix) {
71 if let Ok(idx) = rest.parse::<u32>() {
72 items.push((idx, v.clone()));
73 }
74 }
75 }
76 items.sort_by_key(|(idx, _)| *idx);
77 items.into_iter().map(|(_, v)| v).collect()
78}
79
80#[must_use]
85pub fn parse_tag_list(params: &[(String, String)], prefix: &str) -> Vec<(String, String)> {
86 let member_prefix = format!("{prefix}.member.");
87 let indices = collect_indices(params, &member_prefix);
88 let mut tags = Vec::new();
89 for idx in indices {
90 let name_key = format!("{member_prefix}{idx}.Name");
91 let value_key = format!("{member_prefix}{idx}.Value");
92 if let (Some(name), Some(value)) = (
93 get_optional_param(params, &name_key),
94 get_optional_param(params, &value_key),
95 ) {
96 tags.push((name.to_owned(), value.to_owned()));
97 }
98 }
99 tags
100}
101
102pub fn parse_attributes_map(
104 params: &[(String, String)],
105 prefix: &str,
106) -> Result<HashMap<String, String>, SesError> {
107 let mut result = HashMap::new();
108 let entry_prefix = format!("{prefix}.entry.");
109 let indices = collect_indices(params, &entry_prefix);
110
111 for idx in indices {
112 let key_param = format!("{entry_prefix}{idx}.key");
113 let value_param = format!("{entry_prefix}{idx}.value");
114
115 let key = get_required_param(params, &key_param)?;
116 let value = get_optional_param(params, &value_param).unwrap_or("");
117 result.insert(key.to_owned(), value.to_owned());
118 }
119 Ok(result)
120}
121
122#[must_use]
124pub fn parse_query_params(query: Option<&str>) -> HashMap<String, String> {
125 let mut params = HashMap::new();
126 if let Some(q) = query {
127 for (k, v) in form_urlencoded::parse(q.as_bytes()) {
128 params.insert(k.into_owned(), v.into_owned());
129 }
130 }
131 params
132}
133
134fn collect_indices(params: &[(String, String)], prefix: &str) -> Vec<u32> {
136 let mut indices: Vec<u32> = Vec::new();
137 for (k, _) in params {
138 if let Some(rest) = k.strip_prefix(prefix) {
139 if let Some(idx_str) = rest.split('.').next() {
140 if let Ok(idx) = idx_str.parse::<u32>() {
141 if !indices.contains(&idx) {
142 indices.push(idx);
143 }
144 }
145 }
146 }
147 }
148 indices
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_should_parse_form_params() {
157 let body = b"Action=SendEmail&Source=sender%40example.com&Version=2010-12-01";
158 let params = parse_form_params(body);
159 assert_eq!(params.len(), 3);
160 assert_eq!(params[0], ("Action".to_owned(), "SendEmail".to_owned()));
161 assert_eq!(
162 params[1],
163 ("Source".to_owned(), "sender@example.com".to_owned())
164 );
165 }
166
167 #[test]
168 fn test_should_get_required_param() {
169 let params = vec![("Source".to_owned(), "test@example.com".to_owned())];
170 assert_eq!(
171 get_required_param(¶ms, "Source").unwrap(),
172 "test@example.com"
173 );
174 }
175
176 #[test]
177 fn test_should_error_on_missing_required_param() {
178 let params: Vec<(String, String)> = vec![];
179 let err = get_required_param(¶ms, "Source").unwrap_err();
180 assert!(err.message.contains("Source"));
181 }
182
183 #[test]
184 fn test_should_parse_member_list() {
185 let params = vec![
186 (
187 "Destination.ToAddresses.member.1".to_owned(),
188 "a@example.com".to_owned(),
189 ),
190 (
191 "Destination.ToAddresses.member.2".to_owned(),
192 "b@example.com".to_owned(),
193 ),
194 ];
195 let list = parse_member_list(¶ms, "Destination.ToAddresses");
196 assert_eq!(list, vec!["a@example.com", "b@example.com"]);
197 }
198
199 #[test]
200 fn test_should_parse_tag_list() {
201 let params = vec![
202 ("Tags.member.1.Name".to_owned(), "campaign".to_owned()),
203 ("Tags.member.1.Value".to_owned(), "welcome".to_owned()),
204 ("Tags.member.2.Name".to_owned(), "env".to_owned()),
205 ("Tags.member.2.Value".to_owned(), "test".to_owned()),
206 ];
207 let tags = parse_tag_list(¶ms, "Tags");
208 assert_eq!(tags.len(), 2);
209 assert_eq!(tags[0], ("campaign".to_owned(), "welcome".to_owned()));
210 assert_eq!(tags[1], ("env".to_owned(), "test".to_owned()));
211 }
212
213 #[test]
214 fn test_should_parse_query_params() {
215 let params = parse_query_params(Some("id=abc&email=test@example.com"));
216 assert_eq!(params.get("id").unwrap(), "abc");
217 assert_eq!(params.get("email").unwrap(), "test@example.com");
218 }
219
220 #[test]
221 fn test_should_parse_empty_query_params() {
222 let params = parse_query_params(None);
223 assert!(params.is_empty());
224 }
225}