1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
use chrono::{DateTime, Utc};
use fmterr::fmt_err;
use futures::{
    future::{try_join_all, BoxFuture},
    FutureExt,
};
use serde_json::Value;
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use sycamore::web::SsrNode;

use super::Turbine;
use crate::{
    error_views::ServerErrorData,
    reactor::RenderMode,
    router::{match_route, FullRouteVerdict},
    template::Entity,
};
use crate::{
    errors::*,
    i18n::{TranslationsManager, Translator},
    internal::{PageData, PageDataPartial},
    path::*,
    server::get_path_slice,
    state::StateGeneratorInfo,
    stores::MutableStore,
    template::States,
    Request,
};
use crate::{
    state::{TemplateState, UnknownStateType},
    utils::ssr_fallible,
};

/// This is `PageDataPartial`, but it keeps the state as `TemplateState` for
/// internal convenience.
struct StateAndHead {
    state: TemplateState,
    head: String,
}

impl<M: MutableStore, T: TranslationsManager> Turbine<M, T> {
    /// Gets the state for the given path. This will render the head, but it
    /// will *not* render the contents, and, as a result, will not engage
    /// with any dependencies this page/widget may have. If this is used to
    /// get the state of a capsule, the head will of course be empty.
    ///
    /// This assumes the given locale is actually supported.
    pub async fn get_state_for_path(
        &self,
        path: PathWithoutLocale,
        locale: String,
        entity_name: &str,
        was_incremental: bool,
        req: Request,
    ) -> Result<PageDataPartial, ServerError> {
        let translator = self
            .translations_manager
            .get_translator_for_locale(locale)
            .await?;
        let StateAndHead { state, head } = self
            .get_state_for_path_internal(
                path,
                &translator,
                entity_name,
                was_incremental,
                req,
                None,
                None,
                false, /* This is not an initial load (so the browser will need a little `Ok` in
                        * the `Value`) */
            )
            .await?;

        Ok(PageDataPartial {
            state: state.state,
            head,
        })
    }
    /// Gets the full page data for the given path. This will generate the
    /// state, render the head, and render the content of the page,
    /// resolving all widget dependencies.
    ///
    /// This takes a translator to allow the caller to derive it in a way that
    /// respects the likely need to know the translations string as well,
    /// for error page interpolation.
    ///
    /// Like `.get_state_for_path()`, this returns the page data and the global
    /// state in a tuple.
    ///
    /// # Pitfalls
    /// This currently uses a layer-based dependency resolution algorithm, as a
    /// widget may itself have widgets. However, the widgets a page/widget
    /// uses may be dependent on its state, and therefore we cannot enumerate
    /// the entire dependency tree without knowing all the states involved.
    /// Therefore, we go layer-by-layer. Currently, we wait for each layer
    /// to be fully complete before proceeding to the next one, which leads to
    /// a layer taking as long as the longest state generation within it. This
    /// can lead to poor render times when widgets are highly nested, a
    /// pattern that should be avoided as much as possible.
    ///
    /// In future, this will build with maximal parallelism by not waiting for
    /// each layer to be finished building before proceeding to the next
    /// one.
    pub async fn get_initial_load_for_path(
        &self,
        path: PathWithoutLocale,
        translator: &Translator,
        template: &Entity<SsrNode>,
        was_incremental: bool,
        req: Request,
    ) -> Result<(PageData, TemplateState), ServerError> {
        let locale = translator.get_locale();
        // Get the latest global state, which we'll share around
        let global_state = self.get_full_global_state(clone_req(&req)).await?;
        // Begin by generating the state for this page
        let page_state = self
            .get_state_for_path_internal(
                path.clone(),
                translator,
                &template.get_path(),
                was_incremental,
                clone_req(&req),
                Some(template),
                Some(global_state.clone()),
                true, // This is an initial load
            )
            .await?;

        let path = PathWithoutLocale(path.strip_suffix('/').unwrap_or(&*path).to_string());
        // Yes, this is created twice; no, we don't care
        // If we're interacting with the stores, this is the path this page/widget will
        // be under
        let path_encoded = format!("{}-{}", locale, urlencoding::encode(&path));

        // The page state generation process will have updated any prerendered fragments
        // of this page, which means they're guaranteed to be up-to-date.
        // Importantly, if any of the dependencies weren't build-safe, or if the page
        // uses request-state (which means, as explained above, we don't
        // actually know what the dependencies are yet, let alone if they're
        // build-safe), this fragment won't exist. Basically, if it exists, we
        // can return it straight away with no extra work. Otherwise, we'll have to do a
        // layer-by-layer render, which can handle non-build-safe dependencies.
        // We call this a 'fragment' because it's not a complete HTML shell etc. (TODO?)
        let prerendered_fragment_res = if template.revalidates() {
            self.mutable_store
                .read(&format!("static/{}.html", &path_encoded))
                .await
        } else {
            self.immutable_store
                .read(&format!("static/{}.html", &path_encoded))
                .await
        };
        // Propagate any errors, but if the asset wasn't found, then record that as
        // `None`
        let prerendered_fragment = match prerendered_fragment_res {
            Ok(fragment) => Some(fragment),
            Err(StoreError::NotFound { .. }) => None,
            Err(err) => return Err(err.into()),
        };

        if let Some(prerendered_fragment) = prerendered_fragment {
            // If there was a prerendered fragment, there will also be a record of the
            // widget states we need to send to the client
            let widget_states = if template.revalidates() {
                self.mutable_store
                    .read(&format!("static/{}.widgets.json", &path_encoded))
                    .await?
            } else {
                self.immutable_store
                    .read(&format!("static/{}.widgets.json", &path_encoded))
                    .await?
            };
            // From the build process, these are infallible
            let widget_states = match serde_json::from_str::<
                HashMap<PathMaybeWithLocale, (String, Value)>,
            >(&widget_states)
            {
                Ok(widget_states) => widget_states,
                Err(err) => return Err(ServerError::InvalidPageState { source: err }),
            };
            Ok((
                PageData {
                    content: prerendered_fragment,
                    head: page_state.head,
                    state: page_state.state.state,
                    widget_states: widget_states
                        .into_iter()
                        // Discard the capsule names and create results (to match with the
                        // possibility of request-time failure)
                        .map(|(k, (_, v))| (k, Ok(v)))
                        .collect(),
                },
                global_state,
            ))
        } else {
            // This will block
            let (final_widget_states, prerendered) = self
                .render_all(
                    HashMap::new(), // This starts empty
                    path,
                    locale.to_string(),
                    page_state.state.clone(),
                    template,
                    global_state.clone(),
                    &req,
                    translator,
                )?
                .await?;
            // Convert the `TemplateState`s into `Value`s
            let final_widget_states = final_widget_states
                .into_iter()
                // We need to turn `TemplateState` into its underlying `Value`
                .map(|(k, res)| (k, res.map(|s| s.state)))
                .collect::<HashMap<_, _>>();

            Ok((
                PageData {
                    content: prerendered,
                    head: page_state.head,
                    state: page_state.state.state,
                    widget_states: final_widget_states,
                },
                global_state,
            ))
        }
    }
    /// Recurses through each layer of dependencies and eventually renders the
    /// given page.
    ///
    /// This returns a tuple of widget states and the prerendered result.
    ///
    /// This is deliberately synchronous to avoid making `Self` `Sync`, which is
    /// impossible with Perseus' current design. Thus, this blocks when
    /// resolving each layer.
    #[allow(clippy::too_many_arguments)]
    #[allow(clippy::type_complexity)]
    fn render_all<'a>(
        &'a self,
        // This is a map of widget paths to their states, which we'll populate as
        // we go through. That way, we can just run the exact same render over and over
        // again, getting to a new layer each time, since, if a widget finds its state in
        // this, it'll use it. This will be progressively accumulated over many layers.
        widget_states: HashMap<PathMaybeWithLocale, Result<TemplateState, ServerErrorData>>,
        path: PathWithoutLocale,
        locale: String,
        state: TemplateState,
        entity: &'a Entity<SsrNode>, // Recursion could make this either a template or a capsule
        global_state: TemplateState,
        req: &'a Request,
        translator: &'a Translator,
    ) -> Result<
        BoxFuture<
            'a,
            Result<
                (
                    HashMap<PathMaybeWithLocale, Result<TemplateState, ServerErrorData>>,
                    String,
                ),
                ServerError,
            >,
        >,
        ServerError,
    > {
        // Misleadingly, this only has the locale if we're using i18n!
        let full_path = PathMaybeWithLocale::new(&path, &locale);

        // We put this in an `Rc` so it can be put in the context and given to multiple
        // widgets, but it will never be changed (we could have a lot of states
        // here, so we want to minimize cloning where possible)
        let widget_states_rc = Rc::new(widget_states);
        // This will be used to store the paths of widgets that haven't yet been
        // resolved. It will be cleared between layers.
        let unresolved_widget_accumulator = Rc::new(RefCell::new(Vec::new()));
        // Now we want to render the page in the dependency resolution mode (as opposed
        // to the build mode, which just cancels the render if it finds any
        // non-build-safe widgets).
        let mode = RenderMode::Request {
            widget_states: widget_states_rc.clone(),
            error_views: self.error_views.clone(),
            unresolved_widget_accumulator: unresolved_widget_accumulator.clone(),
        };

        // Start the first render. This registers all our mode stuff on `cx`,
        // which is dropped when this is done. So, we can safely get the widget states
        // back.
        // Now prerender the actual content (a bit roundabout for error handling)
        let prerendered = ssr_fallible(|cx| {
            entity.render_for_template_server(
                full_path.clone(),
                state.clone(),
                global_state.clone(),
                mode.clone(),
                cx,
                translator,
            )
        })?;
        // // As explained above, this should never fail, because all references have
        // been // dropped
        // let mut widget_states = Rc::try_unwrap(widget_states_rc).unwrap();
        // TODO Avoid cloning here...
        let mut widget_states = (*widget_states_rc).clone();

        // We'll just have accumulated a ton of unresolved widgets, probably. If not,
        // then we're done! If yes, we'll need to build all their states.
        // TODO ...and here
        let mut accumulator = (*unresolved_widget_accumulator).clone().into_inner();

        let fut = async move {
            if accumulator.is_empty() {
                Ok((widget_states, prerendered))
            } else {
                // First, deduplicate (relevant if the same widget is used more than once). We
                // don't care about unstable sorting because these are strings.
                accumulator.sort_unstable();
                accumulator.dedup();

                let mut futs = Vec::new();
                for widget_path in accumulator.into_iter() {
                    let global_state = global_state.clone();
                    let locale = locale.clone();
                    futs.push(async move {
                        // Resolve the route
                        // Get a route verdict to determine the capsule this widget path maps to
                        let localized_widget_path = PathMaybeWithLocale::new(&widget_path, &locale);
                        let path_slice = get_path_slice(&localized_widget_path);
                        let verdict = match_route(
                            &path_slice,
                            &self.render_cfg,
                            &self.entities,
                            &self.locales,
                        );

                        let res = match verdict.into_full(&self.entities) {
                            FullRouteVerdict::Found(route_info) => {
                                let capsule_name = route_info.entity.get_path();

                                // Now build the state; if this fails, we won't fail the whole
                                // page, we'll just load an error for this particular widget
                                // (allowing the user to still see the rest of the page). If this
                                // sort of thing were to happen in a subsequent load, the browser
                                // would be responsible for this.
                                self.get_state_for_path_internal(
                                    widget_path.clone(),
                                    translator,
                                    &capsule_name,
                                    route_info.was_incremental_match,
                                    clone_req(req),
                                    // We do happen to actually have this from the routing
                                    Some(route_info.entity),
                                    Some(global_state),
                                    true, /* This is an initial load, so don't put an `Ok` in
                                           * the `Value` */
                                )
                                .await
                                // The error handling systems will need a client-style error,
                                // so we just make the same conversion that would be made on
                                // the browser-side
                                .map_err(|err| ServerErrorData {
                                    status: err_to_status_code(&err),
                                    msg: fmt_err(&err),
                                })
                                // And discard the head (it's a widget)
                                .map(|state| state.state)
                            }
                            // This is just completely wrong, and implies a corruption, so it's made
                            // a page-level error
                            FullRouteVerdict::LocaleDetection(_) => {
                                return Err(ServerError::ResolveDepLocaleRedirection {
                                    locale: locale.to_string(),
                                    widget: widget_path.to_string(),
                                })
                            }
                            // But a widget that isn't found will be made a widget-only error
                            FullRouteVerdict::NotFound { .. } => {
                                let err = ServerError::ResolveDepNotFound {
                                    locale: locale.to_string(),
                                    widget: widget_path.to_string(),
                                };
                                Err(ServerErrorData {
                                    status: err_to_status_code(&err),
                                    msg: fmt_err(&err),
                                })
                            }
                        };

                        // Return the tuples that'll go into `widget_states`
                        Ok((localized_widget_path, res))
                    });
                }
                let tuples = try_join_all(futs).await?;
                widget_states.extend(tuples);

                // We've rendered this layer, and we're ready for the next one
                self.render_all(
                    widget_states,
                    path,
                    locale,
                    state,
                    entity,
                    global_state,
                    req,
                    translator,
                )?
                .await
            }
        }
        .boxed();
        Ok(fut)
    }

    /// The internal version allows sharing a global state so we don't
    /// constantly regenerate it in recursion.
    ///
    /// This assumes the given locale is supported.
    #[allow(clippy::too_many_arguments)]
    async fn get_state_for_path_internal(
        &self,
        path: PathWithoutLocale, /* This must not contain the locale, but it *will* contain the
                                  * entity name */
        translator: &Translator,
        entity_name: &str,
        was_incremental: bool,
        req: Request,
        // If these are `None`, we'll generate them
        entity: Option<&Entity<SsrNode>>, // Not for recursion, just convenience
        global_state: Option<TemplateState>,
        is_initial: bool,
    ) -> Result<StateAndHead, ServerError> {
        let locale = translator.get_locale();
        // This could be very different from the build-time global state
        let global_state = match global_state {
            Some(global_state) => global_state,
            None => self.get_full_global_state(clone_req(&req)).await?,
        };

        let entity = match entity {
            Some(entity) => entity,
            None => self
                .entities
                .get(entity_name)
                .ok_or(ServeError::PageNotFound {
                    path: path.to_string(),
                })?,
        };

        let path = PathWithoutLocale(path.strip_suffix('/').unwrap_or(&*path).to_string());
        // If we're interacting with the stores, this is the path this page/widget will
        // be under
        let path_encoded = format!("{}-{}", locale, urlencoding::encode(&path));

        // Any work we do with the build logic will expect the path without the template
        // name, so we need to strip it (this could only fail if we'd mismatched
        // the path to the entity name, which would be either a malformed
        // request or a *critical* Perseus routing bug)
        let pure_path = path
            .strip_prefix(entity_name)
            .ok_or(ServerError::TemplateNameNotInPath)?;
        let pure_path = pure_path.strip_prefix('/').unwrap_or(pure_path);
        let pure_path = PurePath(pure_path.to_string());

        // If the entity is basic (i.e. has no state), bail early
        if entity.is_basic() {
            // Get the head (since this is basic, it has no state, and therefore
            // this would've been written at build-time)
            let head = if entity.is_capsule {
                String::new()
            } else {
                self.immutable_store
                    .read(&format!("static/{}.head.html", &path_encoded))
                    .await?
            };

            return Ok(StateAndHead {
                // No, this state is never written anywhere at build-time
                state: TemplateState::empty(),
                head,
            });
        }

        // No matter what we end up doing, we're probably going to need this (which will
        // always exist)
        let build_extra = match self
            .immutable_store
            .read(&format!(
                "static/{}.extra.json",
                urlencoding::encode(&entity.get_path())
            ))
            .await
        {
            Ok(state) => {
                TemplateState::from_str(&state).map_err(|err| ServerError::InvalidBuildExtra {
                    template_name: entity.get_path(),
                    source: err,
                })?
            }
            // If this happens, then the immutable store has been tampered with, since
            // the build logic generates some kind of state for everything
            Err(_) => {
                return Err(ServerError::MissingBuildExtra {
                    template_name: entity.get_path(),
                })
            }
        };
        // We'll need this too for any sort of state generation
        let build_info = StateGeneratorInfo {
            path: path.to_string(),
            locale: locale.to_string(),
            extra: build_extra.clone(),
        };

        // The aim of this next block is purely to ensure that whatever is in the
        // im/mutable store is the latest and most valid version of the build
        // state, if we're even using build state.
        //
        // Note that `was_incremental_match` will not be `true` for pages built
        // with build paths, even if the template uses incremental generation. Thus,
        // if it is `true`, we use the mutable store.
        //
        // If incremental and generated and not revalidating; get from *mutable*.
        // If incremental and not generated; generate.
        // If incremental and generated and revalidating; either get from mutable or
        // revalidate.
        // If not incremental and revalidating; either get from
        // mutable or revalidate.
        // If not incremental and not revalidating; get
        // from immutable.
        if was_incremental {
            // If we have something in the mutable store, then this has already been
            // generated
            let res = self
                .mutable_store
                .read(&format!("static/{}.json", &path_encoded))
                .await;
            // Propagate any errors, but if the asset wasn't found, then record that as
            // `None`
            let built_state = match res {
                Ok(built_state) => Some(built_state),
                Err(StoreError::NotFound { .. }) => None,
                Err(err) => return Err(err.into()),
            };

            if built_state.is_some() {
                // This has been generated already, so we need to check for the possibility of
                // revalidation
                let should_revalidate = self
                    .page_or_widget_should_revalidate(
                        &path_encoded,
                        entity,
                        build_info.clone(),
                        clone_req(&req),
                    )
                    .await?;
                if should_revalidate {
                    // We need to rebuild, which we can do with the build-time logic (which will use
                    // the mutable store)
                    self.build_path_or_widget_for_locale(
                        pure_path,
                        entity,
                        &build_extra,
                        &locale,
                        global_state.clone(),
                        false,
                        true,
                    )
                    .await?;
                } else {
                    // We don't need to revalidate, so whatever is in the
                    // mutable store is valid
                }
            } else {
                // This is a new page, we need to actually generate it (which will handle any
                // revalidation timestamps etc.). For this, we can use the usual
                // build state logic, which will perform a full render, unless the
                // dependencies aren't build-safe. Of course, we can guarantee if we're actually
                // generating it now that it won't be revalidating.
                // We can provide the most up-to-date global state to this.
                self.build_path_or_widget_for_locale(
                    pure_path,
                    entity,
                    &build_extra,
                    &locale,
                    global_state.clone(),
                    false,
                    // This makes sure we use the mutable store no matter what (incremental)
                    true,
                )
                .await?;
            }
        } else {
            let should_revalidate = self
                .page_or_widget_should_revalidate(
                    &path_encoded,
                    entity,
                    build_info.clone(),
                    clone_req(&req),
                )
                .await?;
            if should_revalidate {
                // We need to rebuild, which we can do with the build-time logic
                self.build_path_or_widget_for_locale(
                    pure_path,
                    entity,
                    &build_extra,
                    &locale,
                    global_state.clone(),
                    false,
                    false,
                )
                .await?;
            } else {
                // We don't need to revalidate, so whatever is in the immutable
                // store is valid
            }
        }

        // Whatever is in the im/mutable store is now valid and up-to-date, so fetch it
        let build_state = if entity.uses_build_state() {
            let state_str = if was_incremental || entity.revalidates() {
                self.mutable_store
                    .read(&format!("static/{}.json", &path_encoded))
                    .await?
            } else {
                self.immutable_store
                    .read(&format!("static/{}.json", &path_encoded))
                    .await?
            };
            TemplateState::from_str(&state_str)
                .map_err(|err| ServerError::InvalidPageState { source: err })?
        } else {
            TemplateState::empty()
        };

        // Now get the request state if we're using it (of course, this must be
        // re-generated for every request)
        let request_state = if entity.uses_request_state() {
            entity
                .get_request_state(build_info.clone(), clone_req(&req))
                .await?
        } else {
            TemplateState::empty()
        };

        // Now handle the possibility of amalgamation
        let states = States {
            build_state,
            request_state,
        };
        let final_state = if states.both_defined() && entity.can_amalgamate_states() {
            entity
                .amalgamate_states(build_info, states.build_state, states.request_state)
                .await?
        } else if states.both_defined() && !entity.can_amalgamate_states() {
            // We have both states, but can't amalgamate, so prioritze request state, as
            // it's more personalized and more recent
            states.request_state
        } else {
            // This only errors if both are defined, and we just checked that
            states.get_defined().unwrap()
        };

        // We now need to render the head. Whatever is on the im/mutable store is the
        // most up-to-date, and that won't have been written if we have an
        // entity that uses request state (since it would always be invalid).
        // Therefore, if we don't use request state, it'll be in the appropriate store,
        // otherwise we'll need to render it ourselves. Of course, capsules
        // don't have heads.
        let head_str = if !entity.is_capsule {
            if entity.uses_request_state() {
                entity.render_head_str(final_state.clone(), global_state.clone(), translator)?
            } else {
                // The im/mutable store was updated by the last whole block (since any
                // incremental generation or revalidation would have re-written
                // the head if request state isn't being used)
                if was_incremental || entity.revalidates() {
                    self.mutable_store
                        .read(&format!("static/{}.head.html", &path_encoded))
                        .await?
                } else {
                    self.immutable_store
                        .read(&format!("static/{}.head.html", &path_encoded))
                        .await?
                }
            }
        } else {
            String::new()
        };

        // On the browser-side, widgets states will always be parsed as fallible, so, if
        // this is from a capsule, we'll wrap it in `Ok` (working around this on
        // the browser-side is more complex than a simple fix here) --- note
        // that the kinds of `Err` variants on widget states that can be caused
        // in the initial load process would just be returned directly as errors
        // earlier from here (and would be accordingly handled on the browser-side).
        //
        // We should only do this on subsequent loads (initial loads do error handling
        // more normally).
        let final_state = if entity.is_capsule && !is_initial {
            let val = final_state.state;
            let ok_val = serde_json::to_value(Ok::<Value, ()>(val)).unwrap();
            TemplateState::from_value(ok_val)
        } else {
            final_state
        };

        Ok(StateAndHead {
            state: final_state,
            head: head_str,
        })
    }

    /// Checks timestamps and runs user-provided logic to determine if the given
    /// widget/path should revalidate at the present time.
    async fn page_or_widget_should_revalidate(
        &self,
        path_encoded: &str,
        entity: &Entity<SsrNode>,
        build_info: StateGeneratorInfo<UnknownStateType>,
        req: Request,
    ) -> Result<bool, ServerError> {
        let mut should_revalidate = false;
        // If it revalidates after a certain period of time, we need to check that
        // BEFORE the custom logic (clearly documented)
        if entity.revalidates_with_time() {
            // Get the time when it should revalidate (RFC 3339)
            // This will be updated, so it's in a mutable store
            let datetime_to_revalidate_str = self
                .mutable_store
                .read(&format!("static/{}.revld.txt", path_encoded))
                .await?;
            let datetime_to_revalidate = DateTime::parse_from_rfc3339(&datetime_to_revalidate_str)
                .map_err(|err| {
                    ServerError::ServeError(ServeError::BadRevalidate { source: err })
                })?;
            // Get the current time (UTC)
            let now = Utc::now();

            // If the datetime to revalidate is still in the future, end with `false` (the
            // custom logic is only executed if the time-based one passes)
            if datetime_to_revalidate > now {
                return Ok(false);
            }
            should_revalidate = true;
        }

        // Now run the user's custom revalidation logic
        if entity.revalidates_with_logic() {
            should_revalidate = entity.should_revalidate(build_info, req).await?;
        }
        Ok(should_revalidate)
    }
    /// Gets the full global state from the state generated at build-time and
    /// the generator itself.
    ///
    /// This should only be called once per API call.
    pub(crate) async fn get_full_global_state(
        &self,
        req: Request,
    ) -> Result<TemplateState, ServerError> {
        let gsc = &self.global_state_creator;
        // We know the locale is supported
        let built_state = &self.global_state;

        let global_state = if gsc.uses_request_state() {
            let req_state = gsc.get_request_state(req).await?;
            // If we have a non-empty build-time state, we'll need to amalgamate
            if !built_state.is_empty() {
                if gsc.can_amalgamate_states() {
                    gsc.amalgamate_states(built_state.clone(), req_state)
                        .await?
                } else {
                    // No amalgamation capability, request time state takes priority
                    req_state
                }
            } else {
                req_state
            }
        } else {
            // This global state is purely generated at build-time (or nonexistent)
            built_state.clone()
        };

        Ok(global_state)
    }
}

/// Clones a `Request` from its internal parts.
fn clone_req(raw: &Request) -> Request {
    let mut builder = Request::builder();

    for (name, val) in raw.headers() {
        builder = builder.header(name, val);
    }

    builder
        .uri(raw.uri())
        .method(raw.method())
        .version(raw.version())
        // We always use an empty body because, in a Perseus request, only the URI matters
        // Any custom data should therefore be sent in headers (if you're doing that, consider a
        // dedicated API)
        .body(())
        .unwrap() // This should never fail...
}