use std::fmt;
use http::{Method, StatusCode};
use http_error::HttpError;
use serde::Deserialize;
use crate::body::Body;
use crate::IntoResponse;
pub type Request = http::Request<Body>;
pub trait RequestExt: Sized {
fn route(&self) -> Route<'_>;
fn query<'de, T: Deserialize<'de>>(&'de self) -> Result<T, DecodeQueryError>;
}
pub struct Route<'a> {
method: &'a Method,
segments: Vec<&'a str>,
}
#[derive(Debug, thiserror::Error)]
#[error("invalid query string")]
pub struct DecodeQueryError(#[from] serde_urlencoded::de::Error);
impl<'a> Route<'a> {
pub fn to_tuple(&'a self) -> (Method, &'a [&'a str]) {
(self.method.clone(), self.segments.as_slice())
}
}
impl RequestExt for Request {
fn route(&self) -> Route<'_> {
let path = cleanup_path(self.uri().path());
Route {
method: self.method(),
segments: if path.is_empty() {
Vec::new()
} else {
path.split('/').collect()
},
}
}
fn query<'de, T: Deserialize<'de>>(&'de self) -> Result<T, DecodeQueryError> {
let uri = self.uri();
let query = uri.query();
serde_urlencoded::from_str(query.unwrap_or("")).map_err(DecodeQueryError)
}
}
fn cleanup_path(segment: &str) -> &str {
let segment = segment.strip_prefix('/').unwrap_or(segment);
let segment = segment.strip_suffix('/').unwrap_or(segment);
segment
}
impl IntoResponse for DecodeQueryError {
fn into_response(self) -> crate::Response {
StatusCode::BAD_REQUEST.into_response()
}
}
impl HttpError for DecodeQueryError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
fn reason(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("invalid query string")
}
}
#[cfg(test)]
mod tests {
use http::Method;
use hyper::body::Incoming;
use super::RequestExt;
use crate::body::Body;
#[test]
fn test_root_path() {
let req = http::Request::builder()
.uri("https://example.org/")
.body(Body::<Incoming, hyper::Error>::empty())
.unwrap();
assert_eq!(req.route().to_tuple(), (Method::GET, &[] as &[&str]));
}
}