1use crate::{children::TypedChildren, IntoView};
2use futures::{channel::oneshot, future::join_all};
3use hydration_context::{SerializedDataId, SharedContext};
4use leptos_macro::component;
5use or_poisoned::OrPoisoned;
6use reactive_graph::{
7 computed::ArcMemo,
8 effect::RenderEffect,
9 owner::{provide_context, ArcStoredValue, Owner},
10 signal::ArcRwSignal,
11 traits::{Get, Update, With, WithUntracked, WriteValue},
12};
13use rustc_hash::FxHashMap;
14use std::{
15 collections::VecDeque,
16 fmt::Debug,
17 mem,
18 sync::{Arc, Mutex},
19};
20use tachys::{
21 html::attribute::{any_attribute::AnyAttribute, Attribute},
22 hydration::Cursor,
23 reactive_graph::OwnedView,
24 ssr::{StreamBuilder, StreamChunk},
25 view::{
26 add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
27 RenderHtml,
28 },
29};
30use throw_error::{Error, ErrorHook, ErrorId};
31
32#[component]
78pub fn ErrorBoundary<FalFn, Fal, Chil>(
79 children: TypedChildren<Chil>,
81 fallback: FalFn,
83) -> impl IntoView
84where
85 FalFn: FnMut(ArcRwSignal<Errors>) -> Fal + Send + 'static,
86 Fal: IntoView + Send + 'static,
87 Chil: IntoView + Send + 'static,
88{
89 let sc = Owner::current_shared_context();
90 let boundary_id = sc.as_ref().map(|sc| sc.next_id()).unwrap_or_default();
91 let initial_errors =
92 sc.map(|sc| sc.errors(&boundary_id)).unwrap_or_default();
93
94 let hook = Arc::new(ErrorBoundaryErrorHook::new(
95 boundary_id.clone(),
96 initial_errors,
97 ));
98 let errors = hook.errors.clone();
99 let errors_empty = ArcMemo::new({
100 let errors = errors.clone();
101 move |_| errors.with(|map| map.is_empty())
102 });
103 let hook = hook as Arc<dyn ErrorHook>;
104
105 let _guard = throw_error::set_error_hook(Arc::clone(&hook));
106 let suspended_children = ErrorBoundarySuspendedChildren::default();
107
108 let owner = Owner::new();
109 let children = owner.with(|| {
110 provide_context(Arc::clone(&hook));
111 provide_context(suspended_children.clone());
112 children.into_inner()()
113 });
114
115 OwnedView::new_with_owner(
116 ErrorBoundaryView {
117 hook,
118 boundary_id,
119 errors_empty,
120 children,
121 errors,
122 fallback,
123 suspended_children,
124 },
125 owner,
126 )
127}
128
129pub(crate) type ErrorBoundarySuspendedChildren =
130 ArcStoredValue<Vec<oneshot::Receiver<()>>>;
131
132struct ErrorBoundaryView<Chil, FalFn> {
133 hook: Arc<dyn ErrorHook>,
134 boundary_id: SerializedDataId,
135 errors_empty: ArcMemo<bool>,
136 children: Chil,
137 fallback: FalFn,
138 errors: ArcRwSignal<Errors>,
139 suspended_children: ErrorBoundarySuspendedChildren,
140}
141
142struct ErrorBoundaryViewState<Chil, Fal> {
143 children: Chil,
145 fallback: Option<Fal>,
146}
147
148impl<Chil, Fal> Mountable for ErrorBoundaryViewState<Chil, Fal>
149where
150 Chil: Mountable,
151 Fal: Mountable,
152{
153 fn unmount(&mut self) {
154 if let Some(fallback) = &mut self.fallback {
155 fallback.unmount();
156 } else {
157 self.children.unmount();
158 }
159 }
160
161 fn mount(
162 &mut self,
163 parent: &tachys::renderer::types::Element,
164 marker: Option<&tachys::renderer::types::Node>,
165 ) {
166 if let Some(fallback) = &mut self.fallback {
167 fallback.mount(parent, marker);
168 } else {
169 self.children.mount(parent, marker);
170 }
171 }
172
173 fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
174 if let Some(fallback) = &self.fallback {
175 fallback.insert_before_this(child)
176 } else {
177 self.children.insert_before_this(child)
178 }
179 }
180
181 fn elements(&self) -> Vec<tachys::renderer::types::Element> {
182 if let Some(fallback) = &self.fallback {
183 fallback.elements()
184 } else {
185 self.children.elements()
186 }
187 }
188}
189
190impl<Chil, FalFn, Fal> Render for ErrorBoundaryView<Chil, FalFn>
191where
192 Chil: Render + 'static,
193 FalFn: FnMut(ArcRwSignal<Errors>) -> Fal + Send + 'static,
194 Fal: Render + 'static,
195{
196 type State = RenderEffect<ErrorBoundaryViewState<Chil::State, Fal::State>>;
197
198 fn build(mut self) -> Self::State {
199 let hook = Arc::clone(&self.hook);
200 let _hook = throw_error::set_error_hook(Arc::clone(&hook));
201 let mut children = Some(self.children.build());
202 RenderEffect::new(
203 move |prev: Option<
204 ErrorBoundaryViewState<Chil::State, Fal::State>,
205 >| {
206 let _hook = throw_error::set_error_hook(Arc::clone(&hook));
207 if let Some(mut state) = prev {
208 match (self.errors_empty.get(), &mut state.fallback) {
209 (true, Some(fallback)) => {
211 fallback.insert_before_this(&mut state.children);
212 fallback.unmount();
213 state.fallback = None;
214 }
215 (false, None) => {
217 state.fallback = Some(
218 (self.fallback)(self.errors.clone()).build(),
219 );
220 state
221 .children
222 .insert_before_this(&mut state.fallback);
223 state.children.unmount();
224 }
225 _ => {}
229 }
230 state
231 } else {
232 let fallback = (!self.errors_empty.get())
233 .then(|| (self.fallback)(self.errors.clone()).build());
234 ErrorBoundaryViewState {
235 children: children.take().unwrap(),
236 fallback,
237 }
238 }
239 },
240 )
241 }
242
243 fn rebuild(self, state: &mut Self::State) {
244 let new = self.build();
245 let mut old = std::mem::replace(state, new);
246 old.insert_before_this(state);
247 old.unmount();
248 }
249}
250
251impl<Chil, FalFn, Fal> AddAnyAttr for ErrorBoundaryView<Chil, FalFn>
252where
253 Chil: RenderHtml + 'static,
254 FalFn: FnMut(ArcRwSignal<Errors>) -> Fal + Send + 'static,
255 Fal: RenderHtml + Send + 'static,
256{
257 type Output<SomeNewAttr: Attribute> =
258 ErrorBoundaryView<Chil::Output<SomeNewAttr::CloneableOwned>, FalFn>;
259
260 fn add_any_attr<NewAttr: Attribute>(
261 self,
262 attr: NewAttr,
263 ) -> Self::Output<NewAttr>
264 where
265 Self::Output<NewAttr>: RenderHtml,
266 {
267 let ErrorBoundaryView {
268 hook,
269 boundary_id,
270 errors_empty,
271 children,
272 fallback,
273 errors,
274 suspended_children,
275 } = self;
276 ErrorBoundaryView {
277 hook,
278 boundary_id,
279 errors_empty,
280 children: children.add_any_attr(attr.into_cloneable_owned()),
281 fallback,
282 errors,
283 suspended_children,
284 }
285 }
286}
287
288impl<Chil, FalFn, Fal> RenderHtml for ErrorBoundaryView<Chil, FalFn>
289where
290 Chil: RenderHtml + Send + 'static,
291 FalFn: FnMut(ArcRwSignal<Errors>) -> Fal + Send + 'static,
292 Fal: RenderHtml + Send + 'static,
293{
294 type AsyncOutput = ErrorBoundaryView<Chil::AsyncOutput, FalFn>;
295 type Owned = Self;
296
297 const MIN_LENGTH: usize = Chil::MIN_LENGTH;
298
299 fn dry_resolve(&mut self) {
300 self.children.dry_resolve();
301 }
302
303 async fn resolve(self) -> Self::AsyncOutput {
304 let ErrorBoundaryView {
305 hook,
306 boundary_id,
307 errors_empty,
308 children,
309 fallback,
310 errors,
311 suspended_children,
312 ..
313 } = self;
314 ErrorBoundaryView {
315 hook,
316 boundary_id,
317 errors_empty,
318 children: children.resolve().await,
319 fallback,
320 errors,
321 suspended_children,
322 }
323 }
324
325 fn to_html_with_buf(
326 mut self,
327 buf: &mut String,
328 position: &mut Position,
329 escape: bool,
330 mark_branches: bool,
331 extra_attrs: Vec<AnyAttribute>,
332 ) {
333 let _hook = throw_error::set_error_hook(self.hook);
335 let mut new_buf = String::with_capacity(Chil::MIN_LENGTH);
336 let mut new_pos = *position;
337 self.children.to_html_with_buf(
338 &mut new_buf,
339 &mut new_pos,
340 escape,
341 mark_branches,
342 extra_attrs.clone(),
343 );
344
345 if self.errors.with_untracked(|map| map.is_empty()) {
347 buf.push_str(&new_buf);
348 } else {
349 (self.fallback)(self.errors).to_html_with_buf(
351 buf,
352 position,
353 escape,
354 mark_branches,
355 extra_attrs,
356 );
357 }
358 }
359
360 fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
361 mut self,
362 buf: &mut StreamBuilder,
363 position: &mut Position,
364 escape: bool,
365 mark_branches: bool,
366 extra_attrs: Vec<AnyAttribute>,
367 ) where
368 Self: Sized,
369 {
370 let _hook = throw_error::set_error_hook(Arc::clone(&self.hook));
371
372 let mut new_buf = StreamBuilder::new(buf.clone_id());
374 let mut new_pos = *position;
375 self.children.to_html_async_with_buf::<OUT_OF_ORDER>(
376 &mut new_buf,
377 &mut new_pos,
378 escape,
379 mark_branches,
380 extra_attrs.clone(),
381 );
382
383 let suspense_children =
384 mem::take(&mut *self.suspended_children.write_value());
385
386 if suspense_children.is_empty() {
388 if self.errors.with_untracked(|map| map.is_empty()) {
390 buf.append(new_buf);
391 } else {
392 let mut fallback = String::with_capacity(Fal::MIN_LENGTH);
394 (self.fallback)(self.errors).to_html_with_buf(
395 &mut fallback,
396 position,
397 escape,
398 mark_branches,
399 extra_attrs,
400 );
401 buf.push_sync(&fallback);
402 }
403 } else {
404 let mut position = *position;
405 let mut view_buf = StreamBuilder::new(new_buf.clone_id());
409 view_buf.next_id();
410 let hook = Arc::clone(&self.hook);
411 view_buf.push_async(async move {
412 let _hook = throw_error::set_error_hook(Arc::clone(&hook));
413 let _ = join_all(suspense_children).await;
414
415 let mut my_chunks = VecDeque::new();
416 for chunk in new_buf.take_chunks() {
417 match chunk {
418 StreamChunk::Sync(data) => {
419 my_chunks.push_back(StreamChunk::Sync(data))
420 }
421 StreamChunk::Async { chunks } => {
422 let chunks = chunks.await;
423 my_chunks.extend(chunks);
424 }
425 StreamChunk::OutOfOrder { chunks } => {
426 let chunks = chunks.await;
427 my_chunks.push_back(StreamChunk::OutOfOrder {
428 chunks: Box::pin(async move { chunks }),
429 });
430 }
431 }
432 }
433
434 if self.errors.with_untracked(|map| map.is_empty()) {
435 my_chunks
437 } else {
438 let mut fallback = String::with_capacity(Fal::MIN_LENGTH);
440 (self.fallback)(self.errors).to_html_with_buf(
441 &mut fallback,
442 &mut position,
443 escape,
444 mark_branches,
445 extra_attrs,
446 );
447 my_chunks.clear();
448 my_chunks.push_back(StreamChunk::Sync(fallback));
449 my_chunks
450 }
451 });
452 buf.append(view_buf);
453 }
454 }
455
456 fn hydrate<const FROM_SERVER: bool>(
457 mut self,
458 cursor: &Cursor,
459 position: &PositionState,
460 ) -> Self::State {
461 let mut children = Some(self.children);
462 let hook = Arc::clone(&self.hook);
463 let cursor = cursor.to_owned();
464 let position = position.to_owned();
465 RenderEffect::new(
466 move |prev: Option<
467 ErrorBoundaryViewState<Chil::State, Fal::State>,
468 >| {
469 let _hook = throw_error::set_error_hook(Arc::clone(&hook));
470 if let Some(mut state) = prev {
471 match (self.errors_empty.get(), &mut state.fallback) {
472 (true, Some(fallback)) => {
474 fallback.insert_before_this(&mut state.children);
475 state.fallback.unmount();
476 state.fallback = None;
477 }
478 (false, None) => {
480 state.fallback = Some(
481 (self.fallback)(self.errors.clone()).build(),
482 );
483 state
484 .children
485 .insert_before_this(&mut state.fallback);
486 state.children.unmount();
487 }
488 _ => {}
492 }
493 state
494 } else {
495 let children = children.take().unwrap();
496 let (children, fallback) = if self.errors_empty.get() {
497 (
498 children.hydrate::<FROM_SERVER>(&cursor, &position),
499 None,
500 )
501 } else {
502 (
503 children.build(),
504 Some(
505 (self.fallback)(self.errors.clone())
506 .hydrate::<FROM_SERVER>(&cursor, &position),
507 ),
508 )
509 };
510
511 ErrorBoundaryViewState { children, fallback }
512 }
513 },
514 )
515 }
516
517 async fn hydrate_async(
518 self,
519 cursor: &Cursor,
520 position: &PositionState,
521 ) -> Self::State {
522 let mut children = Some(self.children);
523 let hook = Arc::clone(&self.hook);
524 let cursor = cursor.to_owned();
525 let position = position.to_owned();
526
527 let fallback_fn = Arc::new(Mutex::new(self.fallback));
528 let initial = {
529 let errors_empty = self.errors_empty.clone();
530 let errors = self.errors.clone();
531 let fallback_fn = Arc::clone(&fallback_fn);
532 async move {
533 let children = children.take().unwrap();
534 let (children, fallback) = if errors_empty.get() {
535 (children.hydrate_async(&cursor, &position).await, None)
536 } else {
537 let children = children.build();
538 let fallback =
539 (fallback_fn.lock().or_poisoned())(errors.clone());
540 let fallback =
541 fallback.hydrate_async(&cursor, &position).await;
542 (children, Some(fallback))
543 };
544
545 ErrorBoundaryViewState { children, fallback }
546 }
547 };
548
549 RenderEffect::new_with_async_value(
550 move |prev: Option<
551 ErrorBoundaryViewState<Chil::State, Fal::State>,
552 >| {
553 let _hook = throw_error::set_error_hook(Arc::clone(&hook));
554 if let Some(mut state) = prev {
555 match (self.errors_empty.get(), &mut state.fallback) {
556 (true, Some(fallback)) => {
558 fallback.insert_before_this(&mut state.children);
559 state.fallback.unmount();
560 state.fallback = None;
561 }
562 (false, None) => {
564 state.fallback = Some(
565 (fallback_fn.lock().or_poisoned())(
566 self.errors.clone(),
567 )
568 .build(),
569 );
570 state
571 .children
572 .insert_before_this(&mut state.fallback);
573 state.children.unmount();
574 }
575 _ => {}
579 }
580 state
581 } else {
582 unreachable!()
583 }
584 },
585 initial,
586 )
587 .await
588 }
589
590 fn into_owned(self) -> Self::Owned {
591 self
592 }
593}
594
595#[derive(Debug)]
596struct ErrorBoundaryErrorHook {
597 errors: ArcRwSignal<Errors>,
598 id: SerializedDataId,
599 shared_context: Option<Arc<dyn SharedContext + Send + Sync>>,
600}
601
602impl ErrorBoundaryErrorHook {
603 pub fn new(
604 id: SerializedDataId,
605 initial_errors: impl IntoIterator<Item = (ErrorId, Error)>,
606 ) -> Self {
607 Self {
608 errors: ArcRwSignal::new(Errors(
609 initial_errors.into_iter().collect(),
610 )),
611 id,
612 shared_context: Owner::current_shared_context(),
613 }
614 }
615}
616
617impl ErrorHook for ErrorBoundaryErrorHook {
618 fn throw(&self, error: Error) -> ErrorId {
619 let key: ErrorId = Owner::current_shared_context()
621 .map(|sc| sc.next_id())
622 .unwrap_or_default()
623 .into();
624
625 if let Some(sc) = &self.shared_context {
628 sc.register_error(self.id.clone(), key.clone(), error.clone());
629 }
630
631 self.errors.update(|map| {
633 map.insert(key.clone(), error);
634 });
635
636 key
639 }
640
641 fn clear(&self, id: &throw_error::ErrorId) {
642 self.errors.update(|map| {
643 map.remove(id);
644 });
645 }
646}
647
648#[derive(Debug, Clone, Default)]
650#[repr(transparent)]
651pub struct Errors(FxHashMap<ErrorId, Error>);
652
653impl Errors {
654 #[inline(always)]
656 pub fn is_empty(&self) -> bool {
657 self.0.is_empty()
658 }
659
660 pub fn insert<E>(&mut self, key: ErrorId, error: E)
662 where
663 E: Into<Error>,
664 {
665 self.0.insert(key, error.into());
666 }
667
668 pub fn insert_with_default_key<E>(&mut self, error: E)
670 where
671 E: Into<Error>,
672 {
673 self.0.insert(Default::default(), error.into());
674 }
675
676 pub fn remove(&mut self, key: &ErrorId) -> Option<Error> {
678 self.0.remove(key)
679 }
680
681 #[inline(always)]
683 pub fn iter(&self) -> Iter<'_> {
684 Iter(self.0.iter())
685 }
686}
687
688impl IntoIterator for Errors {
689 type Item = (ErrorId, Error);
690 type IntoIter = IntoIter;
691
692 #[inline(always)]
693 fn into_iter(self) -> Self::IntoIter {
694 IntoIter(self.0.into_iter())
695 }
696}
697
698#[repr(transparent)]
700pub struct IntoIter(std::collections::hash_map::IntoIter<ErrorId, Error>);
701
702impl Iterator for IntoIter {
703 type Item = (ErrorId, Error);
704
705 #[inline(always)]
706 fn next(
707 &mut self,
708 ) -> std::option::Option<<Self as std::iter::Iterator>::Item> {
709 self.0.next()
710 }
711}
712
713#[repr(transparent)]
715pub struct Iter<'a>(std::collections::hash_map::Iter<'a, ErrorId, Error>);
716
717impl<'a> Iterator for Iter<'a> {
718 type Item = (&'a ErrorId, &'a Error);
719
720 #[inline(always)]
721 fn next(
722 &mut self,
723 ) -> std::option::Option<<Self as std::iter::Iterator>::Item> {
724 self.0.next()
725 }
726}