cratestack_axum/idempotency/
layer.rs1use std::sync::Arc;
4use std::time::Duration;
5
6use axum::extract::Request;
7use http::header;
8use sha2::{Digest, Sha256};
9use tower::Layer;
10
11use super::service::IdempotencyService;
12use super::store::IdempotencyStore;
13
14#[derive(Clone)]
16pub struct IdempotencyLayer {
17 pub(super) store: Arc<dyn IdempotencyStore>,
18 pub(super) ttl: Duration,
19 pub(super) principal_fingerprint: Arc<dyn Fn(&Request) -> String + Send + Sync>,
20}
21
22impl IdempotencyLayer {
23 pub fn new(store: Arc<dyn IdempotencyStore>, ttl: Duration) -> Self {
27 Self {
28 store,
29 ttl,
30 principal_fingerprint: Arc::new(default_principal_fingerprint),
31 }
32 }
33
34 pub fn with_principal_fingerprint(
38 mut self,
39 f: impl Fn(&Request) -> String + Send + Sync + 'static,
40 ) -> Self {
41 self.principal_fingerprint = Arc::new(f);
42 self
43 }
44}
45
46pub(super) fn default_principal_fingerprint(req: &Request) -> String {
47 req.headers()
48 .get(header::AUTHORIZATION)
49 .and_then(|v| v.to_str().ok())
50 .map(|s| {
51 let mut h = Sha256::new();
52 h.update(s.as_bytes());
53 format!("{:x}", h.finalize())
54 })
55 .unwrap_or_else(|| "anonymous".to_owned())
56}
57
58impl<S> Layer<S> for IdempotencyLayer {
59 type Service = IdempotencyService<S>;
60
61 fn layer(&self, inner: S) -> Self::Service {
62 IdempotencyService {
63 inner,
64 store: self.store.clone(),
65 ttl: self.ttl,
66 principal_fingerprint: self.principal_fingerprint.clone(),
67 }
68 }
69}