Skip to content

Commit

Permalink
feat!: passed path, locale, and request to logic-based revalidation
Browse files Browse the repository at this point in the history
  • Loading branch information
arctic-hen7 committed Jul 13, 2022
1 parent 869000b commit 5473683
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 11 deletions.
6 changes: 5 additions & 1 deletion examples/core/state_generation/src/templates/revalidation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool> {
pub async fn should_revalidate(
_path: String,
_locale: String,
_req: perseus::Request,
) -> RenderFnResultWithCause<bool> {
// For simplicity's sake, this will always say we should revalidate, but you
// could amke this check any condition
Ok(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ pub async fn get_build_paths() -> RenderFnResult<Vec<String>> {
// 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<bool> {
pub async fn should_revalidate(
_path: String,
_locale: String,
_req: perseus::Request,
) -> RenderFnResultWithCause<bool> {
// For simplicity's sake, this will always say we should revalidate, but you
// could amke this check any condition
Ok(true)
Expand Down
2 changes: 1 addition & 1 deletion packages/perseus-macro/src/state_fns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
},
Expand Down
55 changes: 50 additions & 5 deletions packages/perseus/src/server/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -170,6 +189,9 @@ async fn should_revalidate(
template: &Template<SsrNode>,
path_encoded: &str,
mutable_store: &impl MutableStore,
translator: &Translator,
path: &str,
req: Request,
) -> Result<bool, ServerError> {
let mut should_revalidate = false;
// If it revalidates after a certain period of time, we needd to check that
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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<M: MutableStore, T: TranslationsManager>(
GetPageProps {
raw_path,
Expand All @@ -313,6 +336,10 @@ pub async fn get_page_for_template<M: MutableStore, T: TranslationsManager>(
}: GetPageProps<'_, M, T>,
template: &Template<SsrNode>,
) -> Result<PageData, ServerError> {
// 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())
Expand Down Expand Up @@ -350,7 +377,16 @@ pub async fn get_page_for_template<M: MutableStore, T: TranslationsManager>(
// 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,
Expand Down Expand Up @@ -448,7 +484,16 @@ pub async fn get_page_for_template<M: MutableStore, T: TranslationsManager>(

// 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,
Expand Down Expand Up @@ -492,7 +537,7 @@ pub async fn get_page_for_template<M: MutableStore, T: TranslationsManager>(
// 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;
}

Expand Down
17 changes: 14 additions & 3 deletions packages/perseus/src/template/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,13 @@ make_async_trait!(
req: Request
);
#[cfg(not(target_arch = "wasm32"))]
make_async_trait!(ShouldRevalidateFnType, RenderFnResultWithCause<bool>);
make_async_trait!(
ShouldRevalidateFnType,
RenderFnResultWithCause<bool>,
path: String,
locale: String,
req: Request
);
#[cfg(not(target_arch = "wasm32"))]
make_async_trait!(
AmalgamateStatesFnType,
Expand Down Expand Up @@ -423,9 +429,14 @@ impl<G: Html> Template<G> {
/// 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<bool, ServerError> {
pub async fn should_revalidate(
&self,
path: String,
locale: String,
req: Request,
) -> Result<bool, ServerError> {
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 {
Expand Down

0 comments on commit 5473683

Please sign in to comment.