Skip to main content

Module idempotency

Module idempotency 

Source
Expand description

Idempotency-key middleware.

Protects mutating routes against duplicate execution. On the first request with a given Idempotency-Key, the handler runs and the captured response is persisted. Subsequent requests with the same key replay the stored response if the request body hashes match, or return 422 with a idempotency_key_conflict code if a different body is sent under the same key (per the draft IETF spec).

Usage:

use cratestack_axum::idempotency::{IdempotencyLayer, SqlxIdempotencyStore};
let store = std::sync::Arc::new(SqlxIdempotencyStore::new(pool.clone()));
let router = generated_router.layer(IdempotencyLayer::new(store, std::time::Duration::from_secs(24 * 3600)));

In Phase 1 the layer is opt-in at the consumer’s router. A follow-up will wire it into macro-generated routers by default, gated by a @no_idempotency opt-out attribute already recognised by the parser.

Structs§

IdempotencyLayer
Tower layer that wires an IdempotencyStore into the request pipeline.
IdempotencyRecord
Persisted idempotency record returned on a replay. Banks need an invariant view of the captured response — the store rebuilds this from its persisted columns when the second caller asks to replay.
IdempotencyService

Enums§

ReservationOutcome
Outcome of an atomic reserve_or_fetch call.

Constants§

IDEMPOTENCY_TABLE_DDL
SQL DDL for the idempotency table. Banks typically run migrations through their own tooling — cratestack currently ships migrations as raw DDL since the migration engine is deferred to Phase 3.

Traits§

IdempotencyStore

Functions§

decode_headers
Decode a blob produced by encode_headers back into a HeaderMap. Returns an empty map on malformed input rather than failing the replay — a corrupt headers blob is a recoverable curiosity, not a reason to drop the response status and body the caller is waiting for.
encode_headers
Encode a response’s headers into the opaque blob that the store persists. Format: little-endian length-prefixed (name, value) pairs. Header values can carry arbitrary bytes (per RFC 9110 they may include any opaque-data octet, with the exception of CR/LF), so a binary blob is the only correct representation — JSON would force lossy UTF-8 coercion on values like opaque ETag tokens that may already be quoted-string blobs.
hash_request
Stable fingerprint of a request: SHA-256 over method, path + query, content-type, and body bytes. Used to detect when a duplicate key is reused with a different payload (the conflict case the draft spec calls out). The path argument should include the query string so modifier-style flags (?dry_run=true, ?confirm=true) don’t collide — the middleware passes Uri::path_and_query for that reason.
is_idempotent_target_method
Returns true if the HTTP method is one we’d guard with idempotency. We apply only to mutating verbs — GETs are already safely repeatable.
parse_idempotency_key
Parse the Idempotency-Key request header. Returns Ok(None) if absent. The key must be ASCII and reasonably short to avoid storage abuse.