1mod blob;
9mod captcha;
10mod clock;
11mod database;
12mod defer;
13mod dispatcher;
14mod http;
15mod idgen;
16mod kv;
17mod mailer;
18mod payments;
19mod push;
20mod rate_limiter;
21pub(crate) mod signer;
22
23pub use blob::{Blob, BlobError, BlobObject, ScopedBlob};
24pub use captcha::{Captcha, CaptchaError, Verdict};
25pub use clock::{Clock, SystemClock, timeout};
26pub use database::{Database, DbError, Row, Rows, Statement, TryFromValue};
27pub use defer::{Defer, NoopDefer};
28pub use dispatcher::{DispatchError, Dispatcher};
29pub use http::{HttpClient, HttpError};
30pub use idgen::{IdGen, UlidIdGen};
31pub use kv::{KeyValue, KvError};
32pub use mailer::{MailError, Mailer, Message, SendOutcome};
33pub use payments::{
34 Charge, CheckoutRequest, CheckoutSession, ConnectAccountLink, ConnectAccountLinkRequest,
35 LineItem, Money, Payments, PaymentsError, Refund, RefundRequest, SubscriptionCheckoutRequest,
36 TransferCharge, WebhookEvent,
37};
38pub use push::{Notification, Priority, Push, PushError, PushOutcome};
39pub use rate_limiter::{Decision, RateLimitError, RateLimiter};
40pub use signer::{Kid, Payload, SignatureError, Signer};
41
42use crate::config::Config;
43use crate::module::Module;
44use std::sync::Arc;
45use tracing::warn;
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50pub enum Port {
51 Db,
52 Mailer,
53 Captcha,
54 RateLimiter,
55 Signer,
56 KeyValue,
57 Blob,
58 Push,
59 Payments,
60 HttpClient,
61 Clock,
62 IdGen,
63 Defer,
64}
65
66impl Port {
67 pub const ALL: &'static [Port] = &[
68 Port::Db,
69 Port::Mailer,
70 Port::Captcha,
71 Port::RateLimiter,
72 Port::Signer,
73 Port::KeyValue,
74 Port::Blob,
75 Port::Push,
76 Port::Payments,
77 Port::HttpClient,
78 Port::Clock,
79 Port::IdGen,
80 Port::Defer,
81 ];
82
83 pub fn name(&self) -> &'static str {
84 match self {
85 Port::Db => "Database",
86 Port::Mailer => "Mailer",
87 Port::Captcha => "Captcha",
88 Port::RateLimiter => "RateLimiter",
89 Port::Signer => "Signer",
90 Port::KeyValue => "KeyValue",
91 Port::Blob => "Blob",
92 Port::Push => "Push",
93 Port::Payments => "Payments",
94 Port::HttpClient => "HttpClient",
95 Port::Clock => "Clock",
96 Port::IdGen => "IdGen",
97 Port::Defer => "Defer",
98 }
99 }
100}
101
102pub struct Ports {
108 pub config: Arc<dyn Config>,
109 pub db: Option<Arc<dyn Database>>,
110 pub mailer: Option<Arc<dyn Mailer>>,
111 pub captcha: Option<Arc<dyn Captcha>>,
112 pub rate_limiter: Option<Arc<dyn RateLimiter>>,
113 pub signer: Option<Arc<dyn Signer>>,
114 pub kv: Option<Arc<dyn KeyValue>>,
115 pub blob: Option<Arc<dyn Blob>>,
116 pub push: Option<Arc<dyn Push>>,
117 pub payments: Option<Arc<dyn Payments>>,
118 pub http: Option<Arc<dyn HttpClient>>,
119 pub clock: Option<Arc<dyn Clock>>,
120 pub id_gen: Option<Arc<dyn IdGen>>,
121 pub defer: Option<Arc<dyn Defer>>,
122 pub dispatcher: Option<Arc<dyn Dispatcher>>,
125}
126
127impl Ports {
128 pub fn empty() -> Self {
131 Self::with_config(Arc::new(crate::config::EmptyConfig))
132 }
133
134 pub fn with_config(config: Arc<dyn Config>) -> Self {
135 Self {
136 config,
137 db: None,
138 mailer: None,
139 captcha: None,
140 rate_limiter: None,
141 signer: None,
142 kv: None,
143 blob: None,
144 push: None,
145 payments: None,
146 http: None,
147 clock: None,
148 id_gen: None,
149 defer: None,
150 dispatcher: None,
151 }
152 }
153
154 pub fn provides(&self) -> Vec<Port> {
156 let mut provided = Vec::new();
157 if self.db.is_some() {
158 provided.push(Port::Db);
159 }
160 if self.mailer.is_some() {
161 provided.push(Port::Mailer);
162 }
163 if self.captcha.is_some() {
164 provided.push(Port::Captcha);
165 }
166 if self.rate_limiter.is_some() {
167 provided.push(Port::RateLimiter);
168 }
169 if self.signer.is_some() {
170 provided.push(Port::Signer);
171 }
172 if self.kv.is_some() {
173 provided.push(Port::KeyValue);
174 }
175 if self.blob.is_some() {
176 provided.push(Port::Blob);
177 }
178 if self.push.is_some() {
179 provided.push(Port::Push);
180 }
181 if self.payments.is_some() {
182 provided.push(Port::Payments);
183 }
184 if self.http.is_some() {
185 provided.push(Port::HttpClient);
186 }
187 if self.clock.is_some() {
188 provided.push(Port::Clock);
189 }
190 if self.id_gen.is_some() {
191 provided.push(Port::IdGen);
192 }
193 if self.defer.is_some() {
194 provided.push(Port::Defer);
195 }
196 provided
197 }
198
199 #[must_use]
204 pub fn view_for(&self, module: &dyn Module) -> Self {
205 let declared = module
206 .requires()
207 .iter()
208 .chain(module.optional())
209 .copied()
210 .collect::<Vec<_>>();
211 let allows = |p: &[Port], port: Port| p.contains(&port);
212 let mut view = Ports::with_config(self.config.clone());
213 if allows(&declared, Port::Db) {
214 view.db.clone_from(&self.db);
215 }
216 if allows(&declared, Port::Mailer) {
217 view.mailer.clone_from(&self.mailer);
218 }
219 if allows(&declared, Port::Captcha) {
220 view.captcha.clone_from(&self.captcha);
221 }
222 if allows(&declared, Port::RateLimiter) {
223 view.rate_limiter.clone_from(&self.rate_limiter);
224 }
225 if allows(&declared, Port::Signer) {
226 view.signer.clone_from(&self.signer);
227 }
228 if allows(&declared, Port::KeyValue) {
229 view.kv.clone_from(&self.kv);
230 }
231 if allows(&declared, Port::Blob) {
232 view.blob = self.blob.as_ref().map(|blob| {
235 Arc::new(ScopedBlob::new(Arc::clone(blob), module.name())) as Arc<dyn Blob>
236 });
237 }
238 if allows(&declared, Port::Push) {
239 view.push.clone_from(&self.push);
240 }
241 if allows(&declared, Port::Payments) {
242 view.payments.clone_from(&self.payments);
243 }
244 if allows(&declared, Port::HttpClient) {
245 view.http.clone_from(&self.http);
246 }
247 if allows(&declared, Port::Clock) {
248 view.clock.clone_from(&self.clock);
249 }
250 if allows(&declared, Port::IdGen) {
251 view.id_gen.clone_from(&self.id_gen);
252 }
253 if allows(&declared, Port::Defer) {
254 view.defer.clone_from(&self.defer);
255 }
256 view
257 }
258}
259
260pub(crate) fn warn_undeclared_ports(module: &dyn Module, provided: &[Port]) {
263 for port in provided {
264 if !module.requires().contains(port) && !module.optional().contains(port) {
265 warn!(
266 module = module.name(),
267 port = port.name(),
268 "runtime provides a port the module did not declare; hiding it",
269 );
270 }
271 }
272}