Skip to content

Commit a09ebd4

Browse files
authored
Merge pull request #19 from yoshuawuyts/pch/reuse_http_fields_impls
swap out local definition of Fields for http::HeaderMap
2 parents bfa3b51 + 284d9ee commit a09ebd4

File tree

9 files changed

+231
-181
lines changed

9 files changed

+231
-181
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ authors = [
1616
[features]
1717

1818
[dependencies]
19+
http.workspace = true
1920
slab.workspace = true
20-
url.workspace = true
2121
wasi.workspace = true
2222
wstd-macro.workspace = true
2323

@@ -46,13 +46,13 @@ license = "MIT OR Apache-2.0 OR Apache-2.0 WITH LLVM-exception"
4646
anyhow = "1"
4747
cargo_metadata = "0.18.1"
4848
heck = "0.5"
49+
http = "1.1"
4950
quote = "1.0"
5051
serde_json = "1"
5152
slab = "0.4.9"
5253
syn = "2.0"
5354
test-programs = { path = "test-programs" }
5455
test-programs-artifacts = { path = "test-programs/artifacts" }
55-
url = "2.5.0"
5656
wasi = "0.13.1"
5757
wasmtime = "26"
5858
wasmtime-wasi = "26"

examples/http_get.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
use std::error::Error;
2-
use wstd::http::{Client, Method, Request, Url};
2+
use wstd::http::{Client, HeaderValue, Method, Request};
33
use wstd::io::AsyncRead;
44

55
#[wstd::main]
66
async fn main() -> Result<(), Box<dyn Error>> {
7-
let request = Request::new(Method::Get, Url::parse("https://postman-echo.com/get")?);
7+
let mut request = Request::new(Method::GET, "https://postman-echo.com/get".parse()?);
8+
request
9+
.headers_mut()
10+
.insert("my-header", HeaderValue::from_str("my-value")?);
11+
812
let mut response = Client::new().send(request).await?;
913

1014
let content_type = response
1115
.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");
16+
.get("Content-Type")
17+
.ok_or_else(|| "response expected to have Content-Type header")?;
18+
assert_eq!(content_type, "application/json; charset=utf-8");
1619

1720
// Would much prefer read_to_end here:
1821
let mut body_buf = vec![0; 4096];
@@ -30,5 +33,15 @@ async fn main() -> Result<(), Box<dyn Error>> {
3033
"expected body url to contain the authority and path, got: {body_url}"
3134
);
3235

36+
assert_eq!(
37+
val.get("headers")
38+
.ok_or_else(|| "body json has headers")?
39+
.get("my-header")
40+
.ok_or_else(|| "headers contains my-header")?
41+
.as_str()
42+
.ok_or_else(|| "my-header is a str")?,
43+
"my-value"
44+
);
45+
3346
Ok(())
3447
}

src/http/client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ impl Client {
1818

1919
/// Send an HTTP request.
2020
pub async fn send<B: Body>(&self, req: Request<B>) -> Result<Response<IncomingBody>> {
21-
let (wasi_req, body) = req.into_outgoing();
21+
let (wasi_req, body) = req.into_outgoing()?;
2222
let wasi_body = wasi_req.body().unwrap();
2323
let body_stream = wasi_body.write().unwrap();
2424

src/http/error.rs

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,104 @@
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+
ErrorVariant::Method(e) => write!(f, "method error: {e:?}"),
23+
ErrorVariant::Other(e) => write!(f, "{e}"),
24+
}
25+
}
26+
}
27+
28+
impl fmt::Display for Error {
29+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30+
match &self.variant {
31+
ErrorVariant::WasiHttp(e) => write!(f, "wasi http error: {e}"),
32+
ErrorVariant::WasiHeader(e) => write!(f, "wasi header error: {e}"),
33+
ErrorVariant::HeaderName(e) => write!(f, "header name error: {e}"),
34+
ErrorVariant::HeaderValue(e) => write!(f, "header value error: {e}"),
35+
ErrorVariant::Method(e) => write!(f, "method error: {e}"),
36+
ErrorVariant::Other(e) => write!(f, "{e}"),
37+
}
38+
}
39+
}
40+
41+
impl std::error::Error for Error {}
42+
43+
impl Error {
44+
pub(crate) fn other(s: impl Into<String>) -> Self {
45+
ErrorVariant::Other(s.into()).into()
46+
}
47+
pub(crate) fn context(self, s: impl Into<String>) -> Self {
48+
let mut context = self.context;
49+
context.push(s.into());
50+
Self {
51+
variant: self.variant,
52+
context,
53+
}
54+
}
55+
}
56+
57+
impl From<ErrorVariant> for Error {
58+
fn from(variant: ErrorVariant) -> Error {
59+
Error {
60+
variant,
61+
context: Vec::new(),
62+
}
63+
}
64+
}
65+
66+
impl From<wasi::http::types::ErrorCode> for Error {
67+
fn from(e: wasi::http::types::ErrorCode) -> Error {
68+
ErrorVariant::WasiHttp(e).into()
69+
}
70+
}
71+
72+
impl From<wasi::http::types::HeaderError> for Error {
73+
fn from(e: wasi::http::types::HeaderError) -> Error {
74+
ErrorVariant::WasiHeader(e).into()
75+
}
76+
}
77+
78+
impl From<http::header::InvalidHeaderValue> for Error {
79+
fn from(e: http::header::InvalidHeaderValue) -> Error {
80+
ErrorVariant::HeaderValue(e).into()
81+
}
82+
}
83+
84+
impl From<http::header::InvalidHeaderName> for Error {
85+
fn from(e: http::header::InvalidHeaderName) -> Error {
86+
ErrorVariant::HeaderName(e).into()
87+
}
88+
}
89+
90+
impl From<http::method::InvalidMethod> for Error {
91+
fn from(e: http::method::InvalidMethod) -> Error {
92+
ErrorVariant::Method(e).into()
93+
}
94+
}
95+
96+
#[derive(Debug)]
97+
pub enum ErrorVariant {
98+
WasiHttp(wasi::http::types::ErrorCode),
99+
WasiHeader(wasi::http::types::HeaderError),
100+
HeaderName(http::header::InvalidHeaderName),
101+
HeaderValue(http::header::InvalidHeaderValue),
102+
Method(http::method::InvalidMethod),
103+
Other(String),
104+
}

src/http/fields.rs

Lines changed: 21 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,26 @@
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.
5-
pub type Headers = Fields;
6-
7-
/// A type alias for [`Fields`] when used as HTTP trailers.
8-
pub type Trailers = Fields;
9-
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)
1+
pub use http::header::{HeaderMap, HeaderName, HeaderValue};
2+
3+
use super::{Error, Result};
4+
use wasi::http::types::Fields;
5+
6+
pub(crate) fn header_map_from_wasi(wasi_fields: Fields) -> Result<HeaderMap> {
7+
let mut output = HeaderMap::new();
8+
for (key, value) in wasi_fields.entries() {
9+
let key = HeaderName::from_bytes(key.as_bytes())
10+
.map_err(|e| Error::from(e).context("header name {key}"))?;
11+
let value = HeaderValue::from_bytes(&value)
12+
.map_err(|e| Error::from(e).context("header value for {key}"))?;
13+
output.append(key, value);
6114
}
15+
Ok(output)
6216
}
6317

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)?)
18+
pub(crate) fn header_map_to_wasi(header_map: &HeaderMap) -> Result<Fields> {
19+
let wasi_fields = Fields::new();
20+
for (key, value) in header_map {
21+
wasi_fields
22+
.append(&key.as_str().to_owned(), &value.as_bytes().to_owned())
23+
.map_err(|e| Error::from(e).context("header named {key}"))?;
7424
}
25+
Ok(wasi_fields)
7526
}

src/http/method.rs

Lines changed: 29 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,36 @@
11
use wasi::http::types::Method as WasiMethod;
22

3-
/// The method for the HTTP request
4-
#[derive(Debug)]
5-
#[non_exhaustive]
6-
pub enum Method {
7-
/// The GET method requests transfer of a current selected representation
8-
/// for the target resource.
9-
Get,
10-
/// The HEAD method is identical to GET except that the server MUST NOT send a message body in
11-
/// the response.
12-
Head,
13-
/// The POST method requests that the target resource process the representation enclosed in
14-
/// the request according to the resource's own specific semantics.
15-
Post,
16-
/// The PUT method requests that the state of the target resource be created or replaced with
17-
/// the state defined by the representation enclosed in the request message payload.
18-
Put,
19-
/// The DELETE method requests that the origin server remove the association between the target
20-
/// resource and its current functionality.
21-
Delete,
22-
/// The CONNECT method requests that the recipient establish a tunnel to the destination origin
23-
/// server identified by the request-target and, if successful, thereafter restrict its
24-
/// behavior to blind forwarding of packets, in both directions, until the tunnel is closed.
25-
Connect,
26-
/// The OPTIONS method requests information about the communication options available for the
27-
/// target resource, at either the origin server or an intervening intermediary.
28-
Options,
29-
/// The TRACE method requests a remote, application-level loop-back of the request message.
30-
Trace,
31-
/// The PATCH method requests that a set of changes described in the request entity be applied
32-
/// to the resource identified by the Request- URI.
33-
///
34-
Patch,
35-
/// Send a method not covered by this list.
36-
Other(String),
37-
}
3+
use super::Result;
4+
pub use http::Method;
385

39-
impl From<Method> for WasiMethod {
40-
fn from(value: Method) -> Self {
41-
match value {
42-
Method::Get => WasiMethod::Get,
43-
Method::Head => WasiMethod::Head,
44-
Method::Post => WasiMethod::Post,
45-
Method::Put => WasiMethod::Put,
46-
Method::Delete => WasiMethod::Delete,
47-
Method::Connect => WasiMethod::Connect,
48-
Method::Options => WasiMethod::Options,
49-
Method::Trace => WasiMethod::Trace,
50-
Method::Patch => WasiMethod::Patch,
51-
Method::Other(s) => WasiMethod::Other(s),
52-
}
6+
pub(crate) fn to_wasi_method(value: Method) -> WasiMethod {
7+
match value {
8+
Method::GET => WasiMethod::Get,
9+
Method::HEAD => WasiMethod::Head,
10+
Method::POST => WasiMethod::Post,
11+
Method::PUT => WasiMethod::Put,
12+
Method::DELETE => WasiMethod::Delete,
13+
Method::CONNECT => WasiMethod::Connect,
14+
Method::OPTIONS => WasiMethod::Options,
15+
Method::TRACE => WasiMethod::Trace,
16+
Method::PATCH => WasiMethod::Patch,
17+
other => WasiMethod::Other(other.as_str().to_owned()),
5318
}
5419
}
5520

56-
impl From<WasiMethod> for Method {
57-
fn from(value: WasiMethod) -> Self {
58-
match value {
59-
WasiMethod::Get => Method::Get,
60-
WasiMethod::Head => Method::Head,
61-
WasiMethod::Post => Method::Post,
62-
WasiMethod::Put => Method::Put,
63-
WasiMethod::Delete => Method::Delete,
64-
WasiMethod::Connect => Method::Connect,
65-
WasiMethod::Options => Method::Options,
66-
WasiMethod::Trace => Method::Trace,
67-
WasiMethod::Patch => Method::Patch,
68-
WasiMethod::Other(s) => Method::Other(s),
69-
}
70-
}
21+
// This will become useful once we support IncomingRequest
22+
#[allow(dead_code)]
23+
pub(crate) fn from_wasi_method(value: WasiMethod) -> Result<Method> {
24+
Ok(match value {
25+
WasiMethod::Get => Method::GET,
26+
WasiMethod::Head => Method::HEAD,
27+
WasiMethod::Post => Method::POST,
28+
WasiMethod::Put => Method::PUT,
29+
WasiMethod::Delete => Method::DELETE,
30+
WasiMethod::Connect => Method::CONNECT,
31+
WasiMethod::Options => Method::OPTIONS,
32+
WasiMethod::Trace => Method::TRACE,
33+
WasiMethod::Patch => Method::PATCH,
34+
WasiMethod::Other(s) => Method::from_bytes(s.as_bytes())?,
35+
})
7136
}

src/http/mod.rs

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

55
#[doc(inline)]
66
pub use body::{Body, IntoBody};
77
pub use client::Client;
88
pub use error::{Error, Result};
9-
pub use fields::{FieldName, FieldValue, Fields, Headers, Trailers};
9+
pub use fields::{HeaderMap, HeaderName, HeaderValue};
1010
pub use method::Method;
1111
pub use request::Request;
1212
pub use response::Response;

0 commit comments

Comments
 (0)