use crate::detect::Format;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum StryptError {
#[error("i/o failure while {action}")]
Io {
action: IoAction,
#[source]
source: std::io::Error,
},
#[error("input is larger than the configured limit of {limit} bytes")]
InputTooLarge {
limit: u64,
actual: Option<u64>,
},
#[error("{format} is not supported in this release")]
UnsupportedFormat {
format: UnsupportedKind,
},
#[error("the content does not match any format strypt recognises")]
UnrecognisedFormat,
#[error("malformed {format}{}: {detail}", .offset.map_or_else(String::new, |o| format!(" at byte offset {o}")))]
Malformed {
format: Format,
offset: Option<u64>,
detail: MalformedDetail,
},
#[error("{format} parsing exceeded the {limit} limit")]
LimitExceeded {
format: Format,
limit: ResourceLimit,
},
#[error("verification failed: {residual} metadata item(s) survived stripping")]
VerificationFailed {
format: Format,
residual: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum IoAction {
ReadingInput,
MeasuringInput,
CreatingTemporary,
WritingOutput,
SyncingOutput,
ReplacingDestination,
SettingPermissions,
CleaningUp,
}
impl std::fmt::Display for IoAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::ReadingInput => "reading the input",
Self::MeasuringInput => "measuring the input",
Self::CreatingTemporary => "creating a temporary file",
Self::WritingOutput => "writing output",
Self::SyncingOutput => "syncing output to disk",
Self::ReplacingDestination => "replacing the destination file",
Self::SettingPermissions => "setting output permissions",
Self::CleaningUp => "removing a temporary file",
};
f.write_str(s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum UnsupportedKind {
NotYetImplemented(crate::detect::Format),
ZipContainer,
Gif,
Tiff,
IsoBaseMedia,
Mp3,
Ogg,
Flac,
OtherRiff,
Xml,
}
impl std::fmt::Display for UnsupportedKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::NotYetImplemented(format) => {
return write!(f, "{format}, whose handler has not landed yet");
}
Self::ZipContainer => "a ZIP container (Office, OpenDocument, or archive)",
Self::Gif => "GIF",
Self::Tiff => "TIFF",
Self::IsoBaseMedia => "an ISO base-media file (MP4, HEIF, or AVIF)",
Self::Mp3 => "MP3",
Self::Ogg => "Ogg",
Self::Flac => "FLAC",
Self::OtherRiff => "a RIFF container other than WebP",
Self::Xml => "an XML document (possibly SVG)",
};
f.write_str(s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum MalformedDetail {
Truncated,
LengthOutOfRange,
MissingMarker,
UnexpectedMarker,
BrokenIndex,
CyclicReference,
UnsupportedFeature,
DependencyPanic,
}
impl std::fmt::Display for MalformedDetail {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::Truncated => "the file ends mid-structure",
Self::LengthOutOfRange => "a declared length or offset falls outside the file",
Self::MissingMarker => "a required structural marker is missing",
Self::UnexpectedMarker => "a structural marker appeared where it is not allowed",
Self::BrokenIndex => "the cross-reference structure is unusable",
Self::CyclicReference => "objects reference each other in a cycle",
Self::UnsupportedFeature => "the file uses a feature strypt will not process",
Self::DependencyPanic => "the parser failed on this file and it was not processed",
};
f.write_str(s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ResourceLimit {
Depth,
ItemCount,
ExpandedSize,
}
impl std::fmt::Display for ResourceLimit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::Depth => "nesting depth",
Self::ItemCount => "item count",
Self::ExpandedSize => "expanded size",
};
f.write_str(s)
}
}
pub type Result<T> = std::result::Result<T, StryptError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_messages_carry_structure_not_values() {
let e = StryptError::VerificationFailed {
format: Format::Pdf,
residual: 3,
};
let rendered = e.to_string();
assert!(rendered.contains('3'));
assert!(!rendered.contains("Author"));
}
#[test]
fn unsupported_is_distinguishable_from_unrecognised() {
let known = StryptError::UnsupportedFormat {
format: UnsupportedKind::ZipContainer,
};
assert!(matches!(known, StryptError::UnsupportedFormat { .. }));
assert!(matches!(
StryptError::UnrecognisedFormat,
StryptError::UnrecognisedFormat
));
}
}