use std::borrow::Cow;
use std::pin::Pin;
use topcoat_core::{context::Cx, error::Result};
use topcoat_view::View;
use crate::{Body, IntoResponse, Methods, OwnedMethods, Path, Route, RouteFuture};
pub type PageRenderFn = for<'cx> fn(
cx: &'cx Cx,
body: Body,
) -> Pin<Box<dyn Future<Output = Result<View>> + Send + 'cx>>;
#[derive(Debug, Clone)]
pub struct PageFn {
methods: OwnedMethods,
path: Cow<'static, Path>,
render: PageRenderFn,
}
impl PageFn {
pub fn new(
methods: impl Into<OwnedMethods>,
path: Cow<'static, Path>,
render: PageRenderFn,
) -> Self {
Self::const_new(methods.into(), path, render)
}
pub const fn const_new(
methods: OwnedMethods,
path: Cow<'static, Path>,
render: PageRenderFn,
) -> Self {
Self {
methods,
path,
render,
}
}
#[must_use]
pub fn methods(&self) -> Methods<'_> {
self.methods.as_methods()
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
pub fn render<'cx>(
&self,
cx: &'cx Cx,
body: Body,
) -> Pin<Box<dyn Future<Output = Result<View>> + Send + 'cx>> {
(self.render)(cx, body)
}
}
#[cfg(feature = "discover")]
inventory::collect!(PageFn);
pub type LayoutRenderFn = for<'cx> fn(
cx: &'cx Cx,
slot: Result<View>,
) -> Pin<Box<dyn Future<Output = Result<View>> + Send + 'cx>>;
#[derive(Debug, Clone)]
pub struct LayoutFn {
path: Cow<'static, Path>,
render: LayoutRenderFn,
}
impl LayoutFn {
pub const fn new(path: Cow<'static, Path>, render: LayoutRenderFn) -> Self {
Self { path, render }
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
pub fn render<'cx>(
&self,
cx: &'cx Cx,
slot: Result<View>,
) -> Pin<Box<dyn Future<Output = Result<View>> + Send + 'cx>> {
(self.render)(cx, slot)
}
}
#[cfg(feature = "discover")]
inventory::collect!(LayoutFn);
pub struct PageWithLayouts {
page: PageFn,
layouts: Vec<LayoutFn>,
}
impl PageWithLayouts {
#[must_use]
pub fn new(page: PageFn, layouts: Vec<LayoutFn>) -> Self {
Self { page, layouts }
}
}
impl Route for PageWithLayouts {
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)
})
}
}