Skip to content

Commit 76ed6fc

Browse files
committed
fix: fixed revalidation time strings
1 parent 24d7edc commit 76ed6fc

File tree

4 files changed

+138
-88
lines changed

4 files changed

+138
-88
lines changed

examples/core/state_generation/src/templates/revalidation.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use perseus::{RenderFnResultWithCause, Template};
2-
use std::time::Duration;
32
use sycamore::prelude::{view, Html, Scope, View};
43

54
#[perseus::make_rx(PageStateRx)]
@@ -18,7 +17,7 @@ pub fn get_template<G: Html>() -> Template<G> {
1817
Template::new("revalidation")
1918
.template(revalidation_page)
2019
// This page will revalidate every five seconds (and so the time displayed will be updated)
21-
.revalidate_after(Duration::new(5, 0))
20+
.revalidate_after("5s")
2221
// This is an alternative method of revalidation that uses logic, which will be executed
2322
// every itme a user tries to load this page. For that reason, this should NOT do
2423
// long-running work, as requests will be delayed. If both this

packages/perseus/src/template/core.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ use crate::utils::provide_context_signal_replace;
1414
use crate::utils::AsyncFnReturn;
1515
#[cfg(not(target_arch = "wasm32"))]
1616
use crate::utils::ComputedDuration;
17+
use crate::utils::PerseusDuration; /* We do actually want this in both the engine and the
18+
* browser */
1719
use crate::Html;
1820
#[cfg(not(target_arch = "wasm32"))]
1921
use crate::Request;
@@ -23,7 +25,6 @@ use crate::SsrNode;
2325
use futures::Future;
2426
#[cfg(not(target_arch = "wasm32"))]
2527
use http::header::HeaderMap;
26-
use std::time::Duration;
2728
use sycamore::prelude::{Scope, View};
2829
#[cfg(not(target_arch = "wasm32"))]
2930
use sycamore::utils::hydrate::with_no_hydration_context;
@@ -675,8 +676,14 @@ impl<G: Html> Template<G> {
675676
/// - y: year (365 days always, leap years ignored, if you want them add
676677
/// them as days)
677678
#[cfg(not(target_arch = "wasm32"))]
678-
pub fn revalidate_after<I: Into<Duration>>(mut self, val: I) -> Template<G> {
679-
self.revalidate_after = Some(ComputedDuration::new(val));
679+
pub fn revalidate_after<I: PerseusDuration>(mut self, val: I) -> Template<G> {
680+
let computed_duration = match val.into_computed() {
681+
Ok(val) => val,
682+
// This is fine, because this will be checked when we try to build the app (i.e. it'll
683+
// show up before runtime)
684+
Err(_) => panic!("invalid revalidation interval"),
685+
};
686+
self.revalidate_after = Some(computed_duration);
680687
self
681688
}
682689
/// Enables the *revalidation* strategy (time variant). This takes a time
@@ -691,7 +698,7 @@ impl<G: Html> Template<G> {
691698
/// - y: year (365 days always, leap years ignored, if you want them add
692699
/// them as days)
693700
#[cfg(target_arch = "wasm32")]
694-
pub fn revalidate_after<I: Into<Duration>>(self, _val: I) -> Template<G> {
701+
pub fn revalidate_after<I: PerseusDuration>(self, _val: I) -> Template<G> {
695702
self
696703
}
697704

+125-79
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,138 @@
1-
use chrono::Utc;
2-
use std::{convert::TryFrom, time};
3-
4-
/// Represents a duration that can be computed relative to the current time.
5-
#[derive(Debug, Clone)]
6-
pub struct ComputedDuration(chrono::Duration);
7-
8-
impl ComputedDuration {
9-
/// Creates a new [`ComputedDuration`] from the given duration-like type.
10-
pub fn new<I: Into<time::Duration>>(duration: I) -> Self {
11-
let duration = chrono::Duration::from_std(duration.into()).unwrap();
12-
Self(duration)
1+
#[cfg(not(target_arch = "wasm32"))]
2+
mod engine {
3+
use super::InvalidDuration;
4+
use chrono::Utc;
5+
use std::time;
6+
7+
/// Represents a duration that can be computed relative to the current time.
8+
/// This should be created through [`PerseusDuration::into_computed`] only.
9+
#[derive(Debug, Clone)]
10+
pub struct ComputedDuration(chrono::Duration);
11+
12+
impl ComputedDuration {
13+
/// Get the timestamp of the duration added to the current time.
14+
pub fn compute_timestamp(&self) -> String {
15+
let current = Utc::now();
16+
let datetime = current + self.0;
17+
datetime.to_rfc3339()
18+
}
1319
}
1420

15-
/// Get the timestamp of the duration added to the current time.
16-
pub fn compute_timestamp(&self) -> String {
17-
let current = Utc::now();
18-
let datetime = current + self.0;
19-
datetime.to_rfc3339()
21+
/// A trait that represents anything we'll accept for specifying durations
22+
/// for revalidation. Anything that implements thus must be able to be
23+
/// transformed into a [`ComputedDuration`];
24+
pub trait PerseusDuration {
25+
/// Converts this into a `[ComputedDuration]` for use in Perseus'
26+
/// internal revalidation systems.
27+
fn into_computed(self) -> Result<ComputedDuration, InvalidDuration>;
2028
}
21-
}
2229

23-
/// A simpler representation of a duration based on individual components.
24-
///
25-
/// Note that months are assumed to be 30 days long, and years 365 days long.
26-
#[derive(Default, Debug)]
27-
pub struct Duration {
28-
years: i64,
29-
months: i64,
30-
weeks: i64,
31-
days: i64,
32-
hours: i64,
33-
minutes: i64,
34-
seconds: i64,
35-
}
30+
// We'll accept strings, and standard library durations.
31+
// We don't accept Chrono durations because that would create a difference to
32+
// the browser dummy API (since Chrono is only used on the engine side), and
33+
// Chrono durations can be trivially converted into standard ones.
34+
impl PerseusDuration for chrono::Duration {
35+
fn into_computed(self) -> Result<ComputedDuration, InvalidDuration> {
36+
Ok(ComputedDuration(self))
37+
}
38+
}
39+
impl PerseusDuration for &str {
40+
// NOTE: Logic to define how we parse time strings is here
41+
fn into_computed(self) -> Result<ComputedDuration, InvalidDuration> {
42+
let mut duration = chrono::Duration::zero();
3643

37-
/// An error type for invalid `String` durations.
38-
pub struct InvalidDuration;
44+
// A working variable to store the '123' part of an interval until we reach the
45+
// indicator and can do the full conversion
46+
let mut curr_duration_length = String::new();
3947

40-
impl From<Duration> for time::Duration {
41-
fn from(duration: Duration) -> Self {
42-
let duration = chrono::Duration::seconds(duration.seconds)
43-
+ chrono::Duration::minutes(duration.minutes)
44-
+ chrono::Duration::hours(duration.hours)
45-
+ chrono::Duration::days(duration.days)
46-
+ chrono::Duration::weeks(duration.weeks)
47-
+ chrono::Duration::days(duration.months * 30) // Assumed length of a month
48-
+ chrono::Duration::days(duration.years * 365); // Assumed length of a year
49-
50-
duration.to_std().unwrap()
51-
}
52-
}
48+
for c in self.chars() {
49+
// If we have a number, append it to the working cache
50+
// If we have an indicator character, we'll match it to a duration
51+
if c.is_numeric() {
52+
curr_duration_length.push(c);
53+
} else {
54+
let interval_length: i64 = curr_duration_length.parse().unwrap(); // It's just a string of numbers, we know more than the compiler
55+
if interval_length <= 0 {
56+
return Err(InvalidDuration);
57+
}
5358

54-
impl TryFrom<&str> for Duration {
55-
type Error = InvalidDuration;
56-
57-
fn try_from(value: &str) -> Result<Self, Self::Error> {
58-
let mut duration = Self::default();
59-
60-
// A working variable to store the '123' part of an interval until we reach the
61-
// indicator and can do the full conversion
62-
let mut curr_duration_length = String::new();
63-
64-
for c in value.chars() {
65-
// If we have a number, append it to the working cache
66-
// If we have an indicator character, we'll match it to a duration
67-
if c.is_numeric() {
68-
curr_duration_length.push(c);
69-
} else {
70-
let interval_length: i64 = curr_duration_length.parse().unwrap(); // It's just a string of numbers, we know more than the compiler
71-
if interval_length <= 0 {
72-
return Err(InvalidDuration);
73-
}
59+
match c {
60+
's' => duration = duration + chrono::Duration::seconds(interval_length),
61+
'm' => duration = duration + chrono::Duration::minutes(interval_length),
62+
'h' => duration = duration + chrono::Duration::hours(interval_length),
63+
'd' => duration = duration + chrono::Duration::days(interval_length),
64+
'w' => duration = duration + chrono::Duration::weeks(interval_length),
65+
'M' => duration = duration + chrono::Duration::days(interval_length * 30), /* Assumed length of a month */
66+
'y' => duration = duration + chrono::Duration::days(interval_length * 365), /* Assumed length of a year */
67+
_ => return Err(InvalidDuration),
68+
};
7469

75-
match c {
76-
's' if duration.seconds == 0 => duration.seconds = interval_length,
77-
'm' if duration.minutes == 0 => duration.minutes = interval_length,
78-
'h' if duration.hours == 0 => duration.hours = interval_length,
79-
'd' if duration.days == 0 => duration.days = interval_length,
80-
'w' if duration.weeks == 0 => duration.weeks = interval_length,
81-
'M' if duration.months == 0 => duration.months = interval_length,
82-
'y' if duration.years == 0 => duration.years = interval_length,
83-
_ => return Err(InvalidDuration),
84-
};
85-
86-
curr_duration_length = String::new();
70+
curr_duration_length = String::new();
71+
}
8772
}
73+
74+
Ok(ComputedDuration(duration))
8875
}
76+
}
77+
impl PerseusDuration for time::Duration {
78+
fn into_computed(self) -> Result<ComputedDuration, InvalidDuration> {
79+
let duration = chrono::Duration::from_std(self).map_err(|_| InvalidDuration)?;
80+
Ok(ComputedDuration(duration))
81+
}
82+
}
83+
}
84+
#[cfg(target_arch = "wasm32")]
85+
mod browser {
86+
use super::InvalidDuration;
87+
use std::time;
88+
89+
/// Represents a duration that can be computed relative to the current time.
90+
/// This should be created through [`PerseusDuration::into_computed`] only.
91+
#[derive(Debug, Clone)]
92+
pub struct ComputedDuration;
8993

90-
Ok(duration)
94+
/// A trait that represents anything we'll accept for specifying durations
95+
/// for revalidation. Anything that implements thus must be able to be
96+
/// transformed into a [`ComputedDuration`];
97+
pub trait PerseusDuration {
98+
/// Converts this into a `[ComputedDuration]` for use in Perseus'
99+
/// internal revalidation systems.
100+
fn into_computed(self) -> Result<ComputedDuration, InvalidDuration>
101+
where
102+
Self: Sized,
103+
{
104+
// In the browser, this function should never be called
105+
unreachable!("computed durations can only be created on the engine-side")
106+
}
91107
}
108+
109+
// Dummy implementations for the browser follow (we only need this for generic
110+
// validation)
111+
impl PerseusDuration for &str {}
112+
impl PerseusDuration for time::Duration {}
92113
}
114+
115+
/// An error type for invalid durations.
116+
#[derive(Debug)]
117+
pub struct InvalidDuration;
118+
119+
#[cfg(target_arch = "wasm32")]
120+
pub use browser::{ComputedDuration, PerseusDuration};
121+
#[cfg(not(target_arch = "wasm32"))]
122+
pub use engine::{ComputedDuration, PerseusDuration};
123+
124+
// // We can convert from our duration type into the standard library's
125+
// impl From<Duration> for time::Duration {
126+
// fn from(duration: Duration) -> Self {
127+
// let duration = chrono::Duration::seconds(duration.seconds)
128+
// + chrono::Duration::minutes(duration.minutes)
129+
// + chrono::Duration::hours(duration.hours)
130+
// + chrono::Duration::days(duration.days)
131+
// + chrono::Duration::weeks(duration.weeks)
132+
// + chrono::Duration::days(duration.months * 30) // Assumed length
133+
// of a month + chrono::Duration::days(duration.years * 365); //
134+
// Assumed length of a year
135+
136+
// duration.to_std().unwrap()
137+
// }
138+
// }

packages/perseus/src/utils/mod.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ mod cache_res;
44
#[cfg(target_arch = "wasm32")]
55
mod checkpoint;
66
mod context;
7-
#[cfg(not(target_arch = "wasm32"))]
87
mod decode_time_str;
98
#[cfg(target_arch = "wasm32")]
109
mod fetch;
@@ -20,8 +19,7 @@ pub use cache_res::{cache_fallible_res, cache_res};
2019
#[cfg(target_arch = "wasm32")]
2120
pub use checkpoint::checkpoint;
2221
pub(crate) use context::provide_context_signal_replace;
23-
#[cfg(not(target_arch = "wasm32"))]
24-
pub use decode_time_str::{ComputedDuration, Duration, InvalidDuration};
22+
pub use decode_time_str::{ComputedDuration, InvalidDuration, PerseusDuration}; /* These have dummy equivalents for the browser */
2523
#[cfg(target_arch = "wasm32")]
2624
pub(crate) use fetch::fetch;
2725
pub use path_prefix::*;

0 commit comments

Comments
 (0)