wasm_framework/
request.rs

1use crate::js_values;
2use crate::{Error, Method};
3use serde::de::DeserializeOwned;
4use std::borrow::Cow;
5use url::Url;
6use wasm_bindgen::JsValue;
7
8/// Incoming HTTP request (to Worker).
9#[derive(Debug, Clone)]
10pub struct Request {
11    method: Method,
12    url: Url,
13    headers: web_sys::Headers,
14    body: Option<Vec<u8>>,
15}
16unsafe impl Sync for Request {}
17
18impl Request {
19    /// Creates Request object representing incoming HTTP request
20    pub fn new(
21        method: Method,
22        url: Url,
23        headers: web_sys::Headers,
24        body: Option<Vec<u8>>,
25    ) -> Request {
26        Request {
27            method,
28            url,
29            headers,
30            body,
31        }
32    }
33
34    /// Creates Request from javascript object
35    pub(crate) fn from_js(map: &js_sys::Map) -> Result<Self, JsValue> {
36        Ok(Request::new(
37            Method::from(
38                &js_values::get_map_str(&map, "method")
39                    .ok_or_else(|| JsValue::from_str("invalid_req.method"))?,
40            )?,
41            Url::parse(
42                &js_values::get_map_str(&map, "url")
43                    .ok_or_else(|| JsValue::from_str("invalid_req.url"))?,
44            )
45            .map_err(|e| JsValue::from_str(&format!("invalid req.url:{}", e.to_string())))?,
46            js_values::get_map_headers(&map, "headers")
47                .ok_or_else(|| JsValue::from_str("invalid_req"))?,
48            js_values::get_map_bytes(&map, "body"),
49        ))
50    }
51
52    /// Returns the HTTP method
53    pub fn method(&self) -> Method {
54        self.method
55    }
56
57    /// Returns the parsed url
58    pub fn url(&self) -> &Url {
59        &self.url
60    }
61
62    /// Returns the set of request headers
63    pub fn headers(&self) -> &web_sys::Headers {
64        &self.headers
65    }
66
67    /// Returns the value of the header, or None if the header is not set.
68    /// Header name search is case-insensitive.
69    pub fn get_header(&self, name: &str) -> Option<String> {
70        match self.headers.get(name) {
71            Ok(v) => v,
72            Err(_) => None,
73        }
74    }
75
76    /// Returns true if the header is set. Name is case-insensitive.
77    pub fn has_header(&self, name: &str) -> bool {
78        self.headers.has(name).unwrap_or(false)
79    }
80
81    /// Returns true if the body is empty
82    pub fn is_empty(&self) -> bool {
83        self.body.is_none() || self.body.as_ref().unwrap().is_empty()
84    }
85
86    /// Returns request body as byte vector, or None if body is empty
87    pub fn body(&self) -> Option<&Vec<u8>> {
88        self.body.as_ref()
89    }
90
91    /// Interpret body as json object.
92    pub fn json<T: DeserializeOwned>(&self) -> Result<T, Error> {
93        if let Some(vec) = self.body.as_ref() {
94            Ok(serde_json::from_slice(vec)?)
95        } else {
96            Err(Error::Other("body is empty".to_string()))
97        }
98    }
99
100    /// Returns the cookie string, if set
101    pub fn get_cookie_value(&self, cookie_name: &str) -> Option<String> {
102        self.get_header("cookie")
103            .map(|cookie| {
104                (&cookie)
105                    .split(';')
106                    // allow spaces around ';'
107                    .map(|s| s.trim())
108                    // if name=value, return value
109                    .find_map(|part| cookie_value(part, cookie_name))
110                    .map(|v| v.to_string())
111            })
112            .unwrap_or_default()
113    }
114
115    /// returns the query variable from the url, or None if not found
116    pub fn get_query_value<'req>(&'req self, key: &'_ str) -> Option<Cow<'req, str>> {
117        self.url()
118            .query_pairs()
119            .find(|(k, _)| k == key)
120            .map(|(_, v)| v)
121    }
122}
123
124// If 'part' is of the form 'name=value', return value
125fn cookie_value<'cookie>(part: &'cookie str, name: &str) -> Option<&'cookie str> {
126    if part.len() > name.len() {
127        let (left, right) = part.split_at(name.len());
128        if left == name && right.starts_with('=') {
129            return Some(&right[1..]);
130        }
131    }
132    None
133}
134
135#[test]
136// test cookie_value function. Additional tests of Request are in tests/request.rs
137fn test_cookie_value() {
138    // short value
139    assert_eq!(cookie_value("x=y", "x"), Some("y"));
140
141    // longer value
142    assert_eq!(cookie_value("foo=bar", "foo"), Some("bar"));
143
144    // missing value
145    assert_eq!(cookie_value("x=y", "z"), None);
146
147    // empty value
148    assert_eq!(cookie_value("foo=", "foo"), Some(""));
149}