Skip to main content

encryption/integrations/
mod.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! 框架集成:把 [`Guard`](crate::guard::Guard) 按各框架的惯例交给处理器。
4//!
5//! 每个框架一个 feature,全部 opt-in,默认构建一个都不拉。
6//!
7//! ```text
8//! features = ["axum"]       ->  Guard 作为 axum 提取器
9//! features = ["actix-web"]  ->  Guard 实现 FromRequest
10//! features = ["rocket"]     ->  Guard 作为 rocket 请求守卫
11//! features = ["poem"]       ->  Guard 实现 FromRequest
12//! features = ["salvo"]      ->  Guard 实现 Extractible
13//! features = ["warp"]       ->  with_guard 组合子
14//! features = ["bee-rust"]   ->  bee-rust 路由吃 axum handler,复用 axum 适配
15//! features = ["ecat"]       ->  e-cat 的 tower Layer/Service
16//! ```
17//!
18//! # 一致的心智模型
19//!
20//! 框架各不相同,但它们在这里要解决的是同一个问题:**处理器怎么拿到那个共享的
21//! 加密器**。所有适配层都归结为同一件事 —— 从应用状态里取出 [`Guard`],克隆一份
22//! (`Arc` 递增引用计数,不含密钥拷贝)交给处理器。
23//!
24//! # 为什么需要一个 [`Guarded`] trait
25//!
26//! 真实应用的状态结构体不会只有一把加密器,通常还挂着连接池、配置、限流器。
27//! 如果直接实现「从状态里取守卫」,那就得为每个状态类型写一遍提取代码。所以这里
28//! 定义一个只有一件事的 trait:你的状态告诉本库「守卫在哪个字段」,剩下的提取
29//! 逻辑由适配层提供。
30//!
31//! ```no_run
32//! # #[cfg(feature = "axum")]
33//! # mod demo {
34//! use std::sync::Arc;
35//! use encryption::guard::Guard;
36//! use encryption::integrations::Guarded;
37//!
38//! #[derive(Clone)]
39//! struct AppState {
40//!     pool: Arc<()>,          // 你的连接池、配置……
41//!     encryption: Guard,      // 加密守卫
42//! }
43//!
44//! impl Guarded for AppState {
45//!     fn guard(&self) -> &Guard { &self.encryption }
46//! }
47//!
48//! // 处理器直接声明它要一把守卫,不必再 `State(state)` 再手动取字段
49//! async fn create_user(guard: Guard) -> String {
50//!     format!("{:?}", guard.encrypt("13800138000".as_bytes()))
51//! }
52//! # }
53//! ```
54
55use crate::guard::Guard;
56
57/// 把 [`Guard`] 从应用状态里取出来。
58///
59/// 实现它只需要指向一个字段。适配层据此提供各框架的提取器 / 守卫。
60///
61/// [`Guard`] 自身与 `Arc<Guard>` 已经实现了它,所以「状态就是一把守卫」这种
62/// 最小场景不需要写任何代码。
63pub trait Guarded: Send + Sync {
64    /// 取出守卫。永远成功 —— 取不到说明状态装配错了,那是编译期该发现的事。
65    fn guard(&self) -> &Guard;
66}
67
68impl Guarded for Guard {
69    fn guard(&self) -> &Guard {
70        self
71    }
72}
73
74impl Guarded for std::sync::Arc<Guard> {
75    fn guard(&self) -> &Guard {
76        self
77    }
78}
79
80/// 应用没有把守卫注册进框架的状态/数据槽。
81///
82/// 这是**装配错误**:代码编译得过,但启动时忘了 `manage(guard)` / `app_data(...)`。
83/// 各框架把它翻译成自己的拒绝类型,最终都是一条 500 —— 用户没做错任何事,
84/// 服务端配置漏了一步,所以不该是 4xx。
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub struct GuardNotConfigured;
87
88impl std::fmt::Display for GuardNotConfigured {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        f.write_str(
91            "应用状态里没有注册加密守卫(encryption Guard)—— \
92             请检查框架的 manage / app_data / with_state / data 装配步骤",
93        )
94    }
95}
96
97impl std::error::Error for GuardNotConfigured {}
98
99#[cfg(feature = "axum")]
100pub mod axum;
101
102#[cfg(feature = "actix-web")]
103pub mod actix;
104
105#[cfg(feature = "rocket")]
106pub mod rocket;
107
108#[cfg(feature = "poem")]
109pub mod poem;
110
111#[cfg(feature = "salvo")]
112pub mod salvo;
113
114#[cfg(feature = "warp")]
115pub mod warp;
116
117/// bee-rust(`bee_router`)的路由直接接收 axum handler,状态也走 axum 的
118/// `State`,所以适配层就是 axum 那一层,这里只把文档与再导出处放好。
119///
120/// 需要留意的是:`bee_router` 里还有一套 Beego 风格的
121/// `Filter`/`Controller`/`Context` 链路,但那套**没有**接到 `Router` 上 ——
122/// 框架自己的示例与 admin 项目走的都是 `Router` + axum handler。所以这里提供
123/// 的是 `Router` 路径的适配,`Filter` 路径要拿守卫得自己从 `Context` 里取。
124#[cfg(feature = "bee-rust")]
125pub mod bee_rust {
126    pub use super::Guarded;
127    pub use super::axum::GuardExtractor;
128
129    /// bee-rust 集成的用法要点(示例不参与编译,bee-rust 的路由构造需要真实状态)。
130    ///
131    /// ```text
132    /// use encryption::guard::Guard;
133    /// use encryption::integrations::Guarded;
134    /// use bee_router::Router;
135    ///
136    /// #[derive(Clone)]
137    /// struct AppState { encryption: Guard }
138    /// impl Guarded for AppState {
139    ///     fn guard(&self) -> &Guard { &self.encryption }
140    /// }
141    ///
142    /// async fn health(guard: Guard) -> String {
143    ///     format!("algorithms={}", guard.identifiers().len())
144    /// }
145    ///
146    /// let app = Router::new()
147    ///     .ns("/api/v1", |ns| ns.get("/health", health))
148    ///     .with_state(AppState { encryption: guard });
149    /// ```
150    pub const USAGE: &str = "见本模块文档";
151}
152
153#[cfg(feature = "ecat")]
154pub mod ecat;
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159    use std::sync::Arc;
160
161    fn guard() -> Guard {
162        Guard::from_master_key(&[7u8; 32], "aes-256-gcm").unwrap()
163    }
164
165    /// 最小场景:状态就是一把守卫,零样板。
166    #[test]
167    fn guard_is_itself_guarded() {
168        let g = guard();
169        assert_eq!(g.guard().identifiers().len(), 5);
170    }
171
172    #[test]
173    fn arc_guard_is_guarded() {
174        let g = Arc::new(guard());
175        assert_eq!(g.guard().identifiers().len(), 5);
176    }
177
178    /// 真实场景:守卫是状态结构体的一个字段。
179    #[test]
180    fn a_larger_state_can_point_at_its_guard() {
181        struct AppState {
182            #[allow(dead_code)]
183            pool: Arc<()>,
184            encryption: Guard,
185        }
186        impl Guarded for AppState {
187            fn guard(&self) -> &Guard {
188                &self.encryption
189            }
190        }
191
192        let state = AppState {
193            pool: Arc::new(()),
194            encryption: guard(),
195        };
196
197        let blob = state.guard().encrypt(b"x").unwrap();
198        assert_eq!(state.guard().decrypt(&blob).unwrap(), b"x");
199    }
200}