1use crate::format::Format;
2use std::fmt;
3
4#[derive(Debug)]
6pub enum Error {
7 UnknownFormat,
9 Malformed(String),
11 NotFound,
13 EmptySource,
15 MalformedReference(String),
17 MultipleManifests(Format),
22 MalformedExclusion(String),
29 HashMismatch,
31 UnsupportedAlgorithm(String),
33 Io(std::io::Error),
34}
35
36impl Error {
37 pub fn code(&self) -> Option<&'static str> {
43 Some(match self {
44 Self::MultipleManifests(Format::Onnx) => "manifest.onnx.multipleManifests",
45 Self::MultipleManifests(Format::SafeTensors) => {
46 "manifest.safetensors.multipleManifests"
47 }
48 Self::MultipleManifests(Format::Gguf) => return None,
50 Self::MalformedExclusion(_) => "assertion.dataHash.malformed",
51 Self::HashMismatch => "assertion.dataHash.mismatch",
52 Self::UnsupportedAlgorithm(_) => "algorithm.unsupported",
53 Self::UnknownFormat
54 | Self::Malformed(_)
55 | Self::NotFound
56 | Self::EmptySource
57 | Self::MalformedReference(_)
58 | Self::Io(_) => return None,
59 })
60 }
61}
62
63impl fmt::Display for Error {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 match self {
66 Self::UnknownFormat => {
67 write!(f, "unrecognized ML model container format")
68 }
69 Self::Malformed(s) => write!(f, "malformed model container: {s}"),
70 Self::NotFound => write!(f, "no C2PA manifest found in model"),
71 Self::EmptySource => {
72 write!(f, "manifest source has neither an embedded store nor a URI")
73 }
74 Self::MalformedReference(s) => write!(f, "malformed manifest reference: {s}"),
75 Self::MultipleManifests(container) => {
76 write!(
77 f,
78 "more than one c2pa:manifest entry in the {} container",
79 container.name()
80 )
81 }
82 Self::MalformedExclusion(s) => write!(f, "data hash exclusion is malformed: {s}"),
83 Self::HashMismatch => write!(f, "data hash does not match the model content"),
84 Self::UnsupportedAlgorithm(a) => write!(f, "unsupported hash algorithm: {a}"),
85 Self::Io(e) => write!(f, "I/O error: {e}"),
86 }
87 }
88}
89
90impl std::error::Error for Error {}
91
92impl From<std::io::Error> for Error {
93 fn from(e: std::io::Error) -> Self {
94 Self::Io(e)
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 fn all() -> Vec<Error> {
103 vec![
104 Error::UnknownFormat,
105 Error::Malformed("x".into()),
106 Error::NotFound,
107 Error::EmptySource,
108 Error::MalformedReference("x".into()),
109 Error::MultipleManifests(Format::Gguf),
110 Error::MultipleManifests(Format::Onnx),
111 Error::MultipleManifests(Format::SafeTensors),
112 Error::MalformedExclusion("x".into()),
113 Error::HashMismatch,
114 Error::UnsupportedAlgorithm("sha1".into()),
115 ]
116 }
117
118 #[test]
119 fn display_composes_into_a_sentence_for_every_variant() {
120 for e in all() {
121 let s = e.to_string();
122 assert!(!s.is_empty(), "{e:?} rendered empty");
123 assert!(!s.ends_with('.'), "{e:?} ends with a period: {s}");
124 let first = s.chars().next().expect("checked non-empty above");
125 assert!(!first.is_uppercase(), "{e:?} starts uppercase: {s}");
126 }
127 }
128
129 #[test]
130 fn multiplicity_codes_are_per_format_and_as_registered() {
131 assert_eq!(
132 Error::MultipleManifests(Format::Onnx).code(),
133 Some("manifest.onnx.multipleManifests")
134 );
135 assert_eq!(
136 Error::MultipleManifests(Format::SafeTensors).code(),
137 Some("manifest.safetensors.multipleManifests")
138 );
139 assert_eq!(Error::MultipleManifests(Format::Gguf).code(), None);
142 }
143
144 #[test]
145 fn every_code_is_a_registered_identifier() {
146 for e in all() {
147 if let Some(code) = e.code() {
148 assert!(
149 matches!(
150 code,
151 "manifest.onnx.multipleManifests"
152 | "manifest.safetensors.multipleManifests"
153 | "assertion.dataHash.malformed"
154 | "assertion.dataHash.mismatch"
155 | "algorithm.unsupported"
156 ),
157 "{e:?} reports an unregistered code: {code}"
158 );
159 }
160 }
161 }
162
163 #[test]
164 fn reading_and_embedding_errors_carry_no_code() {
165 for e in [
166 Error::UnknownFormat,
167 Error::Malformed("x".into()),
168 Error::NotFound,
169 Error::EmptySource,
170 Error::MalformedReference("x".into()),
171 ] {
172 assert_eq!(e.code(), None, "{e:?} must not report a status code");
173 }
174 }
175
176 #[test]
177 fn format_names_itself_in_the_message() {
178 assert!(Error::MultipleManifests(Format::Onnx)
179 .to_string()
180 .contains("ONNX"));
181 assert!(Error::MultipleManifests(Format::SafeTensors)
182 .to_string()
183 .contains("SafeTensors"));
184 }
185}