Skip to content

Commit 3670a84

Browse files
authored
Merge pull request #254 from http-rs/allow
Add `server::Allow`
2 parents 47410a8 + 59eec78 commit 3670a84

File tree

3 files changed

+215
-0
lines changed

3 files changed

+215
-0
lines changed

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ pub mod headers;
124124
pub mod mime;
125125
pub mod other;
126126
pub mod proxies;
127+
pub mod server;
127128

128129
mod body;
129130
mod error;

src/server/allow.rs

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
//! List the set of methods supported by a resource.
2+
3+
use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, ALLOW};
4+
use crate::Method;
5+
6+
use std::collections::{hash_set, HashSet};
7+
use std::fmt::{self, Debug, Write};
8+
use std::iter::Iterator;
9+
use std::option;
10+
use std::str::FromStr;
11+
12+
/// List the set of methods supported by a resource.
13+
///
14+
/// # Specifications
15+
///
16+
/// - [RFC 7231, section 7.4.1: Allow](https://tools.ietf.org/html/rfc7231#section-7.4.1)
17+
///
18+
/// # Examples
19+
///
20+
/// ```
21+
/// # fn main() -> http_types::Result<()> {
22+
/// #
23+
/// use http_types::{Method, Response};
24+
/// use http_types::server::Allow;
25+
///
26+
/// let mut allow = Allow::new();
27+
/// allow.insert(Method::Put);
28+
/// allow.insert(Method::Post);
29+
///
30+
/// let mut res = Response::new(200);
31+
/// allow.apply(&mut res);
32+
///
33+
/// let allow = Allow::from_headers(res)?.unwrap();
34+
/// assert!(allow.contains(Method::Put));
35+
/// assert!(allow.contains(Method::Post));
36+
/// #
37+
/// # Ok(()) }
38+
/// ```
39+
pub struct Allow {
40+
entries: HashSet<Method>,
41+
}
42+
43+
impl Allow {
44+
/// Create a new instance of `Allow`.
45+
pub fn new() -> Self {
46+
Self {
47+
entries: HashSet::new(),
48+
}
49+
}
50+
51+
/// Create a new instance from headers.
52+
pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
53+
let mut entries = HashSet::new();
54+
let headers = match headers.as_ref().get(ALLOW) {
55+
Some(headers) => headers,
56+
None => return Ok(None),
57+
};
58+
59+
for value in headers {
60+
for part in value.as_str().trim().split(',') {
61+
let method = Method::from_str(part.trim())?;
62+
entries.insert(method);
63+
}
64+
}
65+
66+
Ok(Some(Self { entries }))
67+
}
68+
69+
/// Sets the `Allow` header.
70+
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
71+
headers.as_mut().insert(ALLOW, self.value());
72+
}
73+
74+
/// Get the `HeaderName`.
75+
pub fn name(&self) -> HeaderName {
76+
ALLOW
77+
}
78+
79+
/// Get the `HeaderValue`.
80+
pub fn value(&self) -> HeaderValue {
81+
let mut output = String::new();
82+
for (n, method) in self.entries.iter().enumerate() {
83+
match n {
84+
0 => write!(output, "{}", method).unwrap(),
85+
_ => write!(output, ", {}", method).unwrap(),
86+
};
87+
}
88+
89+
// SAFETY: the internal string is validated to be ASCII.
90+
unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
91+
}
92+
93+
/// Push a method into the set of methods.
94+
pub fn insert(&mut self, method: Method) {
95+
self.entries.insert(method);
96+
}
97+
98+
/// An iterator visiting all server entries.
99+
pub fn iter(&self) -> Iter<'_> {
100+
Iter {
101+
inner: self.entries.iter(),
102+
}
103+
}
104+
105+
/// Returns `true` if the header contains the `Method`.
106+
pub fn contains(&self, method: Method) -> bool {
107+
self.entries.contains(&method)
108+
}
109+
}
110+
111+
impl IntoIterator for Allow {
112+
type Item = Method;
113+
type IntoIter = IntoIter;
114+
115+
#[inline]
116+
fn into_iter(self) -> Self::IntoIter {
117+
IntoIter {
118+
inner: self.entries.into_iter(),
119+
}
120+
}
121+
}
122+
123+
impl<'a> IntoIterator for &'a Allow {
124+
type Item = &'a Method;
125+
type IntoIter = Iter<'a>;
126+
127+
#[inline]
128+
fn into_iter(self) -> Self::IntoIter {
129+
self.iter()
130+
}
131+
}
132+
133+
/// A borrowing iterator over entries in `Allow`.
134+
#[derive(Debug)]
135+
pub struct IntoIter {
136+
inner: hash_set::IntoIter<Method>,
137+
}
138+
139+
impl Iterator for IntoIter {
140+
type Item = Method;
141+
142+
fn next(&mut self) -> Option<Self::Item> {
143+
self.inner.next()
144+
}
145+
146+
#[inline]
147+
fn size_hint(&self) -> (usize, Option<usize>) {
148+
self.inner.size_hint()
149+
}
150+
}
151+
152+
/// A lending iterator over entries in `Allow`.
153+
#[derive(Debug)]
154+
pub struct Iter<'a> {
155+
inner: hash_set::Iter<'a, Method>,
156+
}
157+
158+
impl<'a> Iterator for Iter<'a> {
159+
type Item = &'a Method;
160+
161+
fn next(&mut self) -> Option<Self::Item> {
162+
self.inner.next()
163+
}
164+
165+
#[inline]
166+
fn size_hint(&self) -> (usize, Option<usize>) {
167+
self.inner.size_hint()
168+
}
169+
}
170+
171+
impl ToHeaderValues for Allow {
172+
type Iter = option::IntoIter<HeaderValue>;
173+
fn to_header_values(&self) -> crate::Result<Self::Iter> {
174+
// A HeaderValue will always convert into itself.
175+
Ok(self.value().to_header_values().unwrap())
176+
}
177+
}
178+
179+
impl Debug for Allow {
180+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181+
let mut list = f.debug_list();
182+
for method in &self.entries {
183+
list.entry(method);
184+
}
185+
list.finish()
186+
}
187+
}
188+
189+
#[cfg(test)]
190+
mod test {
191+
use super::*;
192+
use crate::headers::Headers;
193+
194+
#[test]
195+
fn smoke() -> crate::Result<()> {
196+
let mut allow = Allow::new();
197+
allow.insert(Method::Put);
198+
allow.insert(Method::Post);
199+
200+
let mut headers = Headers::new();
201+
allow.apply(&mut headers);
202+
203+
let allow = Allow::from_headers(headers)?.unwrap();
204+
assert!(allow.contains(Method::Put));
205+
assert!(allow.contains(Method::Post));
206+
Ok(())
207+
}
208+
}

src/server/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//! HTTP Server Context headers.
2+
3+
pub mod allow;
4+
5+
#[doc(inline)]
6+
pub use allow::Allow;

0 commit comments

Comments
 (0)