mod error;
mod extract;
#[cfg(feature = "validation-validator")]
mod validator;
use super::interceptor::HttpInterceptorResult;
use axum::{
body::Bytes as BodyBytes,
http::{Extensions, HeaderMap, HeaderName, HeaderValue, Method, Uri},
middleware::Next,
};
use axum_responses::JsonResponse;
use serde::de::DeserializeOwned;
use std::{collections::HashMap, fmt::Display, str::FromStr};
use sword_core::layers::RequestId;
use sword_layers::cookies::Cookies;
pub use error::*;
#[allow(unused_imports)]
pub use extract::*;
#[cfg(feature = "validation-validator")]
pub use validator::ValidatorRequestValidation;
#[derive(Debug)]
pub struct Request {
params: HashMap<String, String>,
body_bytes: BodyBytes,
method: Method,
headers: HeaderMap,
uri: Uri,
next: Option<Next>,
pub extensions: Extensions,
}
impl Request {
pub fn uri(&self) -> String {
self.uri.to_string()
}
pub const fn method(&self) -> &Method {
&self.method
}
pub fn header(&self, key: &str) -> Option<&str> {
self.headers.get(key).and_then(|value| value.to_str().ok())
}
pub const fn headers(&self) -> &HeaderMap {
&self.headers
}
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}
pub fn set_header(
&mut self,
name: impl Into<String>,
value: impl Into<String>,
) -> Result<(), RequestError> {
let header_name = name.into();
let header_value = value.into();
let header_name = header_name
.parse::<HeaderName>()
.map_err(|_| RequestError::InvalidHeaderName(header_name))?;
let header_value = HeaderValue::from_str(&header_value)
.map_err(|_| RequestError::InvalidHeaderValue(header_value))?;
self.headers.insert(header_name, header_value);
Ok(())
}
pub fn param<T>(&self, key: &str) -> Result<T, RequestError>
where
T: FromStr,
T::Err: Display,
{
if let Some(value) = self.params.get(key) {
return value.parse::<T>().map_err(|e| {
RequestError::parse_error(
format!("Invalid parameter format for '{key}'"),
format!("Parse Error: {e}"),
)
});
}
Err(RequestError::parse_error(
"Parameter not found",
format!("Parameter '{key}' not found in request parameters"),
))
}
pub fn body<T: DeserializeOwned>(&self) -> Result<T, RequestError> {
if self.body_bytes.is_empty() {
return Err(RequestError::BodyIsEmpty);
}
if !self.is_content_type_json() {
return Err(RequestError::unsupported_media_type(
"Expected Content-Type to be application/json",
));
}
serde_json::from_slice(&self.body_bytes).map_err(|e| {
RequestError::deserialization_error(
"Invalid request body",
"Failed to deserialize request body to the required type.".into(),
e.into(),
)
})
}
pub fn query<T: DeserializeOwned>(&self) -> Result<Option<T>, RequestError> {
let Some(query_string) = self.uri.query() else {
return Ok(None);
};
if query_string.is_empty() {
return Ok(None);
}
let deserializer = serde_urlencoded::Deserializer::new(
form_urlencoded::parse(query_string.as_bytes()),
);
let deserialized = T::deserialize(deserializer).map_err(|e| {
RequestError::deserialization_error(
"Invalid query parameters",
"Failed to deserialize query params to the required type.".into(),
e.into(),
)
})?;
Ok(Some(deserialized))
}
pub fn cookies(&self) -> Result<&Cookies, JsonResponse> {
self.extensions.get::<Cookies>().ok_or_else(|| {
JsonResponse::InternalServerError()
.message("Can't extract cookies. Is `CookieManagerLayer` enabled?")
})
}
pub fn authorization(&self) -> Option<&str> {
self.header("Authorization")
}
pub fn user_agent(&self) -> Option<&str> {
self.header("User-Agent")
}
pub fn ip(&self) -> Option<&str> {
self.header("X-Forwarded-For")
}
pub fn ips(&self) -> Option<Vec<&str>> {
self.header("X-Forwarded-For")
.map(|ips| ips.split(',').map(|s| s.trim()).collect())
}
pub fn protocol(&self) -> &str {
self.header("X-Forwarded-Proto").unwrap_or("http")
}
pub fn content_length(&self) -> Option<u64> {
self.header("Content-Length")
.and_then(|value| value.parse::<u64>().ok())
}
pub fn id(&self) -> String {
if let Some(id) = self.extensions.get::<RequestId>() {
return id.header_value().to_str().unwrap_or_default().to_string();
}
"unknown".to_string()
}
pub fn content_type(&self) -> Option<&str> {
self.header("Content-Type")
}
#[cfg(feature = "multipart")]
pub async fn multipart(
self,
) -> Result<axum::extract::multipart::Multipart, RequestError> {
use axum::extract::FromRequest;
Ok(axum::extract::Multipart::from_request(self.try_into()?, &()).await?)
}
pub(crate) fn is_content_type_json(&self) -> bool {
let Some(content_type) = self.content_type() else {
return false;
};
let Ok(mime) = content_type.parse::<mime::Mime>() else {
return false;
};
mime.type_() == "application"
&& (mime.subtype() == "json"
|| mime.suffix().is_some_and(|name| name == "json"))
}
#[doc(hidden)]
pub fn clear_next(&mut self) {
self.next = None;
}
#[doc(hidden)]
pub fn set_next(&mut self, next: Next) {
self.next = Some(next);
}
pub async fn next(mut self) -> HttpInterceptorResult {
let Some(next) = self.next.take() else {
tracing::error!(
"Attempted to call `next()` on Request in a context that is not a `OnRequest` `Interceptor`"
);
return Err(JsonResponse::InternalServerError());
};
Ok(next.run(self.try_into()?).await)
}
}