use std::{convert::Infallible, ops::ControlFlow};
use axum::{http::Method, response::IntoResponse};
use bon::{Builder, bon, builder};
use serde::Serialize;
use crate::{extension::InertiaExtension, inertia::RequestHeaders};
#[derive(Default, Builder)]
#[builder(state_mod(vis = "pub(crate)"))]
pub struct Prop<F> {
#[builder(start_fn)]
value: F,
#[builder(default, with = || true)]
lazy: bool,
#[builder(default, with = || true)]
defer: bool,
#[builder(default, with = || true)]
always: bool,
#[builder(default, with = || true)]
merge: bool,
}
#[bon]
impl<F> Prop<F> {
#[builder(finish_fn = build)]
pub(crate) fn eval(
self,
#[builder(start_fn)] name: &'static str,
req_headers: RequestHeaders<'_>,
method: &Method,
component: Option<&'static str>,
) -> EvalOutput<F> {
let mut defer = false;
let mut merge = false;
let is_partial_request = req_headers.partial(method, component);
let is_this_prop_requested = req_headers
.partial_except()
.map(|mut s| s.all(|p| p != name))
.unwrap_or(true)
&& req_headers
.partial_data()
.map(|mut s| s.any(|p| p == name))
.unwrap_or(true);
let prop = self.always
|| match (is_partial_request, is_this_prop_requested) {
(true, true) => true,
(true, false) => {
if self.defer {
defer = true;
}
false
}
(false, false) => false,
(false, true) => !self.lazy,
};
if prop && self.merge {
merge = true;
}
EvalOutput {
prop_value: self.value,
prop,
lazy: self.lazy,
defer,
merge,
}
}
}
impl<F, P> From<F> for PropBuilder<F>
where
F: AsyncFnOnce() -> P,
P: PropControlFlow,
{
fn from(f: F) -> Self {
Prop::builder(f)
}
}
pub fn lazy<F>(f: F) -> PropBuilder<F, prop_builder::SetLazy<prop_builder::Empty>> {
Prop::builder(f).lazy()
}
pub fn merge<F>(f: F) -> PropBuilder<F, prop_builder::SetMerge<prop_builder::Empty>> {
Prop::builder(f).merge()
}
pub fn defer<F>(f: F) -> PropBuilder<F, prop_builder::SetDefer<prop_builder::Empty>> {
Prop::builder(f).defer()
}
pub fn always<F>(f: F) -> PropBuilder<F, prop_builder::SetAlways<prop_builder::Empty>> {
Prop::builder(f).always()
}
pub fn infallible<T>(t: T) -> Result<T, Infallible> {
Ok(t)
}
pub fn null() -> Option<()> {
None
}
#[derive(Debug)]
pub struct EvalOutput<F> {
lazy: bool,
prop_value: F,
prop: bool,
defer: bool,
merge: bool,
}
impl<F> EvalOutput<F> {
pub async fn apply<P, Fut>(
self,
prop_name: &'static str,
extension: &InertiaExtension,
) -> ControlFlow<P::Err>
where
F: FnOnce() -> Fut + Send,
Fut: Future<Output = P> + Send,
P: PropControlFlow,
{
if self.prop {
let prop_control_flow = (self.prop_value)().await.into_control_flow();
extension.add_prop(
prop_name,
serde_json::to_value(prop_control_flow?).expect("failed to serialize prop value"),
);
} else if self.lazy {
extension.add_prop(prop_name, serde_json::Value::Null);
}
if self.merge {
extension.merge_prop(prop_name);
}
if self.defer {
extension.defer_prop(prop_name);
}
ControlFlow::Continue(())
}
}
pub trait PropControlFlow {
type Value: Serialize;
type Err: IntoResponse;
fn into_control_flow(self) -> ControlFlow<Self::Err, Self::Value>;
}
impl<T> PropControlFlow for Option<T>
where
T: Serialize,
{
type Value = Option<T>;
type Err = Infallible;
fn into_control_flow(self) -> ControlFlow<Self::Err, Self::Value> {
ControlFlow::Continue(self)
}
}
impl<T, E> PropControlFlow for Result<T, E>
where
T: Serialize,
E: IntoResponse,
{
type Value = T;
type Err = E;
fn into_control_flow(self) -> ControlFlow<<Self as PropControlFlow>::Err, Self::Value> {
match self {
Ok(t) => ControlFlow::Continue(t),
Err(e) => ControlFlow::Break(e),
}
}
}