1mod blob;
9mod captcha;
10mod clock;
11mod database;
12mod defer;
13mod dispatcher;
14mod http;
15mod idgen;
16mod kv;
17mod mailer;
18mod rate_limiter;
19pub(crate) mod signer;
20
21pub use blob::{Blob, BlobError, BlobObject, ScopedBlob};
22pub use captcha::{Captcha, CaptchaError, Verdict};
23pub use clock::{Clock, SystemClock, timeout};
24pub use database::{Database, DbError, Row, Rows, Statement, TryFromValue};
25pub use defer::{Defer, NoopDefer};
26pub use dispatcher::{DispatchError, Dispatcher};
27pub use http::{HttpClient, HttpError};
28pub use idgen::{IdGen, UlidIdGen};
29pub use kv::{KeyValue, KvError};
30pub use mailer::{MailError, Mailer, Message, SendOutcome};
31pub use rate_limiter::{Decision, RateLimitError, RateLimiter};
32pub use signer::{Kid, Payload, SignatureError, Signer};
33
34use crate::config::Config;
35use crate::module::Module;
36use std::sync::Arc;
37use tracing::warn;
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub enum Port {
43 Db,
44 Mailer,
45 Captcha,
46 RateLimiter,
47 Signer,
48 KeyValue,
49 Blob,
50 HttpClient,
51 Clock,
52 IdGen,
53 Defer,
54}
55
56impl Port {
57 pub const ALL: &'static [Port] = &[
58 Port::Db,
59 Port::Mailer,
60 Port::Captcha,
61 Port::RateLimiter,
62 Port::Signer,
63 Port::KeyValue,
64 Port::Blob,
65 Port::HttpClient,
66 Port::Clock,
67 Port::IdGen,
68 Port::Defer,
69 ];
70
71 pub fn name(&self) -> &'static str {
72 match self {
73 Port::Db => "Database",
74 Port::Mailer => "Mailer",
75 Port::Captcha => "Captcha",
76 Port::RateLimiter => "RateLimiter",
77 Port::Signer => "Signer",
78 Port::KeyValue => "KeyValue",
79 Port::Blob => "Blob",
80 Port::HttpClient => "HttpClient",
81 Port::Clock => "Clock",
82 Port::IdGen => "IdGen",
83 Port::Defer => "Defer",
84 }
85 }
86}
87
88pub struct Ports {
94 pub config: Arc<dyn Config>,
95 pub db: Option<Arc<dyn Database>>,
96 pub mailer: Option<Arc<dyn Mailer>>,
97 pub captcha: Option<Arc<dyn Captcha>>,
98 pub rate_limiter: Option<Arc<dyn RateLimiter>>,
99 pub signer: Option<Arc<dyn Signer>>,
100 pub kv: Option<Arc<dyn KeyValue>>,
101 pub blob: Option<Arc<dyn Blob>>,
102 pub http: Option<Arc<dyn HttpClient>>,
103 pub clock: Option<Arc<dyn Clock>>,
104 pub id_gen: Option<Arc<dyn IdGen>>,
105 pub defer: Option<Arc<dyn Defer>>,
106 pub dispatcher: Option<Arc<dyn Dispatcher>>,
109}
110
111impl Ports {
112 pub fn empty() -> Self {
115 Self::with_config(Arc::new(crate::config::EmptyConfig))
116 }
117
118 pub fn with_config(config: Arc<dyn Config>) -> Self {
119 Self {
120 config,
121 db: None,
122 mailer: None,
123 captcha: None,
124 rate_limiter: None,
125 signer: None,
126 kv: None,
127 blob: None,
128 http: None,
129 clock: None,
130 id_gen: None,
131 defer: None,
132 dispatcher: None,
133 }
134 }
135
136 pub fn provides(&self) -> Vec<Port> {
138 let mut provided = Vec::new();
139 if self.db.is_some() {
140 provided.push(Port::Db);
141 }
142 if self.mailer.is_some() {
143 provided.push(Port::Mailer);
144 }
145 if self.captcha.is_some() {
146 provided.push(Port::Captcha);
147 }
148 if self.rate_limiter.is_some() {
149 provided.push(Port::RateLimiter);
150 }
151 if self.signer.is_some() {
152 provided.push(Port::Signer);
153 }
154 if self.kv.is_some() {
155 provided.push(Port::KeyValue);
156 }
157 if self.http.is_some() {
158 provided.push(Port::HttpClient);
159 }
160 if self.clock.is_some() {
161 provided.push(Port::Clock);
162 }
163 if self.id_gen.is_some() {
164 provided.push(Port::IdGen);
165 }
166 if self.defer.is_some() {
167 provided.push(Port::Defer);
168 }
169 provided
170 }
171
172 #[must_use]
177 pub fn view_for(&self, module: &dyn Module) -> Self {
178 let declared = module
179 .requires()
180 .iter()
181 .chain(module.optional())
182 .copied()
183 .collect::<Vec<_>>();
184 let allows = |p: &[Port], port: Port| p.contains(&port);
185 let mut view = Ports::with_config(self.config.clone());
186 if allows(&declared, Port::Db) {
187 view.db.clone_from(&self.db);
188 }
189 if allows(&declared, Port::Mailer) {
190 view.mailer.clone_from(&self.mailer);
191 }
192 if allows(&declared, Port::Captcha) {
193 view.captcha.clone_from(&self.captcha);
194 }
195 if allows(&declared, Port::RateLimiter) {
196 view.rate_limiter.clone_from(&self.rate_limiter);
197 }
198 if allows(&declared, Port::Signer) {
199 view.signer.clone_from(&self.signer);
200 }
201 if allows(&declared, Port::KeyValue) {
202 view.kv.clone_from(&self.kv);
203 }
204 if allows(&declared, Port::Blob) {
205 view.blob = self.blob.as_ref().map(|blob| {
208 Arc::new(ScopedBlob::new(Arc::clone(blob), module.name())) as Arc<dyn Blob>
209 });
210 }
211 if allows(&declared, Port::HttpClient) {
212 view.http.clone_from(&self.http);
213 }
214 if allows(&declared, Port::Clock) {
215 view.clock.clone_from(&self.clock);
216 }
217 if allows(&declared, Port::IdGen) {
218 view.id_gen.clone_from(&self.id_gen);
219 }
220 if allows(&declared, Port::Defer) {
221 view.defer.clone_from(&self.defer);
222 }
223 view
224 }
225}
226
227pub(crate) fn warn_undeclared_ports(module: &dyn Module, provided: &[Port]) {
230 for port in provided {
231 if !module.requires().contains(port) && !module.optional().contains(port) {
232 warn!(
233 module = module.name(),
234 port = port.name(),
235 "runtime provides a port the module did not declare; hiding it",
236 );
237 }
238 }
239}