use crate::{ActionContext, Frame, FrameFuture, Handler, Router, SystemContext};
use super::{Open, Stateful, Stateless};
#[derive(Clone, Default)]
pub struct Routed<Status, State> {
router: Router<State>,
status: Status,
}
impl<Status, State> Routed<Status, State>
where
State: Clone + Send + Sync + 'static,
{
pub fn action_context(&self) -> ActionContext<State> {
SystemContext::from(self.router.clone()).into()
}
pub fn handle_frame_with_state(&self, frame: Frame, state: State) -> FrameFuture {
self.router.handle_frame_with_state(frame, state)
}
fn merge(&mut self, route: impl AsRef<str>, other: SystemContext<State>)
where
State: Clone + Send + Sync + 'static,
{
self.router
.scope(route.as_ref(), other.router)
.expect("Unable to merge given router for {route}");
}
pub fn on<ActionHandler, Args>(mut self, route: impl AsRef<str>, action: ActionHandler) -> Self
where
ActionHandler: Handler<Args, State> + Clone + Send + Sync + 'static,
Args: Clone + Send + Sync + 'static,
{
if let ActionContext::System(other) = action.context() {
self.merge(route, other);
} else {
self.router
.insert(route.as_ref(), action)
.expect("failed to insert route: {route}");
}
self
}
pub fn routes(&self) -> Vec<String> {
self.router.routes()
}
}
impl<State> Routed<Open, State>
where
State: Clone + Send + Sync + 'static,
{
pub fn init() -> Self {
Self {
router: Router::new(),
status: Open,
}
}
pub fn with_state(&self, state: State) -> Routed<Stateful<State>, State> {
Routed {
router: self.router.clone(),
status: Stateful(state),
}
}
}
impl Routed<Open, ()> {
pub fn without_state(self) -> Routed<Stateless, ()> {
Routed {
router: self.router,
status: Stateless,
}
}
pub fn handle_frame(self, frame: Frame) -> FrameFuture {
self.handle_frame_with_state(frame, ())
}
}
impl Routed<Stateless, ()> {
pub fn handle_frame(self, frame: Frame) -> FrameFuture {
self.handle_frame_with_state(frame, ())
}
}
impl<State> Routed<Stateful<State>, State>
where
State: Clone + Send + Sync + 'static,
{
pub fn state(&self) -> State {
self.status.0.clone()
}
pub fn handle_frame(self, frame: Frame) -> FrameFuture {
let state = self.state();
self.handle_frame_with_state(frame, state)
}
}
impl<Status, State> std::fmt::Debug for Routed<Status, State> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Routed")
.field("router", &self.router)
.finish()
}
}
#[tokio::test]
async fn calling_with_a_frame_statelessly() -> Result<(), tower::BoxError> {
assert_eq!(
Routed::init()
.without_state()
.handle_frame(Frame::default())
.await?,
Frame::default()
);
assert_eq!(
Routed::init().handle_frame(Frame::default()).await?,
Frame::default()
);
Ok(())
}
#[tokio::test]
async fn calling_with_a_frame_statefully() -> Result<(), tower::BoxError> {
#[derive(Clone)]
struct ArbitraryState;
assert_eq!(
Routed::init()
.with_state(ArbitraryState)
.handle_frame(Frame::default())
.await?,
Frame::default()
);
Ok(())
}
#[tokio::test]
async fn targeting_a_root_path() -> Result<(), tower::BoxError> {
assert_eq!(
Routed::init()
.on("/", || async { "hi".to_string() })
.handle_frame(Frame::message("/", (), ()))
.await?,
Frame::from("hi".to_string())
);
Ok(())
}
#[tokio::test]
async fn targeting_a_nested_path() -> Result<(), tower::BoxError> {
use crate::System;
let health = System::routed().on("/", || async { "health".to_string() });
let greetings = System::routed().on("/", || async { "hello".to_string() });
let albums = System::routed().on("/:id", || async { "albums".to_string() });
let search = System::routed().on("/:fragments*", || async { "search".to_string() });
let api = System::routed()
.on("/health", health)
.on("/greetings", greetings)
.on("/albums", albums)
.on("/search", search)
.on("/", || async { "api root".to_string() });
let router = Routed::init().on("/api", api);
assert_eq!(
router
.clone()
.handle_frame(Frame::message("/api", (), ()))
.await?,
Frame::from("api root".to_string())
);
assert_eq!(
router
.clone()
.handle_frame(Frame::message("/api/greetings", (), ()))
.await?,
Frame::from("hello".to_string())
);
assert_eq!(
router
.clone()
.handle_frame(Frame::message("/api/albums/1", (), ()))
.await?,
Frame::from("albums".to_string())
);
assert_eq!(
router
.clone()
.handle_frame(Frame::message(
"/api/search/a/bunch/of/search/paths",
(),
()
))
.await?,
Frame::from("search".to_string())
);
Ok(())
}