cratefield_runtime_native/
runtime.rs1use std::sync::Arc;
21use std::sync::atomic::{AtomicBool, Ordering};
22
23use cratefield_core::{
24 Blob, Captcha, Database, HarnessConfig, KeyValue, Mailer, Payments, Port, Ports, Push,
25 RateLimiter, Runtime, UlidIdGen,
26};
27
28use crate::config::EnvConfig;
29use crate::ports::{ReqwestClient, SpawnDefer, TokioClock};
30
31static WARNED_SIGNER: AtomicBool = AtomicBool::new(false);
32
33fn warn_once(flag: &AtomicBool, message: &str) {
34 if !flag.load(Ordering::Relaxed) {
35 flag.store(true, Ordering::Relaxed);
36 tracing::warn!("{message}");
37 }
38}
39
40#[derive(Clone, Default)]
51pub struct Native {
52 db: Option<Arc<dyn Database>>,
53 rate_limiter: Option<Arc<dyn RateLimiter>>,
54 kv: Option<Arc<dyn KeyValue>>,
55 blob: Option<Arc<dyn Blob>>,
56 push: Option<Arc<dyn Push>>,
57 payments: Option<Arc<dyn Payments>>,
58 mailer: Option<Arc<dyn Mailer>>,
59 captcha: Option<Arc<dyn Captcha>>,
60}
61
62impl Native {
63 pub fn new() -> Self {
64 Self::default()
65 }
66
67 #[must_use]
71 pub fn db(mut self, db: impl Database + 'static) -> Self {
72 self.db = Some(Arc::new(db));
73 self
74 }
75
76 #[must_use]
78 pub fn db_arc(mut self, db: Arc<dyn Database>) -> Self {
79 self.db = Some(db);
80 self
81 }
82
83 #[must_use]
86 pub fn rate_limiter(mut self, limiter: impl RateLimiter + 'static) -> Self {
87 self.rate_limiter = Some(Arc::new(limiter));
88 self
89 }
90
91 #[must_use]
93 pub fn rate_limiter_arc(mut self, limiter: Arc<dyn RateLimiter>) -> Self {
94 self.rate_limiter = Some(limiter);
95 self
96 }
97
98 #[must_use]
100 pub fn kv(mut self, kv: impl KeyValue + 'static) -> Self {
101 self.kv = Some(Arc::new(kv));
102 self
103 }
104
105 #[must_use]
107 pub fn kv_arc(mut self, kv: Arc<dyn KeyValue>) -> Self {
108 self.kv = Some(kv);
109 self
110 }
111
112 #[must_use]
115 pub fn blob_arc(mut self, blob: Arc<dyn Blob>) -> Self {
116 self.blob = Some(blob);
117 self
118 }
119
120 #[must_use]
123 pub fn push_arc(mut self, push: Arc<dyn Push>) -> Self {
124 self.push = Some(push);
125 self
126 }
127
128 #[must_use]
131 pub fn payments_arc(mut self, payments: Arc<dyn Payments>) -> Self {
132 self.payments = Some(payments);
133 self
134 }
135
136 #[must_use]
137 pub fn mailer(mut self, mailer: impl Mailer + 'static) -> Self {
138 self.mailer = Some(Arc::new(mailer));
139 self
140 }
141
142 #[must_use]
143 pub fn mailer_arc(mut self, mailer: Arc<dyn Mailer>) -> Self {
144 self.mailer = Some(mailer);
145 self
146 }
147
148 #[must_use]
149 pub fn captcha(mut self, captcha: impl Captcha + 'static) -> Self {
150 self.captcha = Some(Arc::new(captcha));
151 self
152 }
153
154 #[must_use]
155 pub fn captcha_arc(mut self, captcha: Arc<dyn Captcha>) -> Self {
156 self.captcha = Some(captcha);
157 self
158 }
159
160 #[must_use]
170 pub fn ports(&self) -> Ports {
171 let config: Arc<dyn cratefield_core::Config> = Arc::new(EnvConfig);
172 let mut ports = Ports::with_config(Arc::clone(&config));
173
174 ports.db.clone_from(&self.db);
175 ports.rate_limiter.clone_from(&self.rate_limiter);
176 ports.kv.clone_from(&self.kv);
177 ports.blob.clone_from(&self.blob);
178 ports.push.clone_from(&self.push);
179 ports.payments.clone_from(&self.payments);
180 ports.mailer.clone_from(&self.mailer);
181 ports.captcha.clone_from(&self.captcha);
182
183 match HarnessConfig::from_config(&*config) {
184 Ok(parsed) => ports.signer = Some(Arc::new(parsed.signer())),
185 Err(_) => warn_once(
186 &WARNED_SIGNER,
187 "HARNESS_SECRET missing or invalid: Signer port not provided",
188 ),
189 }
190
191 ports.http = Some(Arc::new(ReqwestClient::new()));
192 ports.clock = Some(Arc::new(TokioClock));
193 ports.id_gen = Some(Arc::new(UlidIdGen));
194 ports.defer = Some(Arc::new(SpawnDefer));
195 ports
196 }
197}
198
199impl Runtime for Native {
200 fn provides(&self) -> Vec<Port> {
204 let mut provided = vec![
205 Port::Signer,
206 Port::HttpClient,
207 Port::Clock,
208 Port::IdGen,
209 Port::Defer,
210 ];
211 if self.db.is_some() {
212 provided.push(Port::Db);
213 }
214 if self.rate_limiter.is_some() {
215 provided.push(Port::RateLimiter);
216 }
217 if self.kv.is_some() {
218 provided.push(Port::KeyValue);
219 }
220 if self.blob.is_some() {
221 provided.push(Port::Blob);
222 }
223 if self.push.is_some() {
224 provided.push(Port::Push);
225 }
226 if self.payments.is_some() {
227 provided.push(Port::Payments);
228 }
229 if self.mailer.is_some() {
230 provided.push(Port::Mailer);
231 }
232 if self.captcha.is_some() {
233 provided.push(Port::Captcha);
234 }
235 provided
236 }
237}
238
239pub(crate) fn clone_ports(ports: &Ports) -> Ports {
244 let mut snapshot = Ports::with_config(Arc::clone(&ports.config));
245 snapshot.db.clone_from(&ports.db);
246 snapshot.mailer.clone_from(&ports.mailer);
247 snapshot.captcha.clone_from(&ports.captcha);
248 snapshot.rate_limiter.clone_from(&ports.rate_limiter);
249 snapshot.signer.clone_from(&ports.signer);
250 snapshot.kv.clone_from(&ports.kv);
251 snapshot.blob.clone_from(&ports.blob);
252 snapshot.push.clone_from(&ports.push);
253 snapshot.payments.clone_from(&ports.payments);
254 snapshot.http.clone_from(&ports.http);
255 snapshot.clock.clone_from(&ports.clock);
256 snapshot.id_gen.clone_from(&ports.id_gen);
257 snapshot.defer.clone_from(&ports.defer);
258 snapshot
259}