-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added type-safety to time-based revalidation
commit 3ccc6e7 Merge: 0983d68 12d735e Author: arctic_hen7 <[email protected]> Date: Mon Jul 11 14:29:06 2022 +1000 chore: resolved merge conflicts commit 12d735e Author: Vukašin Stepanović <[email protected]> Date: Thu Jan 13 10:55:40 2022 +0100 Add Duration struct that parses time strings commit 06e8d55 Author: Vukašin Stepanović <[email protected]> Date: Wed Jan 12 11:40:43 2022 +0100 Use newtype instead of string for time revalidation
- Loading branch information
1 parent
0983d68
commit 7b3ff88
Showing
7 changed files
with
115 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,62 +1,91 @@ | ||
use crate::errors::*; | ||
use chrono::{Duration, Utc}; | ||
|
||
// Decodes time strings like '1w' into actual datetimes from the present moment. If you've ever used NodeJS's [`jsonwebtoken`](https://www.npmjs.com/package/jsonwebtoken) module, this is | ||
/// very similar (based on Vercel's [`ms`](https://github.com/vercel/ms) module for JavaScript). | ||
/// Accepts strings of the form 'xXyYzZ...', where the lower-case letters are | ||
/// numbers meaning a number of the intervals X/Y/Z (e.g. 1m4d -- one month four | ||
/// days). The available intervals are: | ||
use chrono::Utc; | ||
use std::{convert::TryFrom, time}; | ||
|
||
/// Represents a duration that can be computed relative to the current time. | ||
#[derive(Debug, Clone)] | ||
pub struct ComputedDuration(chrono::Duration); | ||
|
||
impl ComputedDuration { | ||
/// Creates a new [`ComputedDuration`] from the given duration-like type. | ||
pub fn new<I: Into<time::Duration>>(duration: I) -> Self { | ||
let duration = chrono::Duration::from_std(duration.into()).unwrap(); | ||
Self(duration) | ||
} | ||
|
||
/// Get the timestamp of the duration added to the current time. | ||
pub fn compute_timestamp(&self) -> String { | ||
let current = Utc::now(); | ||
let datetime = current + self.0; | ||
datetime.to_rfc3339() | ||
} | ||
} | ||
|
||
/// A simpler representation of a duration based on individual components. | ||
/// | ||
/// - s: second, | ||
/// - m: minute, | ||
/// - h: hour, | ||
/// - d: day, | ||
/// - w: week, | ||
/// - M: month (30 days used here, 12M ≠ 1y!), | ||
/// - y: year (365 days always, leap years ignored, if you want them add them as | ||
/// days) | ||
pub fn decode_time_str(time_str: &str) -> Result<String, BuildError> { | ||
let mut duration_after_current = Duration::zero(); | ||
// Get the current datetime since Unix epoch, we'll add to that | ||
let current = Utc::now(); | ||
// A working variable to store the '123' part of an interval until we reach the | ||
// idnicator and can do the full conversion | ||
let mut curr_duration_length = String::new(); | ||
// Iterate through the time string's characters to get each interval | ||
for c in time_str.chars() { | ||
// If we have a number, append it to the working cache | ||
// If we have an indicator character, we'll match it to a duration | ||
if c.is_numeric() { | ||
curr_duration_length.push(c); | ||
} else { | ||
// Parse the working variable into an actual number | ||
let interval_length = curr_duration_length.parse::<i64>().unwrap(); // It's just a string of numbers, we know more than the compiler | ||
let duration = match c { | ||
's' => Duration::seconds(interval_length), | ||
'm' => Duration::minutes(interval_length), | ||
'h' => Duration::hours(interval_length), | ||
'd' => Duration::days(interval_length), | ||
'w' => Duration::weeks(interval_length), | ||
'M' => Duration::days(interval_length * 30), /* Multiplying the number of months */ | ||
// by 30 days (assumed length of a | ||
// month) | ||
'y' => Duration::days(interval_length * 365), /* Multiplying the number of years */ | ||
// by 365 days (assumed length of | ||
// a year) | ||
c => { | ||
return Err(BuildError::InvalidDatetimeIntervalIndicator { | ||
indicator: c.to_string(), | ||
}) | ||
/// Note that months are assumed to be 30 days long, and years 365 days long. | ||
#[derive(Default, Debug)] | ||
pub struct Duration { | ||
years: i64, | ||
months: i64, | ||
weeks: i64, | ||
days: i64, | ||
hours: i64, | ||
minutes: i64, | ||
seconds: i64, | ||
} | ||
|
||
/// An error type for invalid `String` durations. | ||
pub struct InvalidDuration; | ||
|
||
impl From<Duration> for time::Duration { | ||
fn from(duration: Duration) -> Self { | ||
let duration = chrono::Duration::seconds(duration.seconds) | ||
+ chrono::Duration::minutes(duration.minutes) | ||
+ chrono::Duration::hours(duration.hours) | ||
+ chrono::Duration::days(duration.days) | ||
+ chrono::Duration::weeks(duration.weeks) | ||
+ chrono::Duration::days(duration.months * 30) // Assumed length of a month | ||
+ chrono::Duration::days(duration.years * 365); // Assumed length of a year | ||
|
||
duration.to_std().unwrap() | ||
} | ||
} | ||
|
||
impl TryFrom<&str> for Duration { | ||
type Error = InvalidDuration; | ||
|
||
fn try_from(value: &str) -> Result<Self, Self::Error> { | ||
let mut duration = Self::default(); | ||
|
||
// A working variable to store the '123' part of an interval until we reach the indicator and can do the full conversion | ||
let mut curr_duration_length = String::new(); | ||
|
||
for c in value.chars() { | ||
// If we have a number, append it to the working cache | ||
// If we have an indicator character, we'll match it to a duration | ||
if c.is_numeric() { | ||
curr_duration_length.push(c); | ||
} else { | ||
let interval_length: i64 = curr_duration_length.parse().unwrap(); // It's just a string of numbers, we know more than the compiler | ||
if interval_length <= 0 { | ||
return Err(InvalidDuration); | ||
} | ||
}; | ||
duration_after_current = duration_after_current + duration; | ||
// Reset that working variable | ||
curr_duration_length = String::new(); | ||
|
||
match c { | ||
's' if duration.seconds == 0 => duration.seconds = interval_length, | ||
'm' if duration.minutes == 0 => duration.minutes = interval_length, | ||
'h' if duration.hours == 0 => duration.hours = interval_length, | ||
'd' if duration.days == 0 => duration.days = interval_length, | ||
'w' if duration.weeks == 0 => duration.weeks = interval_length, | ||
'M' if duration.months == 0 => duration.months = interval_length, | ||
'y' if duration.years == 0 => duration.years = interval_length, | ||
_ => return Err(InvalidDuration), | ||
}; | ||
|
||
curr_duration_length = String::new(); | ||
} | ||
} | ||
} | ||
// Form the final duration by reducing the durations vector into one | ||
let datetime = current + duration_after_current; | ||
|
||
// We return an easily parsible format (RFC 3339) | ||
Ok(datetime.to_rfc3339()) | ||
Ok(duration) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters