pub mod body;
pub mod cookie;
pub mod cookie_named;
pub(crate) mod cookie_util;
pub mod extract;
pub mod form;
pub mod has_inner;
pub mod header;
pub mod header_named;
pub mod json;
#[cfg(feature = "multipart")]
pub mod multipart;
pub mod path;
pub mod query;
pub mod validation;
use reinhardt_http::Error as CoreError;
use std::any::TypeId;
use std::collections::HashMap;
use std::sync::Mutex;
use thiserror::Error;
pub use reinhardt_core::exception::{ParamErrorContext, ParamType};
pub use reinhardt_http::Request;
use reinhardt_core::exception::param_error::{
extract_field_from_serde_error, extract_field_from_urlencoded_error,
};
pub use body::Body;
pub use cookie::{Cookie, CookieStruct};
pub use cookie_named::{CookieName, CookieNamed, CsrfToken, SessionId};
pub use extract::FromRequest;
pub use form::Form;
pub use has_inner::HasInner;
pub use header::{Header, HeaderStruct};
pub use header_named::{Authorization, ContentType, HeaderName, HeaderNamed};
pub use json::Json;
#[cfg(feature = "multipart")]
pub use multipart::Multipart;
pub use path::{Path, PathStruct};
pub use query::Query;
#[cfg(feature = "validation")]
pub use validation::Validated;
pub use validation::{
ValidatedForm, ValidatedPath, ValidatedQuery, ValidationConstraints, WithValidation,
};
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ParamError {
#[error("Missing required parameter: {0}")]
MissingParameter(String),
#[error("{}", .0.format_error())]
InvalidParameter(Box<ParamErrorContext>),
#[error("{}", .0.format_error())]
ParseError(Box<ParamErrorContext>),
#[error("{}", .0.format_error())]
DeserializationError(Box<ParamErrorContext>),
#[error("{}", .0.format_error())]
UrlEncodingError(Box<ParamErrorContext>),
#[error("Request body error: {0}")]
BodyError(String),
#[error("Payload too large: {0}")]
PayloadTooLarge(String),
#[error("Authentication required: {0}")]
Authentication(String),
#[error("Internal extractor error: {0}")]
Internal(String),
#[cfg(feature = "validation")]
#[error("{}", .0.format_error())]
ValidationError(Box<ParamErrorContext>),
#[cfg(feature = "validation")]
#[error("Validation failed: {0:?}")]
ValidationFailed(Box<reinhardt_core::validators::ValidationErrors>),
}
impl ParamError {
pub fn json_deserialization<T>(err: serde_json::Error, raw_value: Option<String>) -> Self {
let field_name = extract_field_from_serde_error(&err);
let mut ctx = ParamErrorContext::new(ParamType::Json, err.to_string())
.with_expected_type::<T>()
.with_source(Box::new(err));
if let Some(field) = field_name {
ctx = ctx.with_field(field);
}
if let Some(raw) = raw_value {
ctx = ctx.with_raw_value(raw);
}
ParamError::DeserializationError(Box::new(ctx))
}
pub fn url_encoding<T>(
param_type: ParamType,
err: serde_urlencoded::de::Error,
raw_value: Option<String>,
) -> Self {
let field_name = extract_field_from_urlencoded_error(&err);
let mut ctx = ParamErrorContext::new(param_type, err.to_string())
.with_expected_type::<T>()
.with_source(Box::new(err));
if let Some(field) = field_name {
ctx = ctx.with_field(field);
}
if let Some(raw) = raw_value {
ctx = ctx.with_raw_value(raw);
}
ParamError::UrlEncodingError(Box::new(ctx))
}
pub fn invalid<T>(param_type: ParamType, message: impl Into<String>) -> Self {
let ctx = ParamErrorContext::new(param_type, message).with_expected_type::<T>();
ParamError::InvalidParameter(Box::new(ctx))
}
pub fn parse<T>(
param_type: ParamType,
message: impl Into<String>,
source: Box<dyn std::error::Error + Send + Sync>,
) -> Self {
let ctx = ParamErrorContext::new(param_type, message)
.with_expected_type::<T>()
.with_source(source);
ParamError::ParseError(Box::new(ctx))
}
pub fn context(&self) -> Option<&ParamErrorContext> {
match self {
ParamError::InvalidParameter(ctx) => Some(ctx),
ParamError::ParseError(ctx) => Some(ctx),
ParamError::DeserializationError(ctx) => Some(ctx),
ParamError::UrlEncodingError(ctx) => Some(ctx),
#[cfg(feature = "validation")]
ParamError::ValidationError(ctx) => Some(ctx),
_ => None,
}
}
pub fn format_multiline(&self, include_raw_value: bool) -> String {
match self.context() {
Some(ctx) => ctx.format_multiline(include_raw_value),
None => format!(" {}", self),
}
}
}
impl From<ParamError> for CoreError {
fn from(err: ParamError) -> Self {
let err = match err {
ParamError::Authentication(msg) => return CoreError::Authentication(msg),
ParamError::Internal(msg) => return CoreError::Internal(msg),
other => other,
};
match err.context() {
Some(ctx) => CoreError::ParamValidation(Box::new(ctx.clone())),
None => CoreError::Validation(err.to_string()),
}
}
}
pub type ParamResult<T> = std::result::Result<T, ParamError>;
pub struct ParamContext {
pub path_params: reinhardt_http::PathParams,
header_names: HashMap<TypeId, &'static str>,
cookie_names: HashMap<TypeId, &'static str>,
body_cache: Mutex<Option<bytes::Bytes>>,
}
impl ParamContext {
pub fn new() -> Self {
Self {
path_params: reinhardt_http::PathParams::new(),
header_names: HashMap::new(),
cookie_names: HashMap::new(),
body_cache: Mutex::new(None),
}
}
pub fn with_path_params(path_params: impl Into<reinhardt_http::PathParams>) -> Self {
Self {
path_params: path_params.into(),
header_names: HashMap::new(),
cookie_names: HashMap::new(),
body_cache: Mutex::new(None),
}
}
pub fn get_path_param(&self, name: &str) -> Option<&str> {
self.path_params.get(name).map(|s| s.as_str())
}
pub fn set_header_name<T: 'static>(&mut self, name: &'static str) {
self.header_names.insert(TypeId::of::<T>(), name);
}
pub fn get_header_name<T: 'static>(&self) -> Option<&'static str> {
self.header_names.get(&TypeId::of::<T>()).copied()
}
pub fn set_cookie_name<T: 'static>(&mut self, name: &'static str) {
self.cookie_names.insert(TypeId::of::<T>(), name);
}
pub fn get_cookie_name<T: 'static>(&self) -> Option<&'static str> {
self.cookie_names.get(&TypeId::of::<T>()).copied()
}
pub fn read_body_cached(&self, request: &reinhardt_http::Request) -> ParamResult<bytes::Bytes> {
let mut guard = self
.body_cache
.lock()
.expect("ParamContext body_cache mutex poisoned");
if let Some(bytes) = guard.as_ref() {
return Ok(bytes.clone());
}
let bytes = request
.read_body()
.map_err(|e| ParamError::BodyError(format!("Failed to read body: {}", e)))?;
*guard = Some(bytes.clone());
Ok(bytes)
}
}
impl Default for ParamContext {
fn default() -> Self {
Self::new()
}
}