Skip to content

Commit 4b3bad8

Browse files
committed
feat(fetch): Add typed headers
1 parent 480537c commit 4b3bad8

File tree

5 files changed

+77
-29
lines changed

5 files changed

+77
-29
lines changed

examples/fetch/src/post.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
2424
Msg::Submit => {
2525
orders.skip(); // No need to rerender
2626

27+
let token = "YWxhZGRpbjpvcGVuc2VzYW1l";
2728
// Created outside async block for lifetime reasons.
2829
let request = Request::new("/")
2930
.method(Method::Post)
31+
.header(header::custom("Accept-Language", "Rust"))
32+
.header(header::custom("Authorization", format!("Basic {}", token)))
3033
.json(&model.form)
3134
.expect("Serialization failed");
3235

src/browser/fetch.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ use std::borrow::Cow;
3636
use wasm_bindgen_futures::JsFuture;
3737
use web_sys;
3838

39+
pub mod header;
3940
mod method;
4041
mod request;
4142
mod response;
4243
mod status;
4344

45+
pub use header::{Header, Headers};
4446
pub use method::*;
4547
pub use request::*;
4648
pub use response::*;
@@ -85,7 +87,7 @@ pub enum FetchError {
8587
/// `From` implementations for those types.
8688
pub enum Resource<'a> {
8789
String(Cow<'a, str>),
88-
Request(Request),
90+
Request(Request<'a>),
8991
}
9092

9193
impl<'a> From<&'a str> for Resource<'a> {
@@ -106,8 +108,8 @@ impl From<Url> for Resource<'_> {
106108
}
107109
}
108110

109-
impl From<Request> for Resource<'_> {
110-
fn from(request: Request) -> Resource<'static> {
111+
impl<'a> From<Request<'a>> for Resource<'a> {
112+
fn from(request: Request<'a>) -> Resource<'a> {
111113
Resource::Request(request)
112114
}
113115
}

src/browser/fetch/header.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//! HTTP headers
2+
3+
use std::borrow::Cow;
4+
5+
/// Request headers.
6+
#[derive(Clone, Debug, Default)]
7+
pub struct Headers<'a>(Vec<Header<'a>>);
8+
9+
impl<'a> Headers<'a> {
10+
/// Sets a new value for an existing header or adds the header if
11+
/// it does not already exist.
12+
pub fn set(&mut self, header: Header<'a>) {
13+
self.0.retain(|Header { name, .. }| &header.name != name);
14+
self.0.push(header);
15+
}
16+
}
17+
18+
impl<'a> IntoIterator for Headers<'a> {
19+
type Item = Header<'a>;
20+
type IntoIter = std::vec::IntoIter<Self::Item>;
21+
22+
fn into_iter(self) -> Self::IntoIter {
23+
self.0.into_iter()
24+
}
25+
}
26+
27+
#[derive(Clone, Debug)]
28+
pub struct Header<'a> {
29+
pub(crate) name: Cow<'a, str>,
30+
pub(crate) value: Cow<'a, str>,
31+
}
32+
33+
/// Create `Content-Type` header.
34+
pub fn content_type<'a>(value: impl Into<Cow<'a, str>>) -> Header<'a> {
35+
custom("Content-Type", value.into())
36+
}
37+
38+
/// Create `Authorization` header.
39+
pub fn authorization<'a>(value: impl Into<Cow<'a, str>>) -> Header<'a> {
40+
custom("Authorization", value)
41+
}
42+
43+
/// Create `Authorization: Bearer xxx` header.
44+
pub fn bearer<'a>(value: impl Into<Cow<'a, str>>) -> Header<'a> {
45+
custom("Authorization", format!("Bearer {}", value.into()))
46+
}
47+
48+
/// Create custom header.
49+
pub fn custom<'a>(name: impl Into<Cow<'a, str>>, value: impl Into<Cow<'a, str>>) -> Header<'a> {
50+
Header { name: name.into(), value: value.into() }
51+
}

src/browser/fetch/request.rs

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
//! The Request of the Fetch API.
22
3-
use super::{FetchError, Method, Result};
3+
use super::{header, FetchError, Header, Headers, Method, Result};
44
use gloo_timers::callback::Timeout;
55
use serde::Serialize;
6-
use std::{borrow::Cow, cell::RefCell, collections::HashMap, rc::Rc};
6+
use std::{borrow::Cow, cell::RefCell, rc::Rc};
77
use wasm_bindgen::JsValue;
88

99
/// Its methods configure the request, and handle the response. Many of them return the original
1010
/// struct, and are intended to be used chained together.
1111
#[derive(Debug, Clone, Default)]
12-
pub struct Request {
13-
url: Cow<'static, str>,
14-
headers: Headers,
12+
pub struct Request<'a> {
13+
url: Cow<'a, str>,
14+
headers: Headers<'a>,
1515
method: Method,
1616
body: Option<JsValue>,
1717
cache: Option<web_sys::RequestCache>,
@@ -25,14 +25,14 @@ pub struct Request {
2525
controller: RequestController,
2626
}
2727

28-
impl Request {
28+
impl<'a> Request<'a> {
2929
/// Create new request based on the provided url.
3030
///
3131
/// To get a [`Response`](./struct.Response.html) you need to pass
3232
/// `Request` to the [`fetch`](./fn.fetch.html) function.
3333
///
3434
/// [MDN reference](https://developer.mozilla.org/en-US/docs/Web/API/Request)
35-
pub fn new(url: impl Into<Cow<'static, str>>) -> Self {
35+
pub fn new(url: impl Into<Cow<'a, str>>) -> Self {
3636
Self {
3737
url: url.into(),
3838
..Self::default()
@@ -43,14 +43,14 @@ impl Request {
4343
#[allow(clippy::missing_const_for_fn)]
4444
/// Set headers for this request.
4545
/// It will replace any existing headers.
46-
pub fn headers(mut self, headers: Headers) -> Self {
46+
pub fn headers(mut self, headers: Headers<'a>) -> Self {
4747
self.headers = headers;
4848
self
4949
}
5050

5151
/// Set specific header.
52-
pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
53-
self.headers.insert(name.into(), value.into());
52+
pub fn header(mut self, header: Header<'a>) -> Self {
53+
self.headers.set(header);
5454
self
5555
}
5656

@@ -85,10 +85,8 @@ impl Request {
8585
/// This method can fail if JSON serialization fail. It will then
8686
/// return `FetchError::SerdeError`.
8787
pub fn json<T: Serialize + ?Sized>(mut self, data: &T) -> Result<Self> {
88-
self.headers.insert(
89-
"Content-Type".to_owned(),
90-
"application/json; charset=utf-8".to_owned(),
91-
);
88+
self.headers
89+
.set(header::content_type("application/json; charset=utf-8"));
9290
let body = serde_json::to_string(data).map_err(FetchError::SerdeError)?;
9391
self.body = Some(body.into());
9492
Ok(self)
@@ -97,10 +95,8 @@ impl Request {
9795
/// Set request body to a provided string.
9896
/// It will also set `Content-Type` header to `text/plain; charset=utf-8`.
9997
pub fn text(mut self, text: impl AsRef<str>) -> Self {
100-
self.headers.insert(
101-
"Content-Type".to_owned(),
102-
"text/plain; charset=utf-8".to_owned(),
103-
);
98+
self.headers
99+
.set(header::content_type("text/plain; charset=utf-8"));
104100
self.body = Some(JsValue::from(text.as_ref()));
105101
self
106102
}
@@ -162,15 +158,15 @@ impl Request {
162158
}
163159
}
164160

165-
impl From<Request> for web_sys::Request {
161+
impl From<Request<'_>> for web_sys::Request {
166162
fn from(request: Request) -> Self {
167163
let mut init = web_sys::RequestInit::new();
168164

169165
// headers
170166
let headers = web_sys::Headers::new().expect("fetch: cannot create headers");
171-
for (name, value) in &request.headers {
167+
for header in request.headers {
172168
headers
173-
.append(name.as_str(), value.as_str())
169+
.append(&header.name, &header.value)
174170
.expect("fetch: cannot create header")
175171
}
176172
init.headers(&headers);
@@ -242,10 +238,6 @@ impl From<Request> for web_sys::Request {
242238
}
243239
}
244240

245-
// TODO cows?
246-
/// Request headers.
247-
pub type Headers = HashMap<String, String>;
248-
249241
#[allow(clippy::module_name_repetitions)]
250242
#[derive(Debug, Clone)]
251243
/// It allows to abort request or disable request's timeout.

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ pub mod prelude {
9090
browser::dom::event_handler::{
9191
ev, input_ev, keyboard_ev, mouse_ev, pointer_ev, raw_ev, simple_ev,
9292
},
93-
browser::fetch::{self, fetch, Method, Request, Response, Status},
93+
browser::fetch::{self, fetch, header, Method, Request, Response, Status},
9494
browser::util::{
9595
request_animation_frame, ClosureNew, RequestAnimationFrameHandle,
9696
RequestAnimationFrameTime,

0 commit comments

Comments
 (0)