Skip to content

Commit c6933bf

Browse files
committed
Add content::ContentLength
1 parent 82e3d5e commit c6933bf

File tree

2 files changed

+104
-0
lines changed

2 files changed

+104
-0
lines changed

src/content/content_length.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use crate::headers::{HeaderName, HeaderValue, Headers, CONTENT_LENGTH};
2+
use crate::Status;
3+
4+
/// Credentials to authenticate a user agent with a server.
5+
///
6+
/// # Specifications
7+
///
8+
/// - [RFC 7230, section 3.3.2: Content-Length](https://tools.ietf.org/html/rfc7230#section-3.3.2)
9+
///
10+
/// # Examples
11+
///
12+
/// ```
13+
/// # fn main() -> http_types::Result<()> {
14+
/// #
15+
/// use http_types::Response;
16+
/// use http_types::content::{ContentLength};
17+
///
18+
/// let content_len = ContentLength::new(12);
19+
///
20+
/// let mut res = Response::new(200);
21+
/// content_len.apply(&mut res);
22+
///
23+
/// let content_len = ContentLength::from_headers(res)?.unwrap();
24+
/// assert_eq!(content_len.len(), 12);
25+
/// #
26+
/// # Ok(()) }
27+
/// ```
28+
#[derive(Debug)]
29+
pub struct ContentLength {
30+
length: usize,
31+
}
32+
33+
impl ContentLength {
34+
/// Create a new instance of `Authorization`.
35+
pub fn new(length: usize) -> Self {
36+
Self { length }
37+
}
38+
39+
/// Create a new instance from headers.
40+
pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
41+
let headers = match headers.as_ref().get(CONTENT_LENGTH) {
42+
Some(headers) => headers,
43+
None => return Ok(None),
44+
};
45+
46+
// If we successfully parsed the header then there's always at least one
47+
// entry. We want the last entry.
48+
let value = headers.iter().last().unwrap();
49+
let length = value.as_str().trim().parse().status(400)?;
50+
Ok(Some(Self { length }))
51+
}
52+
53+
/// Sets the header.
54+
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
55+
headers.as_mut().insert(self.name(), self.value());
56+
}
57+
58+
/// Get the `HeaderName`.
59+
pub fn name(&self) -> HeaderName {
60+
CONTENT_LENGTH
61+
}
62+
63+
/// Get the `HeaderValue`.
64+
pub fn value(&self) -> HeaderValue {
65+
let output = format!("{}", self.length);
66+
67+
// SAFETY: the internal string is validated to be ASCII.
68+
unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
69+
}
70+
71+
/// Get the content length.
72+
pub fn len(&self) -> usize {
73+
self.length
74+
}
75+
}
76+
77+
#[cfg(test)]
78+
mod test {
79+
use super::*;
80+
use crate::headers::Headers;
81+
82+
#[test]
83+
fn smoke() -> crate::Result<()> {
84+
let content_len = ContentLength::new(12);
85+
86+
let mut headers = Headers::new();
87+
content_len.apply(&mut headers);
88+
89+
let content_len = ContentLength::from_headers(headers)?.unwrap();
90+
assert_eq!(content_len.len(), 12);
91+
Ok(())
92+
}
93+
94+
#[test]
95+
fn bad_request_on_parse_error() -> crate::Result<()> {
96+
let mut headers = Headers::new();
97+
headers.insert(CONTENT_LENGTH, "<nori ate the tag. yum.>");
98+
let err = ContentLength::from_headers(headers).unwrap_err();
99+
assert_eq!(err.status(), 400);
100+
Ok(())
101+
}
102+
}

src/content/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
pub mod accept_encoding;
99
pub mod content_encoding;
1010

11+
mod content_length;
1112
mod encoding;
1213
mod encoding_proposal;
1314

1415
#[doc(inline)]
1516
pub use accept_encoding::AcceptEncoding;
1617
#[doc(inline)]
1718
pub use content_encoding::ContentEncoding;
19+
pub use content_length::ContentLength;
1820
pub use encoding::Encoding;
1921
pub use encoding_proposal::EncodingProposal;

0 commit comments

Comments
 (0)