From 5473683a59db2aa19aabe5986461b739458a0470 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Wed, 13 Jul 2022 16:27:40 +1000 Subject: [PATCH] feat!: passed path, locale, and request to logic-based revalidation --- .../src/templates/revalidation.rs | 6 +- ...revalidation_and_incremental_generation.rs | 6 +- packages/perseus-macro/src/state_fns.rs | 2 +- packages/perseus/src/server/render.rs | 55 +++++++++++++++++-- packages/perseus/src/template/core.rs | 17 +++++- 5 files changed, 75 insertions(+), 11 deletions(-) diff --git a/examples/core/state_generation/src/templates/revalidation.rs b/examples/core/state_generation/src/templates/revalidation.rs index a01ba8bad7..5295a0bb1f 100644 --- a/examples/core/state_generation/src/templates/revalidation.rs +++ b/examples/core/state_generation/src/templates/revalidation.rs @@ -40,7 +40,11 @@ pub async fn get_build_state(_path: String, _locale: String) -> RenderFnResultWi // revalidated This acts as a secondary check, and can perform arbitrary logic // to check if we should actually revalidate a page #[perseus::should_revalidate] -pub async fn should_revalidate() -> RenderFnResultWithCause { +pub async fn should_revalidate( + _path: String, + _locale: String, + _req: perseus::Request, +) -> RenderFnResultWithCause { // For simplicity's sake, this will always say we should revalidate, but you // could amke this check any condition Ok(true) diff --git a/examples/core/state_generation/src/templates/revalidation_and_incremental_generation.rs b/examples/core/state_generation/src/templates/revalidation_and_incremental_generation.rs index c83f247aaf..66ca207d43 100644 --- a/examples/core/state_generation/src/templates/revalidation_and_incremental_generation.rs +++ b/examples/core/state_generation/src/templates/revalidation_and_incremental_generation.rs @@ -53,7 +53,11 @@ pub async fn get_build_paths() -> RenderFnResult> { // revalidated This acts as a secondary check, and can perform arbitrary logic // to check if we should actually revalidate a page #[perseus::should_revalidate] -pub async fn should_revalidate() -> RenderFnResultWithCause { +pub async fn should_revalidate( + _path: String, + _locale: String, + _req: perseus::Request, +) -> RenderFnResultWithCause { // For simplicity's sake, this will always say we should revalidate, but you // could amke this check any condition Ok(true) diff --git a/packages/perseus-macro/src/state_fns.rs b/packages/perseus-macro/src/state_fns.rs index 1c306b8fef..1690c0638e 100644 --- a/packages/perseus-macro/src/state_fns.rs +++ b/packages/perseus-macro/src/state_fns.rs @@ -251,7 +251,7 @@ pub fn state_fn_impl(input: StateFn, fn_type: StateFnType) -> TokenStream { // This normal version is identical to the user's (we know it won't have any arguments, and we know its return type) // We use the user's return type to prevent unused imports warnings in their code #[cfg(not(target_arch = "wasm32"))] - #vis async fn #name() -> #return_type { + #vis async fn #name(path: ::std::string::String, locale: ::std::string::String, req: ::perseus::Request) -> #return_type { #block } }, diff --git a/packages/perseus/src/server/render.rs b/packages/perseus/src/server/render.rs index c67959df17..a3b7be2225 100644 --- a/packages/perseus/src/server/render.rs +++ b/packages/perseus/src/server/render.rs @@ -8,6 +8,25 @@ use crate::Request; use crate::SsrNode; use chrono::{DateTime, Utc}; +/// 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... +} + /// Gets the path with the locale, returning it without if i18n isn't being /// used. fn get_path_with_locale(path_without_locale: &str, translator: &Translator) -> String { @@ -170,6 +189,9 @@ async fn should_revalidate( template: &Template, path_encoded: &str, mutable_store: &impl MutableStore, + translator: &Translator, + path: &str, + req: Request, ) -> Result { let mut should_revalidate = false; // If it revalidates after a certain period of time, we needd to check that @@ -197,7 +219,9 @@ async fn should_revalidate( // Now run the user's custom revalidation logic if template.revalidates_with_logic() { - should_revalidate = template.should_revalidate().await?; + should_revalidate = template + .should_revalidate(path.to_string(), translator.get_locale(), req) + .await?; } Ok(should_revalidate) } @@ -299,7 +323,6 @@ pub struct GetPageProps<'a, M: MutableStore, T: TranslationsManager> { /// load server-side routing). Because this handles templates with potentially /// revalidation and incremental generation, it uses both mutable and immutable /// stores. -// TODO possible further optimizations on this for futures? pub async fn get_page_for_template( GetPageProps { raw_path, @@ -313,6 +336,10 @@ pub async fn get_page_for_template( }: GetPageProps<'_, M, T>, template: &Template, ) -> Result { + // Since `Request` is not actually `Clone`able, we hack our way around needing + // it twice An `Rc` won't work because of future constraints, and an `Arc` + // seems a little unnecessary + let req_2 = clone_req(&req); // Get a translator for this locale (for sanity we hope the manager is caching) let translator = translations_manager .get_translator_for_locale(locale.to_string()) @@ -350,7 +377,16 @@ pub async fn get_page_for_template( // It's cached Some((html_val, head_val)) => { // Check if we need to revalidate - if should_revalidate(template, &path_encoded, mutable_store).await? { + if should_revalidate( + template, + &path_encoded, + mutable_store, + &translator, + path, + req, + ) + .await? + { let (html_val, head_val, state) = revalidate( template, &translator, @@ -448,7 +484,16 @@ pub async fn get_page_for_template( // Handle if we need to revalidate // It'll be in the mutable store if we do - if should_revalidate(template, &path_encoded, mutable_store).await? { + if should_revalidate( + template, + &path_encoded, + mutable_store, + &translator, + path, + req, + ) + .await? + { let (html_val, head_val, state) = revalidate( template, &translator, @@ -492,7 +537,7 @@ pub async fn get_page_for_template( // page will be built soon If we're not, and there's no build state, // then we still need to build, which we'll do after we've checked for // amalgamation - let state = get_request_state(template, &translator, path, req).await?; + let state = get_request_state(template, &translator, path, req_2).await?; states.request_state = state; } diff --git a/packages/perseus/src/template/core.rs b/packages/perseus/src/template/core.rs index 2dd1225803..273cfb3ebe 100644 --- a/packages/perseus/src/template/core.rs +++ b/packages/perseus/src/template/core.rs @@ -69,7 +69,13 @@ make_async_trait!( req: Request ); #[cfg(not(target_arch = "wasm32"))] -make_async_trait!(ShouldRevalidateFnType, RenderFnResultWithCause); +make_async_trait!( + ShouldRevalidateFnType, + RenderFnResultWithCause, + path: String, + locale: String, + req: Request +); #[cfg(not(target_arch = "wasm32"))] make_async_trait!( AmalgamateStatesFnType, @@ -423,9 +429,14 @@ impl Template { /// can be caused by either the server or the client, so the /// user must specify an [`ErrorCause`]. #[cfg(not(target_arch = "wasm32"))] - pub async fn should_revalidate(&self) -> Result { + pub async fn should_revalidate( + &self, + path: String, + locale: String, + req: Request, + ) -> Result { if let Some(should_revalidate) = &self.should_revalidate { - let res = should_revalidate.call().await; + let res = should_revalidate.call(path, locale, req).await; match res { Ok(res) => Ok(res), Err(GenericErrorWithCause { error, cause }) => Err(ServerError::RenderFnFailed {