Skip to content

Commit be1a113

Browse files
authored
Merge pull request #253 from http-rs/content-length
Add `content::ContentLength`
2 parents 3670a84 + e96a1ee commit be1a113

File tree

2 files changed

+110
-0
lines changed

2 files changed

+110
-0
lines changed

src/content/content_length.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use crate::headers::{HeaderName, HeaderValue, Headers, CONTENT_LENGTH};
2+
use crate::Status;
3+
4+
/// The size of the entity-body, in bytes, sent to the recipient.
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: u64,
31+
}
32+
33+
#[allow(clippy::len_without_is_empty)]
34+
impl ContentLength {
35+
/// Create a new instance.
36+
pub fn new(length: u64) -> Self {
37+
Self { length }
38+
}
39+
40+
/// Create a new instance from headers.
41+
pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
42+
let headers = match headers.as_ref().get(CONTENT_LENGTH) {
43+
Some(headers) => headers,
44+
None => return Ok(None),
45+
};
46+
47+
// If we successfully parsed the header then there's always at least one
48+
// entry. We want the last entry.
49+
let value = headers.iter().last().unwrap();
50+
let length = value.as_str().trim().parse().status(400)?;
51+
Ok(Some(Self { length }))
52+
}
53+
54+
/// Sets the header.
55+
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
56+
headers.as_mut().insert(self.name(), self.value());
57+
}
58+
59+
/// Get the `HeaderName`.
60+
pub fn name(&self) -> HeaderName {
61+
CONTENT_LENGTH
62+
}
63+
64+
/// Get the `HeaderValue`.
65+
pub fn value(&self) -> HeaderValue {
66+
let output = format!("{}", self.length);
67+
68+
// SAFETY: the internal string is validated to be ASCII.
69+
unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
70+
}
71+
72+
/// Get the content length.
73+
pub fn len(&self) -> u64 {
74+
self.length
75+
}
76+
77+
/// Set the content length.
78+
pub fn set_len(&mut self, len: u64) {
79+
self.length = len;
80+
}
81+
}
82+
83+
#[cfg(test)]
84+
mod test {
85+
use super::*;
86+
use crate::headers::Headers;
87+
88+
#[test]
89+
fn smoke() -> crate::Result<()> {
90+
let content_len = ContentLength::new(12);
91+
92+
let mut headers = Headers::new();
93+
content_len.apply(&mut headers);
94+
95+
let content_len = ContentLength::from_headers(headers)?.unwrap();
96+
assert_eq!(content_len.len(), 12);
97+
Ok(())
98+
}
99+
100+
#[test]
101+
fn bad_request_on_parse_error() -> crate::Result<()> {
102+
let mut headers = Headers::new();
103+
headers.insert(CONTENT_LENGTH, "<nori ate the tag. yum.>");
104+
let err = ContentLength::from_headers(headers).unwrap_err();
105+
assert_eq!(err.status(), 400);
106+
Ok(())
107+
}
108+
}

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)