pub struct Webhooks { /* private fields */ }Expand description
A module that delivers signed outbound webhooks.
Implementations§
Source§impl Webhooks
impl Webhooks
Sourcepub fn from_config(cfg: &dyn Config) -> Result<Self, ConfigError>
pub fn from_config(cfg: &dyn Config) -> Result<Self, ConfigError>
The module as the deployment configures it. WEBHOOKS_SIGNATURE_ALIAS
names a second header that carries the same signature, for receivers
that verify under their product’s own header name; unset is
Webhooks::new. WEBHOOKS_MAX_ATTEMPTS is still read by the drain.
§Errors
ConfigError when the alias is not an HTTP header token, or names a
header the module or the transport already sends. Build the module
with this at boot to refuse a bad alias then. The drain reads the
alias from its own config on every pass, so it refuses one then too.
Sourcepub fn signature_headers(&self) -> Vec<&str>
pub fn signature_headers(&self) -> Vec<&str>
The signature header names Webhooks::from_config stored:
SIGNATURE_HEADER, then the alias if there is one. All of them carry
the same value. The drain takes its names from the config it is given.
Sourcepub fn max_attempts(self, max_attempts: u32) -> Self
pub fn max_attempts(self, max_attempts: u32) -> Self
Dead-letter a delivery after this many failed attempts instead of
five. Values outside 1..=1_000 clamp to the nearest bound — the
same range WEBHOOKS_MAX_ATTEMPTS must pass validation with.
WEBHOOKS_MAX_ATTEMPTS overrides this when set.
Sourcepub async fn create_endpoint(
&self,
db: &dyn Database,
subject: &str,
url: &str,
event_types: &[&str],
now: &str,
) -> Result<CreatedEndpoint, EndpointError>
pub async fn create_endpoint( &self, db: &dyn Database, subject: &str, url: &str, event_types: &[&str], now: &str, ) -> Result<CreatedEndpoint, EndpointError>
Registers one delivery endpoint for subject and returns its
signing secret — the only time the secret is ever handed back.
List the endpoints later and the secret is not in the answer.
event_types is a filter; an empty slice means “everything”
(*). A URL that is not http(s), or a filter naming an event type
that could never ride a header, is refused here rather than failed
at every delivery.
§Errors
EndpointError for a malformed URL or subject, or a filter
naming an unusable event type, and DbError when the insert
fails.
Sourcepub async fn endpoints(
&self,
db: &dyn Database,
subject: &str,
) -> Result<Vec<Endpoint>, DbError>
pub async fn endpoints( &self, db: &dyn Database, subject: &str, ) -> Result<Vec<Endpoint>, DbError>
Sourcepub async fn delete_endpoint(
&self,
db: &dyn Database,
subject: &str,
endpoint_id: &str,
) -> Result<bool, DbError>
pub async fn delete_endpoint( &self, db: &dyn Database, subject: &str, endpoint_id: &str, ) -> Result<bool, DbError>
Sourcepub async fn publish(
&self,
db: &dyn Database,
subject: &str,
event_type: &str,
data: &Value,
now: &str,
) -> Result<Published, PublishError>
pub async fn publish( &self, db: &dyn Database, subject: &str, event_type: &str, data: &Value, now: &str, ) -> Result<Published, PublishError>
Fans one event out to every endpoint of subject whose filter
matches event_type, and returns the outbox INSERTs — append
Published::into_statements to the caller’s own
db.batch_atomic(..), so the event is durable exactly when the
state change that caused it is. Nothing is written until that
batch runs.
An event no endpoint matches is Published::endpoints == 0 and
nothing else — not an error: “nobody subscribed to that” is an
ordinary answer.
§Errors
PublishError::InvalidEventType for an event type that could
never ride the Cratefield-Event-Type header (refused before any
read, so it cannot burn attempts later), and DbError when the
endpoint read fails.
Sourcepub async fn drain_with(
&self,
ctx: &ModuleContext,
) -> Result<DrainReport, DeliveryError>
pub async fn drain_with( &self, ctx: &ModuleContext, ) -> Result<DrainReport, DeliveryError>
Delivers due outbox rows through the ports of ctx, spending no
more than what the invocation’s ctx.scheduled budget allows (ADR
0023). Call it from the venture’s scheduled entry point — a cron
invocation builds no router, so the context must be the one the
runtime hands in, not one parked at router-build time.
Each row is signed and delivered once per pass: 2xx completes it,
anything else retries with exponential backoff (410 Gone and
SSRF-blocked destinations dead-letter immediately), and
MAX_ATTEMPTS failures dead-letter it for
Webhooks::replay. One delivery attempt spends one subrequest;
when the budget runs out the rows it never reached stay queued, due
now, for the next tick. A per-row failure never aborts the pass.
The signature names come from ctx’s WEBHOOKS_SIGNATURE_ALIAS, on
every pass, whichever constructor built this module.
§Errors
DeliveryError when a port is missing, the claim fails, the outbox
refuses a completion write, or the alias is not usable. An unusable
alias is refused before any row is claimed.
Sourcepub async fn replay(
&self,
db: &dyn Database,
subject: &str,
dead_letter_id: &str,
now: &str,
) -> Result<bool, DbError>
pub async fn replay( &self, db: &dyn Database, subject: &str, dead_letter_id: &str, now: &str, ) -> Result<bool, DbError>
Re-enqueues one dead letter of subject — attempts reset to zero —
and removes it, in one batch. The delivery log keeps the history.
false — the letter left exactly where it was — when the id names
no dead letter of this subject, or when the letter’s endpoint
is gone: a replay into a deleted endpoint would consume the letter
and then be dropped by the drain, destroying the event for good.
§Errors
DbError when the read or the batch fails.
Sourcepub async fn deliveries(
&self,
db: &dyn Database,
subject: &str,
limit: u64,
) -> Result<Vec<Delivery>, DbError>
pub async fn deliveries( &self, db: &dyn Database, subject: &str, limit: u64, ) -> Result<Vec<Delivery>, DbError>
Sourcepub async fn dead_letters(
&self,
db: &dyn Database,
subject: &str,
limit: u64,
) -> Result<Vec<DeadLetter>, DbError>
pub async fn dead_letters( &self, db: &dyn Database, subject: &str, limit: u64, ) -> Result<Vec<DeadLetter>, DbError>
A subject’s dead letters, oldest first — the ids
Webhooks::replay takes.
§Errors
DbError when the read fails.
Trait Implementations§
Source§impl Module for Webhooks
impl Module for Webhooks
Source§fn personal_data(&self) -> &'static [PersonalDataSet]
fn personal_data(&self) -> &'static [PersonalDataSet]
Every table keys its rows on the subject the event (or endpoint)
belongs to, so erasure reaches all four; the endpoint secret is
credential material and is exported redacted.
Source§fn router(&self, _ctx: ModuleContext) -> Router
fn router(&self, _ctx: ModuleContext) -> Router
Deliberately empty: see the crate documentation — the Rust API is this module’s surface, and a management route would need an auth decision this crate does not make.
Source§fn scheduled<'a>(
&'a self,
ctx: &'a ModuleContext,
cron: &'a str,
) -> BoxFuture<'a, Result<(), AnyError>>
fn scheduled<'a>( &'a self, ctx: &'a ModuleContext, cron: &'a str, ) -> BoxFuture<'a, Result<(), AnyError>>
The recovery half of the outbox contract: whatever a caller’s
immediate drain never got to is delivered on the next tick — as far
as the invocation’s budget reaches (ADR 0023), with the rest left
due for the ticks after — and the dead letters pile up here for
Webhooks::replay.
Source§fn requires(&self) -> &'static [Port]
fn requires(&self) -> &'static [Port]
Source§fn tables(&self) -> &'static [&'static str]
fn tables(&self) -> &'static [&'static str]
Source§fn migrations(&self) -> Migrations
fn migrations(&self) -> Migrations
Source§fn validate_config(&self, cfg: &dyn Config) -> Result<(), ConfigError>
fn validate_config(&self, cfg: &dyn Config) -> Result<(), ConfigError>
Source§fn harness_api(&self) -> u32
fn harness_api(&self) -> u32
Harness::build.Source§fn depends_on(&self) -> &'static [&'static str]
fn depends_on(&self) -> &'static [&'static str]
Source§fn actor_kinds(&self) -> &'static [&'static str]
fn actor_kinds(&self) -> &'static [&'static str]
[a-z0-9_-]; duplicates across
modules are a build error, and so is a kind with no handler registered
through
HarnessBuilder::actor. Read moreSource§fn emits(&self) -> &'static [&'static str]
fn emits(&self) -> &'static [&'static str]
"<module>.<event>"), listed by
/__health.Source§fn public_writes(&self) -> bool
fn public_writes(&self) -> bool
Source§fn public_write_policy(&self) -> RoutePolicy
fn public_write_policy(&self) -> RoutePolicy
RoutePolicy on
(issue #143). Read moreSource§fn signature_verification(&self) -> SignatureVerification
fn signature_verification(&self) -> SignatureVerification
RoutePolicy::Signature
deliveries (issue #533): how a webhook handler checks a machine
signature before acting. Read moreSource§fn max_body_bytes(&self, _cfg: &dyn Config) -> usize
fn max_body_bytes(&self, _cfg: &dyn Config) -> usize
Source§fn streaming_routes(&self) -> &'static [StreamRoute]
fn streaming_routes(&self) -> &'static [StreamRoute]
StreamRoute::max_bytes ceiling. Opt-in: the default
is empty, and every existing route keeps the buffered behaviour it
always had. Read moreSource§fn max_blob_object_bytes(&self) -> u64
fn max_blob_object_bytes(&self) -> u64
ScopedBlob the harness hands the module on
put_stream, upload_part and complete_multipart. Read moreSource§fn self_check(&self) -> Vec<String>
fn self_check(&self) -> Vec<String>
Source§fn well_known(&self) -> Option<Router>
fn well_known(&self) -> Option<Router>
/.well-known, for
spec-mandated discovery documents (OIDC openid-configuration,
jwks.json) that must live outside /v1 (issue #46). Paths are
relative to the prefix: register /jwks.json, not
/.well-known/jwks.json. Read moreSource§fn surface(&self) -> Surface
fn surface(&self) -> Surface
Action::input::<Body>()), so the
declaration cannot drift from the route. Harness::build
validates it; GET /__surface serves the composition. Default:
nothing, and a module that declares nothing renders nothing.