use rustolio_utils::http;
use crate::ServerFnError;
pub trait Extract<B, C>: Sized {
fn extract(
req: &http::Request<B>,
ctx: C,
) -> impl std::future::Future<Output = Result<Self, ServerFnError>>;
}
#[cfg(test)]
mod tests {
use rustolio_utils::http::StatusCode;
use rustolio_utils::prelude::*;
use super::*;
#[derive(Debug)]
struct MyCustomError;
struct User {
username: String,
}
#[derive(Debug)]
struct Context {}
impl<B, C> Extract<B, C> for User {
async fn extract(req: &http::Request<B>, _ctx: C) -> Result<Self, ServerFnError> {
let username = req
.header("x-user-name")
.ok_or(ServerFnError::http_error(
StatusCode::FORBIDDEN,
"Not logged in",
))?
.to_str()
.map_err(|_| {
ServerFnError::http_error(StatusCode::BAD_REQUEST, "Could not read HeaderValue")
})?
.to_string();
Ok(Self { username })
}
}
async fn rpc_fn<B>(
req: http::Request<B>,
ctx: &'static Context,
) -> Result<String, ServerFnError<MyCustomError>> {
let user: User = match User::extract(&req, ctx).await {
Ok(v) => v,
Err(e) => return Err(e.into()),
};
Ok(user.username)
}
#[tokio::test]
async fn test_rpc_extractor() {
let ctx = Context {};
service!(ctx as Context);
let req = http::Request::get("/").build().unwrap();
let e = rpc_fn(req, ctx)
.await
.expect_err("Extractor should fail as there is no header set");
assert!(matches!(
e,
ServerFnError::HttpError(StatusCode::FORBIDDEN, _)
));
let req = http::Request::get("/")
.header("x-user-name", "SomeUser")
.build()
.unwrap();
let username = rpc_fn(req, ctx)
.await
.expect("User should be extracter as there is a username in the header");
assert_eq!(username, "SomeUser");
}
}