cratestack_axum/idempotency/hash.rs
1//! Stable request fingerprint.
2
3use http::Method;
4use sha2::{Digest, Sha256};
5
6/// Stable fingerprint of a request: SHA-256 over method, path + query,
7/// content-type, and body bytes. Used to detect when a duplicate key is
8/// reused with a different payload (the conflict case the draft spec
9/// calls out). The `path` argument should include the query string so
10/// modifier-style flags (`?dry_run=true`, `?confirm=true`) don't collide
11/// — the middleware passes `Uri::path_and_query` for that reason.
12pub fn hash_request(
13 method: &Method,
14 path: &str,
15 content_type: Option<&str>,
16 body: &[u8],
17) -> [u8; 32] {
18 let mut hasher = Sha256::new();
19 hasher.update(method.as_str().as_bytes());
20 hasher.update(b"\0");
21 hasher.update(path.as_bytes());
22 hasher.update(b"\0");
23 hasher.update(content_type.unwrap_or("").as_bytes());
24 hasher.update(b"\0");
25 hasher.update(body);
26 hasher.finalize().into()
27}
28
29/// Returns true if the HTTP method is one we'd guard with idempotency. We
30/// apply only to mutating verbs — GETs are already safely repeatable.
31pub fn is_idempotent_target_method(method: &Method) -> bool {
32 matches!(
33 method,
34 &Method::POST | &Method::PATCH | &Method::PUT | &Method::DELETE
35 )
36}