Skip to main content

Crate cratefield_push_wiring

Crate cratefield_push_wiring 

Source
Expand description

cratefield-push-wiring (issue #191): the one place that turns a venture’s environment into the Push port.

Three adapters serve three transports — cratefield-adapter-apns, cratefield-adapter-fcm and cratefield-adapter-webpush — and each is built from a handful of environment variables. Read those names in serve(), again in fz push send and a third time in fz doctor and they drift; the drift shows up in production as a notification that silently never sends. So the names live in exactly one table (PUSH_ENV), one function assembles the adapters from it (build_push), and everything else calls that function.

§The partial case is the point

A venture that meant to enable FCM and mistyped one variable must not boot into a state where every Android send answers PushOutcome::NotConfigured and nothing says why. build_push therefore returns a PushWiring report alongside the router, and the report distinguishes:

  • Configured — every variable the transport needs is set and the adapter accepted them.
  • Absent — not one of its variables is set. The venture did not wire that transport, which is a choice, not a defect.
  • Partial — some but not all. Somebody meant to wire it.
  • Invalid — all of them are set and the adapter refused them (a .p8 that is not a PKCS#8 key, an APNS_HOST that is neither host).

Partial and Invalid are an error in production and a warning everywhere else (PushWiring::severity): a developer wiring APNs one variable at a time must still be able to boot.

§Nothing that came out of the environment goes into the report

The report is logged whole at cold start, so a single value copied into it reaches Workers Logs. An adapter’s error is free to quote what it was handed — VapidError::Subject does — and Vapid::new validates the subject before the key, so VAPID_SUBJECT and VAPID_PRIVATE_KEY pasted the wrong way round (the mistake VapidKeys’ own doc warns about) would have put the private key in the log. So an adapter’s error string is never copied into TransportWiring::Invalid: every reason is a fixed phrase naming the variable and what was expected, and the_report_never_prints_any_variables_value holds every variable in the table to it.

§Degraded, never broken

A transport that is not configured is simply not routed: the RoutingPush answers NotConfigured for its recipients, which is the same contract each adapter’s not_configured() constructor gives. With nothing configured at all the router is empty, every send is NotConfigured, and the Worker still boots.

§Call sites

  • serve() on both runtimes, through Cloudflare::push_from_env() / Native::push_from_env(), which build the router once per isolate or process and log the report once at cold start.
  • fz doctor, through inspect_push — the same function with an offline HTTP client, because the doctor never sends.
  • fz push send and fz push doctor, when the CLI lands (issue #184): they call build_push and must not read any of these names themselves. crates/cli-acceptance/tests/push_env_guard.rs fails the build if they do.

§The table is the documentation

docs/PUSH-ENV.md is generated from PUSH_ENV by cargo run -p cratefield-push-wiring --example push-env-doc, and CI fails on drift — so the doc cannot describe variables the reader does not read.

cratefield-push-wiring — One env table.

MIT

§cratefield-push-wiring

The runtime wiring for the push adapters (issue #191): given a venture’s environment, which push transports exist, and how do they become the single Arc<dyn Push> a module receives?

One table of variable names, one function that reads it, and every call site — serve() on both runtimes, fz push send, fz doctor — calls that function. Reading the names in three places is how a venture ends up with a serve() that wires FCM, a doctor that checks a different variable, and an Android notification that silently never sends.

ⓘ
use cratefield_push_wiring::{build_push, WiringSeverity};

let (push, wiring) = build_push(&config, http, clock);
// One line, names and verdicts only — never a value:
//   push wiring: apns=configured fcm=partial (set: …; unset: …) web_push=absent
tracing::info!("{}", wiring.summary());
match wiring.severity(env) {
    WiringSeverity::Ok => {}
    WiringSeverity::Warning => for p in wiring.problems() { tracing::warn!("{p}") },
    WiringSeverity::Error => for p in wiring.problems() { tracing::error!("{p}") },
}
let runtime = Cloudflare::new().push_arc(push);

Or let the runtime do it, which is what the example venture does:

ⓘ
let runtime = Cloudflare::new().db("DB").push_from_env();  // feature = "push"

§Verdicts

VerdictWhenConsequence
configuredevery required variable is set and the adapter accepted themrouted
absentnot one of the transport’s variables is setnot routed; NotConfigured. A choice, not a defect
partialsome are set and some are notnot routed. Error in production, warning elsewhere
invalidall are set and the adapter refused themnot routed. Error in production, warning elsewhere

Partial is the case this crate exists for. A venture that meant to enable FCM and mistyped one variable must not boot into a state where every Android send answers NotConfigured and nothing says why.

Nothing is ever broken by a bad environment: an unrouted transport degrades to PushOutcome::NotConfigured, the same contract each adapter’s not_configured() constructor gives, so a Worker with nothing configured still boots and still answers every send.

§The variables

docs/PUSH-ENV.md is generated from this crate’s PUSH_ENV table:

cargo run -p cratefield-push-wiring --example push-env-doc          # write
cargo run -p cratefield-push-wiring --example push-env-doc -- --check

No other Rust source in the workspace may name one of those keys in a string literal; crates/cli-acceptance/tests/push_env_guard.rs fails the build if one does.

§The browser’s application server key

vapid_public_key(config) answers the applicationServerKey a browser passes to pushManager.subscribe(), or None when Web Push is not configured. It lives here for the same reason build_push does — these are the same two variables — and it derives the public half from the private key rather than reading a third variable, so the key a venture serves to browsers cannot drift from the one its sends are signed with. A venture hands it to the notifications module, which serves it:

ⓘ
Notifications::new().vapid_public_key(cratefield::push_wiring::vapid_public_key)

MIT. Built in the open for Cratefield, a Factory Zero venture.

Structs§

PushVar
One environment variable a push transport reads.
PushWiring
Which push transports a venture’s environment configured, which it did not, and which it half did.

Enums§

PushKey
One push environment variable, as a type.
TransportWiring
How one transport was configured.
WiringSeverity
How loudly a PushWiring report should be reported.

Constants§

PUSH_ENV
Every environment variable the push adapters read, in one table.
TRANSPORTS
The transports, in report order.

Functions§

build_push
Assembles the Push port from a venture’s environment, and reports what it found.
inspect_push
The same verdicts as build_push, for a caller that has no HTTP client and needs none — fz doctor, which reports on an environment rather than sending through it.
notifications_doc
docs/PUSH-ENV.md, generated from PUSH_ENV so the document cannot describe variables the reader does not read. Written and drift-checked by cargo run -p cratefield-push-wiring --example push-env-doc.
transport_key
The RoutingPush builder name for a transport — apns, fcm, web_push — which is what a report line and a log say.
transport_name
The transport’s name in prose (APNs, FCM, Web Push).
vapid_public_key
The applicationServerKey a browser must pass to pushManager.subscribe(), base64url — or None when this venture has not configured Web Push (issue #183).
vars_for
The variables one transport reads.