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;
21mod realtime;
22pub(crate) mod signer;
23
24pub use blob::{Blob, BlobError, BlobObject, MAX_BLOB_BYTES, ScopedBlob, check_blob_size};
25pub use captcha::{Captcha, CaptchaBinding, CaptchaError, Verdict};
26pub use clock::{Clock, SystemClock, timeout};
27pub use database::{Database, DbError, Row, Rows, Statement, TryFromValue};
28pub use defer::{Defer, NoopDefer};
29pub use dispatcher::{DispatchError, Dispatcher};
30pub use http::{
31 BoundedHttpClient, DEFAULT_RESPONSE_TIMEOUT, HttpClient, HttpError, HttpPolicy,
32 MAX_CONCURRENT_REQUESTS, MAX_RESPONSE_BYTES, MAX_RESPONSE_TIMEOUT, declared_content_length,
33};
34pub use idgen::{IdGen, UlidIdGen};
35pub use kv::{KeyValue, KvError};
36pub use mailer::{MailError, Mailer, Message, SendOutcome};
37pub use payments::{
38 Charge, CheckoutRequest, CheckoutSession, ConnectAccountLink, ConnectAccountLinkRequest,
39 LineItem, Money, Payments, PaymentsError, Refund, RefundRequest, SubscriptionCheckoutRequest,
40 TransferCharge, WebhookEvent,
41};
42pub use push::{
43 LocKeys, Notification, Platform, Priority, Push, PushError, PushOutcome, Recipient,
44 RoutingPush, retry_after, ttl_secs,
45};
46pub use rate_limiter::{Decision, RateLimitError, RateLimiter};
47pub use realtime::{Member, Realtime, RealtimeError, RoomContext, RoomHandler};
48pub use signer::{Kid, MAX_KID_NAME, Payload, SignatureError, Signer};
49
50use crate::config::Config;
51use crate::module::Module;
52use std::sync::Arc;
53use tracing::warn;
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
58pub enum Port {
59 Db,
60 Mailer,
61 Captcha,
62 RateLimiter,
63 Signer,
64 KeyValue,
65 Blob,
66 Push,
67 Payments,
68 Realtime,
69 HttpClient,
70 Clock,
71 IdGen,
72 Defer,
73}
74
75impl Port {
76 pub const ALL: &'static [Port] = &[
77 Port::Db,
78 Port::Mailer,
79 Port::Captcha,
80 Port::RateLimiter,
81 Port::Signer,
82 Port::KeyValue,
83 Port::Blob,
84 Port::Push,
85 Port::Payments,
86 Port::Realtime,
87 Port::HttpClient,
88 Port::Clock,
89 Port::IdGen,
90 Port::Defer,
91 ];
92
93 pub fn name(&self) -> &'static str {
94 match self {
95 Port::Db => "Database",
96 Port::Mailer => "Mailer",
97 Port::Captcha => "Captcha",
98 Port::RateLimiter => "RateLimiter",
99 Port::Signer => "Signer",
100 Port::KeyValue => "KeyValue",
101 Port::Blob => "Blob",
102 Port::Push => "Push",
103 Port::Payments => "Payments",
104 Port::Realtime => "Realtime",
105 Port::HttpClient => "HttpClient",
106 Port::Clock => "Clock",
107 Port::IdGen => "IdGen",
108 Port::Defer => "Defer",
109 }
110 }
111}
112
113pub struct Ports {
119 pub config: Arc<dyn Config>,
120 pub db: Option<Arc<dyn Database>>,
121 pub mailer: Option<Arc<dyn Mailer>>,
122 pub captcha: Option<Arc<dyn Captcha>>,
123 pub rate_limiter: Option<Arc<dyn RateLimiter>>,
124 pub signer: Option<Arc<dyn Signer>>,
125 pub kv: Option<Arc<dyn KeyValue>>,
126 pub blob: Option<Arc<dyn Blob>>,
127 pub push: Option<Arc<dyn Push>>,
128 pub payments: Option<Arc<dyn Payments>>,
129 pub realtime: Option<Arc<dyn Realtime>>,
130 pub http: Option<Arc<dyn HttpClient>>,
131 pub clock: Option<Arc<dyn Clock>>,
132 pub id_gen: Option<Arc<dyn IdGen>>,
133 pub defer: Option<Arc<dyn Defer>>,
134 pub dispatcher: Option<Arc<dyn Dispatcher>>,
137 pub tenants: Option<Arc<dyn crate::tenant::TenantRouting>>,
148}
149
150impl Ports {
151 pub fn empty() -> Self {
154 Self::with_config(Arc::new(crate::config::EmptyConfig))
155 }
156
157 pub fn with_config(config: Arc<dyn Config>) -> Self {
158 Self {
159 config,
160 db: None,
161 mailer: None,
162 captcha: None,
163 rate_limiter: None,
164 signer: None,
165 kv: None,
166 blob: None,
167 push: None,
168 payments: None,
169 realtime: None,
170 http: None,
171 clock: None,
172 id_gen: None,
173 defer: None,
174 dispatcher: None,
175 tenants: None,
176 }
177 }
178
179 pub fn provides(&self) -> Vec<Port> {
181 let mut provided = Vec::new();
182 if self.db.is_some() {
183 provided.push(Port::Db);
184 }
185 if self.mailer.is_some() {
186 provided.push(Port::Mailer);
187 }
188 if self.captcha.is_some() {
189 provided.push(Port::Captcha);
190 }
191 if self.rate_limiter.is_some() {
192 provided.push(Port::RateLimiter);
193 }
194 if self.signer.is_some() {
195 provided.push(Port::Signer);
196 }
197 if self.kv.is_some() {
198 provided.push(Port::KeyValue);
199 }
200 if self.blob.is_some() {
201 provided.push(Port::Blob);
202 }
203 if self.push.is_some() {
204 provided.push(Port::Push);
205 }
206 if self.payments.is_some() {
207 provided.push(Port::Payments);
208 }
209 if self.realtime.is_some() {
210 provided.push(Port::Realtime);
211 }
212 if self.http.is_some() {
213 provided.push(Port::HttpClient);
214 }
215 if self.clock.is_some() {
216 provided.push(Port::Clock);
217 }
218 if self.id_gen.is_some() {
219 provided.push(Port::IdGen);
220 }
221 if self.defer.is_some() {
222 provided.push(Port::Defer);
223 }
224 provided
225 }
226
227 #[must_use]
232 pub fn view_for(&self, module: &dyn Module) -> Self {
233 let declared = module
234 .requires()
235 .iter()
236 .chain(module.optional())
237 .copied()
238 .collect::<Vec<_>>();
239 let allows = |p: &[Port], port: Port| p.contains(&port);
240 let mut view = Ports::with_config(self.config.clone());
241 if allows(&declared, Port::Db) {
242 view.db.clone_from(&self.db);
243 }
244 if allows(&declared, Port::Mailer) {
245 view.mailer.clone_from(&self.mailer);
246 }
247 if allows(&declared, Port::Captcha) {
248 view.captcha.clone_from(&self.captcha);
249 }
250 if allows(&declared, Port::RateLimiter) {
251 view.rate_limiter.clone_from(&self.rate_limiter);
252 }
253 if allows(&declared, Port::Signer) {
254 view.signer.clone_from(&self.signer);
255 }
256 if allows(&declared, Port::KeyValue) {
257 view.kv.clone_from(&self.kv);
258 }
259 if allows(&declared, Port::Blob) {
260 view.blob = self.blob.as_ref().map(|blob| {
263 Arc::new(ScopedBlob::new(Arc::clone(blob), module.name())) as Arc<dyn Blob>
264 });
265 }
266 if allows(&declared, Port::Push) {
267 view.push.clone_from(&self.push);
268 }
269 if allows(&declared, Port::Payments) {
270 view.payments.clone_from(&self.payments);
271 }
272 if allows(&declared, Port::Realtime) {
273 view.realtime.clone_from(&self.realtime);
274 }
275 if allows(&declared, Port::HttpClient) {
276 view.http.clone_from(&self.http);
277 }
278 if allows(&declared, Port::Clock) {
279 view.clock.clone_from(&self.clock);
280 }
281 if allows(&declared, Port::IdGen) {
282 view.id_gen.clone_from(&self.id_gen);
283 }
284 if allows(&declared, Port::Defer) {
285 view.defer.clone_from(&self.defer);
286 }
287 view
288 }
289}
290
291pub(crate) fn warn_undeclared_ports(module: &dyn Module, provided: &[Port]) {
294 for port in provided {
295 if !module.requires().contains(port) && !module.optional().contains(port) {
296 warn!(
297 module = module.name(),
298 port = port.name(),
299 "runtime provides a port the module did not declare; hiding it",
300 );
301 }
302 }
303}