Skip to main content

rustack_ses_core/
handler.rs

1//! SES handler implementation bridging HTTP to business logic.
2//!
3//! Parses form-urlencoded request bodies, dispatches to the provider,
4//! and serializes XML responses following the awsQuery protocol.
5//!
6//! Covers Phase 0-3: identity management, email sending, templates,
7//! configuration sets, event destinations, receipt rules, identity
8//! notification/DKIM/mail-from configuration, and sending authorization.
9
10use std::{future::Future, pin::Pin, sync::Arc};
11
12use base64::Engine;
13use bytes::Bytes;
14use rustack_ses_http::{
15    body::SesResponseBody,
16    dispatch::SesHandler,
17    request::{
18        get_optional_bool, get_optional_param, get_required_param, parse_form_params,
19        parse_member_list, parse_tag_list,
20    },
21    response::{XmlWriter, xml_response},
22};
23use rustack_ses_model::{
24    error::SesError,
25    input::{
26        CloneReceiptRuleSetInput, CreateConfigurationSetEventDestinationInput,
27        CreateConfigurationSetInput, CreateReceiptRuleInput, CreateReceiptRuleSetInput,
28        CreateTemplateInput, DeleteConfigurationSetEventDestinationInput,
29        DeleteConfigurationSetInput, DeleteIdentityInput, DeleteIdentityPolicyInput,
30        DeleteReceiptRuleInput, DeleteReceiptRuleSetInput, DeleteTemplateInput,
31        DeleteVerifiedEmailAddressInput, DescribeActiveReceiptRuleSetInput,
32        DescribeConfigurationSetInput, DescribeReceiptRuleSetInput, GetIdentityDkimAttributesInput,
33        GetIdentityMailFromDomainAttributesInput, GetIdentityNotificationAttributesInput,
34        GetIdentityPoliciesInput, GetIdentityVerificationAttributesInput, GetTemplateInput,
35        ListConfigurationSetsInput, ListIdentitiesInput, ListIdentityPoliciesInput,
36        ListTemplatesInput, PutIdentityPolicyInput, SendEmailInput, SendRawEmailInput,
37        SendTemplatedEmailInput, SetActiveReceiptRuleSetInput,
38        SetIdentityFeedbackForwardingEnabledInput, SetIdentityMailFromDomainInput,
39        SetIdentityNotificationTopicInput, UpdateConfigurationSetEventDestinationInput,
40        UpdateTemplateInput, VerifyDomainDkimInput, VerifyDomainIdentityInput,
41        VerifyEmailAddressInput, VerifyEmailIdentityInput,
42    },
43    operations::SesOperation,
44    types::{
45        BehaviorOnMXFailure, Body, ConfigurationSet, Content, Destination, EventDestination,
46        IdentityType, Message, MessageTag, NotificationType, RawMessage, ReceiptRule, Template,
47    },
48};
49
50use crate::provider::RustackSes;
51
52/// Handler that bridges the HTTP layer to the SES provider.
53#[derive(Debug)]
54pub struct RustackSesHandler {
55    provider: Arc<RustackSes>,
56}
57
58impl RustackSesHandler {
59    /// Create a new handler wrapping a provider.
60    #[must_use]
61    pub fn new(provider: Arc<RustackSes>) -> Self {
62        Self { provider }
63    }
64
65    /// Get access to the underlying provider.
66    #[must_use]
67    pub fn provider(&self) -> &RustackSes {
68        &self.provider
69    }
70}
71
72impl SesHandler for RustackSesHandler {
73    fn handle_operation(
74        &self,
75        op: SesOperation,
76        body: Bytes,
77    ) -> Pin<Box<dyn Future<Output = Result<http::Response<SesResponseBody>, SesError>> + Send>>
78    {
79        let provider = Arc::clone(&self.provider);
80        Box::pin(async move { dispatch(&provider, op, &body) })
81    }
82
83    fn handle_v2_operation(
84        &self,
85        _method: http::Method,
86        _path: String,
87        _body: Bytes,
88    ) -> Pin<Box<dyn Future<Output = Result<http::Response<SesResponseBody>, SesError>> + Send>>
89    {
90        Box::pin(async { Err(SesError::not_implemented("SES v2")) })
91    }
92
93    fn query_emails(&self, filter_id: Option<&str>, filter_source: Option<&str>) -> String {
94        let messages = self.provider.emails.query(filter_id, filter_source);
95        let body = serde_json::json!({ "messages": messages });
96        body.to_string()
97    }
98
99    fn clear_emails(&self, filter_id: Option<&str>) {
100        if let Some(id) = filter_id {
101            self.provider.emails.remove(id);
102        } else {
103            self.provider.emails.clear();
104        }
105    }
106}
107
108/// Dispatch an SES operation to the appropriate handler method.
109#[allow(clippy::too_many_lines)]
110fn dispatch(
111    provider: &RustackSes,
112    op: SesOperation,
113    body: &[u8],
114) -> Result<http::Response<SesResponseBody>, SesError> {
115    let params = parse_form_params(body);
116    let request_id = uuid::Uuid::new_v4().to_string();
117
118    match op {
119        // ---------------------------------------------------------------
120        // Phase 0: Core Sending + Identities
121        // ---------------------------------------------------------------
122        SesOperation::VerifyEmailIdentity => {
123            let input = VerifyEmailIdentityInput {
124                email_address: get_required_param(&params, "EmailAddress")?.to_owned(),
125            };
126            provider.verify_email_identity(input)?;
127            Ok(empty_xml_response("VerifyEmailIdentity", &request_id))
128        }
129
130        SesOperation::VerifyDomainIdentity => {
131            let input = VerifyDomainIdentityInput {
132                domain: get_required_param(&params, "Domain")?.to_owned(),
133            };
134            let output = provider.verify_domain_identity(input)?;
135            let mut w = XmlWriter::new();
136            w.start_response("VerifyDomainIdentity");
137            w.start_result("VerifyDomainIdentity");
138            w.write_element("VerificationToken", &output.verification_token);
139            w.end_element("VerifyDomainIdentityResult");
140            w.write_response_metadata(&request_id);
141            w.end_element("VerifyDomainIdentityResponse");
142            Ok(xml_response(w.into_string(), &request_id))
143        }
144
145        SesOperation::ListIdentities => {
146            let identity_type = get_optional_param(&params, "IdentityType").map(|s| {
147                if s == "EmailAddress" {
148                    IdentityType::EmailAddress
149                } else {
150                    IdentityType::Domain
151                }
152            });
153            let input = ListIdentitiesInput {
154                identity_type,
155                max_items: None,
156                next_token: None,
157            };
158            let output = provider.list_identities(input)?;
159            let mut w = XmlWriter::new();
160            w.start_response("ListIdentities");
161            w.start_result("ListIdentities");
162            w.raw("<Identities>");
163            for identity in &output.identities {
164                w.write_element("member", identity);
165            }
166            w.raw("</Identities>");
167            w.write_optional_element("NextToken", output.next_token.as_deref());
168            w.end_element("ListIdentitiesResult");
169            w.write_response_metadata(&request_id);
170            w.end_element("ListIdentitiesResponse");
171            Ok(xml_response(w.into_string(), &request_id))
172        }
173
174        SesOperation::DeleteIdentity => {
175            let input = DeleteIdentityInput {
176                identity: get_required_param(&params, "Identity")?.to_owned(),
177            };
178            provider.delete_identity(input)?;
179            Ok(empty_xml_response("DeleteIdentity", &request_id))
180        }
181
182        SesOperation::GetIdentityVerificationAttributes => {
183            let identities = parse_member_list(&params, "Identities");
184            let input = GetIdentityVerificationAttributesInput { identities };
185            let output = provider.get_identity_verification_attributes(input)?;
186            let mut w = XmlWriter::new();
187            w.start_response("GetIdentityVerificationAttributes");
188            w.start_result("GetIdentityVerificationAttributes");
189            w.raw("<VerificationAttributes>");
190            for (identity, attrs) in &output.verification_attributes {
191                w.raw("<entry>");
192                w.write_element("key", identity);
193                w.raw("<value>");
194                w.write_element("VerificationStatus", attrs.verification_status.as_str());
195                w.write_optional_element("VerificationToken", attrs.verification_token.as_deref());
196                w.raw("</value>");
197                w.raw("</entry>");
198            }
199            w.raw("</VerificationAttributes>");
200            w.end_element("GetIdentityVerificationAttributesResult");
201            w.write_response_metadata(&request_id);
202            w.end_element("GetIdentityVerificationAttributesResponse");
203            Ok(xml_response(w.into_string(), &request_id))
204        }
205
206        SesOperation::VerifyEmailAddress => {
207            let input = VerifyEmailAddressInput {
208                email_address: get_required_param(&params, "EmailAddress")?.to_owned(),
209            };
210            provider.verify_email_address(input)?;
211            Ok(empty_xml_response("VerifyEmailAddress", &request_id))
212        }
213
214        SesOperation::DeleteVerifiedEmailAddress => {
215            let input = DeleteVerifiedEmailAddressInput {
216                email_address: get_required_param(&params, "EmailAddress")?.to_owned(),
217            };
218            provider.delete_verified_email_address(input)?;
219            Ok(empty_xml_response(
220                "DeleteVerifiedEmailAddress",
221                &request_id,
222            ))
223        }
224
225        SesOperation::ListVerifiedEmailAddresses => {
226            let output = provider.list_verified_email_addresses()?;
227            let mut w = XmlWriter::new();
228            w.start_response("ListVerifiedEmailAddresses");
229            w.start_result("ListVerifiedEmailAddresses");
230            w.raw("<VerifiedEmailAddresses>");
231            for email in &output.verified_email_addresses {
232                w.write_element("member", email);
233            }
234            w.raw("</VerifiedEmailAddresses>");
235            w.end_element("ListVerifiedEmailAddressesResult");
236            w.write_response_metadata(&request_id);
237            w.end_element("ListVerifiedEmailAddressesResponse");
238            Ok(xml_response(w.into_string(), &request_id))
239        }
240
241        SesOperation::SendEmail => {
242            let input = deserialize_send_email(&params)?;
243            let output = provider.send_email(input)?;
244            let mut w = XmlWriter::new();
245            w.start_response("SendEmail");
246            w.start_result("SendEmail");
247            w.write_element("MessageId", &output.message_id);
248            w.end_element("SendEmailResult");
249            w.write_response_metadata(&request_id);
250            w.end_element("SendEmailResponse");
251            Ok(xml_response(w.into_string(), &request_id))
252        }
253
254        SesOperation::SendRawEmail => {
255            let input = deserialize_send_raw_email(&params)?;
256            let output = provider.send_raw_email(input)?;
257            let mut w = XmlWriter::new();
258            w.start_response("SendRawEmail");
259            w.start_result("SendRawEmail");
260            w.write_element("MessageId", &output.message_id);
261            w.end_element("SendRawEmailResult");
262            w.write_response_metadata(&request_id);
263            w.end_element("SendRawEmailResponse");
264            Ok(xml_response(w.into_string(), &request_id))
265        }
266
267        SesOperation::GetSendQuota => {
268            let output = provider.get_send_quota()?;
269            let mut w = XmlWriter::new();
270            w.start_response("GetSendQuota");
271            w.start_result("GetSendQuota");
272            if let Some(v) = output.max24_hour_send {
273                w.write_f64_element("Max24HourSend", v);
274            }
275            if let Some(v) = output.max_send_rate {
276                w.write_f64_element("MaxSendRate", v);
277            }
278            if let Some(v) = output.sent_last24_hours {
279                w.write_f64_element("SentLast24Hours", v);
280            }
281            w.end_element("GetSendQuotaResult");
282            w.write_response_metadata(&request_id);
283            w.end_element("GetSendQuotaResponse");
284            Ok(xml_response(w.into_string(), &request_id))
285        }
286
287        SesOperation::GetSendStatistics => {
288            let output = provider.get_send_statistics()?;
289            let mut w = XmlWriter::new();
290            w.start_response("GetSendStatistics");
291            w.start_result("GetSendStatistics");
292            w.raw("<SendDataPoints>");
293            for dp in &output.send_data_points {
294                w.raw("<member>");
295                if let Some(v) = dp.delivery_attempts {
296                    w.write_i64_element("DeliveryAttempts", v);
297                }
298                if let Some(v) = dp.bounces {
299                    w.write_i64_element("Bounces", v);
300                }
301                if let Some(v) = dp.complaints {
302                    w.write_i64_element("Complaints", v);
303                }
304                if let Some(v) = dp.rejects {
305                    w.write_i64_element("Rejects", v);
306                }
307                if let Some(ts) = &dp.timestamp {
308                    w.write_element("Timestamp", &ts.to_rfc3339());
309                }
310                w.raw("</member>");
311            }
312            w.raw("</SendDataPoints>");
313            w.end_element("GetSendStatisticsResult");
314            w.write_response_metadata(&request_id);
315            w.end_element("GetSendStatisticsResponse");
316            Ok(xml_response(w.into_string(), &request_id))
317        }
318
319        // ---------------------------------------------------------------
320        // Phase 1: Templates + Configuration Sets
321        // ---------------------------------------------------------------
322        SesOperation::CreateTemplate => {
323            let input = deserialize_create_template(&params)?;
324            provider.create_template(input)?;
325            Ok(empty_xml_response("CreateTemplate", &request_id))
326        }
327
328        SesOperation::GetTemplate => {
329            let input = GetTemplateInput {
330                template_name: get_required_param(&params, "TemplateName")?.to_owned(),
331            };
332            let output = provider.get_template(input)?;
333            let mut w = XmlWriter::new();
334            w.start_response("GetTemplate");
335            w.start_result("GetTemplate");
336            if let Some(t) = &output.template {
337                w.raw("<Template>");
338                w.write_element("TemplateName", &t.template_name);
339                w.write_optional_element("SubjectPart", t.subject_part.as_deref());
340                w.write_optional_element("TextPart", t.text_part.as_deref());
341                w.write_optional_element("HtmlPart", t.html_part.as_deref());
342                w.raw("</Template>");
343            }
344            w.end_element("GetTemplateResult");
345            w.write_response_metadata(&request_id);
346            w.end_element("GetTemplateResponse");
347            Ok(xml_response(w.into_string(), &request_id))
348        }
349
350        SesOperation::UpdateTemplate => {
351            let input = deserialize_update_template(&params)?;
352            provider.update_template(input)?;
353            Ok(empty_xml_response("UpdateTemplate", &request_id))
354        }
355
356        SesOperation::DeleteTemplate => {
357            let input = DeleteTemplateInput {
358                template_name: get_required_param(&params, "TemplateName")?.to_owned(),
359            };
360            provider.delete_template(input)?;
361            Ok(empty_xml_response("DeleteTemplate", &request_id))
362        }
363
364        SesOperation::ListTemplates => {
365            let input = ListTemplatesInput {
366                max_items: None,
367                next_token: None,
368            };
369            let output = provider.list_templates(input)?;
370            let mut w = XmlWriter::new();
371            w.start_response("ListTemplates");
372            w.start_result("ListTemplates");
373            w.raw("<TemplatesMetadata>");
374            for meta in &output.templates_metadata {
375                w.raw("<member>");
376                w.write_optional_element("Name", meta.name.as_deref());
377                if let Some(ts) = &meta.created_timestamp {
378                    w.write_element("CreatedTimestamp", &ts.to_rfc3339());
379                }
380                w.raw("</member>");
381            }
382            w.raw("</TemplatesMetadata>");
383            w.write_optional_element("NextToken", output.next_token.as_deref());
384            w.end_element("ListTemplatesResult");
385            w.write_response_metadata(&request_id);
386            w.end_element("ListTemplatesResponse");
387            Ok(xml_response(w.into_string(), &request_id))
388        }
389
390        SesOperation::SendTemplatedEmail => {
391            let input = deserialize_send_templated_email(&params)?;
392            let output = provider.send_templated_email(input)?;
393            let mut w = XmlWriter::new();
394            w.start_response("SendTemplatedEmail");
395            w.start_result("SendTemplatedEmail");
396            w.write_element("MessageId", &output.message_id);
397            w.end_element("SendTemplatedEmailResult");
398            w.write_response_metadata(&request_id);
399            w.end_element("SendTemplatedEmailResponse");
400            Ok(xml_response(w.into_string(), &request_id))
401        }
402
403        SesOperation::CreateConfigurationSet => {
404            let name = get_required_param(&params, "ConfigurationSet.Name")?.to_owned();
405            let input = CreateConfigurationSetInput {
406                configuration_set: ConfigurationSet { name },
407            };
408            provider.create_configuration_set(input)?;
409            Ok(empty_xml_response("CreateConfigurationSet", &request_id))
410        }
411
412        SesOperation::DeleteConfigurationSet => {
413            let input = DeleteConfigurationSetInput {
414                configuration_set_name: get_required_param(&params, "ConfigurationSetName")?
415                    .to_owned(),
416            };
417            provider.delete_configuration_set(input)?;
418            Ok(empty_xml_response("DeleteConfigurationSet", &request_id))
419        }
420
421        SesOperation::DescribeConfigurationSet => {
422            let input = DescribeConfigurationSetInput {
423                configuration_set_name: get_required_param(&params, "ConfigurationSetName")?
424                    .to_owned(),
425                configuration_set_attribute_names: Vec::new(),
426            };
427            let output = provider.describe_configuration_set(input)?;
428            let mut w = XmlWriter::new();
429            w.start_response("DescribeConfigurationSet");
430            w.start_result("DescribeConfigurationSet");
431            if let Some(cs) = &output.configuration_set {
432                w.raw("<ConfigurationSet>");
433                w.write_element("Name", &cs.name);
434                w.raw("</ConfigurationSet>");
435            }
436            w.raw("<EventDestinations>");
437            for dest in &output.event_destinations {
438                write_event_destination_xml(&mut w, dest);
439            }
440            w.raw("</EventDestinations>");
441            w.end_element("DescribeConfigurationSetResult");
442            w.write_response_metadata(&request_id);
443            w.end_element("DescribeConfigurationSetResponse");
444            Ok(xml_response(w.into_string(), &request_id))
445        }
446
447        SesOperation::ListConfigurationSets => {
448            let input = ListConfigurationSetsInput {
449                max_items: None,
450                next_token: None,
451            };
452            let output = provider.list_configuration_sets(input)?;
453            let mut w = XmlWriter::new();
454            w.start_response("ListConfigurationSets");
455            w.start_result("ListConfigurationSets");
456            w.raw("<ConfigurationSets>");
457            for cs in &output.configuration_sets {
458                w.raw("<member>");
459                w.write_element("Name", &cs.name);
460                w.raw("</member>");
461            }
462            w.raw("</ConfigurationSets>");
463            w.write_optional_element("NextToken", output.next_token.as_deref());
464            w.end_element("ListConfigurationSetsResult");
465            w.write_response_metadata(&request_id);
466            w.end_element("ListConfigurationSetsResponse");
467            Ok(xml_response(w.into_string(), &request_id))
468        }
469
470        // ---------------------------------------------------------------
471        // Phase 2: Event Destinations + Receipt Rules
472        // ---------------------------------------------------------------
473        SesOperation::CreateConfigurationSetEventDestination => {
474            let input = deserialize_create_config_set_event_dest(&params)?;
475            provider.create_configuration_set_event_destination(input)?;
476            Ok(empty_xml_response(
477                "CreateConfigurationSetEventDestination",
478                &request_id,
479            ))
480        }
481
482        SesOperation::UpdateConfigurationSetEventDestination => {
483            let input = deserialize_update_config_set_event_dest(&params)?;
484            provider.update_configuration_set_event_destination(input)?;
485            Ok(empty_xml_response(
486                "UpdateConfigurationSetEventDestination",
487                &request_id,
488            ))
489        }
490
491        SesOperation::DeleteConfigurationSetEventDestination => {
492            let input = DeleteConfigurationSetEventDestinationInput {
493                configuration_set_name: get_required_param(&params, "ConfigurationSetName")?
494                    .to_owned(),
495                event_destination_name: get_required_param(&params, "EventDestinationName")?
496                    .to_owned(),
497            };
498            provider.delete_configuration_set_event_destination(input)?;
499            Ok(empty_xml_response(
500                "DeleteConfigurationSetEventDestination",
501                &request_id,
502            ))
503        }
504
505        SesOperation::CreateReceiptRuleSet => {
506            let input = CreateReceiptRuleSetInput {
507                rule_set_name: get_required_param(&params, "RuleSetName")?.to_owned(),
508            };
509            provider.create_receipt_rule_set(input)?;
510            Ok(empty_xml_response("CreateReceiptRuleSet", &request_id))
511        }
512
513        SesOperation::DeleteReceiptRuleSet => {
514            let input = DeleteReceiptRuleSetInput {
515                rule_set_name: get_required_param(&params, "RuleSetName")?.to_owned(),
516            };
517            provider.delete_receipt_rule_set(input)?;
518            Ok(empty_xml_response("DeleteReceiptRuleSet", &request_id))
519        }
520
521        SesOperation::CreateReceiptRule => {
522            let input = deserialize_create_receipt_rule(&params)?;
523            provider.create_receipt_rule(input)?;
524            Ok(empty_xml_response("CreateReceiptRule", &request_id))
525        }
526
527        SesOperation::DeleteReceiptRule => {
528            let input = DeleteReceiptRuleInput {
529                rule_set_name: get_required_param(&params, "RuleSetName")?.to_owned(),
530                rule_name: get_required_param(&params, "RuleName")?.to_owned(),
531            };
532            provider.delete_receipt_rule(input)?;
533            Ok(empty_xml_response("DeleteReceiptRule", &request_id))
534        }
535
536        SesOperation::DescribeReceiptRuleSet => {
537            let input = DescribeReceiptRuleSetInput {
538                rule_set_name: get_required_param(&params, "RuleSetName")?.to_owned(),
539            };
540            let output = provider.describe_receipt_rule_set(input)?;
541            let mut w = XmlWriter::new();
542            w.start_response("DescribeReceiptRuleSet");
543            w.start_result("DescribeReceiptRuleSet");
544            if let Some(meta) = &output.metadata {
545                w.raw("<Metadata>");
546                w.write_optional_element("Name", meta.name.as_deref());
547                if let Some(ts) = &meta.created_timestamp {
548                    w.write_element("CreatedTimestamp", &ts.to_rfc3339());
549                }
550                w.raw("</Metadata>");
551            }
552            w.raw("<Rules>");
553            for rule in &output.rules {
554                write_receipt_rule_xml(&mut w, rule);
555            }
556            w.raw("</Rules>");
557            w.end_element("DescribeReceiptRuleSetResult");
558            w.write_response_metadata(&request_id);
559            w.end_element("DescribeReceiptRuleSetResponse");
560            Ok(xml_response(w.into_string(), &request_id))
561        }
562
563        SesOperation::CloneReceiptRuleSet => {
564            let input = CloneReceiptRuleSetInput {
565                original_rule_set_name: get_required_param(&params, "OriginalRuleSetName")?
566                    .to_owned(),
567                rule_set_name: get_required_param(&params, "RuleSetName")?.to_owned(),
568            };
569            provider.clone_receipt_rule_set(input)?;
570            Ok(empty_xml_response("CloneReceiptRuleSet", &request_id))
571        }
572
573        SesOperation::DescribeActiveReceiptRuleSet => {
574            let output =
575                provider.describe_active_receipt_rule_set(DescribeActiveReceiptRuleSetInput {})?;
576            let mut w = XmlWriter::new();
577            w.start_response("DescribeActiveReceiptRuleSet");
578            w.start_result("DescribeActiveReceiptRuleSet");
579            if let Some(meta) = &output.metadata {
580                w.raw("<Metadata>");
581                w.write_optional_element("Name", meta.name.as_deref());
582                if let Some(ts) = &meta.created_timestamp {
583                    w.write_element("CreatedTimestamp", &ts.to_rfc3339());
584                }
585                w.raw("</Metadata>");
586            }
587            w.raw("<Rules>");
588            for rule in &output.rules {
589                write_receipt_rule_xml(&mut w, rule);
590            }
591            w.raw("</Rules>");
592            w.end_element("DescribeActiveReceiptRuleSetResult");
593            w.write_response_metadata(&request_id);
594            w.end_element("DescribeActiveReceiptRuleSetResponse");
595            Ok(xml_response(w.into_string(), &request_id))
596        }
597
598        SesOperation::SetActiveReceiptRuleSet => {
599            let input = SetActiveReceiptRuleSetInput {
600                rule_set_name: get_optional_param(&params, "RuleSetName").map(str::to_owned),
601            };
602            provider.set_active_receipt_rule_set(input)?;
603            Ok(empty_xml_response("SetActiveReceiptRuleSet", &request_id))
604        }
605
606        // ---------------------------------------------------------------
607        // Phase 3: Identity Configuration + Sending Authorization
608        // ---------------------------------------------------------------
609        SesOperation::SetIdentityNotificationTopic => {
610            let notification_type_str = get_required_param(&params, "NotificationType")?;
611            let notification_type = NotificationType::from(notification_type_str);
612            let input = SetIdentityNotificationTopicInput {
613                identity: get_required_param(&params, "Identity")?.to_owned(),
614                notification_type,
615                sns_topic: get_optional_param(&params, "SnsTopic").map(str::to_owned),
616            };
617            provider.set_identity_notification_topic(input)?;
618            Ok(empty_xml_response(
619                "SetIdentityNotificationTopic",
620                &request_id,
621            ))
622        }
623
624        SesOperation::SetIdentityFeedbackForwardingEnabled => {
625            let input = SetIdentityFeedbackForwardingEnabledInput {
626                identity: get_required_param(&params, "Identity")?.to_owned(),
627                forwarding_enabled: get_optional_bool(&params, "ForwardingEnabled").unwrap_or(true),
628            };
629            provider.set_identity_feedback_forwarding_enabled(input)?;
630            Ok(empty_xml_response(
631                "SetIdentityFeedbackForwardingEnabled",
632                &request_id,
633            ))
634        }
635
636        SesOperation::GetIdentityNotificationAttributes => {
637            let identities = parse_member_list(&params, "Identities");
638            let input = GetIdentityNotificationAttributesInput { identities };
639            let output = provider.get_identity_notification_attributes(input)?;
640            let mut w = XmlWriter::new();
641            w.start_response("GetIdentityNotificationAttributes");
642            w.start_result("GetIdentityNotificationAttributes");
643            w.raw("<NotificationAttributes>");
644            for (identity, attrs) in &output.notification_attributes {
645                w.raw("<entry>");
646                w.write_element("key", identity);
647                w.raw("<value>");
648                w.write_element("BounceTopic", &attrs.bounce_topic);
649                w.write_element("ComplaintTopic", &attrs.complaint_topic);
650                w.write_element("DeliveryTopic", &attrs.delivery_topic);
651                w.write_bool_element("ForwardingEnabled", attrs.forwarding_enabled);
652                w.write_bool_element(
653                    "HeadersInBounceNotificationsEnabled",
654                    attrs
655                        .headers_in_bounce_notifications_enabled
656                        .unwrap_or(false),
657                );
658                w.write_bool_element(
659                    "HeadersInComplaintNotificationsEnabled",
660                    attrs
661                        .headers_in_complaint_notifications_enabled
662                        .unwrap_or(false),
663                );
664                w.write_bool_element(
665                    "HeadersInDeliveryNotificationsEnabled",
666                    attrs
667                        .headers_in_delivery_notifications_enabled
668                        .unwrap_or(false),
669                );
670                w.raw("</value>");
671                w.raw("</entry>");
672            }
673            w.raw("</NotificationAttributes>");
674            w.end_element("GetIdentityNotificationAttributesResult");
675            w.write_response_metadata(&request_id);
676            w.end_element("GetIdentityNotificationAttributesResponse");
677            Ok(xml_response(w.into_string(), &request_id))
678        }
679
680        SesOperation::VerifyDomainDkim => {
681            let input = VerifyDomainDkimInput {
682                domain: get_required_param(&params, "Domain")?.to_owned(),
683            };
684            let output = provider.verify_domain_dkim(input)?;
685            let mut w = XmlWriter::new();
686            w.start_response("VerifyDomainDkim");
687            w.start_result("VerifyDomainDkim");
688            w.raw("<DkimTokens>");
689            for token in &output.dkim_tokens {
690                w.write_element("member", token);
691            }
692            w.raw("</DkimTokens>");
693            w.end_element("VerifyDomainDkimResult");
694            w.write_response_metadata(&request_id);
695            w.end_element("VerifyDomainDkimResponse");
696            Ok(xml_response(w.into_string(), &request_id))
697        }
698
699        SesOperation::GetIdentityDkimAttributes => {
700            let identities = parse_member_list(&params, "Identities");
701            let input = GetIdentityDkimAttributesInput { identities };
702            let output = provider.get_identity_dkim_attributes(input)?;
703            let mut w = XmlWriter::new();
704            w.start_response("GetIdentityDkimAttributes");
705            w.start_result("GetIdentityDkimAttributes");
706            w.raw("<DkimAttributes>");
707            for (identity, attrs) in &output.dkim_attributes {
708                w.raw("<entry>");
709                w.write_element("key", identity);
710                w.raw("<value>");
711                w.write_bool_element("DkimEnabled", attrs.dkim_enabled);
712                w.write_element(
713                    "DkimVerificationStatus",
714                    attrs.dkim_verification_status.as_str(),
715                );
716                w.raw("<DkimTokens>");
717                for token in &attrs.dkim_tokens {
718                    w.write_element("member", token);
719                }
720                w.raw("</DkimTokens>");
721                w.raw("</value>");
722                w.raw("</entry>");
723            }
724            w.raw("</DkimAttributes>");
725            w.end_element("GetIdentityDkimAttributesResult");
726            w.write_response_metadata(&request_id);
727            w.end_element("GetIdentityDkimAttributesResponse");
728            Ok(xml_response(w.into_string(), &request_id))
729        }
730
731        SesOperation::SetIdentityMailFromDomain => {
732            let behavior =
733                get_optional_param(&params, "BehaviorOnMXFailure").map(BehaviorOnMXFailure::from);
734            let input = SetIdentityMailFromDomainInput {
735                identity: get_required_param(&params, "Identity")?.to_owned(),
736                mail_from_domain: get_optional_param(&params, "MailFromDomain").map(str::to_owned),
737                behavior_on_mx_failure: behavior,
738            };
739            provider.set_identity_mail_from_domain(input)?;
740            Ok(empty_xml_response("SetIdentityMailFromDomain", &request_id))
741        }
742
743        SesOperation::GetIdentityMailFromDomainAttributes => {
744            let identities = parse_member_list(&params, "Identities");
745            let input = GetIdentityMailFromDomainAttributesInput { identities };
746            let output = provider.get_identity_mail_from_domain_attributes(input)?;
747            let mut w = XmlWriter::new();
748            w.start_response("GetIdentityMailFromDomainAttributes");
749            w.start_result("GetIdentityMailFromDomainAttributes");
750            w.raw("<MailFromDomainAttributes>");
751            for (identity, attrs) in &output.mail_from_domain_attributes {
752                w.raw("<entry>");
753                w.write_element("key", identity);
754                w.raw("<value>");
755                w.write_element("MailFromDomain", &attrs.mail_from_domain);
756                w.write_element(
757                    "MailFromDomainStatus",
758                    attrs.mail_from_domain_status.as_str(),
759                );
760                w.write_element("BehaviorOnMXFailure", attrs.behavior_on_mx_failure.as_str());
761                w.raw("</value>");
762                w.raw("</entry>");
763            }
764            w.raw("</MailFromDomainAttributes>");
765            w.end_element("GetIdentityMailFromDomainAttributesResult");
766            w.write_response_metadata(&request_id);
767            w.end_element("GetIdentityMailFromDomainAttributesResponse");
768            Ok(xml_response(w.into_string(), &request_id))
769        }
770
771        SesOperation::GetIdentityPolicies => {
772            let identity = get_required_param(&params, "Identity")?.to_owned();
773            let policy_names = parse_member_list(&params, "PolicyNames");
774            let input = GetIdentityPoliciesInput {
775                identity,
776                policy_names,
777            };
778            let output = provider.get_identity_policies(input)?;
779            let mut w = XmlWriter::new();
780            w.start_response("GetIdentityPolicies");
781            w.start_result("GetIdentityPolicies");
782            w.raw("<Policies>");
783            for (name, policy) in &output.policies {
784                w.raw("<entry>");
785                w.write_element("key", name);
786                w.write_element("value", policy);
787                w.raw("</entry>");
788            }
789            w.raw("</Policies>");
790            w.end_element("GetIdentityPoliciesResult");
791            w.write_response_metadata(&request_id);
792            w.end_element("GetIdentityPoliciesResponse");
793            Ok(xml_response(w.into_string(), &request_id))
794        }
795
796        SesOperation::PutIdentityPolicy => {
797            let input = PutIdentityPolicyInput {
798                identity: get_required_param(&params, "Identity")?.to_owned(),
799                policy_name: get_required_param(&params, "PolicyName")?.to_owned(),
800                policy: get_required_param(&params, "Policy")?.to_owned(),
801            };
802            provider.put_identity_policy(input)?;
803            Ok(empty_xml_response("PutIdentityPolicy", &request_id))
804        }
805
806        SesOperation::DeleteIdentityPolicy => {
807            let input = DeleteIdentityPolicyInput {
808                identity: get_required_param(&params, "Identity")?.to_owned(),
809                policy_name: get_required_param(&params, "PolicyName")?.to_owned(),
810            };
811            provider.delete_identity_policy(input)?;
812            Ok(empty_xml_response("DeleteIdentityPolicy", &request_id))
813        }
814
815        SesOperation::ListIdentityPolicies => {
816            let input = ListIdentityPoliciesInput {
817                identity: get_required_param(&params, "Identity")?.to_owned(),
818            };
819            let output = provider.list_identity_policies(input)?;
820            let mut w = XmlWriter::new();
821            w.start_response("ListIdentityPolicies");
822            w.start_result("ListIdentityPolicies");
823            w.raw("<PolicyNames>");
824            for name in &output.policy_names {
825                w.write_element("member", name);
826            }
827            w.raw("</PolicyNames>");
828            w.end_element("ListIdentityPoliciesResult");
829            w.write_response_metadata(&request_id);
830            w.end_element("ListIdentityPoliciesResponse");
831            Ok(xml_response(w.into_string(), &request_id))
832        }
833    }
834}
835
836// ---------------------------------------------------------------
837// Helper: empty XML response
838// ---------------------------------------------------------------
839
840fn empty_xml_response(operation: &str, request_id: &str) -> http::Response<SesResponseBody> {
841    let mut w = XmlWriter::new();
842    w.start_response(operation);
843    w.start_result(operation);
844    w.end_element(&format!("{operation}Result"));
845    w.write_response_metadata(request_id);
846    w.end_element(&format!("{operation}Response"));
847    xml_response(w.into_string(), request_id)
848}
849
850// ---------------------------------------------------------------
851// Deserialization helpers for complex input types
852// ---------------------------------------------------------------
853
854fn deserialize_send_email(params: &[(String, String)]) -> Result<SendEmailInput, SesError> {
855    let source = get_required_param(params, "Source")?.to_owned();
856    let to = parse_member_list(params, "Destination.ToAddresses");
857    let cc = parse_member_list(params, "Destination.CcAddresses");
858    let bcc = parse_member_list(params, "Destination.BccAddresses");
859    let subject_data = get_optional_param(params, "Message.Subject.Data")
860        .unwrap_or("")
861        .to_owned();
862    let subject_charset = get_optional_param(params, "Message.Subject.Charset").map(str::to_owned);
863    let text_data = get_optional_param(params, "Message.Body.Text.Data").map(str::to_owned);
864    let html_data = get_optional_param(params, "Message.Body.Html.Data").map(str::to_owned);
865
866    let tags = parse_tag_list(params, "Tags");
867
868    Ok(SendEmailInput {
869        source,
870        destination: Destination {
871            to_addresses: to,
872            cc_addresses: cc,
873            bcc_addresses: bcc,
874        },
875        message: Message {
876            subject: Content {
877                data: subject_data,
878                charset: subject_charset,
879            },
880            body: Body {
881                text: text_data.map(|d| Content {
882                    data: d,
883                    charset: get_optional_param(params, "Message.Body.Text.Charset")
884                        .map(str::to_owned),
885                }),
886                html: html_data.map(|d| Content {
887                    data: d,
888                    charset: get_optional_param(params, "Message.Body.Html.Charset")
889                        .map(str::to_owned),
890                }),
891            },
892        },
893        tags: tags
894            .into_iter()
895            .map(|(n, v)| MessageTag { name: n, value: v })
896            .collect(),
897        configuration_set_name: get_optional_param(params, "ConfigurationSetName")
898            .map(str::to_owned),
899        return_path: get_optional_param(params, "ReturnPath").map(str::to_owned),
900        return_path_arn: get_optional_param(params, "ReturnPathArn").map(str::to_owned),
901        source_arn: get_optional_param(params, "SourceArn").map(str::to_owned),
902        reply_to_addresses: parse_member_list(params, "ReplyToAddresses"),
903    })
904}
905
906fn deserialize_send_raw_email(params: &[(String, String)]) -> Result<SendRawEmailInput, SesError> {
907    let raw_data = get_required_param(params, "RawMessage.Data")?.to_owned();
908    let decoded = base64::engine::general_purpose::STANDARD
909        .decode(raw_data.as_bytes())
910        .map_err(|e| {
911            SesError::invalid_parameter_value(format!("RawMessage.Data is not valid base64: {e}"))
912        })?;
913    let tags = parse_tag_list(params, "Tags");
914    Ok(SendRawEmailInput {
915        raw_message: RawMessage { data: decoded },
916        source: get_optional_param(params, "Source").map(str::to_owned),
917        destinations: parse_member_list(params, "Destinations"),
918        tags: tags
919            .into_iter()
920            .map(|(n, v)| MessageTag { name: n, value: v })
921            .collect(),
922        configuration_set_name: get_optional_param(params, "ConfigurationSetName")
923            .map(str::to_owned),
924        from_arn: get_optional_param(params, "FromArn").map(str::to_owned),
925        return_path_arn: get_optional_param(params, "ReturnPathArn").map(str::to_owned),
926        source_arn: get_optional_param(params, "SourceArn").map(str::to_owned),
927    })
928}
929
930fn deserialize_send_templated_email(
931    params: &[(String, String)],
932) -> Result<SendTemplatedEmailInput, SesError> {
933    let source = get_required_param(params, "Source")?.to_owned();
934    let to = parse_member_list(params, "Destination.ToAddresses");
935    let cc = parse_member_list(params, "Destination.CcAddresses");
936    let bcc = parse_member_list(params, "Destination.BccAddresses");
937    let template = get_required_param(params, "Template")?.to_owned();
938    let template_data = get_required_param(params, "TemplateData")?.to_owned();
939    let tags = parse_tag_list(params, "Tags");
940
941    Ok(SendTemplatedEmailInput {
942        source,
943        destination: Destination {
944            to_addresses: to,
945            cc_addresses: cc,
946            bcc_addresses: bcc,
947        },
948        template,
949        template_data,
950        tags: tags
951            .into_iter()
952            .map(|(n, v)| MessageTag { name: n, value: v })
953            .collect(),
954        configuration_set_name: get_optional_param(params, "ConfigurationSetName")
955            .map(str::to_owned),
956        return_path: get_optional_param(params, "ReturnPath").map(str::to_owned),
957        return_path_arn: get_optional_param(params, "ReturnPathArn").map(str::to_owned),
958        source_arn: get_optional_param(params, "SourceArn").map(str::to_owned),
959        template_arn: get_optional_param(params, "TemplateArn").map(str::to_owned),
960        reply_to_addresses: parse_member_list(params, "ReplyToAddresses"),
961    })
962}
963
964fn deserialize_create_template(
965    params: &[(String, String)],
966) -> Result<CreateTemplateInput, SesError> {
967    Ok(CreateTemplateInput {
968        template: Template {
969            template_name: get_required_param(params, "Template.TemplateName")?.to_owned(),
970            subject_part: get_optional_param(params, "Template.SubjectPart").map(str::to_owned),
971            text_part: get_optional_param(params, "Template.TextPart").map(str::to_owned),
972            html_part: get_optional_param(params, "Template.HtmlPart").map(str::to_owned),
973        },
974    })
975}
976
977fn deserialize_update_template(
978    params: &[(String, String)],
979) -> Result<UpdateTemplateInput, SesError> {
980    Ok(UpdateTemplateInput {
981        template: Template {
982            template_name: get_required_param(params, "Template.TemplateName")?.to_owned(),
983            subject_part: get_optional_param(params, "Template.SubjectPart").map(str::to_owned),
984            text_part: get_optional_param(params, "Template.TextPart").map(str::to_owned),
985            html_part: get_optional_param(params, "Template.HtmlPart").map(str::to_owned),
986        },
987    })
988}
989
990fn deserialize_create_config_set_event_dest(
991    params: &[(String, String)],
992) -> Result<CreateConfigurationSetEventDestinationInput, SesError> {
993    let config_set_name = get_required_param(params, "ConfigurationSetName")?.to_owned();
994    let dest_name = get_required_param(params, "EventDestination.Name")?.to_owned();
995    let enabled = get_optional_bool(params, "EventDestination.Enabled");
996    let matching_types = parse_member_list(params, "EventDestination.MatchingEventTypes");
997
998    Ok(CreateConfigurationSetEventDestinationInput {
999        configuration_set_name: config_set_name,
1000        event_destination: EventDestination {
1001            name: dest_name,
1002            enabled,
1003            matching_event_types: matching_types
1004                .into_iter()
1005                .map(|s| s.as_str().into())
1006                .collect(),
1007            ..EventDestination::default()
1008        },
1009    })
1010}
1011
1012fn deserialize_update_config_set_event_dest(
1013    params: &[(String, String)],
1014) -> Result<UpdateConfigurationSetEventDestinationInput, SesError> {
1015    let config_set_name = get_required_param(params, "ConfigurationSetName")?.to_owned();
1016    let dest_name = get_required_param(params, "EventDestination.Name")?.to_owned();
1017    let enabled = get_optional_bool(params, "EventDestination.Enabled");
1018    let matching_types = parse_member_list(params, "EventDestination.MatchingEventTypes");
1019
1020    Ok(UpdateConfigurationSetEventDestinationInput {
1021        configuration_set_name: config_set_name,
1022        event_destination: EventDestination {
1023            name: dest_name,
1024            enabled,
1025            matching_event_types: matching_types
1026                .into_iter()
1027                .map(|s| s.as_str().into())
1028                .collect(),
1029            ..EventDestination::default()
1030        },
1031    })
1032}
1033
1034fn deserialize_create_receipt_rule(
1035    params: &[(String, String)],
1036) -> Result<CreateReceiptRuleInput, SesError> {
1037    let rule_set_name = get_required_param(params, "RuleSetName")?.to_owned();
1038    let after = get_optional_param(params, "After").map(str::to_owned);
1039    let rule_name = get_required_param(params, "Rule.Name")?.to_owned();
1040    let enabled = get_optional_bool(params, "Rule.Enabled");
1041    let scan_enabled = get_optional_bool(params, "Rule.ScanEnabled");
1042    let recipients = parse_member_list(params, "Rule.Recipients");
1043
1044    Ok(CreateReceiptRuleInput {
1045        rule_set_name,
1046        after,
1047        rule: ReceiptRule {
1048            name: rule_name,
1049            enabled,
1050            scan_enabled,
1051            recipients,
1052            actions: Vec::new(),
1053            tls_policy: None,
1054        },
1055    })
1056}
1057
1058// ---------------------------------------------------------------
1059// XML serialization helpers
1060// ---------------------------------------------------------------
1061
1062fn write_event_destination_xml(w: &mut XmlWriter, dest: &EventDestination) {
1063    w.raw("<member>");
1064    w.write_element("Name", &dest.name);
1065    if let Some(enabled) = dest.enabled {
1066        w.write_bool_element("Enabled", enabled);
1067    }
1068    w.raw("<MatchingEventTypes>");
1069    for et in &dest.matching_event_types {
1070        w.write_element("member", et.as_str());
1071    }
1072    w.raw("</MatchingEventTypes>");
1073    w.raw("</member>");
1074}
1075
1076fn write_receipt_rule_xml(w: &mut XmlWriter, rule: &ReceiptRule) {
1077    w.raw("<member>");
1078    w.write_element("Name", &rule.name);
1079    if let Some(enabled) = rule.enabled {
1080        w.write_bool_element("Enabled", enabled);
1081    }
1082    if let Some(scan) = rule.scan_enabled {
1083        w.write_bool_element("ScanEnabled", scan);
1084    }
1085    w.raw("<Recipients>");
1086    for r in &rule.recipients {
1087        w.write_element("member", r);
1088    }
1089    w.raw("</Recipients>");
1090    w.raw("<Actions>");
1091    for _action in &rule.actions {
1092        w.raw("<member/>");
1093    }
1094    w.raw("</Actions>");
1095    w.raw("</member>");
1096}