stenoxide_core/pipeline/
error.rs1use std::fmt;
15
16use crate::cost::hill::CostError;
17use crate::crypto::aead::CryptoError;
18use crate::crypto::expand::ExpandError;
19use crate::crypto::kdf::KdfError;
20use crate::image_io::phash::PHashError;
21use crate::image_io::validate::ValidationError;
22use crate::stego::sizer::SizerError;
23use crate::stego::stc::StcError;
24
25#[derive(Debug)]
30pub enum OutputError {
31 MalformedBuffer,
39 EncodingFailed(String),
41}
42
43impl fmt::Display for OutputError {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 match self {
46 OutputError::MalformedBuffer => write!(
47 f,
48 "the stego image buffer does not match its own dimensions and cannot be encoded"
49 ),
50 OutputError::EncodingFailed(message) => {
51 write!(f, "failed to write the stego image: {message}")
52 }
53 }
54 }
55}
56
57impl std::error::Error for OutputError {}
58
59#[derive(Debug)]
64pub enum PipelineError {
65 Validation(ValidationError),
67 PHash(PHashError),
70 Kdf(KdfError),
72 Expand(ExpandError),
74 Cost(CostError),
76 Sizer(SizerError),
78 Stc(StcError),
80 Crypto(CryptoError),
82 Output(OutputError),
84}
85
86impl fmt::Display for PipelineError {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 match self {
96 PipelineError::Validation(err) => write!(f, "{err}"),
97 PipelineError::PHash(err) => write!(f, "{err}"),
98 PipelineError::Kdf(err) => write!(f, "{err}"),
99 PipelineError::Expand(err) => write!(f, "{err}"),
100 PipelineError::Cost(err) => write!(f, "{err}"),
101 PipelineError::Sizer(err) => write!(f, "{err}"),
102 PipelineError::Stc(err) => write!(f, "{err}"),
103 PipelineError::Crypto(err) => write!(f, "{err}"),
104 PipelineError::Output(err) => write!(f, "{err}"),
105 }
106 }
107}
108
109impl std::error::Error for PipelineError {
110 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
111 match self {
112 PipelineError::Validation(err) => Some(err),
113 PipelineError::PHash(err) => Some(err),
114 PipelineError::Kdf(err) => Some(err),
115 PipelineError::Expand(err) => Some(err),
116 PipelineError::Cost(err) => Some(err),
117 PipelineError::Sizer(err) => Some(err),
118 PipelineError::Stc(err) => Some(err),
119 PipelineError::Crypto(err) => Some(err),
120 PipelineError::Output(err) => Some(err),
121 }
122 }
123}
124
125impl From<ValidationError> for PipelineError {
126 fn from(err: ValidationError) -> Self {
127 PipelineError::Validation(err)
128 }
129}
130
131impl From<PHashError> for PipelineError {
132 fn from(err: PHashError) -> Self {
133 PipelineError::PHash(err)
134 }
135}
136
137impl From<KdfError> for PipelineError {
138 fn from(err: KdfError) -> Self {
139 PipelineError::Kdf(err)
140 }
141}
142
143impl From<ExpandError> for PipelineError {
144 fn from(err: ExpandError) -> Self {
145 PipelineError::Expand(err)
146 }
147}
148
149impl From<CostError> for PipelineError {
150 fn from(err: CostError) -> Self {
151 PipelineError::Cost(err)
152 }
153}
154
155impl From<SizerError> for PipelineError {
156 fn from(err: SizerError) -> Self {
157 PipelineError::Sizer(err)
158 }
159}
160
161impl From<StcError> for PipelineError {
162 fn from(err: StcError) -> Self {
163 PipelineError::Stc(err)
164 }
165}
166
167impl From<CryptoError> for PipelineError {
168 fn from(err: CryptoError) -> Self {
169 PipelineError::Crypto(err)
170 }
171}
172
173impl From<OutputError> for PipelineError {
174 fn from(err: OutputError) -> Self {
175 PipelineError::Output(err)
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 use crate::crypto::aead::AEADError;
184
185 fn one_of_each() -> Vec<PipelineError> {
191 vec![
192 ValidationError::NotPng.into(),
193 PHashError::RecoveryFailed.into(),
194 KdfError::EmptyPassword.into(),
195 ExpandError::HkdfError("output too long".to_owned()).into(),
196 CostError::InsufficientGlobalTexture.into(),
197 SizerError::PayloadTooLarge {
198 payload: 100,
199 available: 10,
200 deficit: 90,
201 }
202 .into(),
203 StcError::InvalidCostMap.into(),
204 CryptoError::AEADError(AEADError::AuthenticationFailed).into(),
205 OutputError::MalformedBuffer.into(),
206 ]
207 }
208
209 #[test]
211 fn every_layer_lifts_into_its_own_variant() {
212 let lifted = one_of_each();
213
214 assert!(matches!(lifted[0], PipelineError::Validation(_)));
215 assert!(matches!(lifted[1], PipelineError::PHash(_)));
216 assert!(matches!(lifted[2], PipelineError::Kdf(_)));
217 assert!(matches!(lifted[3], PipelineError::Expand(_)));
218 assert!(matches!(lifted[4], PipelineError::Cost(_)));
219 assert!(matches!(lifted[5], PipelineError::Sizer(_)));
220 assert!(matches!(lifted[6], PipelineError::Stc(_)));
221 assert!(matches!(lifted[7], PipelineError::Crypto(_)));
222 assert!(matches!(lifted[8], PipelineError::Output(_)));
223 }
224
225 #[test]
228 fn the_message_is_the_message_of_the_wrapped_error() {
229 assert_eq!(
230 PipelineError::from(ValidationError::NotPng).to_string(),
231 ValidationError::NotPng.to_string()
232 );
233
234 for error in one_of_each() {
235 assert!(!error.to_string().is_empty());
236 }
237 }
238
239 #[test]
242 fn every_variant_names_its_cause() {
243 for error in one_of_each() {
244 assert!(
245 std::error::Error::source(&error).is_some(),
246 "no cause behind: {error:?}"
247 );
248 }
249 }
250
251 #[test]
253 fn writing_failures_explain_themselves() {
254 assert!(OutputError::MalformedBuffer
255 .to_string()
256 .contains("dimensions"));
257 assert!(OutputError::EncodingFailed("disk full".to_owned())
258 .to_string()
259 .contains("disk full"));
260 assert!(std::error::Error::source(&OutputError::MalformedBuffer).is_none());
261 }
262}