use crate::format::Format;
use std::fmt;
#[derive(Debug)]
pub enum Error {
UnknownFormat,
Malformed(String),
NotFound,
EmptySource,
MalformedReference(String),
MultipleManifests(Format),
MalformedExclusion(String),
HashMismatch,
UnsupportedAlgorithm(String),
Io(std::io::Error),
}
impl Error {
pub fn code(&self) -> Option<&'static str> {
Some(match self {
Self::MultipleManifests(Format::Onnx) => "manifest.onnx.multipleManifests",
Self::MultipleManifests(Format::SafeTensors) => {
"manifest.safetensors.multipleManifests"
}
Self::MultipleManifests(Format::Gguf) => return None,
Self::MalformedExclusion(_) => "assertion.dataHash.malformed",
Self::HashMismatch => "assertion.dataHash.mismatch",
Self::UnsupportedAlgorithm(_) => "algorithm.unsupported",
Self::UnknownFormat
| Self::Malformed(_)
| Self::NotFound
| Self::EmptySource
| Self::MalformedReference(_)
| Self::Io(_) => return None,
})
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnknownFormat => {
write!(f, "unrecognized ML model container format")
}
Self::Malformed(s) => write!(f, "malformed model container: {s}"),
Self::NotFound => write!(f, "no C2PA manifest found in model"),
Self::EmptySource => {
write!(f, "manifest source has neither an embedded store nor a URI")
}
Self::MalformedReference(s) => write!(f, "malformed manifest reference: {s}"),
Self::MultipleManifests(container) => {
write!(
f,
"more than one c2pa:manifest entry in the {} container",
container.name()
)
}
Self::MalformedExclusion(s) => write!(f, "data hash exclusion is malformed: {s}"),
Self::HashMismatch => write!(f, "data hash does not match the model content"),
Self::UnsupportedAlgorithm(a) => write!(f, "unsupported hash algorithm: {a}"),
Self::Io(e) => write!(f, "I/O error: {e}"),
}
}
}
impl std::error::Error for Error {}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Self::Io(e)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn all() -> Vec<Error> {
vec![
Error::UnknownFormat,
Error::Malformed("x".into()),
Error::NotFound,
Error::EmptySource,
Error::MalformedReference("x".into()),
Error::MultipleManifests(Format::Gguf),
Error::MultipleManifests(Format::Onnx),
Error::MultipleManifests(Format::SafeTensors),
Error::MalformedExclusion("x".into()),
Error::HashMismatch,
Error::UnsupportedAlgorithm("sha1".into()),
]
}
#[test]
fn display_composes_into_a_sentence_for_every_variant() {
for e in all() {
let s = e.to_string();
assert!(!s.is_empty(), "{e:?} rendered empty");
assert!(!s.ends_with('.'), "{e:?} ends with a period: {s}");
let first = s.chars().next().expect("checked non-empty above");
assert!(!first.is_uppercase(), "{e:?} starts uppercase: {s}");
}
}
#[test]
fn multiplicity_codes_are_per_format_and_as_registered() {
assert_eq!(
Error::MultipleManifests(Format::Onnx).code(),
Some("manifest.onnx.multipleManifests")
);
assert_eq!(
Error::MultipleManifests(Format::SafeTensors).code(),
Some("manifest.safetensors.multipleManifests")
);
assert_eq!(Error::MultipleManifests(Format::Gguf).code(), None);
}
#[test]
fn every_code_is_a_registered_identifier() {
for e in all() {
if let Some(code) = e.code() {
assert!(
matches!(
code,
"manifest.onnx.multipleManifests"
| "manifest.safetensors.multipleManifests"
| "assertion.dataHash.malformed"
| "assertion.dataHash.mismatch"
| "algorithm.unsupported"
),
"{e:?} reports an unregistered code: {code}"
);
}
}
}
#[test]
fn reading_and_embedding_errors_carry_no_code() {
for e in [
Error::UnknownFormat,
Error::Malformed("x".into()),
Error::NotFound,
Error::EmptySource,
Error::MalformedReference("x".into()),
] {
assert_eq!(e.code(), None, "{e:?} must not report a status code");
}
}
#[test]
fn format_names_itself_in_the_message() {
assert!(Error::MultipleManifests(Format::Onnx)
.to_string()
.contains("ONNX"));
assert!(Error::MultipleManifests(Format::SafeTensors)
.to_string()
.contains("SafeTensors"));
}
}