rustolio_utils/http/request/
builder.rs1#[cfg(not(target_arch = "wasm32"))]
12use crate::bytes::Bytes;
13#[cfg(target_arch = "wasm32")]
14use js_sys::JsString;
15#[cfg(target_arch = "wasm32")]
16use wasm_bindgen::JsCast as _;
17#[cfg(target_arch = "wasm32")]
18use wasm_bindgen::JsValue;
19#[cfg(target_arch = "wasm32")]
20use web_sys::RequestInit;
21
22use crate::bytes::{IntoUtf8Bytes, Utf8Bytes};
23
24use super::{Error, HeaderName, Incoming, Method, Outgoing, Request, Response, Result, Version};
25
26pub struct Builder<B> {
27 version: Version,
28 uri: Utf8Bytes,
29 method: Method,
30 headers: Vec<(Utf8Bytes, Utf8Bytes)>,
31 body: B,
32}
33
34impl Builder<Outgoing> {
35 pub fn new(method: Method, uri: Utf8Bytes) -> Self {
36 Builder {
37 version: Version::HTTP_11,
38 uri,
39 method,
40 headers: Vec::new(),
41 body: Outgoing::empty(),
42 }
43 }
44}
45
46impl Builder<()> {
47 pub fn new(method: Method, uri: Utf8Bytes) -> Self {
48 Builder {
49 version: Version::HTTP_11,
50 uri,
51 method,
52 headers: Vec::new(),
53 body: (),
54 }
55 }
56}
57
58impl<B> Builder<B> {
59 pub fn header(mut self, key: impl IntoUtf8Bytes, value: impl IntoUtf8Bytes) -> Self {
60 self.headers
61 .push((key.into_utf8_bytes(), value.into_utf8_bytes()));
62 self
63 }
64}
65
66#[cfg(target_arch = "wasm32")]
67impl Builder<()> {
68 pub fn bytes(mut self, body: &[u8]) -> Builder<Outgoing> {
69 self.headers.push((
70 HeaderName::CONTENT_TYPE.into_utf8_bytes(),
71 "application/octet-stream".into_utf8_bytes(),
72 ));
73 let body = JsValue::from(js_sys::Uint8Array::from(body)).into();
74 Builder {
75 version: self.version,
76 uri: self.uri,
77 method: self.method,
78 headers: self.headers,
79 body,
80 }
81 }
82
83 pub fn text(mut self, body: &str) -> Builder<Outgoing> {
84 self.headers.push((
85 HeaderName::CONTENT_TYPE.into_utf8_bytes(),
86 "text/html; charset=utf-8".into_utf8_bytes(),
87 ));
88 let body = JsValue::from(body).into();
89 Builder {
90 version: self.version,
91 uri: self.uri,
92 method: self.method,
93 headers: self.headers,
94 body,
95 }
96 }
97
98 pub fn encoded<T: crate::prelude::Encode>(self, body: &T) -> Result<Builder<Outgoing>> {
99 let encoded = crate::bytes::encoding::encode_to_bytes(body).map_err(|_| Error::Parse)?;
100 Ok(self.bytes(&encoded))
101 }
102
103 pub fn json<T>(mut self, body: T) -> Result<Builder<Outgoing>>
104 where
105 T: serde::ser::Serialize,
106 {
107 self.headers.push((
108 HeaderName::CONTENT_TYPE.into_utf8_bytes(),
109 "application/json".into_utf8_bytes(),
110 ));
111 let js_obj = serde_wasm_bindgen::to_value(&body).map_err(|_| Error::Parse)?;
112 let js_string = js_sys::JSON::stringify(&js_obj).expect("Should always be valid json");
113 let body = JsValue::from(js_string).into();
114 Ok(Builder {
115 version: self.version,
116 uri: self.uri,
117 method: self.method,
118 headers: self.headers,
119 body,
120 })
121 }
122}
123
124#[cfg(not(target_arch = "wasm32"))]
125impl Builder<()> {
126 pub fn bytes(mut self, body: Bytes) -> Builder<Outgoing> {
127 self.headers.push((
128 HeaderName::CONTENT_TYPE.into_utf8_bytes(),
129 "application/octet-stream".into_utf8_bytes(),
130 ));
131 let body = Outgoing::from_bytes(body);
132 Builder {
133 version: self.version,
134 uri: self.uri,
135 method: self.method,
136 headers: self.headers,
137 body,
138 }
139 }
140
141 pub fn text(mut self, body: impl IntoUtf8Bytes) -> Builder<Outgoing> {
142 self.headers.push((
143 HeaderName::CONTENT_TYPE.into_utf8_bytes(),
144 "text/html; charset=utf-8".into_utf8_bytes(),
145 ));
146 let body = Outgoing::from_bytes(body.into());
147 Builder {
148 version: self.version,
149 uri: self.uri,
150 method: self.method,
151 headers: self.headers,
152 body,
153 }
154 }
155
156 pub fn encoded<T: crate::prelude::Encode>(self, body: &T) -> Result<Builder<Outgoing>> {
158 let encoded = crate::bytes::encoding::encode_to_bytes(body).map_err(|_| Error::Parse)?;
159 Ok(self.bytes(encoded))
160 }
161
162 pub fn json<T>(mut self, body: &T) -> Result<Builder<Outgoing>>
163 where
164 T: serde::ser::Serialize,
165 {
166 let body = serde_json::to_vec(body).map_err(|_| Error::Parse)?;
167 self.headers.push((
168 HeaderName::CONTENT_TYPE.into_utf8_bytes(),
169 "application/json".into_utf8_bytes(),
170 ));
171 let body = Outgoing::from_bytes(Bytes::from(body));
172 Ok(Builder {
173 version: self.version,
174 uri: self.uri,
175 method: self.method,
176 headers: self.headers,
177 body,
178 })
179 }
180
181 pub fn body<B>(self, body: B) -> Builder<B> {
182 Builder {
183 version: self.version,
184 uri: self.uri,
185 method: self.method,
186 headers: self.headers,
187 body,
188 }
189 }
190}
191
192#[cfg(not(target_arch = "wasm32"))]
193impl<B> Builder<B> {
194 pub fn build(self) -> Result<Request<B>> {
195 let uri: http::Uri = self.uri.to_vec().try_into().map_err(http::Error::from)?;
196 let mut res = http::Request::builder()
197 .version(self.version)
198 .method(self.method)
199 .uri(uri)
200 .body(self.body)?;
201 let headers = res.headers_mut();
202 for (key, value) in self.headers {
203 let key: http::HeaderName = key.to_vec().try_into().map_err(http::Error::from)?;
204 let value: http::HeaderValue = value.to_vec().try_into().map_err(http::Error::from)?;
205 headers.append(key, value);
206 }
207 Ok(Request(res))
208 }
209}
210
211#[cfg(target_arch = "wasm32")]
212impl Builder<Outgoing> {
213 pub async fn fetch(self) -> Result<Response<Incoming>> {
214 let Builder {
215 uri,
216 method,
217 headers,
218 body,
219 ..
220 } = self;
221
222 let uri = str::from_utf8(&uri).map_err(|_| Error::InvalidUri)?;
223
224 let req = RequestInit::new();
225 req.set_method(method.as_str());
226 req.set_body(&body);
227 let req =
228 web_sys::Request::new_with_str_and_init(uri, &req).map_err(|_| Error::InvalidUri)?;
229
230 for (key, value) in headers {
231 req.headers().set(key.as_str(), value.as_str()).ok(); }
233
234 let window = web_sys::window().expect("No window available");
235 let response: web_sys::Response =
236 wasm_bindgen_futures::JsFuture::from(window.fetch_with_request(&req))
237 .await
238 .map_err(|_| Error::Fetch)?
239 .unchecked_into();
240
241 Ok(Response::from_inner(response))
242 }
243}
244
245#[cfg(not(target_arch = "wasm32"))]
246impl Builder<Outgoing> {
247 pub async fn fetch(self) -> Result<Response<Incoming>> {
248 todo!()
249 }
250}