use std::{error::Error, fmt};
use http::StatusCode;
use crate::profile::Platform;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RejectReason {
HeadersMissing,
IntegrityTokenInvalid,
CreatedTooOld,
CreatedTooFarInFuture,
Replayed,
JwksUnavailable,
NonceStoreUnavailable,
BodyReadTimeout,
ClientDisconnected,
BodyReadFailed,
DeviceIntegrityFailed,
SignatureInvalid,
SignatureInputMalformed,
SignatureMalformed,
ComponentMissing,
AlgMismatch,
NonceInvalid,
SignatureBaseIncomplete,
BodyTooLarge,
}
impl RejectReason {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::HeadersMissing => "headers_missing",
Self::IntegrityTokenInvalid => "integrity_token_invalid",
Self::CreatedTooOld => "created_too_old",
Self::CreatedTooFarInFuture => "created_too_far_in_future",
Self::Replayed => "replayed",
Self::JwksUnavailable => "jwks_unavailable",
Self::NonceStoreUnavailable => "nonce_store_unavailable",
Self::BodyReadTimeout => "body_read_timeout",
Self::ClientDisconnected => "client_disconnected",
Self::BodyReadFailed => "body_read_failed",
Self::DeviceIntegrityFailed => "device_integrity_failed",
Self::SignatureInvalid => "signature_invalid",
Self::SignatureInputMalformed => "signature_input_malformed",
Self::SignatureMalformed => "signature_malformed",
Self::ComponentMissing => "component_missing",
Self::AlgMismatch => "alg_mismatch",
Self::NonceInvalid => "nonce_invalid",
Self::SignatureBaseIncomplete => "signature_base_incomplete",
Self::BodyTooLarge => "body_too_large",
}
}
#[must_use]
pub fn status(self) -> StatusCode {
match self {
Self::HeadersMissing
| Self::IntegrityTokenInvalid
| Self::CreatedTooOld
| Self::CreatedTooFarInFuture
| Self::Replayed
| Self::AlgMismatch => StatusCode::UNAUTHORIZED,
Self::DeviceIntegrityFailed | Self::SignatureInvalid => StatusCode::FORBIDDEN,
Self::SignatureInputMalformed
| Self::SignatureMalformed
| Self::ComponentMissing
| Self::NonceInvalid
| Self::SignatureBaseIncomplete => StatusCode::BAD_REQUEST,
Self::JwksUnavailable | Self::NonceStoreUnavailable => StatusCode::SERVICE_UNAVAILABLE,
Self::BodyReadTimeout => StatusCode::REQUEST_TIMEOUT,
Self::BodyTooLarge => StatusCode::PAYLOAD_TOO_LARGE,
Self::BodyReadFailed => StatusCode::INTERNAL_SERVER_ERROR,
Self::ClientDisconnected => {
StatusCode::from_u16(499).expect("499 is a valid status code")
}
}
}
}
impl fmt::Display for RejectReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug)]
pub struct Rejection {
pub reason: RejectReason,
pub platform: Option<Platform>,
pub source: Option<Box<dyn Error + Send + Sync>>,
}
impl Rejection {
#[must_use]
pub const fn new(reason: RejectReason) -> Self {
Self {
reason,
platform: None,
source: None,
}
}
#[must_use]
pub fn with_source(mut self, source: impl Into<Box<dyn Error + Send + Sync>>) -> Self {
self.source = Some(source.into());
self
}
#[must_use]
pub const fn with_platform(mut self, platform: Platform) -> Self {
self.platform = Some(platform);
self
}
}
impl From<RejectReason> for Rejection {
fn from(reason: RejectReason) -> Self {
Self::new(reason)
}
}
impl fmt::Display for Rejection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "request rejected: {}", self.reason)?;
if let Some(source) = &self.source {
write!(f, ": {source}")?;
}
Ok(())
}
}
impl Error for Rejection {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.source.as_deref().map(|source| source as _)
}
}