Skip to main content

j2k_transcode/jpeg_to_htj2k/
error.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use super::{fmt, DctTransformError, MetricsError, TranscodeStageError};
4
5mod native_encode;
6pub use native_encode::{Htj2kEncodeError, Htj2kEncodeErrorKind};
7
8/// Error returned by the experimental transcode path.
9#[derive(Debug)]
10pub enum JpegToHtj2kError {
11    /// JPEG parse or entropy decode failed.
12    Jpeg(j2k_jpeg::JpegError),
13    /// Input is outside the currently implemented experimental slice.
14    Unsupported(&'static str),
15    /// The 5/3 DCT-grid transform could not execute.
16    Dct53(DctTransformError),
17    /// The 9/7 DCT-grid transform could not execute.
18    Dct97(DctTransformError),
19    /// Optional transform acceleration failed.
20    Accelerator(TranscodeStageError),
21    /// The requested transcode workspace exceeds the shared process safety cap.
22    MemoryCapExceeded {
23        /// Requested host bytes.
24        requested: usize,
25        /// Maximum permitted host bytes.
26        cap: usize,
27    },
28    /// The host allocator could not reserve the requested transcode workspace.
29    HostAllocationFailed {
30        /// Requested host bytes.
31        bytes: usize,
32    },
33    /// Validation metric construction failed.
34    Metrics(MetricsError),
35    /// Validation encountered an out-of-range or non-finite coefficient.
36    Validation(&'static str),
37    /// Internal transcode or batch-scheduler state violated an invariant.
38    InternalInvariant {
39        /// Stable description of the violated invariant.
40        what: &'static str,
41    },
42    /// Native HTJ2K encode failed.
43    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}