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