Skip to main content

encryption/integrations/
salvo.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! salvo 1.0 集成。
4//!
5//! 用 `affix_state` 把守卫放进 `Depot`,处理器用 [`Extractible`] 取出来:
6//!
7//! ```no_run
8//! use encryption::guard::Guard;
9//! use salvo::prelude::*;
10//!
11//! #[handler]
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//! // 注册(`affix_state` 挂在 salvo 的 `affix-state` feature 后面,
19//! // 需要 `cargo add salvo --features affix-state` 才能引用):
20//! //   Router::new().hoop(affix_state::inject(guard)).get(store_phone)
21//! # let _ = guard;
22//! # }
23//! ```
24//!
25//! # 注意 salvo 1.0 的破坏性变更
26//!
27//! salvo 1.0(2026-09-24 发布)把状态中间件挪到了
28//! `salvo_extra::affix_state`,且改为返回 `AffixList` 的自由函数
29//! (`affix_state::inject(v)`)。网上大量写于 0.7x/0.96 的 `Depot::inject`、
30//! `Router::aim` 用法的资料对 1.x **不适用**。
31
32use salvo::extract::Metadata;
33use salvo::http::StatusCode;
34use salvo::prelude::{Depot, Request, Response, Text};
35use salvo::writing::Writer;
36use salvo::{Extractible, async_trait};
37
38use super::GuardNotConfigured;
39use crate::guard::Guard;
40
41/// 让「忘了 inject」这件事在 salvo 里表现为一条 500 响应。
42#[async_trait]
43impl Writer for GuardNotConfigured {
44    async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
45        res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
46        res.render(Text::Plain(self.to_string()));
47    }
48}
49
50// salvo 的 `Extractible` 签名是 `impl Trait`(RPITIT),这里把错误类型收窄为
51// 具体的 `GuardNotConfigured` 是有意为之:让「忘注册」能翻译成 500 响应。
52#[allow(refining_impl_trait)]
53impl<'ex> Extractible<'ex> for Guard {
54    fn metadata() -> &'static Metadata {
55        // salvo 的 `Metadata::new` 是 const fn,直接放进 static
56        static METADATA: Metadata = Metadata::new("Guard");
57        &METADATA
58    }
59
60    async fn extract(
61        _req: &'ex mut Request,
62        depot: &'ex mut Depot,
63    ) -> Result<Self, GuardNotConfigured> {
64        depot
65            .get_typed::<Guard>()
66            .cloned()
67            .map_err(|_| GuardNotConfigured)
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    fn guard() -> Guard {
76        Guard::from_master_key(&[7u8; 32], "aes-256-gcm").unwrap()
77    }
78
79    #[test]
80    fn extracts_from_depot() {
81        let mut depot = Depot::new();
82        depot.insert_typed(guard());
83
84        let mut req = Request::new();
85        let extracted = futures_lite_block_on(Guard::extract(&mut req, &mut depot)).unwrap();
86        assert_eq!(extracted.identifiers().len(), 5);
87    }
88
89    /// 忘了 inject 时是装配错误,翻译成 500。
90    #[test]
91    fn missing_depot_entry_is_an_error() {
92        let mut depot = Depot::new();
93        let mut req = Request::new();
94        let err = futures_lite_block_on(Guard::extract(&mut req, &mut depot)).unwrap_err();
95        assert_eq!(err, GuardNotConfigured);
96    }
97
98    #[test]
99    fn handler_and_state_share_one_encrypter() {
100        let state = guard();
101        let token = state.encrypt("共享".as_bytes()).unwrap();
102
103        let mut depot = Depot::new();
104        depot.insert_typed(state);
105
106        let mut req = Request::new();
107        let extracted = futures_lite_block_on(Guard::extract(&mut req, &mut depot)).unwrap();
108        assert_eq!(extracted.decrypt(&token).unwrap(), "共享".as_bytes());
109    }
110
111    /// salvo 的 `Extractible::extract` 是异步的,但测试里没必要引入完整运行时 ——
112    /// 这些 future 不含真正的 I/O,手动 poll 一次即可完成。
113    fn futures_lite_block_on<F: std::future::Future>(fut: F) -> F::Output {
114        use std::task::{Context, Poll, Waker};
115
116        let mut fut = std::pin::pin!(fut);
117        let waker = Waker::noop();
118        let mut cx = Context::from_waker(waker);
119
120        loop {
121            match fut.as_mut().poll(&mut cx) {
122                Poll::Ready(out) => return out,
123                Poll::Pending => std::thread::yield_now(),
124            }
125        }
126    }
127}