use std::{borrow::Cow, pin::Pin, sync::Arc};
use topcoat_core::{context::Cx, error::Result};
use topcoat_view::View;
use crate::{
Body, IntoPath, Methods, OwnedMethods, Path, Route, RouteFuture, RouteId,
response::IntoResponse,
};
pub type ViewFuture<'cx> = Pin<Box<dyn Future<Output = Result<View>> + Send + 'cx>>;
pub trait Page: Send + Sync + 'static {
fn id(&self) -> RouteId;
fn methods(&self) -> Methods<'_>;
fn path(&self) -> &Path;
fn render<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> ViewFuture<'cx>;
}
impl<P: Page + ?Sized> Page for &'static P {
fn id(&self) -> RouteId {
(**self).id()
}
fn methods(&self) -> Methods<'_> {
(**self).methods()
}
fn path(&self) -> &Path {
(**self).path()
}
fn render<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> ViewFuture<'cx> {
(**self).render(cx, body)
}
}
#[cfg(feature = "discover")]
inventory::collect!(&'static dyn Page);
pub type PageRenderFn = for<'cx> fn(cx: &'cx Cx, body: Body) -> ViewFuture<'cx>;
#[derive(Debug, Clone)]
pub struct PageFn {
id: RouteId,
methods: OwnedMethods,
path: Cow<'static, Path>,
render: PageRenderFn,
}
impl PageFn {
#[track_caller]
pub fn new(
methods: impl Into<OwnedMethods>,
path: impl IntoPath,
render: PageRenderFn,
) -> Self {
Self {
id: RouteId::new(),
methods: methods.into(),
path: path.into_path(),
render,
}
}
}
impl Page for PageFn {
fn id(&self) -> RouteId {
self.id
}
fn methods(&self) -> Methods<'_> {
self.methods.as_methods()
}
fn path(&self) -> &Path {
&self.path
}
fn render<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> ViewFuture<'cx> {
(self.render)(cx, body)
}
}
pub trait Layout: Send + Sync + 'static {
fn path(&self) -> &Path;
fn render<'cx>(&'cx self, cx: &'cx Cx, slot: Result<View>) -> ViewFuture<'cx>;
}
impl<L: Layout + ?Sized> Layout for &'static L {
fn path(&self) -> &Path {
(**self).path()
}
fn render<'cx>(&'cx self, cx: &'cx Cx, slot: Result<View>) -> ViewFuture<'cx> {
(**self).render(cx, slot)
}
}
#[cfg(feature = "discover")]
inventory::collect!(&'static dyn Layout);
pub type LayoutRenderFn = for<'cx> fn(cx: &'cx Cx, slot: Result<View>) -> ViewFuture<'cx>;
#[derive(Debug, Clone)]
pub struct LayoutFn {
path: Cow<'static, Path>,
render: LayoutRenderFn,
}
impl LayoutFn {
#[track_caller]
pub fn new(path: impl IntoPath, render: LayoutRenderFn) -> Self {
Self {
path: path.into_path(),
render,
}
}
}
impl Layout for LayoutFn {
fn path(&self) -> &Path {
&self.path
}
fn render<'cx>(&'cx self, cx: &'cx Cx, slot: Result<View>) -> ViewFuture<'cx> {
(self.render)(cx, slot)
}
}
pub struct PageWithLayouts {
page: Box<dyn Page>,
layouts: Vec<Arc<dyn Layout>>,
}
impl PageWithLayouts {
#[must_use]
pub fn new(page: Box<dyn Page>, layouts: Vec<Arc<dyn Layout>>) -> Self {
Self { page, layouts }
}
}
impl Route for PageWithLayouts {
fn id(&self) -> RouteId {
self.page.id()
}
fn methods(&self) -> Methods<'_> {
self.page.methods()
}
fn path(&self) -> &Path {
self.page.path()
}
fn handle<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> RouteFuture<'cx> {
Box::pin(async move {
let mut slot = self.page.render(cx, body).await;
for layout in self.layouts.iter().rev() {
slot = layout.render(cx, slot).await;
}
slot.into_response(cx)
})
}
}