rovenue/transport/
idempotency.rs1#[derive(Debug, Clone)]
6pub struct IdempotencyKey(String);
7
8fn fnv1a_64(bytes: &[u8]) -> u64 {
12 let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
13 for b in bytes {
14 hash ^= *b as u64;
15 hash = hash.wrapping_mul(0x0000_0100_0000_01B3);
16 }
17 hash
18}
19
20impl IdempotencyKey {
21 pub fn new() -> Self {
22 Self(format!("idem_{}", cuid2::create_id()))
23 }
24
25 pub fn as_str(&self) -> &str {
26 &self.0
27 }
28
29 pub fn for_receipt(store: &str, receipt: &str) -> Self {
34 let mut input = String::with_capacity(store.len() + 1 + receipt.len());
35 input.push_str(store);
36 input.push(':');
37 input.push_str(receipt);
38 Self(format!("idem_rcpt_{:016x}", fnv1a_64(input.as_bytes())))
39 }
40}
41
42impl Default for IdempotencyKey {
43 fn default() -> Self {
44 Self::new()
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 #[test]
53 fn for_receipt_is_deterministic_and_scoped() {
54 let a = IdempotencyKey::for_receipt("apple", "jws-token-xyz");
55 let b = IdempotencyKey::for_receipt("apple", "jws-token-xyz");
56 assert_eq!(a.as_str(), b.as_str(), "same input must yield same key");
57
58 let diff_receipt = IdempotencyKey::for_receipt("apple", "jws-token-zzz");
59 assert_ne!(a.as_str(), diff_receipt.as_str());
60
61 let diff_store = IdempotencyKey::for_receipt("google", "jws-token-xyz");
62 assert_ne!(a.as_str(), diff_store.as_str());
63
64 assert!(a.as_str().starts_with("idem_rcpt_"));
65 }
66}