Skip to main content

encryption/integrations/
axum.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! axum 0.8 集成。
4//!
5//! 实现 [`FromRequestParts`],于是处理器可以直接把 [`Guard`] 写在参数里:
6//!
7//! ```no_run
8//! use axum::{routing::get, Router};
9//! use encryption::guard::Guard;
10//! use encryption::integrations::Guarded;
11//!
12//! #[derive(Clone)]
13//! struct AppState { encryption: Guard }
14//! impl Guarded for AppState {
15//!     fn guard(&self) -> &Guard { &self.encryption }
16//! }
17//!
18//! async fn store_phone(guard: Guard) -> String {
19//!     guard.encrypt("13800138000".as_bytes()).unwrap_or_default().len().to_string()
20//! }
21//!
22//! let state = AppState { encryption: /* 你的守卫 */ todo!() };
23//! let app: Router = Router::new().route("/", get(store_phone)).with_state(state);
24//! # let _ = app;
25//! ```
26//!
27//! # 提取是不会失败的
28//!
29//! `Guarded::guard()` 返回引用,取不到守卫只可能是状态装配错了 —— 那是编译期
30//! 就该发现的事,不是运行期。所以 `Rejection` 是 [`Infallible`]:这个提取器
31//! 永远不会把请求拒掉。
32//!
33//! 加解密本身的失败(密钥不对、密文被改)发生在处理器体里,由调用方决定怎么
34//! 回应 —— 本库不替应用决定「解密失败该返回 500 还是 422」。
35
36use std::convert::Infallible;
37
38use axum::extract::FromRequestParts;
39use axum::http::request::Parts;
40
41use super::Guarded;
42use crate::guard::Guard;
43
44/// 提取器实现的宿主类型。写出来只是为了让「守卫是怎么进来的」这件事
45/// 在文档与再导出里有个名字。
46pub type GuardExtractor = Guard;
47
48impl<S> FromRequestParts<S> for Guard
49where
50    S: Guarded + Send + Sync,
51{
52    type Rejection = Infallible;
53
54    /// 从应用状态取守卫,克隆一份交给处理器。
55    ///
56    /// 克隆的是 `Arc`,不是密钥材料 —— 每请求的成本就是一次引用计数递增。
57    async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
58        Ok(state.guard().clone())
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65    use axum::Router;
66    use axum::body::Body;
67    use axum::extract::Extension;
68    use axum::http::{Request, StatusCode};
69    use axum::routing::get;
70    use tower::ServiceExt as _;
71
72    fn guard() -> Guard {
73        Guard::from_master_key(&[7u8; 32], "aes-256-gcm").unwrap()
74    }
75
76    /// 状态就是一把守卫时,零样板可用。
77    #[derive(Clone)]
78    struct BareState {
79        encryption: Guard,
80    }
81    impl Guarded for BareState {
82        fn guard(&self) -> &Guard {
83            &self.encryption
84        }
85    }
86
87    /// 真实形状:守卫只是状态里的一个字段,旁边还有别的东西。
88    #[derive(Clone)]
89    struct AppState {
90        #[allow(dead_code)]
91        pool: std::sync::Arc<String>,
92        encryption: Guard,
93    }
94    impl Guarded for AppState {
95        fn guard(&self) -> &Guard {
96            &self.encryption
97        }
98    }
99
100    /// 直接调 trait 方法,不起服务器 —— 确认签名接得上。
101    #[tokio::test]
102    async fn extracts_directly_from_parts() {
103        let state = AppState {
104            pool: std::sync::Arc::new("pool".into()),
105            encryption: guard(),
106        };
107        let (mut parts, _) = Request::new(()).into_parts();
108
109        let extracted = Guard::from_request_parts(&mut parts, &state).await.unwrap();
110        assert_eq!(extracted.identifiers().len(), 5);
111    }
112
113    /// 端到端:处理器参数里直接写 `Guard`。
114    #[tokio::test]
115    async fn handler_can_take_a_guard_argument() {
116        async fn handler(guard: Guard) -> String {
117            let blob = guard.encrypt("13800138000".as_bytes()).unwrap();
118            String::from_utf8(guard.decrypt(&blob).unwrap()).unwrap()
119        }
120
121        let state = AppState {
122            pool: std::sync::Arc::new("pool".into()),
123            encryption: guard(),
124        };
125        let app = Router::new().route("/", get(handler)).with_state(state);
126
127        let response = app
128            .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
129            .await
130            .unwrap();
131
132        assert_eq!(response.status(), StatusCode::OK);
133        let body = axum::body::to_bytes(response.into_body(), usize::MAX)
134            .await
135            .unwrap();
136        assert_eq!(&body[..], b"13800138000");
137    }
138
139    /// 处理器拿到的守卫与状态里的是同一把 —— 密文互通。
140    #[tokio::test]
141    async fn handler_and_state_share_one_encrypter() {
142        async fn handler(guard: Guard, Extension(token): Extension<Vec<u8>>) -> String {
143            guard
144                .decrypt(&token)
145                .map(|plain| String::from_utf8_lossy(&plain).into_owned())
146                .unwrap_or_else(|_| "解不开".into())
147        }
148
149        let state = BareState {
150            encryption: guard(),
151        };
152        let token = state.encryption.encrypt("共享".as_bytes()).unwrap();
153
154        let app = Router::new()
155            .route("/", get(handler))
156            .layer(Extension(token))
157            .with_state(state);
158
159        let response = app
160            .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
161            .await
162            .unwrap();
163
164        assert_eq!(response.status(), StatusCode::OK);
165        let body = axum::body::to_bytes(response.into_body(), usize::MAX)
166            .await
167            .unwrap();
168        assert_eq!(&body[..], "共享".as_bytes());
169    }
170}