use std::fmt;
#[derive(Debug)]
pub enum RequestBodyError {
RejectedByPolicy,
DeclaredLengthTooLarge { declared: u64, limit: u64 },
LimitExceeded { limit: u64, received: u64 },
ReadTimeout,
PrematureEof {
received: u64,
expected: Option<u64>,
},
LengthMismatch { declared: u64, actual: u64 },
InvalidChunkFraming(String),
Cancelled,
Disconnected,
AlreadyConsumed,
MixedConsumptionMode,
Transport(String),
}
impl fmt::Display for RequestBodyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::RejectedByPolicy => write!(f, "request body rejected by policy"),
Self::DeclaredLengthTooLarge { declared, limit } => {
write!(
f,
"declared content-length {declared} exceeds limit {limit}"
)
}
Self::LimitExceeded { limit, received } => {
write!(
f,
"body exceeded limit: received {received} bytes, limit is {limit}"
)
}
Self::ReadTimeout => write!(f, "body read timed out"),
Self::PrematureEof { received, expected } => match expected {
Some(exp) => write!(
f,
"premature EOF: received {received} of {exp} expected bytes"
),
None => write!(f, "premature EOF after {received} bytes"),
},
Self::LengthMismatch { declared, actual } => {
write!(
f,
"body length mismatch: declared {declared}, actual {actual}"
)
}
Self::InvalidChunkFraming(msg) => {
write!(f, "invalid chunk framing: {msg}")
}
Self::Cancelled => write!(f, "body consumption cancelled"),
Self::Disconnected => write!(f, "client disconnected"),
Self::AlreadyConsumed => write!(f, "body already consumed"),
Self::MixedConsumptionMode => {
write!(
f,
"mixed consumption mode: cannot switch between read_all and streaming"
)
}
Self::Transport(msg) => write!(f, "transport error: {msg}"),
}
}
}
impl std::error::Error for RequestBodyError {}
impl RequestBodyError {
pub fn is_policy_rejection(&self) -> bool {
matches!(self, Self::RejectedByPolicy)
}
pub fn is_limit_exceeded(&self) -> bool {
matches!(
self,
Self::LimitExceeded { .. } | Self::DeclaredLengthTooLarge { .. }
)
}
pub fn is_timeout(&self) -> bool {
matches!(self, Self::ReadTimeout)
}
pub fn is_disconnect(&self) -> bool {
matches!(self, Self::Disconnected | Self::PrematureEof { .. })
}
pub fn is_consumption_state(&self) -> bool {
matches!(self, Self::AlreadyConsumed | Self::MixedConsumptionMode)
}
pub fn to_status_code(&self) -> u16 {
match self {
Self::RejectedByPolicy => 400,
Self::DeclaredLengthTooLarge { .. } => 413,
Self::LimitExceeded { .. } => 413,
Self::ReadTimeout => 408,
Self::PrematureEof { .. } => 400,
Self::LengthMismatch { .. } => 400,
Self::InvalidChunkFraming(_) => 400,
Self::Cancelled => 499,
Self::Disconnected => 499,
Self::AlreadyConsumed => 500,
Self::MixedConsumptionMode => 500,
Self::Transport(_) => 500,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_messages() {
let err = RequestBodyError::RejectedByPolicy;
assert!(err.to_string().contains("rejected by policy"));
let err = RequestBodyError::LimitExceeded {
limit: 1024,
received: 2048,
};
assert!(err.to_string().contains("2048"));
assert!(err.to_string().contains("1024"));
let err = RequestBodyError::PrematureEof {
received: 5,
expected: Some(10),
};
assert!(err.to_string().contains("5"));
assert!(err.to_string().contains("10"));
}
#[test]
fn classification() {
assert!(RequestBodyError::RejectedByPolicy.is_policy_rejection());
assert!(!RequestBodyError::RejectedByPolicy.is_limit_exceeded());
assert!(RequestBodyError::LimitExceeded {
limit: 100,
received: 200
}
.is_limit_exceeded());
assert!(RequestBodyError::ReadTimeout.is_timeout());
assert!(RequestBodyError::Disconnected.is_disconnect());
assert!(RequestBodyError::AlreadyConsumed.is_consumption_state());
}
#[test]
fn status_codes() {
assert_eq!(RequestBodyError::RejectedByPolicy.to_status_code(), 400);
assert_eq!(
RequestBodyError::LimitExceeded {
limit: 100,
received: 200
}
.to_status_code(),
413
);
assert_eq!(RequestBodyError::ReadTimeout.to_status_code(), 408);
assert_eq!(
RequestBodyError::PrematureEof {
received: 0,
expected: Some(10)
}
.to_status_code(),
400
);
assert_eq!(
RequestBodyError::Transport("oops".into()).to_status_code(),
500
);
}
}