use std::{convert::Infallible, future::Future, pin::Pin, sync::Arc};
use http_body_util::BodyExt;
use hyper::body::Incoming;
use rustack_ses_model::error::SesError;
use crate::{
body::SesResponseBody,
dispatch::SesHandler,
request::parse_query_params,
response::{JSON_CONTENT_TYPE, error_to_json_response, json_response},
};
#[derive(Debug)]
pub struct SesV2HttpService<H: SesHandler> {
handler: Arc<H>,
}
impl<H: SesHandler> SesV2HttpService<H> {
pub fn new(handler: Arc<H>) -> Self {
Self { handler }
}
}
impl<H: SesHandler> Clone for SesV2HttpService<H> {
fn clone(&self) -> Self {
Self {
handler: Arc::clone(&self.handler),
}
}
}
impl<H: SesHandler> hyper::service::Service<http::Request<Incoming>> for SesV2HttpService<H> {
type Response = http::Response<SesResponseBody>;
type Error = Infallible;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn call(&self, req: http::Request<Incoming>) -> Self::Future {
let handler = Arc::clone(&self.handler);
Box::pin(async move {
let method = req.method().clone();
let uri = req.uri().clone();
let path = uri.path().to_owned();
let query = uri.query().map(str::to_owned);
let body = match req
.into_body()
.collect()
.await
.map(http_body_util::Collected::to_bytes)
{
Ok(body) => body,
Err(e) => {
let err = SesError::internal_error(format!("Failed to read body: {e}"));
return Ok(error_to_json_response(&err));
}
};
if path.starts_with("/_aws/ses") {
let query_params = parse_query_params(query.as_deref());
return Ok(handle_retrospection(
handler.as_ref(),
&method,
&query_params,
));
}
let response = match handler.handle_v2_operation(method, path, body).await {
Ok(resp) => resp,
Err(err) => error_to_json_response(&err),
};
Ok(add_v2_headers(response))
})
}
}
fn handle_retrospection<H: SesHandler>(
handler: &H,
method: &http::Method,
query_params: &std::collections::HashMap<String, String>,
) -> http::Response<SesResponseBody> {
match *method {
http::Method::GET => {
let filter_id = query_params.get("id").map(String::as_str);
let filter_source = query_params.get("email").map(String::as_str);
let json = handler.query_emails(filter_id, filter_source);
json_response(json, http::StatusCode::OK)
}
http::Method::DELETE => {
let filter_id = query_params.get("id").map(String::as_str);
handler.clear_emails(filter_id);
json_response("{}".to_owned(), http::StatusCode::OK)
}
_ => {
let err = SesError::invalid_parameter_value(format!(
"Method {method} not supported on /_aws/ses"
));
error_to_json_response(&err)
}
}
}
fn add_v2_headers(
mut response: http::Response<SesResponseBody>,
) -> http::Response<SesResponseBody> {
let headers = response.headers_mut();
headers
.entry("content-type")
.or_insert(http::HeaderValue::from_static(JSON_CONTENT_TYPE));
headers.insert("server", http::HeaderValue::from_static("Rustack"));
headers.insert(
"access-control-allow-origin",
http::HeaderValue::from_static("*"),
);
response
}