use std::convert::TryFrom;
use crate::{
cf::Cf, error::Error, headers::Headers, http::Method, ByteStream, FormData, RequestInit, Result,
};
use serde::de::DeserializeOwned;
use std::borrow::Cow;
use url::{form_urlencoded::Parse, Url};
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use worker_sys::{Request as EdgeRequest, RequestInit as EdgeRequestInit};
#[derive(Debug)]
pub struct Request {
method: Method,
path: String,
headers: Headers,
cf: Cf,
edge_request: EdgeRequest,
body_used: bool,
immutable: bool,
}
impl From<EdgeRequest> for Request {
fn from(req: EdgeRequest) -> Self {
Self {
method: req.method().into(),
path: Url::parse(&req.url())
.map(|u| u.path().into())
.unwrap_or_else(|_| {
let u = req.url();
if !u.starts_with('/') {
return "/".to_string() + &u;
}
u
}),
headers: Headers(req.headers()),
cf: req.cf().into(),
edge_request: req,
body_used: false,
immutable: true,
}
}
}
impl TryFrom<Request> for EdgeRequest {
type Error = Error;
fn try_from(req: Request) -> Result<Self> {
req.inner().clone().map_err(Error::from)
}
}
impl TryFrom<&Request> for EdgeRequest {
type Error = Error;
fn try_from(req: &Request) -> Result<Self> {
req.inner().clone().map_err(Error::from)
}
}
impl Request {
pub fn new(uri: &str, method: Method) -> Result<Self> {
EdgeRequest::new_with_str_and_init(uri, EdgeRequestInit::new().method(method.as_ref()))
.map(|req| {
let mut req: Request = req.into();
req.immutable = false;
req
})
.map_err(|e| {
Error::JsError(
e.as_string()
.unwrap_or_else(|| "invalid URL or method for Request".to_string()),
)
})
}
pub fn new_with_init(uri: &str, init: &RequestInit) -> Result<Self> {
EdgeRequest::new_with_str_and_init(uri, &init.into())
.map(|req| {
let mut req: Request = req.into();
req.immutable = false;
req
})
.map_err(|e| {
Error::JsError(
e.as_string()
.unwrap_or_else(|| "invalid URL or options for Request".to_string()),
)
})
}
pub async fn json<B: DeserializeOwned>(&mut self) -> Result<B> {
if !self.body_used {
self.body_used = true;
return JsFuture::from(self.edge_request.json()?)
.await
.map_err(|e| {
Error::JsError(
e.as_string()
.unwrap_or_else(|| "failed to get JSON for body value".into()),
)
})
.and_then(|val| serde_wasm_bindgen::from_value(val).map_err(Error::from));
}
Err(Error::BodyUsed)
}
pub async fn text(&mut self) -> Result<String> {
if !self.body_used {
self.body_used = true;
return JsFuture::from(self.edge_request.text()?)
.await
.map(|val| val.as_string().unwrap())
.map_err(|e| {
Error::JsError(
e.as_string()
.unwrap_or_else(|| "failed to get text for body value".into()),
)
});
}
Err(Error::BodyUsed)
}
pub async fn bytes(&mut self) -> Result<Vec<u8>> {
if !self.body_used {
self.body_used = true;
return JsFuture::from(self.edge_request.array_buffer()?)
.await
.map(|val| js_sys::Uint8Array::new(&val).to_vec())
.map_err(|e| {
Error::JsError(
e.as_string()
.unwrap_or_else(|| "failed to read array buffer from request".into()),
)
});
}
Err(Error::BodyUsed)
}
pub async fn form_data(&mut self) -> Result<FormData> {
if !self.body_used {
self.body_used = true;
return JsFuture::from(self.edge_request.form_data()?)
.await
.map(|val| val.into())
.map_err(|e| {
Error::JsError(
e.as_string()
.unwrap_or_else(|| "failed to get form data from request".into()),
)
});
}
Err(Error::BodyUsed)
}
pub fn stream(&mut self) -> Result<ByteStream> {
if self.body_used {
return Err(Error::BodyUsed);
}
self.body_used = true;
let stream = self
.edge_request
.body()
.ok_or_else(|| Error::RustError("no body for request".into()))?;
let stream = wasm_streams::ReadableStream::from_raw(stream.dyn_into().unwrap());
Ok(ByteStream {
inner: stream.into_stream(),
})
}
pub fn headers(&self) -> &Headers {
&self.headers
}
pub fn headers_mut(&mut self) -> Result<&mut Headers> {
if self.immutable {
return Err(Error::JsError(
"Cannot get a mutable reference to an immutable headers object.".into(),
));
}
Ok(&mut self.headers)
}
pub fn cf(&self) -> &Cf {
&self.cf
}
pub fn method(&self) -> Method {
self.method.clone()
}
pub fn path(&self) -> String {
self.path.clone()
}
pub fn path_mut(&mut self) -> Result<&mut String> {
if self.immutable {
return Err(Error::JsError(
"Cannot get a mutable reference to an immutable path.".into(),
));
}
Ok(&mut self.path)
}
pub fn url(&self) -> Result<Url> {
let url = self.edge_request.url();
url.parse()
.map_err(|e| Error::RustError(format!("failed to parse Url from {}: {}", e, url)))
}
#[allow(clippy::should_implement_trait)]
pub fn clone(&self) -> Result<Self> {
self.edge_request
.clone()
.map(|req| req.into())
.map_err(Error::from)
}
pub fn inner(&self) -> &EdgeRequest {
&self.edge_request
}
}
pub trait UrlExt {
fn param<'a>(&'a self, key: &'a str) -> Option<Cow<'a, str>> {
self.param_iter(key).next()
}
fn param_iter<'a>(&'a self, key: &'a str) -> ParamIter<'a>;
}
impl UrlExt for Url {
fn param_iter<'a>(&'a self, key: &'a str) -> ParamIter<'a> {
ParamIter {
inner: self.query_pairs(),
key,
}
}
}
pub struct ParamIter<'a> {
inner: Parse<'a>,
key: &'a str,
}
impl<'a> Iterator for ParamIter<'a> {
type Item = Cow<'a, str>;
fn next(&mut self) -> Option<Self::Item> {
let key = self.key;
Some(self.inner.find(|(k, _)| k == key)?.1)
}
}
#[test]
fn url_param_works() {
let url = Url::parse("https://example.com/foo.html?a=foo&b=bar&a=baz").unwrap();
assert_eq!(url.param("a").as_deref(), Some("foo"));
assert_eq!(url.param("b").as_deref(), Some("bar"));
assert_eq!(url.param("c").as_deref(), None);
let mut a_values = url.param_iter("a");
assert_eq!(a_values.next().as_deref(), Some("foo"));
assert_eq!(a_values.next().as_deref(), Some("baz"));
assert_eq!(a_values.next(), None);
}