progress on error boundary that works with nested reactivity

This commit is contained in:
Greg Johnston 2024-02-19 17:53:44 -05:00
parent f584154156
commit a7162d7907
24 changed files with 746 additions and 279 deletions

View file

@ -1,48 +1,59 @@
use leptos::*;
use leptos::{component, create_signal, prelude::*, view, IntoView};
#[component]
pub fn App() -> impl IntoView {
let (value, set_value) = create_signal(Ok(0));
// when input changes, try to parse a number from the input
let on_input =
move |ev| set_value.set(event_target_value(&ev).parse::<i32>());
let (value, set_value) = create_signal(Ok(0)); //"foo".parse::<i32>());
let guard = value.read();
view! {
<h1>"Error Handling"</h1>
<label>
"Type a number (or something that's not a number!)"
<input type="number" on:input=on_input/>
// If an `Err(_) had been rendered inside the <ErrorBoundary/>,
// the fallback will be displayed. Otherwise, the children of the
// <ErrorBoundary/> will be displayed.
<ErrorBoundary
// the fallback receives a signal containing current errors
fallback=|errors| view! {
<div class="error">
<p>"Not a number! Errors: "</p>
// we can render a list of errors
// as strings, if we'd like
<ul>
{move || errors.get()
.into_iter()
.map(|(_, e)| view! { <li>{e.to_string()}</li>})
.collect_view()
}
</ul>
</div>
}
>
<p>
"You entered "
// because `value` is `Result<i32, _>`,
// it will render the `i32` if it is `Ok`,
// and render nothing and trigger the error boundary
// if it is `Err`. It's a signal, so this will dynamically
// update when `value` changes
<strong>{value}</strong>
</p>
</ErrorBoundary>
</label>
}
<h1>"Error Handling"</h1>
<label>
"Type a number (or something that's not a number!)"
<input
type="text"
value=move || value.get().map(|n| n.to_string()).unwrap_or_default()// TODO guard support here
// when input changes, try to parse a number from the input
on:input:target=move |ev| set_value.set(ev.target().value().parse::<i32>())
/>
// If an `Err(_) had been rendered inside the <ErrorBoundary/>,
// the fallback will be displayed. Otherwise, the children of the
// <ErrorBoundary/> will be displayed.
/* <ErrorBoundary
// the fallback receives a signal containing current errors
fallback=|errors| view! {
<div class="error">
<p>"Not a number! Errors: "</p>
// we can render a list of errors
// as strings, if we'd like
<ul>
{move || errors.get()
.into_iter()
.map(|(_, e)| view! { <li>{e.to_string()}</li>})
.collect_view()
}
</ul>
</div>
}
>*/
{move || view! {
<p>
"You entered "
// because `value` is `Result<i32, _>`,
// it will render the `i32` if it is `Ok`,
// and render nothing and trigger the error boundary
// if it is `Err`. It's a signal, so this will dynamically
// update when `value` changes
<strong>{move || value.get()}</strong>
</p>}
.catch(|e| view! {
<p class="error">{e.to_string()}</p>
})
}
//</ErrorBoundary>
</label>
}
}
#[component]
pub fn ErrorBoundary() -> impl IntoView {}

View file

@ -3,51 +3,6 @@ use leptos_dom::{Errors, HydrationCtx, IntoView};
use leptos_macro::{component, view};
use leptos_reactive::{provide_context, run_as_child, signal_prelude::*};
/// When you render a `Result<_, _>` in your view, in the `Err` case it will
/// render nothing, and search up through the view tree for an `<ErrorBoundary/>`.
/// This component lets you define a fallback that should be rendered in that
/// error case, allowing you to handle errors within a section of the interface.
///
/// ```
/// # use leptos_reactive::*;
/// # use leptos_macro::*;
/// # use leptos_dom::*; use leptos::*;
/// # let runtime = create_runtime();
/// # if false {
/// let (value, set_value) = create_signal(Ok(0));
/// let on_input =
/// move |ev| set_value.set(event_target_value(&ev).parse::<i32>());
///
/// view! {
/// <input type="text" on:input=on_input/>
/// <ErrorBoundary
/// fallback=move |_| view! { <p class="error">"Enter a valid number."</p>}
/// >
/// <p>"Value is: " {move || value.get()}</p>
/// </ErrorBoundary>
/// }
/// # ;
/// # }
/// # runtime.dispose();
/// ```
///
/// ## Interaction with `<Suspense/>`
/// If you use this with a `<Suspense/>` or `<Transition/>` component, note that the
/// `<ErrorBoundary/>` should go inside the `<Suspense/>`, not the other way around,
/// if theres a chance that the `<ErrorBoundary/>` will begin in the error state.
/// This is a limitation of the current design of the two components and the way they
/// hydrate. Placing the `<ErrorBoundary/>` outside the `<Suspense/>` means that
/// it is rendered on the server without any knowledge of the suspended view, so it
/// will always be rendered on the server as if there were no errors, but might need
/// to be hydrated with errors, depending on the actual result.
///
/// ```rust,ignore
/// view! {
/// <Suspense fallback=move || view! { <p>"Loading..."</p> }>
/// <ErrorBoundary fallback=|errors| view! { <ErrorTemplate errors=errors/>}>
/// {move || {
/// /* etc. */
/// ```
///
/// ## Beginner's Tip: ErrorBoundary Requires Your Error To Implement std::error::Error.
/// `ErrorBoundary` requires your `Result<T,E>` to implement [IntoView](https://docs.rs/leptos/latest/leptos/trait.IntoView.html).
@ -72,13 +27,13 @@ use leptos_reactive::{provide_context, run_as_child, signal_prelude::*};
/// [thiserror](https://docs.rs/thiserror/latest/thiserror/)
#[component]
pub fn ErrorBoundary<F, IV>(
/// The components inside the tag which will get rendered
/// The elements that will be rendered, which may include one or more `Result<_>` types.
children: Children,
/// A fallback that will be shown if an error occurs.
fallback: F,
) -> impl IntoView
where
F: Fn(RwSignal<Errors>) -> IV + 'static,
F: Fn(Error) -> IV + 'static,
IV: IntoView,
{
run_as_child(move || {

View file

@ -5,13 +5,18 @@ use tachys::{
view::{Mountable, Position, PositionState, Render, RenderHtml},
};
pub struct View<T>(T);
pub struct View<T>(T)
where
T: Sized;
pub trait IntoView: Sized + Render<Dom> + RenderHtml<Dom> {
fn into_view(self) -> View<Self>;
}
impl<T: Render<Dom> + RenderHtml<Dom>> IntoView for T {
impl<T: Render<Dom> + RenderHtml<Dom>> IntoView for T
where
T: Sized,
{
fn into_view(self) -> View<Self> {
View(self)
}
@ -19,6 +24,8 @@ impl<T: Render<Dom> + RenderHtml<Dom>> IntoView for T {
impl<T: Render<Dom>> Render<Dom> for View<T> {
type State = T::State;
type FallibleState = T::FallibleState;
type Error = T::Error;
fn build(self) -> Self::State {
self.0.build()
@ -27,6 +34,17 @@ impl<T: Render<Dom>> Render<Dom> for View<T> {
fn rebuild(self, state: &mut Self::State) {
self.0.rebuild(state)
}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
self.0.try_build()
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
self.0.try_rebuild(state)
}
}
impl<T: RenderHtml<Dom>> RenderHtml<Dom> for View<T> {

View file

@ -3,7 +3,7 @@ mod sets;
mod source;
mod subscriber;
pub(crate) use node::*;
pub use node::*;
pub(crate) use sets::*;
pub use source::*;
pub use subscriber::*;

View file

@ -6,7 +6,7 @@ thread_local! {
static OBSERVER: RefCell<Option<AnySubscriber>> = const { RefCell::new(None) };
}
pub(crate) struct Observer;
pub struct Observer;
impl Observer {
pub fn get() -> Option<AnySubscriber> {

View file

@ -4,7 +4,7 @@ use crate::{
ssr::StreamBuilder,
view::{
either::{Either, EitherState},
Mountable, Position, PositionState, Render, RenderHtml,
Mountable, NeverError, Position, PositionState, Render, RenderHtml,
},
};
use any_spawner::Executor;
@ -63,7 +63,14 @@ where
Fut::Output: Render<Rndr>,
Rndr: Renderer + 'static,
{
type State = Arc<RwLock<EitherState<Fal, Fut::Output, Rndr>>>;
type State = Arc<
RwLock<
EitherState<Fal::State, <Fut::Output as Render<Rndr>>::State, Rndr>,
>,
>;
// TODO fallible state/error
type FallibleState = Self::State;
type Error = NeverError;
fn build(self) -> Self::State {
// poll the future once immediately
@ -90,7 +97,8 @@ where
let state = Arc::clone(&state);
async move {
let value = fut.as_mut().await;
Either::Right(value).rebuild(&mut *state.write());
Either::<Fal, Fut::Output>::Right(value)
.rebuild(&mut *state.write());
}
});
}
@ -101,7 +109,8 @@ where
fn rebuild(self, state: &mut Self::State) {
if !TRANSITION {
// fall back to fallback state
Either::Left(self.fallback).rebuild(&mut *state.write());
Either::<Fal, Fut::Output>::Left(self.fallback)
.rebuild(&mut *state.write());
}
// spawn the future, and rebuild the state when it resolves
@ -109,10 +118,22 @@ where
let state = Arc::clone(state);
async move {
let value = self.fut.await;
Either::Right(value).rebuild(&mut *state.write());
Either::<Fal, Fut::Output>::Right(value)
.rebuild(&mut *state.write());
}
});
}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
todo!()
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
todo!()
}
}
impl<const TRANSITION: bool, Fal, Fut, Rndr> RenderHtml<Rndr>
@ -211,7 +232,8 @@ where
let state = Arc::clone(&state);
async move {
let value = fut.as_mut().await;
Either::Right(value).rebuild(&mut *state.write());
Either::<Fal, Fut::Output>::Right(value)
.rebuild(&mut *state.write());
}
});
}
@ -223,10 +245,8 @@ where
impl<Rndr, Fal, Output> Mountable<Rndr>
for Arc<RwLock<EitherState<Fal, Output, Rndr>>>
where
Fal: Render<Rndr>,
Fal::State: Mountable<Rndr>,
Output: Render<Rndr>,
Output::State: Mountable<Rndr>,
Fal: Mountable<Rndr>,
Output: Mountable<Rndr>,
Rndr: Renderer,
{
fn unmount(&mut self) {

View file

@ -33,3 +33,34 @@ where
Error(Box::new(value))
}
}
/// A generic wrapper for any error.
#[derive(Debug)]
#[repr(transparent)]
pub struct AnyError(Box<dyn error::Error>);
impl AnyError {
pub fn new(err: impl error::Error + 'static) -> Self {
Self(Box::new(err))
}
}
impl ops::Deref for AnyError {
type Target = Box<dyn error::Error>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Display for AnyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl error::Error for AnyError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
self.0.source()
}
}

View file

@ -4,8 +4,7 @@ use crate::{
renderer::{CastFrom, Renderer},
ssr::StreamBuilder,
view::{
FallibleRender, Mountable, Position, PositionState, Render, RenderHtml,
ToTemplate,
Mountable, Position, PositionState, Render, RenderHtml, ToTemplate,
},
};
use const_str_slice_concat::{
@ -167,6 +166,8 @@ where
Rndr: Renderer,
{
type State = ElementState<At::State, Ch::State, Rndr>;
type FallibleState = ElementState<At::State, Ch::FallibleState, Rndr>;
type Error = Ch::Error;
fn rebuild(self, state: &mut Self::State) {
let ElementState {
@ -188,17 +189,6 @@ where
rndr: PhantomData,
}
}
}
impl<E, At, Ch, Rndr> FallibleRender<Rndr> for HtmlElement<E, At, Ch, Rndr>
where
E: CreateElement<Rndr>,
At: Attribute<Rndr>,
Ch: FallibleRender<Rndr>,
Rndr: Renderer,
{
type Error = Ch::Error;
type FallibleState = ElementState<At::State, Ch::FallibleState, Rndr>;
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
let el = Rndr::create_element(self.tag);

View file

@ -3,7 +3,7 @@ use crate::{
prelude::{Render, RenderHtml},
renderer::Renderer,
ssr::StreamBuilder,
view::{Position, PositionState},
view::{NeverError, Position, PositionState},
};
use std::marker::PhantomData;
@ -48,6 +48,8 @@ where
Rndr: Renderer,
{
type State = View::State;
type FallibleState = View::FallibleState;
type Error = View::Error;
fn build(self) -> Self::State {
self.view.build()
@ -56,6 +58,17 @@ where
fn rebuild(self, state: &mut Self::State) {
self.view.rebuild(state);
}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
self.view.try_build()
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
self.view.try_rebuild(state)
}
}
impl<Rndr, View> RenderHtml<Rndr> for Island<Rndr, View>
@ -141,10 +154,23 @@ where
Rndr: Renderer,
{
type State = ();
type FallibleState = Self::State;
type Error = NeverError;
fn build(self) -> Self::State {}
fn rebuild(self, state: &mut Self::State) {}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
Ok(())
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
Ok(())
}
}
impl<Rndr, View> RenderHtml<Rndr> for IslandChildren<Rndr, View>

View file

@ -1,6 +1,6 @@
use crate::{
renderer::Renderer,
view::{Position, Render, RenderHtml},
view::{NeverError, Position, Render, RenderHtml},
};
use std::marker::PhantomData;
@ -27,10 +27,23 @@ pub fn doctype<R: Renderer>(value: &'static str) -> Doctype<R> {
impl<R: Renderer> Render<R> for Doctype<R> {
type State = ();
type FallibleState = Self::State;
type Error = NeverError;
fn build(self) -> Self::State {}
fn rebuild(self, _state: &mut Self::State) {}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
Ok(())
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
Ok(())
}
}
impl<R> RenderHtml<R> for Doctype<R>

View file

@ -5,8 +5,8 @@ use crate::{
prelude::RenderHtml,
renderer::{CastFrom, Renderer},
view::{
strings::StrState, InfallibleRender, Mountable, Position,
PositionState, Render, ToTemplate,
strings::StrState, Mountable, NeverError, Position, PositionState,
Render, ToTemplate,
},
};
use reactive_graph::signal::guards::ReadGuard;
@ -56,6 +56,8 @@ macro_rules! render_primitive {
where G: Deref<Target = $child_type>
{
type State = [<ReadGuard $child_type:camel State>]<R>;
type FallibleState = Self::State;
type Error = NeverError;
fn build(self) -> Self::State {
let node = R::create_text_node(&self.to_string());
@ -69,9 +71,15 @@ macro_rules! render_primitive {
*this = *self;
}
}
}
impl<'a, G> InfallibleRender for ReadGuard<$child_type, G> {}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
Ok(self.build())
}
fn try_rebuild(self, state: &mut Self::FallibleState) -> Result<(), Self::Error> {
Ok(self.rebuild(state))
}
}
impl<'a, G, R> RenderHtml<R> for ReadGuard<$child_type, G>
where
@ -191,6 +199,8 @@ where
G: Deref<Target = String>,
{
type State = ReadGuardStringState<R>;
type FallibleState = Self::State;
type Error = NeverError;
fn build(self) -> Self::State {
let node = R::create_text_node(&self);
@ -208,9 +218,18 @@ where
str.push_str(&self);
}
}
}
impl<G> InfallibleRender for ReadGuard<String, G> {}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
Ok(self.build())
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
Ok(self.rebuild(state))
}
}
impl<G, R> RenderHtml<R> for ReadGuard<String, G>
where

View file

@ -5,11 +5,15 @@ use crate::{
renderer::{DomRenderer, Renderer},
ssr::StreamBuilder,
view::{
InfallibleRender, Mountable, Position, PositionState, Render,
RenderHtml, ToTemplate,
Mountable, Position, PositionState, Render, RenderHtml, ToTemplate,
},
};
use reactive_graph::{computed::ScopedFuture, effect::RenderEffect};
use reactive_graph::{
computed::ScopedFuture,
effect::RenderEffect,
graph::{Observer, ReactiveNode},
untrack,
};
mod class;
mod guards;
@ -42,9 +46,14 @@ where
F: FnMut() -> V + 'static,
V: Render<R>,
V::State: 'static,
V::FallibleState: 'static,
V::Error: 'static,
R: Renderer,
{
type State = RenderEffectState<V::State>;
type FallibleState =
RenderEffectState<Result<V::FallibleState, Option<V::Error>>>;
type Error = V::Error;
#[track_caller]
fn build(mut self) -> Self::State {
@ -60,6 +69,45 @@ where
.into()
}
fn try_build(mut self) -> Result<Self::FallibleState, Self::Error> {
let initial = untrack(|| self().try_build())?;
let parent = Observer::get();
let effect = RenderEffect::new_with_value(
{
move |prev| {
let value = self();
if let Some(mut state) = prev {
match state {
Ok(ref mut state) => {
if let Err(e) = value.try_rebuild(state) {
if let Some(parent) = &parent {
crate::log(
"telling parent to check itself",
);
parent.mark_check();
}
return Err(Some(e));
}
}
Err(e) => {
//if let Some(parent) = parent {
crate::log("need to tell parent to rerender");
//}
return Err(e);
}
}
state
} else {
unreachable!()
}
}
},
Some(Ok(initial)),
);
Ok(effect.into())
}
#[track_caller]
fn rebuild(mut self, state: &mut Self::State) {
// TODO
@ -80,6 +128,22 @@ where
)
.into(); */
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
if let Some(inner) = &mut state.0 {
inner
.with_value_mut(|value| match value {
Err(e) if e.is_some() => Err(e.take().unwrap()),
_ => Ok(()),
})
.unwrap_or(Ok(()))
} else {
Ok(())
}
}
}
pub struct RenderEffectState<T: 'static>(Option<RenderEffect<T>>);
@ -120,35 +184,51 @@ where
}
}
impl<F, V> InfallibleRender for F where F: FnMut() -> V + 'static {}
/* impl<F, V, R> FallibleRender<R> for F
pub struct RenderEffectFallibleState<T, E>
where
F: FnMut() -> V + 'static,
V: FallibleRender<R>,
V::State: 'static,
T: 'static,
E: 'static,
{
effect: Option<RenderEffect<Result<T, E>>>,
}
impl<T, E, R> Mountable<R> for RenderEffectFallibleState<T, E>
where
T: Mountable<R>,
R: Renderer,
{
type FallibleState = V::FallibleState;
type Error = V::Error;
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
todo!()
fn unmount(&mut self) {
if let Some(ref mut inner) = self.effect {
inner.unmount();
}
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
todo!()
fn mount(&mut self, parent: &R::Element, marker: Option<&R::Node>) {
if let Some(ref mut inner) = self.effect {
inner.mount(parent, marker);
}
}
} */
fn insert_before_this(
&self,
parent: &R::Element,
child: &mut dyn Mountable<R>,
) -> bool {
if let Some(inner) = &self.effect {
inner.insert_before_this(parent, child)
} else {
false
}
}
}
impl<F, V, R> RenderHtml<R> for F
where
F: FnMut() -> V + 'static,
V: RenderHtml<R>,
V::State: 'static,
V::FallibleState: 'static,
V::Error: 'static,
R: Renderer + 'static,
R::Node: Clone,
R::Element: Clone,
@ -220,6 +300,40 @@ where
}
}
impl<M, E, R> Mountable<R> for Result<M, E>
where
M: Mountable<R>,
R: Renderer,
{
fn unmount(&mut self) {
if let Ok(ref mut inner) = self {
inner.unmount();
}
}
fn mount(
&mut self,
parent: &<R as Renderer>::Element,
marker: Option<&<R as Renderer>::Node>,
) {
if let Ok(ref mut inner) = self {
inner.mount(parent, marker);
}
}
fn insert_before_this(
&self,
parent: &<R as Renderer>::Element,
child: &mut dyn Mountable<R>,
) -> bool {
if let Ok(inner) = &self {
inner.insert_before_this(parent, child)
} else {
false
}
}
}
// Extends to track suspense
impl<const TRANSITION: bool, Fal, Fut> Suspend<TRANSITION, Fal, Fut> {
pub fn track(self) -> Suspend<TRANSITION, Fal, ScopedFuture<Fut>> {

View file

@ -3,7 +3,7 @@ use crate::{
prelude::Mountable,
renderer::Renderer,
ssr::StreamBuilder,
view::{Position, PositionState, Render, RenderHtml},
view::{NeverError, Position, PositionState, Render, RenderHtml},
};
use reactive_graph::owner::Owner;
use std::marker::PhantomData;
@ -39,21 +39,21 @@ impl<T, R> OwnedView<T, R> {
#[derive(Debug, Clone)]
pub struct OwnedViewState<T, R>
where
T: Render<R>,
T: Mountable<R>,
R: Renderer,
{
owner: Owner,
state: T::State,
state: T,
rndr: PhantomData<R>,
}
impl<T, R> OwnedViewState<T, R>
where
T: Render<R>,
T: Mountable<R>,
R: Renderer,
{
/// Wraps a state with the given owner.
fn new(state: T::State, owner: Owner) -> Self {
fn new(state: T, owner: Owner) -> Self {
Self {
owner,
state,
@ -67,7 +67,9 @@ where
T: Render<R>,
R: Renderer,
{
type State = OwnedViewState<T, R>;
type State = OwnedViewState<T::State, R>;
type FallibleState = OwnedViewState<T::FallibleState, R>;
type Error = T::Error;
fn build(self) -> Self::State {
let state = self.owner.with(|| self.view.build());
@ -79,6 +81,17 @@ where
owner.with(|| view.rebuild(&mut state.state));
state.owner = owner;
}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
todo!()
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
todo!()
}
}
impl<T, R> RenderHtml<R> for OwnedView<T, R>
@ -126,7 +139,7 @@ where
impl<T, R> Mountable<R> for OwnedViewState<T, R>
where
T: Render<R>,
T: Mountable<R>,
R: Renderer,
{
fn unmount(&mut self) {

View file

@ -1,4 +1,6 @@
use super::{Mountable, Position, PositionState, Render, RenderHtml};
use super::{
Mountable, NeverError, Position, PositionState, Render, RenderHtml,
};
use crate::{
hydration::Cursor,
renderer::{CastFrom, Renderer},
@ -198,6 +200,8 @@ where
R: Renderer + 'static,
{
type State = AnyViewState<R>;
type FallibleState = Self::State;
type Error = NeverError;
fn build(self) -> Self::State {
(self.build)(self.value)
@ -206,6 +210,17 @@ where
fn rebuild(self, state: &mut Self::State) {
(self.rebuild)(self.type_id, self.value, state)
}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
todo!()
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
todo!()
}
}
impl<R> RenderHtml<R> for AnyView<R>

View file

@ -1,22 +1,49 @@
use super::{Mountable, Position, PositionState, Render, RenderHtml};
use super::{
Mountable, NeverError, Position, PositionState, Render, RenderHtml,
};
use crate::{
hydration::Cursor,
renderer::{CastFrom, Renderer},
ssr::StreamBuilder,
};
use std::{
error::Error,
fmt::{Debug, Display},
};
#[derive(Debug)]
pub enum Either<A, B> {
Left(A),
Right(B),
}
impl<A, B> Display for Either<A, B>
where
A: Error,
B: Error,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Either::Left(v) => std::fmt::Display::fmt(&v, f),
Either::Right(v) => std::fmt::Display::fmt(&v, f),
}
}
}
impl<A, B> Error for Either<A, B>
where
A: Error,
B: Error,
{
}
pub struct EitherState<A, B, Rndr>
where
A: Render<Rndr>,
B: Render<Rndr>,
A: Mountable<Rndr>,
B: Mountable<Rndr>,
Rndr: Renderer,
{
state: Either<A::State, B::State>,
state: Either<A, B>,
marker: Rndr::Placeholder,
}
@ -26,7 +53,9 @@ where
B: Render<Rndr>,
Rndr: Renderer,
{
type State = EitherState<A, B, Rndr>;
type State = EitherState<A::State, B::State, Rndr>;
type FallibleState = EitherState<A::FallibleState, B::FallibleState, Rndr>;
type Error = Either<A::Error, B::Error>;
fn build(self) -> Self::State {
let marker = Rndr::create_placeholder();
@ -42,7 +71,6 @@ where
}
}
// TODO hold onto old states to avoid rerendering them?
fn rebuild(self, state: &mut Self::State) {
let marker = state.marker.as_ref();
match (self, &mut state.state) {
@ -62,12 +90,23 @@ where
}
}
}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
todo!()
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
todo!()
}
}
impl<A, B, Rndr> Mountable<Rndr> for EitherState<A, B, Rndr>
where
A: Render<Rndr>,
B: Render<Rndr>,
A: Mountable<Rndr>,
B: Mountable<Rndr>,
Rndr: Renderer,
{
fn unmount(&mut self) {
@ -181,10 +220,28 @@ const fn min_usize(vals: &[usize]) -> usize {
macro_rules! tuples {
($num:literal => $($ty:ident),*) => {
paste::paste! {
#[derive(Debug)]
pub enum [<EitherOf $num>]<$($ty,)*> {
$($ty ($ty),)*
}
impl<$($ty,)*> Display for [<EitherOf $num>]<$($ty,)*>
where
$($ty: Display,)*
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
$([<EitherOf $num>]::$ty(this) => this.fmt(f),)*
}
}
}
impl<$($ty,)*> Error for [<EitherOf $num>]<$($ty,)*>
where
$($ty: Error,)*
{
}
pub struct [<EitherOf $num State>]<$($ty,)* Rndr>
where
$($ty: Render<Rndr>,)*
@ -234,6 +291,8 @@ macro_rules! tuples {
Rndr: Renderer
{
type State = [<EitherOf $num State>]<$($ty,)* Rndr>;
type FallibleState = [<EitherOf $num State>]<$($ty,)* Rndr>;
type Error = Box<dyn Error>;
fn build(self) -> Self::State {
let marker = Rndr::create_placeholder();
@ -264,6 +323,17 @@ macro_rules! tuples {
// and store the new state
state.state = new_state;
}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
todo!()
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
todo!()
}
}
impl<Rndr, $($ty,)*> RenderHtml<Rndr> for [<EitherOf $num>]<$($ty,)*>

View file

@ -1,5 +1,9 @@
use super::{either::Either, RenderHtml};
use crate::view::{FallibleRender, Mountable, Render, Renderer};
use super::{either::Either, NeverError, Position, PositionState, RenderHtml};
use crate::{
hydration::Cursor,
ssr::StreamBuilder,
view::{Mountable, Render, Renderer},
};
use std::marker::PhantomData;
impl<R, T, E> Render<R> for Result<T, E>
@ -8,6 +12,8 @@ where
R: Renderer,
{
type State = <Option<T> as Render<R>>::State;
type FallibleState = T::State;
type Error = E;
fn build(self) -> Self::State {
self.ok().build()
@ -16,15 +22,6 @@ where
fn rebuild(self, state: &mut Self::State) {
self.ok().rebuild(state);
}
}
impl<R, T, E> FallibleRender<R> for Result<T, E>
where
T: Render<R>,
R: Renderer,
{
type FallibleState = T::State;
type Error = E;
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
let inner = self?;
@ -42,9 +39,49 @@ where
}
}
impl<R, T, E> RenderHtml<R> for Result<T, E>
where
T: RenderHtml<R>,
R: Renderer,
R::Element: Clone,
R::Node: Clone,
{
const MIN_LENGTH: usize = T::MIN_LENGTH;
fn to_html_with_buf(
self,
buf: &mut String,
position: &mut super::Position,
) {
if let Ok(inner) = self {
inner.to_html_with_buf(buf, position);
}
}
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
self,
buf: &mut StreamBuilder,
position: &mut Position,
) where
Self: Sized,
{
if let Ok(inner) = self {
inner.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position);
}
}
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor<R>,
position: &PositionState,
) -> Self::State {
self.ok().hydrate::<FROM_SERVER>(cursor, position)
}
}
pub trait TryCatchBoundary<Fal, FalFn, Rndr>
where
Self: Sized + FallibleRender<Rndr>,
Self: Sized + Render<Rndr>,
Fal: Render<Rndr>,
FalFn: FnMut(Self::Error) -> Fal,
Rndr: Renderer,
@ -54,7 +91,7 @@ where
impl<T, Fal, FalFn, Rndr> TryCatchBoundary<Fal, FalFn, Rndr> for T
where
T: Sized + FallibleRender<Rndr>,
T: Sized + Render<Rndr>,
Fal: Render<Rndr>,
FalFn: FnMut(Self::Error) -> Fal,
Rndr: Renderer,
@ -66,7 +103,7 @@ where
pub struct Try<T, Fal, FalFn, Rndr>
where
T: FallibleRender<Rndr>,
T: Render<Rndr>,
Fal: Render<Rndr>,
FalFn: FnMut(T::Error) -> Fal,
Rndr: Renderer,
@ -78,7 +115,7 @@ where
impl<T, Fal, FalFn, Rndr> Try<T, Fal, FalFn, Rndr>
where
T: FallibleRender<Rndr>,
T: Render<Rndr>,
Fal: Render<Rndr>,
FalFn: FnMut(T::Error) -> Fal,
Rndr: Renderer,
@ -94,12 +131,14 @@ where
impl<T, Fal, FalFn, Rndr> Render<Rndr> for Try<T, Fal, FalFn, Rndr>
where
T: FallibleRender<Rndr>,
T: Render<Rndr>,
Fal: Render<Rndr>,
FalFn: FnMut(T::Error) -> Fal,
Rndr: Renderer,
{
type State = TryState<T, Fal, Rndr>;
type FallibleState = Self::State;
type Error = NeverError;
fn build(mut self) -> Self::State {
let state = match self.child.try_build() {
@ -133,12 +172,23 @@ where
},
}
}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
Ok(self.build())
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
Ok(self.rebuild(state))
}
}
// TODO RenderHtml implementation for ErrorBoundary
impl<T, Fal, FalFn, Rndr> RenderHtml<Rndr> for Try<T, Fal, FalFn, Rndr>
where
T: FallibleRender<Rndr>,
T: Render<Rndr>,
Fal: RenderHtml<Rndr>,
FalFn: FnMut(T::Error) -> Fal,
Rndr: Renderer,
@ -176,7 +226,7 @@ where
pub struct TryState<T, Fal, Rndr>
where
T: FallibleRender<Rndr>,
T: Render<Rndr>,
Fal: Render<Rndr>,
Rndr: Renderer,
{
@ -186,7 +236,7 @@ where
impl<T, Fal, Rndr> Mountable<Rndr> for TryState<T, Fal, Rndr>
where
T: FallibleRender<Rndr>,
T: Render<Rndr>,
Fal: Render<Rndr>,
Rndr: Renderer,
{

View file

@ -1,17 +1,22 @@
use super::{Mountable, Position, PositionState, Render, RenderHtml};
use super::{
Mountable, NeverError, Position, PositionState, Render, RenderHtml,
};
use crate::{
hydration::Cursor,
renderer::{CastFrom, Renderer},
ssr::StreamBuilder,
};
use itertools::Itertools;
use std::error::Error;
impl<T, R> Render<R> for Option<T>
where
T: Render<R>,
R: Renderer,
{
type State = OptionState<T, R>;
type State = OptionState<T::State, R>;
type FallibleState = OptionState<T::FallibleState, R>;
type Error = T::Error;
fn build(self) -> Self::State {
let placeholder = R::create_placeholder();
@ -41,6 +46,35 @@ where
}
}
}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
match self {
None => {
let placeholder = R::create_placeholder();
Ok(OptionState {
placeholder,
state: None,
})
}
Some(inner) => match inner.try_build() {
Err(e) => return Err(e),
Ok(inner) => {
let placeholder = R::create_placeholder();
Ok(OptionState {
placeholder,
state: Some(inner),
})
}
},
}
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
todo!()
}
}
impl<T, R> RenderHtml<R> for Option<T>
@ -101,18 +135,18 @@ where
/// View state for an optional view.
pub struct OptionState<T, R>
where
T: Render<R>,
T: Mountable<R>,
R: Renderer,
{
/// Marks the location of this view.
placeholder: R::Placeholder,
/// The view state.
state: Option<T::State>,
state: Option<T>,
}
impl<T, R> Mountable<R> for OptionState<T, R>
where
T: Render<R>,
T: Mountable<R>,
R: Renderer,
{
fn unmount(&mut self) {
@ -154,7 +188,9 @@ where
R::Element: Clone,
R::Node: Clone,
{
type State = VecState<T, R>;
type State = VecState<T::State, R>;
type FallibleState = VecState<T::FallibleState, R>;
type Error = T::Error;
fn build(self) -> Self::State {
VecState {
@ -211,21 +247,40 @@ where
old.append(&mut adds);
}
}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
let states = self
.into_iter()
.map(T::try_build)
.collect::<Result<_, _>>()?;
Ok(VecState {
states,
parent: None,
marker: None,
})
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
todo!()
}
}
pub struct VecState<T, R>
where
T: Render<R>,
T: Mountable<R>,
R: Renderer,
{
states: Vec<T::State>,
states: Vec<T>,
parent: Option<R::Element>,
marker: Option<R::Node>,
}
impl<T, R> Mountable<R> for VecState<T, R>
where
T: Render<R>,
T: Mountable<R>,
R: Renderer,
R::Element: Clone,
R::Node: Clone,

View file

@ -3,6 +3,7 @@ use crate::{
hydration::Cursor,
renderer::{CastFrom, Renderer},
ssr::StreamBuilder,
view::NeverError,
};
use drain_filter_polyfill::VecExt as VecDrainFilterExt;
use indexmap::IndexSet;
@ -73,6 +74,9 @@ where
Rndr::Element: Clone,
{
type State = KeyedState<K, V, Rndr>;
// TODO fallible state and try_build()/try_rebuild() here
type FallibleState = Self::State;
type Error = NeverError;
fn build(self) -> Self::State {
let items = self.items.into_iter();
@ -126,6 +130,17 @@ where
*hashed_items = new_hashed_items;
}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
todo!()
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
todo!()
}
}
impl<T, I, K, KF, VF, V, Rndr> RenderHtml<Rndr>

View file

@ -18,33 +18,23 @@ pub mod tuples;
///
/// It is generic over the renderer itself, as long as that implements the [`Renderer`]
/// trait.
pub trait Render<R: Renderer> {
pub trait Render<R: Renderer>: Sized {
/// The “view state” for this type, which can be retained between updates.
///
/// For example, for a text node, `State` might be the actual DOM text node
/// and the previous string, to allow for diffing between updates.
type State: Mountable<R>;
type FallibleState: Mountable<R>;
type Error;
/// Creates the view for the first time, without hydrating from existing HTML.
fn build(self) -> Self::State;
/// Updates the view with new data.
fn rebuild(self, state: &mut Self::State);
}
pub trait InfallibleRender {}
pub trait FallibleRender<R>: Sized + Render<R>
where
R: Renderer,
{
type FallibleState: Mountable<R>;
type Error;
/// Creates the view fallibly, handling any [`Result`] by propagating its `Err`.
fn try_build(self) -> Result<Self::FallibleState, Self::Error>;
/// Updates the view with new data fallibly, handling any [`Result`] by propagating its `Err`.
fn try_rebuild(
self,
state: &mut Self::FallibleState,
@ -62,27 +52,6 @@ impl core::fmt::Display for NeverError {
impl std::error::Error for NeverError {}
impl<T, R> FallibleRender<R> for T
where
T: Render<R> + InfallibleRender,
R: Renderer,
{
type FallibleState = Self::State;
type Error = NeverError;
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
Ok(self.build())
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
self.rebuild(state);
Ok(())
}
}
/// The `RenderHtml` trait allows rendering something to HTML, and transforming
/// that HTML into an interactive interface.
///

View file

@ -1,10 +1,8 @@
use super::{
InfallibleRender, Mountable, Position, PositionState, Render, RenderHtml,
};
use super::{Mountable, Position, PositionState, Render, RenderHtml};
use crate::{
hydration::Cursor,
renderer::{CastFrom, Renderer},
view::ToTemplate,
view::{NeverError, ToTemplate},
};
use std::{
fmt::Write,
@ -48,6 +46,8 @@ macro_rules! render_primitive {
impl<'a, R: Renderer> Render<R> for $child_type {
type State = [<$child_type:camel State>]<R>;
type FallibleState = Self::State;
type Error = NeverError;
fn build(self) -> Self::State {
let node = R::create_text_node(&self.to_string());
@ -61,9 +61,15 @@ macro_rules! render_primitive {
*this = self;
}
}
}
impl<'a> InfallibleRender for $child_type {}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
Ok(self.build())
}
fn try_rebuild(self, state: &mut Self::FallibleState) -> Result<(), Self::Error> {
Ok(self.rebuild(state))
}
}
impl<'a, R> RenderHtml<R> for $child_type
where

View file

@ -1,5 +1,5 @@
use super::{
InfallibleRender, Mountable, Position, PositionState, Render, RenderHtml,
Mountable, NeverError, Position, PositionState, Render, RenderHtml,
ToTemplate,
};
use crate::{
@ -114,6 +114,8 @@ where
R::Text: Mountable<R>,
{
type State = Option<R::Text>;
type FallibleState = Self::State;
type Error = NeverError;
fn build(self) -> Self::State {
// a view state has to be returned so it can be mounted
@ -122,9 +124,18 @@ where
// This type is specified as static, so no rebuilding is done.
fn rebuild(self, _state: &mut Self::State) {}
}
impl<const V: &'static str> InfallibleRender for Static<V> {}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
Ok(Render::<R>::build(self))
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
Ok(Render::<R>::rebuild(self, state))
}
}
impl<const V: &'static str, R> RenderHtml<R> for Static<V>
where

View file

@ -1,5 +1,5 @@
use super::{
InfallibleRender, Mountable, Position, PositionState, Render, RenderHtml,
Mountable, NeverError, Position, PositionState, Render, RenderHtml,
ToTemplate,
};
use crate::{
@ -15,6 +15,8 @@ pub struct StrState<'a, R: Renderer> {
impl<'a, R: Renderer> Render<R> for &'a str {
type State = StrState<'a, R>;
type FallibleState = Self::State;
type Error = NeverError;
fn build(self) -> Self::State {
let node = R::create_text_node(self);
@ -28,9 +30,18 @@ impl<'a, R: Renderer> Render<R> for &'a str {
*str = self;
}
}
}
impl<'a> InfallibleRender for &'a str {}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
Ok(self.build())
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
Ok(self.rebuild(state))
}
}
impl<'a, R> RenderHtml<R> for &'a str
where
@ -129,6 +140,8 @@ pub struct StringState<R: Renderer> {
impl<R: Renderer> Render<R> for String {
type State = StringState<R>;
type FallibleState = Self::State;
type Error = NeverError;
fn build(self) -> Self::State {
let node = R::create_text_node(&self);
@ -142,9 +155,18 @@ impl<R: Renderer> Render<R> for String {
*str = self;
}
}
}
impl InfallibleRender for String {}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
Ok(self.build())
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
Ok(self.rebuild(state))
}
}
impl<R> RenderHtml<R> for String
where
@ -215,6 +237,8 @@ pub struct RcStrState<R: Renderer> {
impl<R: Renderer> Render<R> for Rc<str> {
type State = RcStrState<R>;
type FallibleState = Self::State;
type Error = NeverError;
fn build(self) -> Self::State {
let node = R::create_text_node(&self);
@ -228,9 +252,18 @@ impl<R: Renderer> Render<R> for Rc<str> {
*str = self;
}
}
}
impl InfallibleRender for Rc<str> {}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
Ok(self.build())
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
Ok(self.rebuild(state))
}
}
impl<R> RenderHtml<R> for Rc<str>
where
@ -302,6 +335,8 @@ pub struct ArcStrState<R: Renderer> {
impl<R: Renderer> Render<R> for Arc<str> {
type State = ArcStrState<R>;
type FallibleState = Self::State;
type Error = NeverError;
fn build(self) -> Self::State {
let node = R::create_text_node(&self);
@ -315,9 +350,18 @@ impl<R: Renderer> Render<R> for Arc<str> {
*str = self;
}
}
}
impl InfallibleRender for Arc<str> {}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
Ok(self.build())
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
Ok(self.rebuild(state))
}
}
impl<R> RenderHtml<R> for Arc<str>
where
@ -389,6 +433,8 @@ pub struct CowStrState<'a, R: Renderer> {
impl<'a, R: Renderer> Render<R> for Cow<'a, str> {
type State = CowStrState<'a, R>;
type FallibleState = Self::State;
type Error = NeverError;
fn build(self) -> Self::State {
let node = R::create_text_node(&self);
@ -402,9 +448,18 @@ impl<'a, R: Renderer> Render<R> for Cow<'a, str> {
*str = self;
}
}
}
impl<'a> InfallibleRender for Cow<'a, str> {}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
Ok(self.build())
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
Ok(self.rebuild(state))
}
}
impl<'a, R> RenderHtml<R> for Cow<'a, str>
where

View file

@ -61,6 +61,9 @@ where
V::State: Mountable<Dom>,
{
type State = V::State;
type FallibleState = V::FallibleState;
type Error = V::Error;
// TODO try_build/try_rebuild()
fn build(self) -> Self::State {
let tpl = Self::to_template();
@ -74,6 +77,17 @@ where
fn rebuild(self, state: &mut Self::State) {
self.view.rebuild(state)
}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
todo!()
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
todo!()
}
}
impl<V> RenderHtml<Dom> for ViewTemplate<V>

View file

@ -1,11 +1,8 @@
use super::{
Mountable, Position, PositionState, Render, RenderHtml, Renderer,
ToTemplate,
};
use crate::{
hydration::Cursor,
view::{FallibleRender, InfallibleRender, StreamBuilder},
Mountable, NeverError, Position, PositionState, Render, RenderHtml,
Renderer, ToTemplate,
};
use crate::{error::AnyError, hydration::Cursor, view::StreamBuilder};
use const_str_slice_concat::{
const_concat, const_concat_with_separator, str_from_buffer,
};
@ -13,13 +10,24 @@ use std::error::Error;
impl<R: Renderer> Render<R> for () {
type State = ();
type FallibleState = Self::State;
type Error = NeverError;
fn build(self) -> Self::State {}
fn rebuild(self, _state: &mut Self::State) {}
}
impl InfallibleRender for () {}
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
Ok(())
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> Result<(), Self::Error> {
Ok(())
}
}
impl<R> RenderHtml<R> for ()
where
@ -68,6 +76,8 @@ impl ToTemplate for () {
impl<A: Render<R>, R: Renderer> Render<R> for (A,) {
type State = A::State;
type FallibleState = A::FallibleState;
type Error = A::Error;
fn build(self) -> Self::State {
self.0.build()
@ -76,11 +86,6 @@ impl<A: Render<R>, R: Renderer> Render<R> for (A,) {
fn rebuild(self, state: &mut Self::State) {
self.0.rebuild(state)
}
}
impl<A: FallibleRender<R>, R: Renderer> FallibleRender<R> for (A,) {
type Error = A::Error;
type FallibleState = A::FallibleState;
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
self.0.try_build()
@ -148,18 +153,20 @@ macro_rules! impl_view_for_tuples {
where
$first: Render<Rndr>,
$($ty: Render<Rndr>),*,
$first::Error: Error + Sized + 'static,
$($ty::Error: Error + Sized + 'static),*,
Rndr: Renderer
{
type State = ($first::State, $($ty::State,)*);
type Error = AnyError;
type FallibleState = ($first::FallibleState, $($ty::FallibleState,)*);
fn build(self) -> Self::State {
paste::paste! {
let ([<$first:lower>], $([<$ty:lower>],)*) = self;
(
[<$first:lower>].build(),
$([<$ty:lower>].build()),*
)
}
let ($first, $($ty,)*) = self;
(
$first.build(),
$($ty.build()),*
)
}
fn rebuild(self, state: &mut Self::State) {
@ -170,25 +177,13 @@ macro_rules! impl_view_for_tuples {
$([<$ty:lower>].rebuild([<view_ $ty:lower>]));*
}
}
}
impl<$first, $($ty),*, Rndr> FallibleRender<Rndr> for ($first, $($ty,)*)
where
$first: FallibleRender<Rndr>,
$($ty: FallibleRender<Rndr>),*,
$first::Error: Error + 'static,
$($ty::Error: Error + 'static),*,
Rndr: Renderer
{
type Error = (); /* Box<dyn Error>; */
type FallibleState = ($first::FallibleState, $($ty::FallibleState,)*);
fn try_build(self) -> Result<Self::FallibleState, Self::Error> {
paste::paste! {
let ([<$first:lower>], $([<$ty:lower>],)*) = self;
let ($first, $($ty,)*) = self;
Ok((
[<$first:lower>].try_build().map_err(|_| ())?,
$([<$ty:lower>].try_build().map_err(|_| ())?),*
$first.try_build().map_err(AnyError::new)?,
$($ty.try_build().map_err(AnyError::new)?),*
))
}
}
@ -197,8 +192,8 @@ macro_rules! impl_view_for_tuples {
paste::paste! {
let ([<$first:lower>], $([<$ty:lower>],)*) = self;
let ([<view_ $first:lower>], $([<view_ $ty:lower>],)*) = state;
[<$first:lower>].try_rebuild([<view_ $first:lower>]).map_err(|_| ())?;
$([<$ty:lower>].try_rebuild([<view_ $ty:lower>]).map_err(|_| ())?);*
[<$first:lower>].try_rebuild([<view_ $first:lower>]).map_err(AnyError::new)?;
$([<$ty:lower>].try_rebuild([<view_ $ty:lower>]).map_err(AnyError::new)?);*
}
Ok(())
}
@ -208,6 +203,8 @@ macro_rules! impl_view_for_tuples {
where
$first: RenderHtml<Rndr>,
$($ty: RenderHtml<Rndr>),*,
$first::Error: Error + Sized + 'static,
$($ty::Error: Error + Sized + 'static),*,
Rndr: Renderer,
Rndr::Node: Clone,
Rndr::Element: Clone