Skip to main content

encryption/integrations/
actix.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! actix-web 4 集成。
4//!
5//! 守卫注册进 `app_data`,处理器把它直接写在参数里:
6//!
7//! ```no_run
8//! # mod demo {
9//! use actix_web::{web, App, HttpServer};
10//! use encryption::guard::Guard;
11//!
12//! async fn store_phone(guard: Guard) -> String {
13//!     guard.encrypt("13800138000".as_bytes()).unwrap().len().to_string()
14//! }
15//!
16//! # fn main() {
17//! let guard: Guard = todo!();
18//! let app = move || {
19//!     App::new()
20//!         .app_data(web::Data::new(guard.clone()))
21//!         .route("/", web::get().to(store_phone))
22//! };
23//! # let _ = app;
24//! # }
25//! # }
26//! ```
27//!
28//! # 与 [`Guarded`](super::Guarded) 的关系
29//!
30//! actix 没有「单一应用状态」这个概念,它的 `app_data` 是按**类型**取值的,
31//! 所以这里走的是 `Data<Guard>` 这条路,而不是 [`Guarded`](super::Guarded)。
32//!
33//! 状态结构体比较大时,注册 `Data<AppState>` 并让 `AppState` 实现
34//! [`Guarded`](super::Guarded),然后在处理器里写 `state.guard()` —— 那条路
35//! 一样通,只是取用方式不同。
36
37use std::future::{Ready, ready};
38
39use actix_web::dev::Payload;
40use actix_web::web::Data;
41use actix_web::{Error, FromRequest, HttpRequest};
42
43use super::GuardNotConfigured;
44use crate::guard::Guard;
45
46impl FromRequest for Guard {
47    type Error = Error;
48    /// 纯同步取值,不需要装箱 —— 拷贝一次 `Arc` 而已。
49    type Future = Ready<Result<Self, Self::Error>>;
50
51    fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
52        match req.app_data::<Data<Guard>>() {
53            Some(data) => {
54                // `Data<T>` 的 `Deref::Target` 是 `Arc<T>`(不是 `T`),所以这里
55                // 靠连续两次解引用拿到 `&Guard`,而不是 `(**data).clone()`
56                // —— 那会得到一个 `Arc<Guard>`。
57                let guard: &Guard = data;
58                ready(Ok(guard.clone()))
59            }
60            // 忘了 app_data(...) —— 装配错误,500 而不是 4xx
61            None => ready(Err(actix_web::error::ErrorInternalServerError(
62                GuardNotConfigured,
63            ))),
64        }
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use actix_web::test;
72
73    fn guard() -> Guard {
74        Guard::from_master_key(&[7u8; 32], "aes-256-gcm").unwrap()
75    }
76
77    #[actix_web::test]
78    async fn extracts_from_app_data() {
79        let req = test::TestRequest::default()
80            .app_data(Data::new(guard()))
81            .to_http_request();
82
83        let extracted = Guard::from_request(&req, &mut Payload::None).await.unwrap();
84        assert_eq!(extracted.identifiers().len(), 5);
85    }
86
87    /// 忘了注册时给出明确的 500,而不是 panic 或 400。
88    #[actix_web::test]
89    async fn missing_app_data_is_a_500() {
90        let req = test::TestRequest::default().to_http_request();
91        let err = Guard::from_request(&req, &mut Payload::None)
92            .await
93            .unwrap_err();
94        assert_eq!(
95            err.as_response_error().status_code(),
96            actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
97        );
98    }
99
100    /// 处理器拿到的与注册进去的是同一把密钥 —— 密文互通。
101    #[actix_web::test]
102    async fn handler_and_state_share_one_encrypter() {
103        let state = guard();
104        let token = state.encrypt("共享".as_bytes()).unwrap();
105
106        let req = test::TestRequest::default()
107            .app_data(Data::new(state))
108            .to_http_request();
109        let extracted = Guard::from_request(&req, &mut Payload::None).await.unwrap();
110
111        assert_eq!(extracted.decrypt(&token).unwrap(), "共享".as_bytes());
112    }
113
114    /// `Data::from(arc)` 复用同一个分配,不嵌套第二层 Arc。
115    #[actix_web::test]
116    async fn data_from_arc_shares_the_allocation() {
117        use std::sync::Arc;
118
119        let shared = Arc::new(guard());
120        let token = shared.encrypt(b"x").unwrap();
121
122        let req = test::TestRequest::default()
123            .app_data(Data::from(shared.clone()))
124            .to_http_request();
125        let extracted = Guard::from_request(&req, &mut Payload::None).await.unwrap();
126
127        assert_eq!(extracted.decrypt(&token).unwrap(), b"x");
128        // 原 Arc 与 Data 里的仍是同一份
129        assert_eq!(Arc::strong_count(&shared), 2);
130    }
131}