j2k_transcode/jpeg_to_htj2k/
error.rs1use super::{fmt, DctTransformError, MetricsError, TranscodeStageError};
4
5mod native_encode;
6pub use native_encode::{Htj2kEncodeError, Htj2kEncodeErrorKind};
7
8#[derive(Debug)]
10pub enum JpegToHtj2kError {
11 Jpeg(j2k_jpeg::JpegError),
13 Unsupported(&'static str),
15 Dct53(DctTransformError),
17 Dct97(DctTransformError),
19 Accelerator(TranscodeStageError),
21 MemoryCapExceeded {
23 requested: usize,
25 cap: usize,
27 },
28 HostAllocationFailed {
30 bytes: usize,
32 },
33 Metrics(MetricsError),
35 Validation(&'static str),
37 InternalInvariant {
39 what: &'static str,
41 },
42 Encode(Htj2kEncodeError),
44}
45
46impl fmt::Display for JpegToHtj2kError {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 match self {
49 Self::Jpeg(err) => write!(f, "JPEG extraction failed: {err}"),
50 Self::Unsupported(reason) => write!(f, "unsupported transcode input: {reason}"),
51 Self::Dct53(reason) => write!(f, "5/3 DCT transform failed: {reason}"),
52 Self::Dct97(reason) => write!(f, "9/7 DCT transform failed: {reason}"),
53 Self::Accelerator(reason) => write!(f, "transform accelerator failed: {reason}"),
54 Self::MemoryCapExceeded { requested, cap } => write!(
55 f,
56 "transcode host workspace requires {requested} bytes, exceeding the {cap}-byte cap"
57 ),
58 Self::HostAllocationFailed { bytes } => {
59 write!(f, "transcode host allocation failed for {bytes} bytes")
60 }
61 Self::Metrics(reason) => write!(f, "validation metrics failed: {reason}"),
62 Self::Validation(reason) => write!(f, "validation failed: {reason}"),
63 Self::InternalInvariant { what } => {
64 write!(f, "internal transcode invariant failed: {what}")
65 }
66 Self::Encode(reason) => write!(f, "HTJ2K encode failed: {reason}"),
67 }
68 }
69}
70
71impl std::error::Error for JpegToHtj2kError {
72 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
73 match self {
74 Self::Jpeg(err) => Some(err),
75 Self::Dct53(err) | Self::Dct97(err) => Some(err),
76 Self::Accelerator(err) => Some(err),
77 Self::Metrics(err) => Some(err),
78 Self::Encode(err) => Some(err),
79 Self::Unsupported(_)
80 | Self::MemoryCapExceeded { .. }
81 | Self::HostAllocationFailed { .. }
82 | Self::Validation(_)
83 | Self::InternalInvariant { .. } => None,
84 }
85 }
86}
87
88impl From<j2k_jpeg::JpegError> for JpegToHtj2kError {
89 fn from(value: j2k_jpeg::JpegError) -> Self {
90 Self::Jpeg(value)
91 }
92}
93
94pub(super) fn dct53_transform_error(value: DctTransformError) -> JpegToHtj2kError {
95 map_transform_error(value, JpegToHtj2kError::Dct53)
96}
97
98pub(super) fn dct97_transform_error(value: DctTransformError) -> JpegToHtj2kError {
99 map_transform_error(value, JpegToHtj2kError::Dct97)
100}
101
102pub(super) fn map_encode_error(value: j2k_native::EncodeError) -> JpegToHtj2kError {
103 match value {
104 j2k_native::EncodeError::AllocationTooLarge { requested, cap, .. } => {
105 JpegToHtj2kError::MemoryCapExceeded { requested, cap }
106 }
107 j2k_native::EncodeError::HostAllocationFailed { bytes, .. } => {
108 JpegToHtj2kError::HostAllocationFailed { bytes }
109 }
110 error => JpegToHtj2kError::Encode(Htj2kEncodeError::new(error)),
111 }
112}
113
114fn map_transform_error(
115 value: DctTransformError,
116 transform: fn(DctTransformError) -> JpegToHtj2kError,
117) -> JpegToHtj2kError {
118 match value {
119 DctTransformError::MemoryCapExceeded { requested, cap } => {
120 JpegToHtj2kError::MemoryCapExceeded { requested, cap }
121 }
122 DctTransformError::HostAllocationFailed { bytes } => {
123 JpegToHtj2kError::HostAllocationFailed { bytes }
124 }
125 error => transform(error),
126 }
127}
128
129impl From<MetricsError> for JpegToHtj2kError {
130 fn from(value: MetricsError) -> Self {
131 Self::Metrics(value)
132 }
133}
134
135#[cfg(test)]
136mod transform_mapping_tests;
137
138#[cfg(test)]
139mod tests {
140 use super::{
141 map_encode_error, Htj2kEncodeError, Htj2kEncodeErrorKind, JpegToHtj2kError, MetricsError,
142 };
143 use j2k_native::EncodeError;
144
145 #[test]
146 fn native_encode_resource_errors_lift_into_transcode_resource_categories() {
147 assert!(matches!(
148 map_encode_error(EncodeError::AllocationTooLarge {
149 what: "test encode workspace",
150 requested: 17,
151 cap: 16,
152 }),
153 JpegToHtj2kError::MemoryCapExceeded {
154 requested: 17,
155 cap: 16
156 }
157 ));
158 assert!(matches!(
159 map_encode_error(EncodeError::HostAllocationFailed {
160 what: "test encode workspace",
161 bytes: 17,
162 }),
163 JpegToHtj2kError::HostAllocationFailed { bytes: 17 }
164 ));
165 }
166
167 #[test]
168 fn native_encode_semantic_errors_remain_typed_and_are_error_sources() {
169 let invalid = map_encode_error(EncodeError::InvalidInput {
170 what: "invalid test image",
171 });
172 let JpegToHtj2kError::Encode(invalid_source) = &invalid else {
173 panic!("expected native encode error");
174 };
175 assert_eq!(invalid_source.kind(), Htj2kEncodeErrorKind::InvalidInput);
176 assert_eq!(
177 invalid.to_string(),
178 "HTJ2K encode failed: invalid encode input: invalid test image"
179 );
180 assert_eq!(
181 invalid_source.to_string(),
182 "invalid encode input: invalid test image"
183 );
184 assert_eq!(
185 std::error::Error::source(&invalid)
186 .and_then(|source| source.downcast_ref::<Htj2kEncodeError>()),
187 Some(invalid_source)
188 );
189 assert!(matches!(
190 std::error::Error::source(invalid_source)
191 .and_then(|source| source.downcast_ref::<EncodeError>()),
192 Some(EncodeError::InvalidInput {
193 what: "invalid test image"
194 })
195 ));
196
197 let accelerator = map_encode_error(EncodeError::Accelerator {
198 operation: "test accelerator operation",
199 source: j2k::J2kEncodeStageError::internal_invariant("test backend failure"),
200 });
201 let JpegToHtj2kError::Encode(accelerator_source) = &accelerator else {
202 panic!("expected native accelerator encode error");
203 };
204 assert_eq!(accelerator_source.kind(), Htj2kEncodeErrorKind::Accelerator);
205 let native_source = std::error::Error::source(accelerator_source)
206 .and_then(|source| source.downcast_ref::<EncodeError>())
207 .expect("concrete native encode source");
208 let EncodeError::Accelerator { operation, source } = native_source else {
209 panic!("expected native accelerator source");
210 };
211 assert_eq!(*operation, "test accelerator operation");
212 assert_eq!(source.reason(), "test backend failure");
213 assert_eq!(
214 std::error::Error::source(native_source)
215 .and_then(|source| source.downcast_ref::<j2k::J2kEncodeStageError>()),
216 Some(source)
217 );
218 }
219
220 #[test]
221 fn metrics_failure_retains_its_typed_error_source() {
222 let mismatch = JpegToHtj2kError::from(MetricsError::LengthMismatch {
223 actual: 2,
224 expected: 1,
225 });
226 let mismatch_source = std::error::Error::source(&mismatch)
227 .and_then(|source| source.downcast_ref::<MetricsError>());
228 assert!(matches!(
229 mismatch_source,
230 Some(MetricsError::LengthMismatch {
231 actual: 2,
232 expected: 1,
233 })
234 ));
235
236 let cap = JpegToHtj2kError::from(MetricsError::MemoryCapExceeded {
237 requested: 65,
238 cap: 64,
239 });
240 let cap_source = std::error::Error::source(&cap)
241 .and_then(|source| source.downcast_ref::<MetricsError>());
242 assert!(matches!(
243 cap_source,
244 Some(MetricsError::MemoryCapExceeded {
245 requested: 65,
246 cap: 64,
247 })
248 ));
249
250 let allocation = JpegToHtj2kError::from(MetricsError::HostAllocationFailed { bytes: 4096 });
251 let allocation_source = std::error::Error::source(&allocation)
252 .and_then(|source| source.downcast_ref::<MetricsError>());
253 assert!(matches!(
254 allocation_source,
255 Some(MetricsError::HostAllocationFailed { bytes: 4096 })
256 ));
257 }
258}