Skip to content

Commit 24cf60a

Browse files
authored
Merge pull request #69 from yoshuawuyts/extensions
feat: implement extensions
2 parents 5afcc8c + cd0fb1b commit 24cf60a

File tree

6 files changed

+213
-2
lines changed

6 files changed

+213
-2
lines changed

LICENSE-MIT

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
The MIT License (MIT)
22

33
Copyright (c) 2019 Yoshua Wuyts
4+
Copyright (c) 2017 http-rs authors
5+
Copyright (c) 2020 Jacob Brown
46

57
Permission is hereby granted, free of charge, to any person obtaining a copy
68
of this software and associated documentation files (the "Software"), to deal

src/headers/constants.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,31 @@ pub const TRANSFER_ENCODING: HeaderName = HeaderName::from_lowercase_str("transf
1717

1818
/// The `Date` Header
1919
pub const DATE: HeaderName = HeaderName::from_lowercase_str("date");
20+
21+
/// The `Origin` Header
22+
pub const ORIGIN: HeaderName = HeaderName::from_lowercase_str("origin");
23+
24+
/// The `access-control-max-age` Header
25+
pub const ACCESS_CONTROL_MAX_AGE: HeaderName =
26+
HeaderName::from_lowercase_str("access-control-max-age");
27+
/// The `access-control-allow-origin` Header
28+
pub const ACCESS_CONTROL_ALLOW_ORIGIN: HeaderName =
29+
HeaderName::from_lowercase_str("access-control-allow-origin");
30+
/// The `access-control-allow-headers` Header
31+
pub const ACCESS_CONTROL_ALLOW_HEADERS: HeaderName =
32+
HeaderName::from_lowercase_str("access-control-allow-headers");
33+
/// The `access-control-allow-methods` Header
34+
pub const ACCESS_CONTROL_ALLOW_METHODS: HeaderName =
35+
HeaderName::from_lowercase_str("access-control-allow-methods");
36+
/// The `access-control-expose-headers` Header
37+
pub const ACCESS_CONTROL_EXPOSE_HEADERS: HeaderName =
38+
HeaderName::from_lowercase_str("access-control-expose-headers");
39+
/// The `access-control-request-method` Header
40+
pub const ACCESS_CONTROL_REQUEST_METHOD: HeaderName =
41+
HeaderName::from_lowercase_str("access-control-request-method");
42+
/// The `access-control-request-headers` Header
43+
pub const ACCESS_CONTROL_REQUEST_HEADERS: HeaderName =
44+
HeaderName::from_lowercase_str("access-control-request-headers");
45+
/// The `access-control-allow-credentials` Header
46+
pub const ACCESS_CONTROL_ALLOW_CREDENTIALS: HeaderName =
47+
HeaderName::from_lowercase_str("access-control-allow-credentials");

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ mod method;
121121
mod request;
122122
mod response;
123123
mod status_code;
124+
mod type_map;
124125
mod version;
125126

126127
pub use body::Body;
@@ -152,6 +153,9 @@ pub mod trailers;
152153
#[cfg(feature = "hyperium_http")]
153154
mod hyperium_http;
154155

156+
#[doc(inline)]
157+
pub use crate::type_map::TypeMap;
158+
155159
// Not public API. Referenced by macro-generated code.
156160
#[doc(hidden)]
157161
pub mod private {

src/request.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::headers::{
1212
use crate::mime::Mime;
1313
use crate::trailers::{Trailers, TrailersSender};
1414
use crate::Cookie;
15-
use crate::{Body, Method, Url, Version};
15+
use crate::{Body, Method, TypeMap, Url, Version};
1616

1717
pin_project_lite::pin_project! {
1818
/// An HTTP request.
@@ -35,6 +35,7 @@ pin_project_lite::pin_project! {
3535
receiver: sync::Receiver<crate::Result<Trailers>>,
3636
#[pin]
3737
body: Body,
38+
local_state: TypeMap,
3839
}
3940
}
4041

@@ -50,6 +51,7 @@ impl Request {
5051
body: Body::empty(),
5152
sender: Some(sender),
5253
receiver,
54+
local_state: TypeMap::new(),
5355
}
5456
}
5557

@@ -423,6 +425,31 @@ impl Request {
423425
pub fn header_values<'a>(&'a self) -> Values<'a> {
424426
self.headers.values()
425427
}
428+
429+
/// Returns a reference to the existing local state.
430+
pub fn local_state(&self) -> &TypeMap {
431+
&self.local_state
432+
}
433+
434+
/// Returns a mutuable reference to the existing local state.
435+
///
436+
///
437+
/// # Examples
438+
///
439+
/// ```
440+
/// # fn main() -> Result<(), http_types::Error> {
441+
/// #
442+
/// use http_types::{Url, Method, Request, Version};
443+
///
444+
/// let mut req = Request::new(Method::Get, Url::parse("https://example.com")?);
445+
/// req.local_state_mut().insert("hello from the extension");
446+
/// assert_eq!(req.local_state().get(), Some(&"hello from the extension"));
447+
/// #
448+
/// # Ok(()) }
449+
/// ```
450+
pub fn local_state_mut(&mut self) -> &mut TypeMap {
451+
&mut self.local_state
452+
}
426453
}
427454

428455
impl Read for Request {

src/response.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::headers::{
1111
};
1212
use crate::mime::Mime;
1313
use crate::trailers::{Trailers, TrailersSender};
14-
use crate::{Body, Cookie, StatusCode, Version};
14+
use crate::{Body, Cookie, StatusCode, TypeMap, Version};
1515

1616
pin_project_lite::pin_project! {
1717
/// An HTTP response.
@@ -37,6 +37,7 @@ pin_project_lite::pin_project! {
3737
receiver: sync::Receiver<crate::Result<Trailers>>,
3838
#[pin]
3939
body: Body,
40+
local_state: TypeMap,
4041
}
4142
}
4243

@@ -51,6 +52,7 @@ impl Response {
5152
body: Body::empty(),
5253
sender: Some(sender),
5354
receiver,
55+
local_state: TypeMap::new(),
5456
}
5557
}
5658

@@ -389,6 +391,31 @@ impl Response {
389391
pub fn header_values<'a>(&'a self) -> Values<'a> {
390392
self.headers.values()
391393
}
394+
395+
/// Returns a reference to the existing local_state.
396+
pub fn local_state(&self) -> &TypeMap {
397+
&self.local_state
398+
}
399+
400+
/// Returns a mutuable reference to the existing local state.
401+
///
402+
///
403+
/// # Examples
404+
///
405+
/// ```
406+
/// # fn main() -> Result<(), http_types::Error> {
407+
/// #
408+
/// use http_types::{StatusCode, Response, Version};
409+
///
410+
/// let mut res = Response::new(StatusCode::Ok);
411+
/// res.local_state_mut().insert("hello from the extension");
412+
/// assert_eq!(res.local_state().get(), Some(&"hello from the extension"));
413+
/// #
414+
/// # Ok(()) }
415+
/// ```
416+
pub fn local_state_mut(&mut self) -> &mut TypeMap {
417+
&mut self.local_state
418+
}
392419
}
393420

394421
impl Read for Response {

src/type_map.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Implementation is based on
2+
// - https://github.com/hyperium/http/blob/master/src/extensions.rs
3+
// - https://github.com/kardeiz/type-map/blob/master/src/lib.rs
4+
5+
use std::any::{Any, TypeId};
6+
use std::collections::HashMap;
7+
use std::fmt;
8+
use std::hash::{BuildHasherDefault, Hasher};
9+
10+
/// Map type that allows storing any `Sync + Send + 'static` type as local state
11+
/// on a `Request` or `Response`.
12+
#[derive(Default)]
13+
pub struct TypeMap {
14+
map: Option<HashMap<TypeId, Box<dyn Any + Send + Sync>, BuildHasherDefault<IdHasher>>>,
15+
}
16+
17+
impl TypeMap {
18+
/// Create an empty `TypeMap`.
19+
#[inline]
20+
pub fn new() -> Self {
21+
Self { map: None }
22+
}
23+
24+
/// Insert a value into this `TypeMap`.
25+
///
26+
/// If a value of this type already exists, it will be returned.
27+
pub fn insert<T: Send + Sync + 'static>(&mut self, val: T) -> Option<T> {
28+
self.map
29+
.get_or_insert_with(|| Default::default())
30+
.insert(TypeId::of::<T>(), Box::new(val))
31+
.and_then(|boxed| (boxed as Box<dyn Any>).downcast().ok().map(|boxed| *boxed))
32+
}
33+
34+
/// Check if container contains value for type
35+
pub fn contains<T: 'static>(&self) -> bool {
36+
self.map
37+
.as_ref()
38+
.and_then(|m| m.get(&TypeId::of::<T>()))
39+
.is_some()
40+
}
41+
42+
/// Get a reference to a value previously inserted on this `TypeMap`.
43+
pub fn get<T: 'static>(&self) -> Option<&T> {
44+
self.map
45+
.as_ref()
46+
.and_then(|m| m.get(&TypeId::of::<T>()))
47+
.and_then(|boxed| (&**boxed as &(dyn Any)).downcast_ref())
48+
}
49+
50+
/// Get a mutable reference to a value previously inserted on this `TypeMap`.
51+
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
52+
self.map
53+
.as_mut()
54+
.and_then(|m| m.get_mut(&TypeId::of::<T>()))
55+
.and_then(|boxed| (&mut **boxed as &mut (dyn Any)).downcast_mut())
56+
}
57+
58+
/// Remove a value from this `TypeMap`.
59+
///
60+
/// If a value of this type exists, it will be returned.
61+
pub fn remove<T: 'static>(&mut self) -> Option<T> {
62+
self.map
63+
.as_mut()
64+
.and_then(|m| m.remove(&TypeId::of::<T>()))
65+
.and_then(|boxed| (boxed as Box<dyn Any>).downcast().ok().map(|boxed| *boxed))
66+
}
67+
68+
/// Clear the `TypeMap` of all inserted values.
69+
#[inline]
70+
pub fn clear(&mut self) {
71+
self.map = None;
72+
}
73+
}
74+
75+
impl fmt::Debug for TypeMap {
76+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77+
f.debug_struct("TypeMap").finish()
78+
}
79+
}
80+
81+
// With TypeIds as keys, there's no need to hash them. So we simply use an identy hasher.
82+
#[derive(Default)]
83+
struct IdHasher(u64);
84+
85+
impl Hasher for IdHasher {
86+
fn write(&mut self, _: &[u8]) {
87+
unreachable!("TypeId calls write_u64");
88+
}
89+
90+
#[inline]
91+
fn write_u64(&mut self, id: u64) {
92+
self.0 = id;
93+
}
94+
95+
#[inline]
96+
fn finish(&self) -> u64 {
97+
self.0
98+
}
99+
}
100+
101+
#[cfg(test)]
102+
mod tests {
103+
use super::*;
104+
#[test]
105+
fn test_type_map() {
106+
#[derive(Debug, PartialEq)]
107+
struct MyType(i32);
108+
109+
let mut map = TypeMap::new();
110+
111+
map.insert(5i32);
112+
map.insert(MyType(10));
113+
114+
assert_eq!(map.get(), Some(&5i32));
115+
assert_eq!(map.get_mut(), Some(&mut 5i32));
116+
117+
assert_eq!(map.remove::<i32>(), Some(5i32));
118+
assert!(map.get::<i32>().is_none());
119+
120+
assert_eq!(map.get::<bool>(), None);
121+
assert_eq!(map.get(), Some(&MyType(10)));
122+
}
123+
}

0 commit comments

Comments
 (0)