use http::StatusCode;
use thiserror::Error;
#[non_exhaustive]
#[derive(Clone, Debug, Error)]
pub enum BoundaryRejection {
#[error("request body too large")]
BodyTooLarge,
#[error("invalid or missing Content-Type")]
InvalidContentType,
#[error("malformed or unknown-field request body")]
MalformedBody,
#[error("invalid request parameter")]
InvalidParameter,
#[error("syntactic validation failed")]
SyntaxViolation {
code: &'static str,
},
#[error("semantic validation failed")]
SemanticViolation {
code: &'static str,
},
#[error("request body nesting too deep")]
NestingTooDeep,
#[error("request body has too many fields")]
TooManyFields,
#[error("path traversal detected")]
PathTraversal,
#[error("injection attempt detected")]
InjectionAttempt {
code: &'static str,
},
#[error("SSRF attempt blocked")]
SsrfAttempt,
#[error("XXE attack blocked")]
XxeBlocked,
#[error("invalid header value: CRLF detected")]
InvalidHeaderValue,
}
impl BoundaryRejection {
#[must_use]
pub fn status_code(&self) -> StatusCode {
match self {
Self::BodyTooLarge => StatusCode::PAYLOAD_TOO_LARGE,
Self::InvalidContentType => StatusCode::UNSUPPORTED_MEDIA_TYPE,
Self::MalformedBody
| Self::InvalidParameter
| Self::SyntaxViolation { .. }
| Self::SemanticViolation { .. }
| Self::NestingTooDeep
| Self::TooManyFields
| Self::PathTraversal
| Self::InjectionAttempt { .. }
| Self::SsrfAttempt
| Self::XxeBlocked
| Self::InvalidHeaderValue => StatusCode::UNPROCESSABLE_ENTITY,
}
}
#[must_use]
pub fn client_code(&self) -> &'static str {
match self {
Self::BodyTooLarge => "body_too_large",
Self::InvalidContentType => "invalid_content_type",
Self::MalformedBody => "malformed_body",
Self::InvalidParameter => "invalid_parameter",
Self::SyntaxViolation { .. } => "syntax_violation",
Self::SemanticViolation { .. } => "semantic_violation",
Self::NestingTooDeep => "nesting_too_deep",
Self::TooManyFields => "too_many_fields",
Self::PathTraversal => "path_traversal",
Self::InjectionAttempt { .. } => "injection_attempt",
Self::SsrfAttempt => "ssrf_attempt",
Self::XxeBlocked => "xxe_blocked",
Self::InvalidHeaderValue => "invalid_header_value",
}
}
}
#[cfg(feature = "axum")]
impl axum::response::IntoResponse for BoundaryRejection {
fn into_response(self) -> axum::http::Response<axum::body::Body> {
let status = self.status_code();
let code = self.client_code();
let body = format!(r#"{{"error":{{"code":"{}"}}}}"#, code);
axum::http::Response::builder()
.status(status)
.header("content-type", "application/json")
.body(axum::body::Body::from(body))
.unwrap_or_else(|_| {
axum::http::Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(axum::body::Body::empty())
.expect("static fallback response always builds")
})
}
}