Skip to main content

encryption/integrations/
poem.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! poem 3 集成。
4//!
5//! 用 `.data(guard)` 注册,处理器把守卫写在参数里:
6//!
7//! ```no_run
8//! # mod demo {
9//! use poem::{EndpointExt, Route, get, handler};
10//! use encryption::guard::Guard;
11//!
12//! #[handler]
13//! async fn store_phone(guard: Guard) -> String {
14//!     guard.encrypt("13800138000".as_bytes()).unwrap().len().to_string()
15//! }
16//!
17//! # fn main() {
18//! let guard: Guard = todo!();
19//! let app = Route::new().at("/", get(store_phone)).data(guard);
20//! # let _ = app;
21//! # }
22//! # }
23//! ```
24//!
25//! poem 的 `.data(v)` 要求 `T: Clone + Send + Sync + 'static`;[`Guard`] 满足,
26//! 且克隆的只是一次 `Arc` 引用计数递增。
27
28use poem::http::StatusCode;
29use poem::web::{FromRequest, RequestBody};
30use poem::{Error, Request, Result};
31
32use super::GuardNotConfigured;
33use crate::guard::Guard;
34
35impl<'a> FromRequest<'a> for Guard {
36    async fn from_request(req: &'a Request, _body: &mut RequestBody) -> Result<Self> {
37        req.extensions().get::<Guard>().cloned().ok_or_else(|| {
38            Error::from_string(
39                GuardNotConfigured.to_string(),
40                StatusCode::INTERNAL_SERVER_ERROR,
41            )
42        })
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49    use poem::Request;
50
51    fn guard() -> Guard {
52        Guard::from_master_key(&[7u8; 32], "aes-256-gcm").unwrap()
53    }
54
55    async fn extract(req: &Request) -> Result<Guard> {
56        let mut body = RequestBody::default();
57        Guard::from_request(req, &mut body).await
58    }
59
60    #[tokio::test]
61    async fn extracts_from_request_extensions() {
62        let mut req = Request::builder().finish();
63        req.extensions_mut().insert(guard());
64
65        let extracted = extract(&req).await.unwrap();
66        assert_eq!(extracted.identifiers().len(), 5);
67    }
68
69    /// 忘了 `.data(...)` 时是 500,不是 400 —— 这是服务端装配问题。
70    #[tokio::test]
71    async fn missing_data_is_a_500() {
72        let req = Request::builder().finish();
73        let err = extract(&req).await.unwrap_err();
74        assert_eq!(err.status(), StatusCode::INTERNAL_SERVER_ERROR);
75    }
76
77    #[tokio::test]
78    async fn handler_and_state_share_one_encrypter() {
79        let state = guard();
80        let token = state.encrypt("共享".as_bytes()).unwrap();
81
82        let mut req = Request::builder().finish();
83        req.extensions_mut().insert(state);
84
85        let extracted = extract(&req).await.unwrap();
86        assert_eq!(extracted.decrypt(&token).unwrap(), "共享".as_bytes());
87    }
88}