use std;
use serde_derive::{Deserialize, Serialize};
use time::{self, OffsetDateTime};
#[derive(Eq, Clone, Debug, Serialize, Deserialize)]
pub enum CookieExpiration {
#[serde(with = "crate::rfc3339_fmt")]
AtUtc(OffsetDateTime),
SessionEnd,
}
impl std::cmp::PartialEq for CookieExpiration {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(CookieExpiration::SessionEnd, CookieExpiration::SessionEnd) => true,
(CookieExpiration::AtUtc(this_offset), CookieExpiration::AtUtc(other_offset)) => {
this_offset.date() == other_offset.date()
&& this_offset.time().hour() == other_offset.time().hour()
&& this_offset.time().minute() == other_offset.time().minute()
&& this_offset.time().second() == other_offset.time().second()
}
_ => false,
}
}
}
impl CookieExpiration {
pub fn is_expired(&self) -> bool {
self.expires_by(&time::OffsetDateTime::now_utc())
}
pub fn expires_by(&self, utc_tm: &time::OffsetDateTime) -> bool {
match *self {
CookieExpiration::AtUtc(ref expire_tm) => *expire_tm <= *utc_tm,
CookieExpiration::SessionEnd => false,
}
}
}
const MAX_RFC3339: time::OffsetDateTime = time::macros::date!(9999 - 12 - 31)
.with_time(time::macros::time!(23:59:59))
.assume_utc();
impl From<u64> for CookieExpiration {
fn from(max_age: u64) -> CookieExpiration {
CookieExpiration::from(time::Duration::seconds(std::cmp::min(
time::Duration::MAX.whole_seconds() as u64,
max_age,
) as i64))
}
}
impl From<time::OffsetDateTime> for CookieExpiration {
fn from(utc_tm: OffsetDateTime) -> CookieExpiration {
CookieExpiration::AtUtc(utc_tm.min(MAX_RFC3339))
}
}
impl From<cookie::Expiration> for CookieExpiration {
fn from(expiration: cookie::Expiration) -> CookieExpiration {
match expiration {
cookie::Expiration::DateTime(offset) => CookieExpiration::AtUtc(offset),
cookie::Expiration::Session => CookieExpiration::SessionEnd,
}
}
}
impl From<time::Duration> for CookieExpiration {
fn from(duration: time::Duration) -> Self {
let utc_tm = if duration.is_zero() {
time::OffsetDateTime::UNIX_EPOCH
} else {
let now_utc = time::OffsetDateTime::now_utc();
let d = (MAX_RFC3339 - now_utc).min(duration);
now_utc + d
};
CookieExpiration::from(utc_tm)
}
}
#[cfg(test)]
mod tests {
use super::CookieExpiration;
use time;
use crate::utils::test::*;
#[test]
fn max_age_bounds() {
match CookieExpiration::from(time::Duration::MAX.whole_seconds() as u64 + 1) {
CookieExpiration::AtUtc(_) => assert!(true),
_ => assert!(false),
}
}
#[test]
fn expired() {
let ma = CookieExpiration::from(0u64); assert!(ma.is_expired());
assert!(ma.expires_by(&in_days(-1)));
}
#[test]
fn max_age() {
let ma = CookieExpiration::from(60u64);
assert!(!ma.is_expired());
assert!(ma.expires_by(&in_minutes(2)));
}
#[test]
fn session_end() {
let se = CookieExpiration::SessionEnd;
assert!(!se.is_expired());
assert!(!se.expires_by(&in_days(1)));
assert!(!se.expires_by(&in_days(-1)));
}
#[test]
fn at_utc() {
{
let expire_tmrw = CookieExpiration::from(in_days(1));
assert!(!expire_tmrw.is_expired());
assert!(expire_tmrw.expires_by(&in_days(2)));
}
{
let expired_yest = CookieExpiration::from(in_days(-1));
assert!(expired_yest.is_expired());
assert!(!expired_yest.expires_by(&in_days(-2)));
}
}
}