mod blob;
mod captcha;
mod clock;
mod database;
mod defer;
mod dispatcher;
mod http;
mod idgen;
mod kv;
mod mailer;
mod rate_limiter;
pub(crate) mod signer;
pub use blob::{Blob, BlobError, BlobObject, ScopedBlob};
pub use captcha::{Captcha, CaptchaError, Verdict};
pub use clock::{Clock, SystemClock, timeout};
pub use database::{Database, DbError, Row, Rows, Statement, TryFromValue};
pub use defer::{Defer, NoopDefer};
pub use dispatcher::{DispatchError, Dispatcher};
pub use http::{HttpClient, HttpError};
pub use idgen::{IdGen, UlidIdGen};
pub use kv::{KeyValue, KvError};
pub use mailer::{MailError, Mailer, Message, SendOutcome};
pub use rate_limiter::{Decision, RateLimitError, RateLimiter};
pub use signer::{Kid, Payload, SignatureError, Signer};
use crate::config::Config;
use crate::module::Module;
use std::sync::Arc;
use tracing::warn;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Port {
Db,
Mailer,
Captcha,
RateLimiter,
Signer,
KeyValue,
Blob,
HttpClient,
Clock,
IdGen,
Defer,
}
impl Port {
pub const ALL: &'static [Port] = &[
Port::Db,
Port::Mailer,
Port::Captcha,
Port::RateLimiter,
Port::Signer,
Port::KeyValue,
Port::Blob,
Port::HttpClient,
Port::Clock,
Port::IdGen,
Port::Defer,
];
pub fn name(&self) -> &'static str {
match self {
Port::Db => "Database",
Port::Mailer => "Mailer",
Port::Captcha => "Captcha",
Port::RateLimiter => "RateLimiter",
Port::Signer => "Signer",
Port::KeyValue => "KeyValue",
Port::Blob => "Blob",
Port::HttpClient => "HttpClient",
Port::Clock => "Clock",
Port::IdGen => "IdGen",
Port::Defer => "Defer",
}
}
}
pub struct Ports {
pub config: Arc<dyn Config>,
pub db: Option<Arc<dyn Database>>,
pub mailer: Option<Arc<dyn Mailer>>,
pub captcha: Option<Arc<dyn Captcha>>,
pub rate_limiter: Option<Arc<dyn RateLimiter>>,
pub signer: Option<Arc<dyn Signer>>,
pub kv: Option<Arc<dyn KeyValue>>,
pub blob: Option<Arc<dyn Blob>>,
pub http: Option<Arc<dyn HttpClient>>,
pub clock: Option<Arc<dyn Clock>>,
pub id_gen: Option<Arc<dyn IdGen>>,
pub defer: Option<Arc<dyn Defer>>,
pub dispatcher: Option<Arc<dyn Dispatcher>>,
}
impl Ports {
pub fn empty() -> Self {
Self::with_config(Arc::new(crate::config::EmptyConfig))
}
pub fn with_config(config: Arc<dyn Config>) -> Self {
Self {
config,
db: None,
mailer: None,
captcha: None,
rate_limiter: None,
signer: None,
kv: None,
blob: None,
http: None,
clock: None,
id_gen: None,
defer: None,
dispatcher: None,
}
}
pub fn provides(&self) -> Vec<Port> {
let mut provided = Vec::new();
if self.db.is_some() {
provided.push(Port::Db);
}
if self.mailer.is_some() {
provided.push(Port::Mailer);
}
if self.captcha.is_some() {
provided.push(Port::Captcha);
}
if self.rate_limiter.is_some() {
provided.push(Port::RateLimiter);
}
if self.signer.is_some() {
provided.push(Port::Signer);
}
if self.kv.is_some() {
provided.push(Port::KeyValue);
}
if self.http.is_some() {
provided.push(Port::HttpClient);
}
if self.clock.is_some() {
provided.push(Port::Clock);
}
if self.id_gen.is_some() {
provided.push(Port::IdGen);
}
if self.defer.is_some() {
provided.push(Port::Defer);
}
provided
}
#[must_use]
pub fn view_for(&self, module: &dyn Module) -> Self {
let declared = module
.requires()
.iter()
.chain(module.optional())
.copied()
.collect::<Vec<_>>();
let allows = |p: &[Port], port: Port| p.contains(&port);
let mut view = Ports::with_config(self.config.clone());
if allows(&declared, Port::Db) {
view.db.clone_from(&self.db);
}
if allows(&declared, Port::Mailer) {
view.mailer.clone_from(&self.mailer);
}
if allows(&declared, Port::Captcha) {
view.captcha.clone_from(&self.captcha);
}
if allows(&declared, Port::RateLimiter) {
view.rate_limiter.clone_from(&self.rate_limiter);
}
if allows(&declared, Port::Signer) {
view.signer.clone_from(&self.signer);
}
if allows(&declared, Port::KeyValue) {
view.kv.clone_from(&self.kv);
}
if allows(&declared, Port::Blob) {
view.blob = self.blob.as_ref().map(|blob| {
Arc::new(ScopedBlob::new(Arc::clone(blob), module.name())) as Arc<dyn Blob>
});
}
if allows(&declared, Port::HttpClient) {
view.http.clone_from(&self.http);
}
if allows(&declared, Port::Clock) {
view.clock.clone_from(&self.clock);
}
if allows(&declared, Port::IdGen) {
view.id_gen.clone_from(&self.id_gen);
}
if allows(&declared, Port::Defer) {
view.defer.clone_from(&self.defer);
}
view
}
}
pub(crate) fn warn_undeclared_ports(module: &dyn Module, provided: &[Port]) {
for port in provided {
if !module.requires().contains(port) && !module.optional().contains(port) {
warn!(
module = module.name(),
port = port.name(),
"runtime provides a port the module did not declare; hiding it",
);
}
}
}