Skip to content

Commit 593d118

Browse files
committed
start of derive(Header), split into multiple crates
1 parent 6b859a8 commit 593d118

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+509
-311
lines changed

.gitignore

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
/target
2-
/Cargo.lock
1+
target
2+
Cargo.lock

Cargo.toml

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
[package]
2-
32
name = "headers"
4-
version = "0.12.5" # don't forget to update html_root_url
5-
description = "A fast and correct HTTP library."
3+
version = "0.0.0" # don't forget to update html_root_url
4+
description = "typed HTTP headers"
65
readme = "README.md"
76
homepage = "https://hyper.rs"
87
license = "MIT"
@@ -18,9 +17,18 @@ include = [
1817
"src/**/*"
1918
]
2019

20+
[workspace]
21+
members = [
22+
"./",
23+
"headers-core",
24+
"headers-derive",
25+
"headers-ext",
26+
]
27+
2128
[dependencies]
2229
base64 = "0.9"
2330
bytes = "0.4.4"
31+
headers-derive = { path = "./headers-derive" }
2432
http = "0.1.7"
2533
httparse = "1.3"
2634
language-tags = "0.2"

headers-core/Cargo.toml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "headers-core"
3+
version = "0.0.0" # don't forget to update html_root_url
4+
description = "typed HTTP headers"
5+
authors = ["Sean McArthur <[email protected]>"]
6+
license = "MIT"
7+
8+
[dependencies]
9+
http = "0.1.7"

headers-core/src/error.rs

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#[derive(Debug)]
2+
pub struct Error {
3+
kind: Kind,
4+
}
5+
6+
#[derive(Debug)]
7+
enum Kind {
8+
Invalid,
9+
Empty,
10+
TooMany,
11+
}
12+
13+
pub type Result<T> = ::std::result::Result<T, Error>;
14+
15+
impl Error {
16+
fn new(kind: Kind) -> Self {
17+
Error {
18+
kind,
19+
}
20+
}
21+
22+
pub fn invalid() -> Self {
23+
Error::new(Kind::Invalid)
24+
}
25+
26+
pub fn empty() -> Self {
27+
Error::new(Kind::Empty)
28+
}
29+
30+
pub fn too_many_values() -> Self {
31+
Error::new(Kind::TooMany)
32+
}
33+
}
34+
35+
impl From<::http::header::ToStrError> for Error {
36+
fn from(_: ::http::header::ToStrError) -> Error {
37+
Error::invalid()
38+
}
39+
}
40+

headers-core/src/lib.rs

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
extern crate http;
2+
3+
pub use http::header::{self, HeaderName, HeaderValue};
4+
5+
mod error;
6+
pub mod parsing;
7+
8+
pub use self::error::{Error, Result};
9+
10+
/// A trait for any object that will represent a header field and value.
11+
///
12+
/// This trait represents the construction and identification of headers,
13+
/// and contains trait-object unsafe methods.
14+
pub trait Header {
15+
/// The name of this header.
16+
const NAME: &'static HeaderName;
17+
18+
/// Decode this type from a `HeaderValue`.
19+
fn decode(values: &mut Values) -> Result<Self>
20+
where
21+
Self: Sized;
22+
23+
/// Encode this type to a `HeaderMap`.
24+
///
25+
/// This function should be infallible. Any errors converting to a
26+
/// `HeaderValue` should have been caught when parsing or constructing
27+
/// this value.
28+
fn encode(&self, values: &mut ToValues);
29+
}
30+
31+
#[derive(Debug)]
32+
pub struct Values<'a> {
33+
inner: http::header::ValueIter<'a, http::header::HeaderValue>,
34+
}
35+
36+
impl<'a> Iterator for Values<'a> {
37+
type Item = &'a HeaderValue;
38+
39+
#[inline]
40+
fn next(&mut self) -> Option<Self::Item> {
41+
self.inner.next()
42+
}
43+
44+
fn size_hint(&self) -> (usize, Option<usize>) {
45+
self.inner.size_hint()
46+
}
47+
}
48+
49+
pub struct ToValues<'a> {
50+
state: State<'a>,
51+
}
52+
53+
enum State<'a> {
54+
First(http::header::Entry<'a, HeaderValue>),
55+
Latter(http::header::OccupiedEntry<'a, HeaderValue>),
56+
Tmp,
57+
}
58+
59+
impl<'a> ToValues<'a> {
60+
pub fn append(&mut self, value: HeaderValue) {
61+
let entry = match ::std::mem::replace(&mut self.state, State::Tmp) {
62+
State::First(http::header::Entry::Occupied(mut e)) => {
63+
e.insert(value);
64+
e
65+
},
66+
State::First(http::header::Entry::Vacant(e)) => e.insert_entry(value),
67+
State::Latter(mut e) => {
68+
e.append(value);
69+
e
70+
},
71+
State::Tmp => unreachable!("ToValues State::Tmp"),
72+
};
73+
self.state = State::Latter(entry);
74+
}
75+
}
76+
77+
pub trait HeaderMapExt: self::sealed::Sealed {
78+
fn typed_insert<H>(&mut self, header: H)
79+
where
80+
H: Header;
81+
}
82+
83+
impl HeaderMapExt for http::HeaderMap {
84+
fn typed_insert<H>(&mut self, header: H)
85+
where
86+
H: Header,
87+
{
88+
let entry = self
89+
.entry(H::NAME)
90+
.expect("HeaderName is always valid");
91+
let mut values = ToValues {
92+
state: State::First(entry),
93+
};
94+
header.encode(&mut values);
95+
}
96+
}
97+
98+
mod sealed {
99+
pub trait Sealed {}
100+
impl Sealed for ::http::HeaderMap {}
101+
}

src/parsing.rs renamed to headers-core/src/parsing.rs

+15-17
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,26 @@
11
//! Utility functions for Header implementations.
22
3-
use language_tags::LanguageTag;
4-
use std::str;
5-
use std::str::FromStr;
6-
use std::fmt::{self, Display};
7-
3+
//use language_tags::LanguageTag;
4+
use http::HttpTryFrom;
85
use http::header::HeaderValue;
9-
use percent_encoding;
6+
//use percent_encoding;
107

11-
use shared::Charset;
8+
//use shared::Charset;
129

1310

1411
/// Reads a single raw string when parsing a header.
15-
pub fn from_one_raw_str<T: str::FromStr>(_raw: &HeaderValue) -> ::Result<T> {
16-
unimplemented!("from_one_raw_str");
17-
/*
18-
if let Some(line) = raw.one() {
19-
if !line.is_empty() {
20-
return from_raw_str(line)
21-
}
12+
pub fn from_one_value<'a, T: HttpTryFrom<&'a HeaderValue>>(values: &mut ::Values<'a>) -> ::Result<T> {
13+
if let Some(value) = values.next() {
14+
HttpTryFrom::try_from(value)
15+
.map_err(|_| ::Error::invalid())
16+
} else {
17+
Err(::Error::empty())
2218
}
23-
Err(::Error::Header)
24-
*/
2519
}
2620

2721
/// Reads a comma-delimited raw header into a Vec.
2822
#[inline]
29-
pub fn from_comma_delimited<T: str::FromStr>(_raw: &HeaderValue) -> ::Result<Vec<T>> {
23+
pub fn from_comma_delimited<T: ::std::str::FromStr, E: ::std::iter::FromIterator<T>>(_values: &mut ::Values) -> ::Result<E> {
3024
unimplemented!("from_comma_delimited");
3125
/*
3226
let mut result = Vec::new();
@@ -43,6 +37,7 @@ pub fn from_comma_delimited<T: str::FromStr>(_raw: &HeaderValue) -> ::Result<Vec
4337
*/
4438
}
4539

40+
/*
4641
/// Format an array into a comma-delimited string.
4742
pub fn fmt_comma_delimited<T: Display>(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result {
4843
let mut iter = parts.iter();
@@ -55,7 +50,9 @@ pub fn fmt_comma_delimited<T: Display>(f: &mut fmt::Formatter, parts: &[T]) -> f
5550
}
5651
Ok(())
5752
}
53+
*/
5854

55+
/*
5956
/// An extended header parameter value (i.e., tagged with a character set and optionally,
6057
/// a language), as defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
6158
#[derive(Clone, Debug, PartialEq)]
@@ -248,3 +245,4 @@ mod tests {
248245
format!("{}", extended_value));
249246
}
250247
}
248+
*/

headers-derive/Cargo.toml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
3+
name = "headers-derive"
4+
version = "0.0.0" # don't forget to update html_root_url
5+
description = "derive(Header)"
6+
authors = ["Sean McArthur <[email protected]>"]
7+
8+
[lib]
9+
name = "headers_derive"
10+
proc-macro = true
11+
12+
[dependencies]
13+
proc-macro2 = "0.4"
14+
quote = "0.6"
15+
syn = "0.14"

headers-derive/src/lib.rs

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
extern crate proc_macro;
2+
extern crate proc_macro2;
3+
#[macro_use]
4+
extern crate quote;
5+
extern crate syn;
6+
7+
use proc_macro::TokenStream;
8+
use proc_macro2::Span;
9+
use syn::{Data, Fields, Ident};
10+
11+
#[proc_macro_derive(Header, attributes(header))]
12+
pub fn derive_header(input: TokenStream) -> TokenStream {
13+
let ast = syn::parse(input).unwrap();
14+
impl_header(&ast).into()
15+
}
16+
17+
fn impl_header(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
18+
let fns = match impl_fns(ast) {
19+
Ok(fns) => fns,
20+
Err(msg) => {
21+
return quote! {
22+
compile_error!(#msg);
23+
}.into();
24+
}
25+
};
26+
27+
let decode = fns.decode;
28+
let encode = fns.encode;
29+
30+
let ty = &ast.ident;
31+
let dummy_const = Ident::new(&format!("_IMPL_HEADER_FOR_{}", ty), Span::call_site());
32+
let impl_block = quote! {
33+
impl __hc::Header for #ty {
34+
const NAME: &'static __hc::HeaderName = &__hc::header::HOST;
35+
fn decode(values: &mut __hc::Values) -> __hc::Result<Self> {
36+
#decode
37+
}
38+
39+
fn encode(&self, values: &mut __hc::ToValues) {
40+
#encode
41+
}
42+
}
43+
};
44+
45+
quote! {
46+
const #dummy_const: () = {
47+
extern crate headers_core as __hc;
48+
#impl_block
49+
};
50+
}
51+
}
52+
53+
struct Fns {
54+
encode: proc_macro2::TokenStream,
55+
decode: proc_macro2::TokenStream,
56+
}
57+
58+
fn impl_fns(ast: &syn::DeriveInput) -> Result<Fns, String> {
59+
let ty = &ast.ident;
60+
let st = match ast.data {
61+
Data::Struct(ref st) => st,
62+
_ => {
63+
return Err("derive(Header) only works on structs".into())
64+
}
65+
};
66+
67+
match st.fields {
68+
Fields::Named(ref fields) => {
69+
if fields.named.len() != 1 {
70+
return Err("derive(Header) doesn't support multiple fields".into());
71+
}
72+
73+
let _field = fields
74+
.named
75+
.iter()
76+
.next()
77+
.expect("just checked for len() == 1");
78+
79+
let decode = quote! {
80+
unimplemented!()
81+
};
82+
let encode = quote! {
83+
84+
};
85+
86+
Ok(Fns {
87+
decode,
88+
encode,
89+
})
90+
},
91+
Fields::Unnamed(ref fields) => {
92+
if fields.unnamed.len() != 1 {
93+
return Err("derive(Header) doesn't support multiple fields".into());
94+
}
95+
96+
let decode = quote! {
97+
__hc::parsing::from_one_value(values)
98+
.map(#ty)
99+
};
100+
let encode = quote! {
101+
102+
};
103+
104+
Ok(Fns {
105+
decode,
106+
encode,
107+
})
108+
},
109+
Fields::Unit => {
110+
Err("ah".into())
111+
}
112+
}
113+
}

headers-ext/Cargo.toml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
3+
name = "headers-ext"
4+
version = "0.0.0" # don't forget to update html_root_url
5+
description = "typed HTTP headers"
6+
authors = ["Sean McArthur <[email protected]>"]
7+
license = "MIT"
8+
9+
[dependencies]
10+
headers-core = { path = "../headers-core" }
11+
headers-derive = { path = "../headers-derive" }
12+
http = "0.1.7"
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)