use std::fmt;
use crate::cost::hill::CostError;
use crate::crypto::aead::CryptoError;
use crate::crypto::expand::ExpandError;
use crate::crypto::kdf::KdfError;
use crate::image_io::phash::PHashError;
use crate::image_io::validate::ValidationError;
use crate::stego::sizer::SizerError;
use crate::stego::stc::StcError;
#[derive(Debug)]
pub enum OutputError {
MalformedBuffer,
EncodingFailed(String),
}
impl fmt::Display for OutputError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OutputError::MalformedBuffer => write!(
f,
"the stego image buffer does not match its own dimensions and cannot be encoded"
),
OutputError::EncodingFailed(message) => {
write!(f, "failed to write the stego image: {message}")
}
}
}
}
impl std::error::Error for OutputError {}
#[derive(Debug)]
pub enum PipelineError {
Validation(ValidationError),
PHash(PHashError),
Kdf(KdfError),
Expand(ExpandError),
Cost(CostError),
Sizer(SizerError),
Stc(StcError),
Crypto(CryptoError),
Output(OutputError),
}
impl fmt::Display for PipelineError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PipelineError::Validation(err) => write!(f, "{err}"),
PipelineError::PHash(err) => write!(f, "{err}"),
PipelineError::Kdf(err) => write!(f, "{err}"),
PipelineError::Expand(err) => write!(f, "{err}"),
PipelineError::Cost(err) => write!(f, "{err}"),
PipelineError::Sizer(err) => write!(f, "{err}"),
PipelineError::Stc(err) => write!(f, "{err}"),
PipelineError::Crypto(err) => write!(f, "{err}"),
PipelineError::Output(err) => write!(f, "{err}"),
}
}
}
impl std::error::Error for PipelineError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
PipelineError::Validation(err) => Some(err),
PipelineError::PHash(err) => Some(err),
PipelineError::Kdf(err) => Some(err),
PipelineError::Expand(err) => Some(err),
PipelineError::Cost(err) => Some(err),
PipelineError::Sizer(err) => Some(err),
PipelineError::Stc(err) => Some(err),
PipelineError::Crypto(err) => Some(err),
PipelineError::Output(err) => Some(err),
}
}
}
impl From<ValidationError> for PipelineError {
fn from(err: ValidationError) -> Self {
PipelineError::Validation(err)
}
}
impl From<PHashError> for PipelineError {
fn from(err: PHashError) -> Self {
PipelineError::PHash(err)
}
}
impl From<KdfError> for PipelineError {
fn from(err: KdfError) -> Self {
PipelineError::Kdf(err)
}
}
impl From<ExpandError> for PipelineError {
fn from(err: ExpandError) -> Self {
PipelineError::Expand(err)
}
}
impl From<CostError> for PipelineError {
fn from(err: CostError) -> Self {
PipelineError::Cost(err)
}
}
impl From<SizerError> for PipelineError {
fn from(err: SizerError) -> Self {
PipelineError::Sizer(err)
}
}
impl From<StcError> for PipelineError {
fn from(err: StcError) -> Self {
PipelineError::Stc(err)
}
}
impl From<CryptoError> for PipelineError {
fn from(err: CryptoError) -> Self {
PipelineError::Crypto(err)
}
}
impl From<OutputError> for PipelineError {
fn from(err: OutputError) -> Self {
PipelineError::Output(err)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::aead::AEADError;
fn one_of_each() -> Vec<PipelineError> {
vec![
ValidationError::NotPng.into(),
PHashError::RecoveryFailed.into(),
KdfError::EmptyPassword.into(),
ExpandError::HkdfError("output too long".to_owned()).into(),
CostError::InsufficientGlobalTexture.into(),
SizerError::PayloadTooLarge {
payload: 100,
available: 10,
deficit: 90,
}
.into(),
StcError::InvalidCostMap.into(),
CryptoError::AEADError(AEADError::AuthenticationFailed).into(),
OutputError::MalformedBuffer.into(),
]
}
#[test]
fn every_layer_lifts_into_its_own_variant() {
let lifted = one_of_each();
assert!(matches!(lifted[0], PipelineError::Validation(_)));
assert!(matches!(lifted[1], PipelineError::PHash(_)));
assert!(matches!(lifted[2], PipelineError::Kdf(_)));
assert!(matches!(lifted[3], PipelineError::Expand(_)));
assert!(matches!(lifted[4], PipelineError::Cost(_)));
assert!(matches!(lifted[5], PipelineError::Sizer(_)));
assert!(matches!(lifted[6], PipelineError::Stc(_)));
assert!(matches!(lifted[7], PipelineError::Crypto(_)));
assert!(matches!(lifted[8], PipelineError::Output(_)));
}
#[test]
fn the_message_is_the_message_of_the_wrapped_error() {
assert_eq!(
PipelineError::from(ValidationError::NotPng).to_string(),
ValidationError::NotPng.to_string()
);
for error in one_of_each() {
assert!(!error.to_string().is_empty());
}
}
#[test]
fn every_variant_names_its_cause() {
for error in one_of_each() {
assert!(
std::error::Error::source(&error).is_some(),
"no cause behind: {error:?}"
);
}
}
#[test]
fn writing_failures_explain_themselves() {
assert!(OutputError::MalformedBuffer
.to_string()
.contains("dimensions"));
assert!(OutputError::EncodingFailed("disk full".to_owned())
.to_string()
.contains("disk full"));
assert!(std::error::Error::source(&OutputError::MalformedBuffer).is_none());
}
}