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}