Skip to main content

tachys/view/
error_boundary.rs

1use super::{add_attr::AddAnyAttr, Position, PositionState, RenderHtml};
2use crate::{
3    html::attribute::{any_attribute::AnyAttribute, Attribute},
4    hydration::Cursor,
5    ssr::StreamBuilder,
6    view::{iterators::OptionState, Mountable, Render},
7};
8use either_of::Either;
9use std::sync::Arc;
10use throw_error::{Error as AnyError, ErrorHook};
11
12impl<T, E> Render for Result<T, E>
13where
14    T: Render,
15    E: Into<AnyError> + 'static,
16{
17    type State = ResultState<T>;
18
19    fn build(self) -> Self::State {
20        let hook = throw_error::get_error_hook();
21        let (state, error) = match self {
22            Ok(view) => (Either::Left(view.build()), None),
23            Err(e) => (
24                Either::Right(Render::build(())),
25                Some(throw_error::throw(e.into())),
26            ),
27        };
28        ResultState { state, error, hook }
29    }
30
31    fn rebuild(self, state: &mut Self::State) {
32        let _guard = state.hook.clone().map(throw_error::set_error_hook);
33        match (&mut state.state, self) {
34            // both errors: throw the new error and replace
35            (Either::Right(_), Err(new)) => {
36                if let Some(old_error) = state.error.take() {
37                    throw_error::clear(&old_error);
38                }
39                state.error = Some(throw_error::throw(new.into()));
40            }
41            // both Ok: need to rebuild child
42            (Either::Left(old), Ok(new)) => {
43                T::rebuild(new, old);
44            }
45            // Ok => Err: unmount, replace with marker, and throw
46            (Either::Left(old), Err(err)) => {
47                let mut new_state = Render::build(());
48                old.insert_before_this(&mut new_state);
49                old.unmount();
50                state.state = Either::Right(new_state);
51                state.error = Some(throw_error::throw(err));
52            }
53            // Err => Ok: clear error and build
54            (Either::Right(old), Ok(new)) => {
55                if let Some(err) = state.error.take() {
56                    throw_error::clear(&err);
57                }
58                let mut new_state = new.build();
59                old.insert_before_this(&mut new_state);
60                old.unmount();
61                state.state = Either::Left(new_state);
62            }
63        }
64    }
65}
66
67/// View state for a `Result<_, _>` view.
68pub struct ResultState<T>
69where
70    T: Render,
71{
72    /// The view state.
73    state: OptionState<T>,
74    error: Option<throw_error::ErrorId>,
75    hook: Option<Arc<dyn ErrorHook>>,
76}
77
78impl<T> Drop for ResultState<T>
79where
80    T: Render,
81{
82    fn drop(&mut self) {
83        // when the state is cleared, unregister this error; this item is being dropped and its
84        // error should no longer be shown
85        if let Some(e) = self.error.take() {
86            throw_error::clear(&e);
87        }
88    }
89}
90
91impl<T> Mountable for ResultState<T>
92where
93    T: Render,
94{
95    fn unmount(&mut self) {
96        self.state.unmount();
97    }
98
99    fn mount(
100        &mut self,
101        parent: &crate::renderer::types::Element,
102        marker: Option<&crate::renderer::types::Node>,
103    ) {
104        self.state.mount(parent, marker);
105    }
106
107    fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
108        self.state.insert_before_this(child)
109    }
110
111    fn elements(&self) -> Vec<crate::renderer::types::Element> {
112        self.state.elements()
113    }
114}
115
116impl<T, E> AddAnyAttr for Result<T, E>
117where
118    T: AddAnyAttr,
119
120    E: Into<AnyError> + Send + 'static,
121{
122    type Output<SomeNewAttr: Attribute> =
123        Result<<T as AddAnyAttr>::Output<SomeNewAttr>, E>;
124
125    fn add_any_attr<NewAttr: Attribute>(
126        self,
127        attr: NewAttr,
128    ) -> Self::Output<NewAttr>
129    where
130        Self::Output<NewAttr>: RenderHtml,
131    {
132        self.map(|inner| inner.add_any_attr(attr))
133    }
134}
135
136impl<T, E> RenderHtml for Result<T, E>
137where
138    T: RenderHtml,
139    E: Into<AnyError> + Send + 'static,
140{
141    type AsyncOutput = Result<T::AsyncOutput, E>;
142    type Owned = Result<T::Owned, E>;
143
144    const MIN_LENGTH: usize = T::MIN_LENGTH;
145
146    fn dry_resolve(&mut self) {
147        if let Ok(inner) = self.as_mut() {
148            inner.dry_resolve()
149        }
150    }
151
152    async fn resolve(self) -> Self::AsyncOutput {
153        match self {
154            Ok(view) => Ok(view.resolve().await),
155            Err(e) => Err(e),
156        }
157    }
158
159    fn html_len(&self) -> usize {
160        match self {
161            Ok(i) => i.html_len() + 3,
162            Err(_) => 0,
163        }
164    }
165
166    fn to_html_with_buf(
167        self,
168        buf: &mut String,
169        position: &mut super::Position,
170        escape: bool,
171        mark_branches: bool,
172        extra_attrs: Vec<AnyAttribute>,
173    ) {
174        match self {
175            Ok(inner) => {
176                inner.to_html_with_buf(
177                    buf,
178                    position,
179                    escape,
180                    mark_branches,
181                    extra_attrs,
182                );
183            }
184            Err(e) => {
185                buf.push_str("<!>");
186                throw_error::throw(e);
187            }
188        }
189    }
190
191    fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
192        self,
193        buf: &mut StreamBuilder,
194        position: &mut Position,
195        escape: bool,
196        mark_branches: bool,
197        extra_attrs: Vec<AnyAttribute>,
198    ) where
199        Self: Sized,
200    {
201        match self {
202            Ok(inner) => inner.to_html_async_with_buf::<OUT_OF_ORDER>(
203                buf,
204                position,
205                escape,
206                mark_branches,
207                extra_attrs,
208            ),
209            Err(e) => {
210                buf.push_sync("<!>");
211                throw_error::throw(e);
212            }
213        }
214    }
215
216    fn hydrate<const FROM_SERVER: bool>(
217        self,
218        cursor: &Cursor,
219        position: &PositionState,
220    ) -> Self::State {
221        let hook = throw_error::get_error_hook();
222        let (state, error) = match self {
223            Ok(view) => (
224                Either::Left(view.hydrate::<FROM_SERVER>(cursor, position)),
225                None,
226            ),
227            Err(e) => {
228                let state =
229                    RenderHtml::hydrate::<FROM_SERVER>((), cursor, position);
230                (Either::Right(state), Some(throw_error::throw(e.into())))
231            }
232        };
233        ResultState { state, error, hook }
234    }
235
236    async fn hydrate_async(
237        self,
238        cursor: &Cursor,
239        position: &PositionState,
240    ) -> Self::State {
241        let hook = throw_error::get_error_hook();
242        let (state, error) = match self {
243            Ok(view) => (
244                Either::Left(view.hydrate_async(cursor, position).await),
245                None,
246            ),
247            Err(e) => {
248                let state =
249                    RenderHtml::hydrate_async((), cursor, position).await;
250                (Either::Right(state), Some(throw_error::throw(e.into())))
251            }
252        };
253        ResultState { state, error, hook }
254    }
255
256    fn into_owned(self) -> Self::Owned {
257        match self {
258            Ok(view) => Ok(view.into_owned()),
259            Err(e) => Err(e),
260        }
261    }
262}