Skip to main content

j2k_transcode/jpeg_to_htj2k/error/
native_encode.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use core::fmt;
4
5use j2k_native::EncodeError;
6
7/// Machine-readable class for a native HTJ2K encode failure.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum Htj2kEncodeErrorKind {
11    /// Caller-provided samples, geometry, metadata, or options were invalid.
12    InvalidInput,
13    /// The requested encode feature or shape is not implemented.
14    Unsupported,
15    /// Checked arithmetic overflowed while planning an encode phase.
16    ArithmeticOverflow,
17    /// A host memory limit or allocation rejected the encode workspace.
18    HostResource,
19    /// An optional encode-stage accelerator failed.
20    Accelerator,
21    /// A generated codestream failed validation.
22    CodestreamValidation,
23    /// Native encode state violated an internal invariant.
24    InternalInvariant,
25    /// A newer native failure has no more specific shared classification.
26    Other,
27}
28
29/// Opaque, transcode-owned source for a native HTJ2K encode failure.
30///
31/// The concrete native error remains available through
32/// [`core::error::Error::source`] without making `j2k-native` part of this
33/// crate's public type signatures.
34#[derive(Debug, PartialEq, Eq)]
35pub struct Htj2kEncodeError {
36    source: EncodeError,
37}
38
39impl Htj2kEncodeError {
40    pub(super) const fn new(source: EncodeError) -> Self {
41        Self { source }
42    }
43
44    /// Return the machine-readable failure class.
45    #[must_use]
46    pub const fn kind(&self) -> Htj2kEncodeErrorKind {
47        match self.source {
48            EncodeError::InvalidInput { .. } => Htj2kEncodeErrorKind::InvalidInput,
49            EncodeError::Unsupported { .. } => Htj2kEncodeErrorKind::Unsupported,
50            EncodeError::ArithmeticOverflow { .. } => Htj2kEncodeErrorKind::ArithmeticOverflow,
51            EncodeError::AllocationTooLarge { .. } | EncodeError::HostAllocationFailed { .. } => {
52                Htj2kEncodeErrorKind::HostResource
53            }
54            EncodeError::Accelerator { .. } => Htj2kEncodeErrorKind::Accelerator,
55            EncodeError::CodestreamValidation { .. } => Htj2kEncodeErrorKind::CodestreamValidation,
56            EncodeError::InternalInvariant { .. } => Htj2kEncodeErrorKind::InternalInvariant,
57            _ => Htj2kEncodeErrorKind::Other,
58        }
59    }
60}
61
62impl fmt::Display for Htj2kEncodeError {
63    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
64        self.source.fmt(formatter)
65    }
66}
67
68impl core::error::Error for Htj2kEncodeError {
69    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
70        Some(&self.source)
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::{EncodeError, Htj2kEncodeError, Htj2kEncodeErrorKind};
77
78    #[test]
79    fn native_encode_kinds_remain_structured_behind_the_opaque_boundary() {
80        let cases = [
81            (
82                EncodeError::InvalidInput { what: "invalid" },
83                Htj2kEncodeErrorKind::InvalidInput,
84            ),
85            (
86                EncodeError::Unsupported {
87                    what: "unsupported",
88                },
89                Htj2kEncodeErrorKind::Unsupported,
90            ),
91            (
92                EncodeError::ArithmeticOverflow { what: "overflow" },
93                Htj2kEncodeErrorKind::ArithmeticOverflow,
94            ),
95            (
96                EncodeError::AllocationTooLarge {
97                    what: "workspace",
98                    requested: 17,
99                    cap: 16,
100                },
101                Htj2kEncodeErrorKind::HostResource,
102            ),
103            (
104                EncodeError::HostAllocationFailed {
105                    what: "workspace",
106                    bytes: 16,
107                },
108                Htj2kEncodeErrorKind::HostResource,
109            ),
110            (
111                EncodeError::Accelerator {
112                    operation: "dispatch",
113                    source: j2k::J2kEncodeStageError::internal_invariant("failed"),
114                },
115                Htj2kEncodeErrorKind::Accelerator,
116            ),
117            (
118                EncodeError::CodestreamValidation { detail: "invalid" },
119                Htj2kEncodeErrorKind::CodestreamValidation,
120            ),
121            (
122                EncodeError::InternalInvariant { what: "invalid" },
123                Htj2kEncodeErrorKind::InternalInvariant,
124            ),
125        ];
126
127        for (source, expected) in cases {
128            let error = Htj2kEncodeError::new(source);
129            assert_eq!(error.kind(), expected);
130            assert!(core::error::Error::source(&error)
131                .and_then(|source| source.downcast_ref::<EncodeError>())
132                .is_some());
133        }
134    }
135
136    #[test]
137    fn opaque_native_encode_error_retains_structural_equality() {
138        assert_eq!(
139            Htj2kEncodeError::new(EncodeError::InvalidInput { what: "fixture" }),
140            Htj2kEncodeError::new(EncodeError::InvalidInput { what: "fixture" })
141        );
142    }
143}