Skip to content

Commit 2a291ff

Browse files
committed
Add other::Date header
1 parent 82e3d5e commit 2a291ff

File tree

4 files changed

+149
-0
lines changed

4 files changed

+149
-0
lines changed

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ pub mod conditional;
122122
pub mod content;
123123
pub mod headers;
124124
pub mod mime;
125+
pub mod other;
125126
pub mod proxies;
126127

127128
mod body;

src/other/date.rs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
use crate::headers::{HeaderName, HeaderValue, Headers, DATE};
2+
use crate::utils::HttpDate;
3+
4+
use std::time::SystemTime;
5+
6+
/// The date and time at which the message was originated.
7+
///
8+
/// # Specifications
9+
///
10+
/// - [RFC 7231, section 7.1.1.2: Date](https://tools.ietf.org/html/rfc7231#section-7.1.1.2)
11+
///
12+
/// # Examples
13+
///
14+
/// ```
15+
/// # fn main() -> http_types::Result<()> {
16+
/// #
17+
/// use http_types::Response;
18+
/// use http_types::other::Date;
19+
///
20+
/// use std::time::{Duration, SystemTime};
21+
///
22+
/// let now = SystemTime::now();
23+
/// let date = Date::new(now);
24+
///
25+
/// let mut res = Response::new(200);
26+
/// date.apply(&mut res);
27+
///
28+
/// let date = Date::from_headers(res)?.unwrap();
29+
///
30+
/// // Validate we're within 1 second accurate of the system time.
31+
/// assert!(now.duration_since(date.into())? <= Duration::from_secs(1));
32+
/// #
33+
/// # Ok(()) }
34+
/// ```
35+
#[derive(Debug)]
36+
pub struct Date {
37+
at: SystemTime,
38+
}
39+
40+
impl Date {
41+
/// Create a new instance.
42+
pub fn new(at: SystemTime) -> Self {
43+
Self { at }
44+
}
45+
46+
/// Create a new instance with the date set to now.
47+
pub fn now() -> Self {
48+
Self {
49+
at: SystemTime::now(),
50+
}
51+
}
52+
53+
/// Create a new instance from headers.
54+
pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
55+
let headers = match headers.as_ref().get(DATE) {
56+
Some(headers) => headers,
57+
None => return Ok(None),
58+
};
59+
60+
// If we successfully parsed the header then there's always at least one
61+
// entry. We want the last entry.
62+
let value = headers.iter().last().unwrap();
63+
let date: HttpDate = value
64+
.as_str()
65+
.trim()
66+
.parse()
67+
.map_err(|mut e: crate::Error| {
68+
e.set_status(400);
69+
e
70+
})?;
71+
let at = date.into();
72+
Ok(Some(Self { at }))
73+
}
74+
75+
/// Sets the header.
76+
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
77+
headers.as_mut().insert(self.name(), self.value());
78+
}
79+
80+
/// Get the `HeaderName`.
81+
pub fn name(&self) -> HeaderName {
82+
DATE
83+
}
84+
85+
/// Get the `HeaderValue`.
86+
pub fn value(&self) -> HeaderValue {
87+
let date: HttpDate = self.at.into();
88+
let output = format!("{}", date);
89+
90+
// SAFETY: the internal string is validated to be ASCII.
91+
unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
92+
}
93+
}
94+
95+
impl From<Date> for SystemTime {
96+
fn from(date: Date) -> Self {
97+
date.at
98+
}
99+
}
100+
101+
impl From<SystemTime> for Date {
102+
fn from(time: SystemTime) -> Self {
103+
Self { at: time }
104+
}
105+
}
106+
107+
impl PartialEq<SystemTime> for Date {
108+
fn eq(&self, other: &SystemTime) -> bool {
109+
&self.at == other
110+
}
111+
}
112+
113+
#[cfg(test)]
114+
mod test {
115+
use super::*;
116+
use crate::headers::Headers;
117+
use std::time::Duration;
118+
119+
#[test]
120+
fn smoke() -> crate::Result<()> {
121+
let now = SystemTime::now();
122+
let date = Date::new(now);
123+
124+
let mut headers = Headers::new();
125+
date.apply(&mut headers);
126+
127+
let date = Date::from_headers(headers)?.unwrap();
128+
129+
// Validate we're within 1 second accurate of the system time.
130+
assert!(now.duration_since(date.into())? <= Duration::from_secs(1));
131+
Ok(())
132+
}
133+
134+
#[test]
135+
fn bad_request_on_parse_error() -> crate::Result<()> {
136+
let mut headers = Headers::new();
137+
headers.insert(DATE, "<nori ate the tag. yum.>");
138+
let err = Date::from_headers(headers).unwrap_err();
139+
assert_eq!(err.status(), 400);
140+
Ok(())
141+
}
142+
}

src/other/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//! Miscellaneous HTTP headers.
2+
3+
mod date;
4+
5+
pub use date::Date;

src/utils/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod date;
22

33
pub(crate) use date::fmt_http_date;
44
pub(crate) use date::parse_http_date;
5+
pub(crate) use date::HttpDate;
56

67
use crate::{Error, Status, StatusCode};
78

0 commit comments

Comments
 (0)