use std::future::Future; use http::Method; use http::Request; use http::Response; use http::Uri; use tackt::route; use tackt::Param; use tower_service::Service; fn create_router() -> impl Service, Error = Error, Response = Response> { tackt::routes![home, login, user, content] .mount("/protected", tackt::routes![protected].with(protection)) } #[test] fn test() { let mut router = create_router(); let res = oneshot(router.call(request(Method::GET, "/"))); assert_eq!(res.map(Response::into_body), Ok("home".to_string())); let res = oneshot(router.call(request(Method::GET, "/login"))); assert_eq!(res.map(Response::into_body), Ok("login".to_string())); let res = oneshot(router.call(request(Method::POST, "/login"))); assert_eq!(res.map(Response::into_body), Ok("login".to_string())); let res = oneshot(router.call(request(Method::GET, "/user/1"))); assert_eq!(res.map(Response::into_body), Ok("user 1".to_string())); let res = oneshot(router.call(request(Method::GET, "/content/1/name/path/to/file"))); assert_eq!( res.map(Response::into_body), Ok("content 1 name path/to/file".to_string()) ); let res = oneshot(router.call(request(Method::GET, "/protected"))); assert_eq!(res.map(Response::into_body), Err(Error::Unauthorized)); let mut req = request(Method::GET, "/protected/"); req.headers_mut() .insert("user", "someone".try_into().unwrap()); let res = oneshot(router.call(req)); assert_eq!(res.map(Response::into_body), Ok("someone".to_string())); } fn respond>(body: S) -> Response { Response::new(body.into()) } fn request(method: Method, path: &'static str) -> Request<()> { let mut req = Request::new(()); *req.method_mut() = method; *req.uri_mut() = Uri::from_static(path); req } #[route] async fn home(_: Request<()>) -> Result, Error> { Ok(respond("home")) } #[route(GET, POST: "login")] async fn login(_: Request<()>) -> Result, Error> { Ok(respond("login")) } #[route(GET: "user" / id)] async fn user(_: Request<()>, id: i32) -> Result, Error> { Ok(respond(format!("user {}", id))) } #[derive(Param)] #[route(GET: "content" / id / name / path*)] struct Content { id: i32, name: String, path: String, } async fn content(_: Request<()>, param: Content) -> Result, Error> { Ok(respond(format!( "content {} {} {}", param.id, param.name, param.path ))) } #[route] async fn protected(req: Request<()>) -> Result, Error> { let user = req.headers().get("user").unwrap().to_str().unwrap(); Ok(respond(user)) } async fn protection(req: Request<()>) -> Result, Error> { req.headers().get("user").ok_or(Error::Unauthorized)?; Ok(req) } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] enum Error { Routing(tackt::Error), Unauthorized, } impl std::error::Error for Error {} impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Error::Routing(err) => write!(f, "{err}"), Error::Unauthorized => write!(f, "unauthorized"), } } } impl From for Error { fn from(err: tackt::Error) -> Self { Error::Routing(err) } } struct Waker(std::thread::Thread); impl Waker { fn new() -> std::sync::Arc { std::sync::Arc::new(Waker(std::thread::current())) } } impl std::task::Wake for Waker { fn wake(self: std::sync::Arc) { self.0.unpark(); } } fn oneshot(fut: impl Future) -> T { use std::task::Context; use std::task::Poll; let mut fut = Box::pin(fut); let waker = Waker::new().into(); let mut cx = Context::from_waker(&waker); match fut.as_mut().poll(&mut cx) { Poll::Ready(out) => out, Poll::Pending => panic!("still pending!!!"), } }