1use futures_util::future::LocalBoxFuture;
2use serde::de::DeserializeOwned;
3use trailbase_wasm_common::HttpContext;
4use wstd::http::server::{Finished, Responder};
5use wstd::io::{Cursor, Empty, empty};
6
7pub use http::{HeaderMap, HeaderValue, Method, StatusCode, Version, header};
8pub use trailbase_wasm_common::HttpContextUser as User;
9
10pub type Response<T = BoundedBody<Vec<u8>>> = http::Response<T>;
11
12#[derive(Clone, Debug)]
13pub struct HttpError {
14 pub status: StatusCode,
15 pub message: Option<String>,
16}
17
18impl HttpError {
19 pub fn status(status: StatusCode) -> Self {
20 return Self {
21 status,
22 message: None,
23 };
24 }
25
26 pub fn message(status: StatusCode, message: impl std::string::ToString) -> Self {
27 return Self {
28 status,
29 message: Some(message.to_string()),
30 };
31 }
32}
33
34impl From<HttpError> for Response {
35 fn from(value: HttpError) -> Self {
36 return value.into_response();
37 }
38}
39
40type HttpHandler = Box<
41 dyn FnOnce(
42 HttpContext,
43 http::Request<wstd::http::body::IncomingBody>,
44 wstd::http::server::Responder,
45 ) -> LocalBoxFuture<'static, Finished>,
46>;
47
48pub struct HttpRoute {
49 pub method: Method,
50 pub path: String,
51 pub handler: HttpHandler,
52}
53
54impl HttpRoute {
55 pub fn new<F, R, B>(method: Method, path: impl std::string::ToString, f: F) -> Self
56 where
57 F: (AsyncFn(Request) -> R) + Send + Sync + 'static,
60 R: IntoResponse<B>,
61 B: wstd::http::body::Body,
62 {
63 return Self {
64 method,
65 path: path.to_string(),
66 handler: Box::new(
67 move |context: HttpContext,
68 req: http::Request<wstd::http::body::IncomingBody>,
69 responder: Responder| {
70 let (head, body) = req.into_parts();
71 let Ok(url) = to_url(head.uri) else {
72 return Box::pin(responder.respond(empty_error_response(StatusCode::BAD_REQUEST)));
73 };
74
75 let req = Request {
76 head: Parts {
77 method: head.method,
78 uri: url,
79 version: head.version,
80 headers: head.headers,
81 user: context.user,
82 path_params: context.path_params,
83 },
84 body,
85 };
86
87 return Box::pin(async move {
88 #[allow(clippy::let_and_return)]
89 let response = responder.respond(f(req).await.into_response()).await;
90
91 response
94 });
95 },
96 ),
97 };
98 }
99}
100
101pub mod routing {
102 use super::{HttpRoute, IntoResponse, Method, Request};
103
104 pub fn get<F, R, B>(path: impl std::string::ToString, f: F) -> HttpRoute
105 where
106 F: (AsyncFn(Request) -> R) + Send + Sync + 'static,
107 R: IntoResponse<B>,
108 B: wstd::http::body::Body,
109 {
110 return HttpRoute::new(Method::GET, path, f);
111 }
112
113 pub fn post<F, R, B>(path: impl std::string::ToString, f: F) -> HttpRoute
114 where
115 F: (AsyncFn(Request) -> R) + Send + Sync + 'static,
116 R: IntoResponse<B>,
117 B: wstd::http::body::Body,
118 {
119 return HttpRoute::new(Method::POST, path, f);
120 }
121
122 pub fn patch<F, R, B>(path: impl std::string::ToString, f: F) -> HttpRoute
123 where
124 F: (AsyncFn(Request) -> R) + Send + Sync + 'static,
125 R: IntoResponse<B>,
126 B: wstd::http::body::Body,
127 {
128 return HttpRoute::new(Method::PATCH, path, f);
129 }
130
131 pub fn delete<F, R, B>(path: impl std::string::ToString, f: F) -> HttpRoute
132 where
133 F: (AsyncFn(Request) -> R) + Send + Sync + 'static,
134 R: IntoResponse<B>,
135 B: wstd::http::body::Body,
136 {
137 return HttpRoute::new(Method::DELETE, path, f);
138 }
139}
140
141#[non_exhaustive]
143#[derive(Clone, Debug)]
144pub struct Parts {
145 pub method: Method,
147
148 pub uri: url::Url,
150
151 pub version: Version,
153
154 pub headers: HeaderMap<HeaderValue>,
156
157 pub user: Option<User>,
159
160 pub path_params: Vec<(String, String)>,
162}
163
164#[derive(Debug)]
165pub struct Request {
166 head: Parts,
167 body: wstd::http::body::IncomingBody,
168}
169
170impl Request {
171 #[inline]
172 pub fn body(&mut self) -> &mut wstd::http::body::IncomingBody {
173 return &mut self.body;
174 }
175
176 #[inline]
177 pub fn url(&self) -> &url::Url {
178 return &self.head.uri;
179 }
180
181 pub fn query_parse<T: DeserializeOwned>(&self) -> Result<T, HttpError> {
182 let query = self.head.uri.query().unwrap_or_default();
183 let deserializer =
184 serde_urlencoded::Deserializer::new(url::form_urlencoded::parse(query.as_bytes()));
185 return serde_path_to_error::deserialize(deserializer)
186 .map_err(|err| HttpError::message(StatusCode::BAD_REQUEST, err));
187 }
188
189 pub fn query_param(&self, param: &str) -> Option<String> {
194 return self
195 .head
196 .uri
197 .query_pairs()
198 .find(|(p, _v)| p == param)
199 .map(|(_p, v)| v.to_string());
200 }
201
202 pub fn path_param(&self, param: &str) -> Option<&str> {
203 return self
204 .head
205 .path_params
206 .iter()
207 .find(|(p, _v)| p == param)
208 .map(|(_p, v)| v.as_str());
209 }
210
211 #[inline]
212 pub fn method(&self) -> &Method {
213 return &self.head.method;
214 }
215
216 #[inline]
217 pub fn version(&self) -> &Version {
218 return &self.head.version;
219 }
220
221 #[inline]
222 pub fn header(&self, key: &str) -> Option<&HeaderValue> {
223 return self.head.headers.get(key);
224 }
225
226 #[inline]
227 pub fn user(&self) -> Option<&User> {
228 return self.head.user.as_ref();
229 }
230}
231
232fn to_url(uri: http::Uri) -> Result<url::Url, url::ParseError> {
233 let http::uri::Parts {
234 scheme,
235 authority,
236 path_and_query,
237 ..
238 } = uri.into_parts();
239
240 return match (scheme, authority, path_and_query) {
241 (Some(s), Some(a), Some(p)) => url::Url::parse(&format!("{s}://{a}/{p}")),
242 (_, _, Some(p)) => url::Url::parse(p.as_str()),
243 _ => Err(url::ParseError::RelativeUrlWithCannotBeABaseBase),
244 };
245}
246
247#[derive(Debug, Default)]
249pub struct BoundedBody<T>(Cursor<T>);
250
251impl<T: AsRef<[u8]>> wstd::io::AsyncRead for BoundedBody<T> {
252 async fn read(&mut self, buf: &mut [u8]) -> wstd::io::Result<usize> {
253 self.0.read(buf).await
254 }
255}
256
257impl<T: AsRef<[u8]>> wstd::http::body::Body for BoundedBody<T> {
258 fn len(&self) -> Option<usize> {
259 Some(self.0.get_ref().as_ref().len())
260 }
261}
262
263pub trait IntoBody {
268 type IntoBody: wstd::http::body::Body;
270 fn into_body(self) -> Self::IntoBody;
272}
273
274impl IntoBody for () {
275 type IntoBody = wstd::io::Empty;
276
277 fn into_body(self) -> Self::IntoBody {
278 return wstd::io::empty();
279 }
280}
281
282impl IntoBody for String {
283 type IntoBody = BoundedBody<Vec<u8>>;
284 fn into_body(self) -> Self::IntoBody {
285 BoundedBody(Cursor::new(self.into_bytes()))
286 }
287}
288
289impl IntoBody for &str {
290 type IntoBody = BoundedBody<Vec<u8>>;
291 fn into_body(self) -> Self::IntoBody {
292 BoundedBody(Cursor::new(self.to_owned().into_bytes()))
293 }
294}
295
296impl IntoBody for Vec<u8> {
297 type IntoBody = BoundedBody<Vec<u8>>;
298 fn into_body(self) -> Self::IntoBody {
299 BoundedBody(Cursor::new(self))
300 }
301}
302
303impl IntoBody for &[u8] {
304 type IntoBody = BoundedBody<Vec<u8>>;
305 fn into_body(self) -> Self::IntoBody {
306 BoundedBody(Cursor::new(self.to_owned()))
307 }
308}
309
310pub trait IntoResponse<B> {
311 fn into_response(self) -> http::Response<B>;
312}
313
314impl<B: wstd::http::body::Body> IntoResponse<B> for Response<B> {
315 fn into_response(self) -> http::Response<B> {
316 return self;
317 }
318}
319
320impl<B: wstd::http::body::Body, Err: IntoResponse<B>> IntoResponse<B> for Result<Response<B>, Err> {
321 fn into_response(self) -> http::Response<B> {
322 return match self {
323 Ok(resp) => resp,
324 Err(err) => err.into_response(),
325 };
326 }
327}
328
329impl<B: IntoBody> IntoResponse<B::IntoBody> for B {
330 fn into_response(self) -> http::Response<B::IntoBody> {
331 return http::Response::new(self.into_body());
332 }
333}
334
335impl<B: IntoBody<IntoBody = BoundedBody<Vec<u8>>>> IntoResponse<BoundedBody<Vec<u8>>>
336 for Result<B, HttpError>
337{
338 fn into_response(self) -> http::Response<BoundedBody<Vec<u8>>> {
339 return match self {
340 Ok(body) => http::Response::new(body.into_body()),
341 Err(err) => build_response(err.status, err.message.unwrap_or_default().into_body()),
342 };
343 }
344}
345
346impl IntoResponse<BoundedBody<Vec<u8>>> for HttpError {
347 fn into_response(self) -> http::Response<BoundedBody<Vec<u8>>> {
348 return build_response(self.status, self.message.unwrap_or_default().into_body());
349 }
350}
351
352impl IntoResponse<BoundedBody<Vec<u8>>> for Result<(), HttpError> {
353 fn into_response(self) -> http::Response<BoundedBody<Vec<u8>>> {
354 return match self {
355 Ok(_) => http::Response::new("".into_body()),
356 Err(err) => err.into_response(),
357 };
358 }
359}
360
361#[derive(Debug, Clone, Copy, Default)]
362#[must_use]
363pub struct Json<T>(pub T);
364
365impl<T> IntoResponse<BoundedBody<Vec<u8>>> for Json<T>
366where
367 T: serde::Serialize,
368{
369 fn into_response(self) -> http::Response<BoundedBody<Vec<u8>>> {
370 return build_json_response(StatusCode::OK, self.0);
371 }
372}
373
374impl<T> IntoResponse<BoundedBody<Vec<u8>>> for std::result::Result<Json<T>, HttpError>
375where
376 T: serde::Serialize,
377{
378 fn into_response(self) -> http::Response<BoundedBody<Vec<u8>>> {
379 return match self {
380 Ok(json) => {
381 return build_json_response(StatusCode::OK, json.0);
382 }
383 Err(err) => build_response(err.status, err.message.unwrap_or_default().into_body()),
384 };
385 }
386}
387
388#[derive(Clone, Copy, Debug)]
392#[must_use]
393pub struct Html<T>(pub T);
394
395impl<T> IntoResponse<BoundedBody<Vec<u8>>> for Html<T>
396where
397 T: IntoResponse<BoundedBody<Vec<u8>>>,
398{
399 fn into_response(self) -> Response {
400 let mut r = self.0.into_response();
401 r.headers_mut().insert(
402 http::header::CONTENT_TYPE,
403 http::HeaderValue::from_static("text/html; charset=utf-8"),
404 );
405 return r;
406 }
407}
408
409#[derive(Debug, Clone)]
410#[must_use = "needs to be returned from a handler or otherwise turned into a Response to be useful"]
411pub struct Redirect {
412 status_code: StatusCode,
413 location: http::header::HeaderValue,
414}
415
416impl Redirect {
417 pub fn to(uri: &str) -> Self {
418 Self::with_status_code(StatusCode::SEE_OTHER, uri)
419 }
420
421 pub fn temporary(uri: &str) -> Self {
422 Self::with_status_code(StatusCode::TEMPORARY_REDIRECT, uri)
423 }
424
425 pub fn permanent(uri: &str) -> Self {
426 Self::with_status_code(StatusCode::PERMANENT_REDIRECT, uri)
427 }
428
429 fn with_status_code(status_code: StatusCode, uri: &str) -> Self {
430 assert!(
431 status_code.is_redirection(),
432 "not a redirection status code"
433 );
434
435 Self {
436 status_code,
437 location: HeaderValue::try_from(uri).expect("URI isn't a valid header value"),
438 }
439 }
440}
441
442impl<B: wstd::http::body::Body + Default> IntoResponse<B> for Redirect {
443 fn into_response(self) -> http::Response<B> {
444 let mut response = http::Response::<B>::default();
445 *response.status_mut() = self.status_code;
446 response
447 .headers_mut()
448 .insert(http::header::LOCATION, self.location);
449 return response;
450 }
451}
452
453pub(crate) fn empty_error_response(status: StatusCode) -> http::Response<Empty> {
454 let mut response = http::Response::new(empty());
455 *response.status_mut() = status;
456 return response;
457}
458
459fn internal_error_response() -> http::Response<BoundedBody<Vec<u8>>> {
460 return build_response(StatusCode::INTERNAL_SERVER_ERROR, "".into_body());
461}
462
463#[inline]
464fn build_response(
465 status: StatusCode,
466 body: BoundedBody<Vec<u8>>,
467) -> http::Response<BoundedBody<Vec<u8>>> {
468 let mut response = http::Response::new(body);
469 *response.status_mut() = status;
470 return response;
471}
472
473#[inline]
474fn build_json_response<T: serde::Serialize>(
475 status: StatusCode,
476 value: T,
477) -> http::Response<BoundedBody<Vec<u8>>> {
478 let Ok(bytes) = serde_json::to_vec(&value) else {
479 return internal_error_response();
480 };
481
482 let mut response = build_response(status, bytes.into_body());
483 response.headers_mut().insert(
484 http::header::CONTENT_TYPE,
485 HeaderValue::from_static("application/json"),
486 );
487
488 return response;
489}