Skip to content

Commit 2aba1bd

Browse files
yoshuawuytsFishrock123
authored andcommitted
Add conditional::LastModified
1 parent 86df101 commit 2aba1bd

File tree

4 files changed

+129
-2
lines changed

4 files changed

+129
-2
lines changed

src/conditional/if_modified_since.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ impl IfModifiedSince {
4545
Self { instant }
4646
}
4747

48-
/// Get the expiration time.
48+
/// Returns the last modification time listed.
4949
pub fn modified(&self) -> SystemTime {
5050
self.instant
5151
}

src/conditional/if_unmodified_since.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ impl IfUnmodifiedSince {
4545
Self { instant }
4646
}
4747

48-
/// Get the expiration time.
48+
/// Returns the last modification time listed.
4949
pub fn modified(&self) -> SystemTime {
5050
self.instant
5151
}

src/conditional/last_modified.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, LAST_MODIFIED};
2+
use crate::utils::{fmt_http_date, parse_http_date};
3+
4+
use std::fmt::Debug;
5+
use std::option;
6+
use std::time::SystemTime;
7+
8+
/// HTTP `LastModified` header
9+
///
10+
/// # Specifications
11+
///
12+
/// - [RFC 7232, section 2.2: Last-Modified](https://tools.ietf.org/html/rfc7232#section-2.2)
13+
///
14+
/// # Examples
15+
///
16+
/// ```
17+
/// # fn main() -> http_types::Result<()> {
18+
/// #
19+
/// use http_types::Response;
20+
/// use http_types::conditional::LastModified;
21+
/// use std::time::{SystemTime, Duration};
22+
///
23+
/// let time = SystemTime::now() + Duration::from_secs(5 * 60);
24+
/// let last_modified = LastModified::new(time);
25+
///
26+
/// let mut res = Response::new(200);
27+
/// last_modified.apply(&mut res);
28+
///
29+
/// let last_modified = LastModified::from_headers(res)?.unwrap();
30+
///
31+
/// // HTTP dates only have second-precision.
32+
/// let elapsed = time.duration_since(last_modified.modified())?;
33+
/// assert_eq!(elapsed.as_secs(), 0);
34+
/// #
35+
/// # Ok(()) }
36+
/// ```
37+
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
38+
pub struct LastModified {
39+
instant: SystemTime,
40+
}
41+
42+
impl LastModified {
43+
/// Create a new instance of `LastModified`.
44+
pub fn new(instant: SystemTime) -> Self {
45+
Self { instant }
46+
}
47+
48+
/// Returns the last modification time listed.
49+
pub fn modified(&self) -> SystemTime {
50+
self.instant
51+
}
52+
53+
/// Create an instance of `LastModified` from a `Headers` instance.
54+
pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
55+
let headers = match headers.as_ref().get(LAST_MODIFIED) {
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 header = headers.iter().last().unwrap();
63+
64+
let instant = parse_http_date(header.as_str())?;
65+
Ok(Some(Self { instant }))
66+
}
67+
68+
/// Insert a `HeaderName` + `HeaderValue` pair into a `Headers` instance.
69+
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
70+
headers.as_mut().insert(LAST_MODIFIED, self.value());
71+
}
72+
73+
/// Get the `HeaderName`.
74+
pub fn name(&self) -> HeaderName {
75+
LAST_MODIFIED
76+
}
77+
78+
/// Get the `HeaderValue`.
79+
pub fn value(&self) -> HeaderValue {
80+
let output = fmt_http_date(self.instant);
81+
82+
// SAFETY: the internal string is validated to be ASCII.
83+
unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
84+
}
85+
}
86+
87+
impl ToHeaderValues for LastModified {
88+
type Iter = option::IntoIter<HeaderValue>;
89+
fn to_header_values(&self) -> crate::Result<Self::Iter> {
90+
// A HeaderValue will always convert into itself.
91+
Ok(self.value().to_header_values().unwrap())
92+
}
93+
}
94+
95+
#[cfg(test)]
96+
mod test {
97+
use super::*;
98+
use crate::headers::Headers;
99+
use std::time::Duration;
100+
101+
#[test]
102+
fn smoke() -> crate::Result<()> {
103+
let time = SystemTime::now() + Duration::from_secs(5 * 60);
104+
let last_modified = LastModified::new(time);
105+
106+
let mut headers = Headers::new();
107+
last_modified.apply(&mut headers);
108+
109+
let last_modified = LastModified::from_headers(headers)?.unwrap();
110+
111+
// HTTP dates only have second-precision
112+
let elapsed = time.duration_since(last_modified.modified())?;
113+
assert_eq!(elapsed.as_secs(), 0);
114+
Ok(())
115+
}
116+
117+
#[test]
118+
fn bad_request_on_parse_error() -> crate::Result<()> {
119+
let mut headers = Headers::new();
120+
headers.insert(LAST_MODIFIED, "<nori ate the tag. yum.>");
121+
let err = LastModified::from_headers(headers).unwrap_err();
122+
assert_eq!(err.status(), 400);
123+
Ok(())
124+
}
125+
}

src/conditional/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
mod etag;
1212
mod if_modified_since;
1313
mod if_unmodified_since;
14+
mod last_modified;
1415

1516
pub use etag::ETag;
1617
pub use if_modified_since::IfModifiedSince;
1718
pub use if_unmodified_since::IfUnmodifiedSince;
19+
pub use last_modified::LastModified;

0 commit comments

Comments
 (0)