rustolio_rpc/
extractor.rs1use rustolio_utils::http;
12
13use crate::ServerFnError;
14
15pub trait Extract<B, C>: Sized {
16 fn extract(
17 req: &http::Request<B>,
18 ctx: C,
19 ) -> impl std::future::Future<Output = Result<Self, ServerFnError>>;
20}
21
22#[cfg(test)]
23mod tests {
24 use rustolio_utils::http::StatusCode;
25 use rustolio_utils::prelude::*;
26
27 use super::*;
28
29 #[derive(Debug)]
30 struct MyCustomError;
31
32 struct User {
33 username: String,
34 }
35
36 #[derive(Debug)]
37 struct Context {}
38
39 impl<B, C> Extract<B, C> for User {
40 async fn extract(req: &http::Request<B>, _ctx: C) -> Result<Self, ServerFnError> {
41 let username = req
42 .header("x-user-name")
43 .ok_or(ServerFnError::http_error(
44 StatusCode::FORBIDDEN,
45 "Not logged in",
46 ))?
47 .to_str()
48 .map_err(|_| {
49 ServerFnError::http_error(StatusCode::BAD_REQUEST, "Could not read HeaderValue")
50 })?
51 .to_string();
52 Ok(Self { username })
53 }
54 }
55
56 async fn rpc_fn<B>(
57 req: http::Request<B>,
58 ctx: &'static Context,
59 ) -> Result<String, ServerFnError<MyCustomError>> {
60 let user: User = match User::extract(&req, ctx).await {
62 Ok(v) => v,
63 Err(e) => return Err(e.into()),
64 };
65
66 Ok(user.username)
68 }
69
70 #[tokio::test]
71 async fn test_rpc_extractor() {
72 let ctx = Context {};
73 service!(ctx as Context);
74
75 let req = http::Request::get("/").build().unwrap();
76 let e = rpc_fn(req, ctx)
77 .await
78 .expect_err("Extractor should fail as there is no header set");
79 assert!(matches!(
80 e,
81 ServerFnError::HttpError(StatusCode::FORBIDDEN, _)
82 ));
83
84 let req = http::Request::get("/")
85 .header("x-user-name", "SomeUser")
86 .build()
87 .unwrap();
88 let username = rpc_fn(req, ctx)
89 .await
90 .expect("User should be extracter as there is a username in the header");
91 assert_eq!(username, "SomeUser");
92 }
93}