use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use cratefield_core::{
Blob, Captcha, Database, HarnessConfig, KeyValue, Mailer, Port, Ports, RateLimiter, Runtime,
UlidIdGen,
};
use crate::config::EnvConfig;
use crate::ports::{ReqwestClient, SpawnDefer, TokioClock};
static WARNED_SIGNER: AtomicBool = AtomicBool::new(false);
fn warn_once(flag: &AtomicBool, message: &str) {
if !flag.load(Ordering::Relaxed) {
flag.store(true, Ordering::Relaxed);
tracing::warn!("{message}");
}
}
#[derive(Clone, Default)]
pub struct Native {
db: Option<Arc<dyn Database>>,
rate_limiter: Option<Arc<dyn RateLimiter>>,
kv: Option<Arc<dyn KeyValue>>,
blob: Option<Arc<dyn Blob>>,
mailer: Option<Arc<dyn Mailer>>,
captcha: Option<Arc<dyn Captcha>>,
}
impl Native {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn db(mut self, db: impl Database + 'static) -> Self {
self.db = Some(Arc::new(db));
self
}
#[must_use]
pub fn db_arc(mut self, db: Arc<dyn Database>) -> Self {
self.db = Some(db);
self
}
#[must_use]
pub fn rate_limiter(mut self, limiter: impl RateLimiter + 'static) -> Self {
self.rate_limiter = Some(Arc::new(limiter));
self
}
#[must_use]
pub fn rate_limiter_arc(mut self, limiter: Arc<dyn RateLimiter>) -> Self {
self.rate_limiter = Some(limiter);
self
}
#[must_use]
pub fn kv(mut self, kv: impl KeyValue + 'static) -> Self {
self.kv = Some(Arc::new(kv));
self
}
#[must_use]
pub fn kv_arc(mut self, kv: Arc<dyn KeyValue>) -> Self {
self.kv = Some(kv);
self
}
#[must_use]
pub fn blob_arc(mut self, blob: Arc<dyn Blob>) -> Self {
self.blob = Some(blob);
self
}
#[must_use]
pub fn mailer(mut self, mailer: impl Mailer + 'static) -> Self {
self.mailer = Some(Arc::new(mailer));
self
}
#[must_use]
pub fn mailer_arc(mut self, mailer: Arc<dyn Mailer>) -> Self {
self.mailer = Some(mailer);
self
}
#[must_use]
pub fn captcha(mut self, captcha: impl Captcha + 'static) -> Self {
self.captcha = Some(Arc::new(captcha));
self
}
#[must_use]
pub fn captcha_arc(mut self, captcha: Arc<dyn Captcha>) -> Self {
self.captcha = Some(captcha);
self
}
#[must_use]
pub fn ports(&self) -> Ports {
let config: Arc<dyn cratefield_core::Config> = Arc::new(EnvConfig);
let mut ports = Ports::with_config(Arc::clone(&config));
ports.db.clone_from(&self.db);
ports.rate_limiter.clone_from(&self.rate_limiter);
ports.kv.clone_from(&self.kv);
ports.blob.clone_from(&self.blob);
ports.mailer.clone_from(&self.mailer);
ports.captcha.clone_from(&self.captcha);
match HarnessConfig::from_config(&*config) {
Ok(parsed) => ports.signer = Some(Arc::new(parsed.signer())),
Err(_) => warn_once(
&WARNED_SIGNER,
"HARNESS_SECRET missing or invalid: Signer port not provided",
),
}
ports.http = Some(Arc::new(ReqwestClient::new()));
ports.clock = Some(Arc::new(TokioClock));
ports.id_gen = Some(Arc::new(UlidIdGen));
ports.defer = Some(Arc::new(SpawnDefer));
ports
}
}
impl Runtime for Native {
fn provides(&self) -> Vec<Port> {
let mut provided = vec![
Port::Signer,
Port::HttpClient,
Port::Clock,
Port::IdGen,
Port::Defer,
];
if self.db.is_some() {
provided.push(Port::Db);
}
if self.rate_limiter.is_some() {
provided.push(Port::RateLimiter);
}
if self.kv.is_some() {
provided.push(Port::KeyValue);
}
if self.blob.is_some() {
provided.push(Port::Blob);
}
if self.mailer.is_some() {
provided.push(Port::Mailer);
}
if self.captcha.is_some() {
provided.push(Port::Captcha);
}
provided
}
}
pub(crate) fn clone_ports(ports: &Ports) -> Ports {
let mut snapshot = Ports::with_config(Arc::clone(&ports.config));
snapshot.db.clone_from(&ports.db);
snapshot.mailer.clone_from(&ports.mailer);
snapshot.captcha.clone_from(&ports.captcha);
snapshot.rate_limiter.clone_from(&ports.rate_limiter);
snapshot.signer.clone_from(&ports.signer);
snapshot.kv.clone_from(&ports.kv);
snapshot.blob.clone_from(&ports.blob);
snapshot.http.clone_from(&ports.http);
snapshot.clock.clone_from(&ports.clock);
snapshot.id_gen.clone_from(&ports.id_gen);
snapshot.defer.clone_from(&ports.defer);
snapshot
}