rustack_ses_http/v2/
mod.rs1use std::{convert::Infallible, future::Future, pin::Pin, sync::Arc};
6
7use http_body_util::BodyExt;
8use hyper::body::Incoming;
9use rustack_ses_model::error::SesError;
10
11use crate::{
12 body::SesResponseBody,
13 dispatch::SesHandler,
14 request::parse_query_params,
15 response::{JSON_CONTENT_TYPE, error_to_json_response, json_response},
16};
17
18#[derive(Debug)]
20pub struct SesV2HttpService<H: SesHandler> {
21 handler: Arc<H>,
22}
23
24impl<H: SesHandler> SesV2HttpService<H> {
25 pub fn new(handler: Arc<H>) -> Self {
27 Self { handler }
28 }
29}
30
31impl<H: SesHandler> Clone for SesV2HttpService<H> {
32 fn clone(&self) -> Self {
33 Self {
34 handler: Arc::clone(&self.handler),
35 }
36 }
37}
38
39impl<H: SesHandler> hyper::service::Service<http::Request<Incoming>> for SesV2HttpService<H> {
40 type Response = http::Response<SesResponseBody>;
41 type Error = Infallible;
42 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
43
44 fn call(&self, req: http::Request<Incoming>) -> Self::Future {
45 let handler = Arc::clone(&self.handler);
46
47 Box::pin(async move {
48 let method = req.method().clone();
49 let uri = req.uri().clone();
50 let path = uri.path().to_owned();
51 let query = uri.query().map(str::to_owned);
52
53 let body = match req
54 .into_body()
55 .collect()
56 .await
57 .map(http_body_util::Collected::to_bytes)
58 {
59 Ok(body) => body,
60 Err(e) => {
61 let err = SesError::internal_error(format!("Failed to read body: {e}"));
62 return Ok(error_to_json_response(&err));
63 }
64 };
65
66 if path.starts_with("/_aws/ses") {
68 let query_params = parse_query_params(query.as_deref());
69 return Ok(handle_retrospection(
70 handler.as_ref(),
71 &method,
72 &query_params,
73 ));
74 }
75
76 let response = match handler.handle_v2_operation(method, path, body).await {
78 Ok(resp) => resp,
79 Err(err) => error_to_json_response(&err),
80 };
81
82 Ok(add_v2_headers(response))
83 })
84 }
85}
86
87fn handle_retrospection<H: SesHandler>(
89 handler: &H,
90 method: &http::Method,
91 query_params: &std::collections::HashMap<String, String>,
92) -> http::Response<SesResponseBody> {
93 match *method {
94 http::Method::GET => {
95 let filter_id = query_params.get("id").map(String::as_str);
96 let filter_source = query_params.get("email").map(String::as_str);
97 let json = handler.query_emails(filter_id, filter_source);
98 json_response(json, http::StatusCode::OK)
99 }
100 http::Method::DELETE => {
101 let filter_id = query_params.get("id").map(String::as_str);
102 handler.clear_emails(filter_id);
103 json_response("{}".to_owned(), http::StatusCode::OK)
104 }
105 _ => {
106 let err = SesError::invalid_parameter_value(format!(
107 "Method {method} not supported on /_aws/ses"
108 ));
109 error_to_json_response(&err)
110 }
111 }
112}
113
114fn add_v2_headers(
116 mut response: http::Response<SesResponseBody>,
117) -> http::Response<SesResponseBody> {
118 let headers = response.headers_mut();
119
120 headers
121 .entry("content-type")
122 .or_insert(http::HeaderValue::from_static(JSON_CONTENT_TYPE));
123
124 headers.insert("server", http::HeaderValue::from_static("Rustack"));
125
126 headers.insert(
127 "access-control-allow-origin",
128 http::HeaderValue::from_static("*"),
129 );
130
131 response
132}