Skip to main content

encryption/integrations/
rocket.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! rocket 0.5 集成。
4//!
5//! 用 `manage` 注册,处理器把它写在参数里:
6//!
7//! ```no_run
8//! # mod demo {
9//! use rocket::{get, routes, Build, Rocket};
10//! use encryption::guard::Guard;
11//!
12//! #[get("/")]
13//! 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 rocket: Rocket<Build> = rocket::build().manage(guard).mount("/", routes![store_phone]);
20//! # let _ = rocket;
21//! # }
22//! # }
23//! ```
24//!
25//! rocket 的请求守卫走 `Outcome`,且 trait 是 `#[rocket::async_trait]` 的
26//! (rocket 自己 re-export 了那个宏,所以不必额外依赖 `async-trait`)。
27//!
28//! # 托管状态里放的是 [`Guard`] 本身
29//!
30//! [`Guard`] 内部已经是 `Arc`,克隆一次只是引用计数递增,所以这里是
31//! `manage(guard)` 而不是 `manage(Arc::new(guard))` —— rocket 的守卫拿到的是
32//! 借用,本来也不需要外面再套一层共享。
33
34use rocket::State;
35use rocket::http::Status;
36// 注意是 `request::Outcome`(两参数别名 = `outcome::Outcome<S, (Status, E), Status>`),
37// 不是 `outcome::Outcome`(三参数)。守卫的返回值用的就是这个别名。
38use rocket::request::{FromRequest, Outcome, Request};
39
40use super::GuardNotConfigured;
41use crate::guard::Guard;
42
43#[rocket::async_trait]
44impl<'r> FromRequest<'r> for Guard {
45    type Error = GuardNotConfigured;
46
47    async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
48        match request.guard::<&State<Guard>>().await {
49            Outcome::Success(state) => Outcome::Success(state.inner().clone()),
50            // 忘了 manage(...):装配错误 → 500,不 Forward(没有别的路由能处理它)。
51            // 别名的 Error 分支装的是 `(Status, E)` 而不是裸的 E。
52            Outcome::Error(_) | Outcome::Forward(_) => {
53                Outcome::Error((Status::InternalServerError, GuardNotConfigured))
54            }
55        }
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use rocket::local::blocking::Client;
63    use rocket::{get, routes};
64
65    fn guard() -> Guard {
66        Guard::from_master_key(&[7u8; 32], "aes-256-gcm").unwrap()
67    }
68
69    #[get("/encrypt")]
70    fn encrypt_route(guard: Guard) -> String {
71        let blob = guard.encrypt("13800138000".as_bytes()).unwrap();
72        crate::internal::hex_encode(&blob)
73    }
74
75    #[get("/decrypt/<payload>")]
76    fn decrypt_route(guard: Guard, payload: &str) -> String {
77        let blob = crate::internal::hex_decode(payload).unwrap_or_default();
78        guard
79            .decrypt(&blob)
80            .map(|plain| String::from_utf8_lossy(&plain).into_owned())
81            .unwrap_or_else(|_| "解不开".into())
82    }
83
84    #[test]
85    fn guard_is_injectable_end_to_end() {
86        let rocket = rocket::build()
87            .manage(guard())
88            .mount("/", routes![encrypt_route, decrypt_route]);
89        let client = Client::tracked(rocket).expect("rocket 应当能启动");
90
91        let res = client.get("/encrypt").dispatch();
92        assert_eq!(res.status(), Status::Ok);
93        let cipher = res.into_string().unwrap();
94        assert!(cipher.len() > 20, "应当拿到一段密文,实际 {cipher:?}");
95
96        // 拿刚加密出来的密文再解回去 —— 证明两个请求用的是同一把密钥
97        let res = client.get(format!("/decrypt/{cipher}")).dispatch();
98        assert_eq!(res.status(), Status::Ok);
99        assert_eq!(res.into_string().unwrap(), "13800138000");
100    }
101
102    /// 忘了 `manage(...)` 时是 500,不是 panic。
103    #[test]
104    fn missing_managed_state_is_a_500() {
105        let rocket = rocket::build().mount("/", routes![encrypt_route]);
106        let client = Client::tracked(rocket).expect("rocket 应当能启动");
107
108        let res = client.get("/encrypt").dispatch();
109        assert_eq!(res.status(), Status::InternalServerError);
110    }
111}