Skip to content

Commit 5e2ce16

Browse files
committed
swap out local definitions of Fields, FieldName, FieldValue for http crate
Closes #9
1 parent c0b3ef8 commit 5e2ce16

File tree

5 files changed

+114
-76
lines changed

5 files changed

+114
-76
lines changed

examples/http_get.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
99

1010
let content_type = response
1111
.headers()
12-
.get(&"content-type".into())
13-
.ok_or_else(|| "response expected to have content-type header")?;
14-
assert_eq!(content_type.len(), 1, "one header value for content-type");
15-
assert_eq!(content_type[0], b"application/json; charset=utf-8");
12+
.get("Content-Type")
13+
.ok_or_else(|| "response expected to have Content-Type header")?;
14+
assert_eq!(content_type, "application/json; charset=utf-8");
1615

1716
// Would much prefer read_to_end here:
1817
let mut body_buf = vec![0; 4096];

src/http/error.rs

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,89 @@
1-
/// The `http` error type.
2-
pub type Error = wasi::http::types::ErrorCode;
1+
use std::fmt;
32

43
/// The `http` result type.
54
pub type Result<T> = std::result::Result<T, Error>;
5+
6+
/// The `http` error type.
7+
pub struct Error {
8+
variant: ErrorVariant,
9+
context: Vec<String>,
10+
}
11+
12+
impl fmt::Debug for Error {
13+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14+
for c in self.context.iter() {
15+
write!(f, "in {c}:\n")?;
16+
}
17+
match &self.variant {
18+
ErrorVariant::WasiHttp(e) => write!(f, "wasi http error: {e:?}"),
19+
ErrorVariant::WasiHeader(e) => write!(f, "wasi header error: {e:?}"),
20+
ErrorVariant::HeaderName(e) => write!(f, "header name error: {e:?}"),
21+
ErrorVariant::HeaderValue(e) => write!(f, "header value error: {e:?}"),
22+
}
23+
}
24+
}
25+
26+
impl fmt::Display for Error {
27+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28+
match &self.variant {
29+
ErrorVariant::WasiHttp(e) => write!(f, "wasi http error: {e}"),
30+
ErrorVariant::WasiHeader(e) => write!(f, "wasi header error: {e}"),
31+
ErrorVariant::HeaderName(e) => write!(f, "header name error: {e}"),
32+
ErrorVariant::HeaderValue(e) => write!(f, "header value error: {e}"),
33+
}
34+
}
35+
}
36+
37+
impl std::error::Error for Error {}
38+
39+
impl Error {
40+
pub(crate) fn context(self, s: impl Into<String>) -> Self {
41+
let mut context = self.context;
42+
context.push(s.into());
43+
Self {
44+
variant: self.variant,
45+
context,
46+
}
47+
}
48+
}
49+
50+
impl From<ErrorVariant> for Error {
51+
fn from(variant: ErrorVariant) -> Error {
52+
Error {
53+
variant,
54+
context: Vec::new(),
55+
}
56+
}
57+
}
58+
59+
impl From<wasi::http::types::ErrorCode> for Error {
60+
fn from(e: wasi::http::types::ErrorCode) -> Error {
61+
ErrorVariant::WasiHttp(e).into()
62+
}
63+
}
64+
65+
impl From<wasi::http::types::HeaderError> for Error {
66+
fn from(e: wasi::http::types::HeaderError) -> Error {
67+
ErrorVariant::WasiHeader(e).into()
68+
}
69+
}
70+
71+
impl From<http::header::InvalidHeaderValue> for Error {
72+
fn from(e: http::header::InvalidHeaderValue) -> Error {
73+
ErrorVariant::HeaderValue(e).into()
74+
}
75+
}
76+
77+
impl From<http::header::InvalidHeaderName> for Error {
78+
fn from(e: http::header::InvalidHeaderName) -> Error {
79+
ErrorVariant::HeaderName(e).into()
80+
}
81+
}
82+
83+
#[derive(Debug)]
84+
pub enum ErrorVariant {
85+
WasiHttp(wasi::http::types::ErrorCode),
86+
WasiHeader(wasi::http::types::HeaderError),
87+
HeaderName(http::header::InvalidHeaderName),
88+
HeaderValue(http::header::InvalidHeaderValue),
89+
}

src/http/fields.rs

Lines changed: 20 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,28 @@
1-
use std::{borrow::Cow, collections::HashMap, ops::Deref};
2-
use wasi::http::types::{Fields as WasiFields, HeaderError};
3-
4-
/// A type alias for [`Fields`] when used as HTTP headers.
1+
pub use http::header::{HeaderMap as Fields, HeaderName as FieldName, HeaderValue as FieldValue};
52
pub type Headers = Fields;
6-
7-
/// A type alias for [`Fields`] when used as HTTP trailers.
83
pub type Trailers = Fields;
94

10-
/// An HTTP Field name.
11-
pub type FieldName = Cow<'static, str>;
12-
13-
/// An HTTP Field value.
14-
pub type FieldValue = Vec<u8>;
15-
16-
/// HTTP Fields which can be used as either trailers or headers.
17-
#[derive(Clone, PartialEq, Eq)]
18-
pub struct Fields(pub(crate) HashMap<FieldName, Vec<FieldValue>>);
19-
20-
impl Fields {
21-
pub fn get(&self, k: &FieldName) -> Option<&[FieldValue]> {
22-
self.0.get(k).map(|f| f.deref())
23-
}
24-
}
25-
26-
impl std::fmt::Debug for Fields {
27-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28-
let mut map = f.debug_map();
29-
let mut entries: Vec<_> = self.0.iter().collect();
30-
entries.sort_by_cached_key(|(k, _)| k.to_owned());
31-
for (key, values) in entries {
32-
match values.len() {
33-
0 => {
34-
map.entry(key, &"");
35-
}
36-
1 => {
37-
let value = values.iter().next().unwrap();
38-
let value = String::from_utf8_lossy(value);
39-
map.entry(key, &value);
40-
}
41-
_ => {
42-
let values: Vec<_> =
43-
values.iter().map(|v| String::from_utf8_lossy(v)).collect();
44-
map.entry(key, &values);
45-
}
46-
}
47-
}
48-
map.finish()
49-
}
50-
}
51-
52-
impl From<WasiFields> for Fields {
53-
fn from(wasi_fields: WasiFields) -> Self {
54-
let mut output = HashMap::new();
55-
for (key, value) in wasi_fields.entries() {
56-
let field_name = key.into();
57-
let field_list: &mut Vec<_> = output.entry(field_name).or_default();
58-
field_list.push(value);
59-
}
60-
Self(output)
5+
use super::{Error, Result};
6+
use wasi::http::types::Fields as WasiFields;
7+
8+
pub(crate) fn fields_from_wasi(wasi_fields: WasiFields) -> Result<Fields> {
9+
let mut output = Fields::new();
10+
for (key, value) in wasi_fields.entries() {
11+
let key = FieldName::from_bytes(key.as_bytes())
12+
.map_err(|e| Error::from(e).context("header name {key}"))?;
13+
let value = FieldValue::from_bytes(&value)
14+
.map_err(|e| Error::from(e).context("header value for {key}"))?;
15+
output.append(key, value);
6116
}
17+
Ok(output)
6218
}
6319

64-
impl TryFrom<Fields> for WasiFields {
65-
type Error = HeaderError;
66-
fn try_from(fields: Fields) -> Result<Self, Self::Error> {
67-
let mut list = Vec::with_capacity(fields.0.capacity());
68-
for (name, values) in fields.0.into_iter() {
69-
for value in values {
70-
list.push((name.clone().into_owned(), value));
71-
}
72-
}
73-
Ok(WasiFields::from_list(&list)?)
20+
pub(crate) fn fields_to_wasi(fields: &Fields) -> Result<WasiFields> {
21+
let wasi_fields = WasiFields::new();
22+
for (key, value) in fields {
23+
wasi_fields
24+
.append(&key.as_str().to_owned(), &value.as_bytes().to_owned())
25+
.map_err(|e| Error::from(e).context("header named {key}"))?;
7426
}
27+
Ok(wasi_fields)
7528
}

src/http/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! HTTP networking support
2-
2+
//!
33
pub use http::uri::Uri;
44

55
#[doc(inline)]
@@ -12,6 +12,8 @@ pub use request::Request;
1212
pub use response::Response;
1313
pub use status_code::StatusCode;
1414

15+
pub(crate) use fields::fields_from_wasi;
16+
1517
pub mod body;
1618

1719
mod client;

src/http/response.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use wasi::http::types::{IncomingBody as WasiIncomingBody, IncomingResponse};
22
use wasi::io::streams::{InputStream, StreamError};
33

4-
use super::{Body, Headers, StatusCode};
4+
use super::{fields_from_wasi, Body, Headers, StatusCode};
55
use crate::io::AsyncRead;
66
use crate::runtime::Reactor;
77

@@ -46,7 +46,7 @@ pub struct Response<B: Body> {
4646

4747
impl Response<IncomingBody> {
4848
pub(crate) fn try_from_incoming_response(incoming: IncomingResponse) -> super::Result<Self> {
49-
let headers: Headers = incoming.headers().into();
49+
let headers: Headers = fields_from_wasi(incoming.headers())?;
5050
let status = incoming.status().into();
5151

5252
// `body_stream` is a child of `incoming_body` which means we cannot

0 commit comments

Comments
 (0)