use std::collections::HashMap;
use email_transport::{DynTransport, Transport};
pub use crate::TransportKey;
pub use email_transport::RuntimeBound;
pub trait TransportResolver: RuntimeBound {
fn resolve(&self, transport: &TransportKey) -> Result<&DynTransport, TransportLookupError>;
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum TransportLookupError {
#[error("transport key `{key}` is not configured")]
UnknownKey {
key: String,
},
}
#[derive(Default)]
pub struct StaticTransportRegistry {
transports: HashMap<String, Box<DynTransport>>,
}
impl StaticTransportRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn insert<T>(&mut self, key: impl Into<String>, transport: T) -> Option<Box<DynTransport>>
where
T: Transport + 'static,
{
self.transports.insert(key.into(), Box::new(transport))
}
}
impl TransportResolver for StaticTransportRegistry {
fn resolve(&self, transport: &TransportKey) -> Result<&DynTransport, TransportLookupError> {
self.transports
.get(transport.as_str())
.map(Box::as_ref)
.ok_or_else(|| TransportLookupError::UnknownKey {
key: transport.as_str().to_owned(),
})
}
}
pub struct CatchAllTransportResolver {
transport: Box<DynTransport>,
}
impl CatchAllTransportResolver {
#[must_use]
pub fn new<T>(transport: T) -> Self
where
T: Transport + 'static,
{
Self {
transport: Box::new(transport),
}
}
}
impl TransportResolver for CatchAllTransportResolver {
fn resolve(&self, _transport: &TransportKey) -> Result<&DynTransport, TransportLookupError> {
Ok(self.transport.as_ref())
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestTransport;
impl Transport for TestTransport {
fn send<'a>(
&'a self,
_message: &'a email_message::OutboundMessage,
_options: &'a email_transport::SendOptions,
) -> impl core::future::Future<
Output = Result<email_transport::SendReport, email_transport::TransportError>,
> + Send
+ 'a {
Box::pin(async { Ok(email_transport::SendReport::new("test")) })
}
}
#[test]
fn static_transport_registry_returns_unknown_key_for_missing_entry() {
let registry = StaticTransportRegistry::new();
let Err(error) = registry.resolve(&TransportKey::new_unchecked("missing")) else {
panic!("missing key should fail");
};
assert_eq!(
error,
TransportLookupError::UnknownKey {
key: String::from("missing")
}
);
}
#[test]
fn catch_all_transport_resolver_ignores_requested_key() {
let resolver = CatchAllTransportResolver::new(TestTransport);
let first = resolver
.resolve(&TransportKey::new_unchecked("primary"))
.expect("transport should resolve");
let second = resolver
.resolve(&TransportKey::new_unchecked("missing"))
.expect("transport should resolve");
assert!(std::ptr::addr_eq(
std::ptr::from_ref::<DynTransport>(first),
std::ptr::from_ref::<DynTransport>(second)
));
}
}