use config::mailbaby::{MailbabyAuthScheme, MailbabyChannel, MailbabyConfig}; use mailbaby::grpc; use mailbaby::rest; use mailbaby::{Auth, BatchResponse, Email, SendResponse}; use crate::error::ExportError; #[derive(Clone)] pub enum EmailClient { Rest(rest::MailBabyClient), Grpc(grpc::GrpcClient), } #[derive(Clone)] pub struct AppEmail { config: MailbabyConfig, client: Option, } impl AppEmail { pub fn new(config: MailbabyConfig) -> Result { config.validate().map_err(ExportError::InvalidConfig)?; if !config.enabled() { return Ok(Self { config, client: None, }); } match config.channel() { MailbabyChannel::Rest => { let client = build_rest(&config)?; Ok(Self { config, client: Some(EmailClient::Rest(client)), }) } MailbabyChannel::Grpc => Err(ExportError::InvalidConfig( "grpc channel requires AppEmail::connect".to_string(), )), } } pub async fn connect(config: MailbabyConfig) -> Result { config.validate().map_err(ExportError::InvalidConfig)?; if !config.enabled() { return Ok(Self { config, client: None, }); } let client = match config.channel() { MailbabyChannel::Rest => EmailClient::Rest(build_rest(&config)?), MailbabyChannel::Grpc => EmailClient::Grpc(build_grpc(&config).await?), }; Ok(Self { config, client: Some(client), }) } pub fn config(&self) -> &MailbabyConfig { &self.config } pub fn enabled(&self) -> bool { self.client.is_some() } pub fn channel(&self) -> MailbabyChannel { self.config.channel() } pub fn client(&self) -> Result<&EmailClient, ExportError> { self.client.as_ref().ok_or(ExportError::Disabled) } pub fn apply_defaults(&self, mut email: Email) -> Email { if email.account.is_empty() { email.account = self.config.account().unwrap_or_default().to_string(); } if email.from.is_empty() { email.from = self.config.from_address().unwrap_or_default().to_string(); } if email.from_name.is_empty() { email.from_name = self.config.from_name().unwrap_or_default().to_string(); } if email.reply_to.is_empty() { email.reply_to = self.config.reply_to().unwrap_or_default().to_string(); } email } pub async fn send(&self, email: &Email) -> Result { self.client()?.send(email).await } pub async fn send_queued(&self, email: &Email) -> Result { self.client()?.send_queued(email).await } pub async fn send_batch( &self, emails: &[Email], queue: bool, ) -> Result { self.client()?.send_batch(emails, queue).await } pub async fn ping(&self) -> Result { self.client()?.ping().await } pub async fn live(&self) -> Result<(), ExportError> { self.client()?.live().await } pub async fn ready(&self) -> Result<(), ExportError> { self.client()?.ready().await } } impl EmailClient { pub async fn send(&self, email: &Email) -> Result { match self { EmailClient::Rest(client) => Ok(client.send(email).await?), EmailClient::Grpc(client) => Ok(client.send(email).await?), } } pub async fn send_queued(&self, email: &Email) -> Result { match self { EmailClient::Rest(client) => Ok(client.send_async(email).await?), EmailClient::Grpc(client) => Ok(client.send_async(email).await?), } } pub async fn send_batch( &self, emails: &[Email], queue: bool, ) -> Result { match self { EmailClient::Rest(client) => Ok(client.send_batch(emails, queue).await?), EmailClient::Grpc(client) => Ok(client.send_batch(emails, queue).await?), } } pub async fn ping(&self) -> Result { match self { EmailClient::Grpc(client) => Ok(client.ping().await?), EmailClient::Rest(_) => Err(ExportError::Unsupported( "ping is only available over the grpc channel".to_string(), )), } } pub async fn live(&self) -> Result<(), ExportError> { match self { EmailClient::Rest(client) => Ok(client.live().await?), EmailClient::Grpc(_) => Err(ExportError::Unsupported( "the live probe is only available over the rest channel".to_string(), )), } } pub async fn ready(&self) -> Result<(), ExportError> { match self { EmailClient::Rest(client) => Ok(client.ready().await?), EmailClient::Grpc(_) => Err(ExportError::Unsupported( "the ready probe is only available over the rest channel".to_string(), )), } } } fn build_rest(config: &MailbabyConfig) -> Result { let mut builder = rest::MailBabyClient::builder(config.url()).timeout(config.request_timeout()); if let Some(auth) = build_auth(config)? { builder = builder.auth(auth); } Ok(builder.build()?) } async fn build_grpc(config: &MailbabyConfig) -> Result { let mut builder = grpc::GrpcClient::builder(config.url()).timeout(config.request_timeout()); if let Some(auth) = build_auth(config)? { builder = builder.auth(auth); } Ok(builder.build().await?) } fn build_auth(config: &MailbabyConfig) -> Result, ExportError> { let Some(key) = config.api_key() else { return Ok(None); }; let auth = match config.auth_scheme() { MailbabyAuthScheme::Header => { let auth = Auth::api_key(key); match config.api_key_header() { Some(header) => auth.with_header(header), None => auth, } } MailbabyAuthScheme::Bearer => Auth::api_key(key).bearer(), MailbabyAuthScheme::Query => Auth::api_key(key).query(), }; Ok(Some(auth)) }