use std::str::FromStr;
use super::Request;
use cookie::Cookie;
use http::header::{self, COOKIE};
use serde::de::DeserializeOwned;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum QueryParamsError {
#[error("not query string present in url")]
NotFound,
#[error(transparent)]
Parse(serde_qs::Error),
}
pub trait RequestExt<B> {
fn cookies(&self) -> Result<Vec<Cookie<'static>>, cookie::ParseError>;
fn cookie(&self, name: &str) -> Option<Cookie<'static>>;
fn query_params<Q: DeserializeOwned>(&self) -> Result<Q, QueryParamsError>;
fn content_type(&self) -> Option<mime::Mime>;
}
impl<B> RequestExt<B> for Request<B> {
fn cookies(&self) -> Result<Vec<Cookie<'static>>, cookie::ParseError> {
let mut cookies = Vec::new();
for header_value in self.headers().get_all(COOKIE) {
let raw = std::str::from_utf8(header_value.as_bytes())
.map_err(cookie::ParseError::Utf8Error)?;
for cookie_str in raw.split(';').map(|s| s.trim()) {
if !cookie_str.is_empty() {
cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned());
}
}
}
Ok(cookies)
}
fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
for hdr in self.headers().get_all(COOKIE) {
let Ok(s) = std::str::from_utf8(hdr.as_bytes()) else {
continue;
};
for cookie_str in s.split(';').map(|s| s.trim()) {
if !cookie_str.is_empty() {
match Cookie::parse_encoded(cookie_str) {
Ok(cookie) if cookie.name() == name => {
return Some(cookie.into_owned())
},
_ => {}
}
}
}
}
None
}
fn query_params<Q: DeserializeOwned>(&self) -> Result<Q, QueryParamsError> {
let query_str = self.uri().query().ok_or(QueryParamsError::NotFound)?;
serde_qs::from_str(query_str).map_err(QueryParamsError::Parse)
}
fn content_type(&self) -> Option<mime::Mime> {
self.headers()
.get(header::CONTENT_TYPE)
.and_then(|h| h.to_str().ok())
.and_then(|x| mime::Mime::from_str(x).ok())
}
}