jpegli/
error.rs

1//! Error types for jpegli.
2//!
3//! Errors are organized hierarchically:
4//! - [`ArgumentError`] - Invalid arguments from the user
5//! - [`ResourceError`] - Memory/IO failures
6//! - Decoder-specific errors in [`crate::decoder::error`]
7//! - Encoder-specific errors in [`crate::encoder::error`]
8
9use alloc::string::String;
10use core::fmt;
11use thiserror::Error;
12use whereat::{AtTrace, AtTraceBoxed, AtTraceable};
13
14/// Result type for jpegli operations.
15pub type Result<T> = core::result::Result<T, Error>;
16
17// ============================================================================
18// ScanRead - Control flow for entropy-coded scan reading
19// ============================================================================
20
21/// Result of reading from an entropy-coded scan.
22///
23/// This distinguishes between successful reads, normal end-of-scan conditions,
24/// and truncated data. End-of-scan is not an error - it's the expected signal
25/// that a marker was encountered and the current scan is complete.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum ScanRead<T> {
28    /// Successfully read the value.
29    Value(T),
30    /// Reached end of entropy-coded segment (marker encountered).
31    /// This is normal during progressive JPEG decoding between scans.
32    EndOfScan,
33    /// Data was truncated (end of input without finding a marker).
34    /// Caller can choose to treat this as an error or attempt partial decode.
35    Truncated,
36}
37
38// These helper methods are part of the prerelease decoder API.
39// Some are unused internally but provided for external callers.
40#[allow(dead_code)]
41impl<T> ScanRead<T> {
42    /// Returns the value if `Value`, otherwise returns the provided default.
43    #[inline]
44    pub fn unwrap_or(self, default: T) -> T {
45        match self {
46            Self::Value(v) => v,
47            Self::EndOfScan | Self::Truncated => default,
48        }
49    }
50
51    /// Returns the value if `Value`, otherwise computes it from a closure.
52    #[inline]
53    pub fn unwrap_or_else<F: FnOnce() -> T>(self, f: F) -> T {
54        match self {
55            Self::Value(v) => v,
56            Self::EndOfScan | Self::Truncated => f(),
57        }
58    }
59
60    /// Returns `true` if this is `EndOfScan`.
61    #[inline]
62    pub fn is_end_of_scan(&self) -> bool {
63        matches!(self, Self::EndOfScan)
64    }
65
66    /// Returns `true` if this is `Truncated`.
67    #[inline]
68    pub fn is_truncated(&self) -> bool {
69        matches!(self, Self::Truncated)
70    }
71
72    /// Returns `true` if this is `Value`.
73    #[inline]
74    pub fn is_value(&self) -> bool {
75        matches!(self, Self::Value(_))
76    }
77
78    /// Maps the value if `Value`, passes through `EndOfScan` and `Truncated`.
79    #[inline]
80    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> ScanRead<U> {
81        match self {
82            Self::Value(v) => ScanRead::Value(f(v)),
83            Self::EndOfScan => ScanRead::EndOfScan,
84            Self::Truncated => ScanRead::Truncated,
85        }
86    }
87}
88
89/// Result type for entropy-coded scan reads.
90///
91/// - `Ok(ScanRead::Value(v))` - Successfully read a value
92/// - `Ok(ScanRead::EndOfScan)` - Normal end of scan (marker found)
93/// - `Ok(ScanRead::Truncated)` - Data ended without marker (caller decides how to handle)
94/// - `Err(e)` - Actual error (corruption, internal error, etc.)
95pub type ScanResult<T> = Result<ScanRead<T>>;
96
97// ============================================================================
98// Shared Error Types - Used by both encoder and decoder
99// ============================================================================
100
101/// Errors caused by invalid arguments from the caller.
102///
103/// These indicate bugs in the calling code, not runtime failures.
104#[derive(Debug, Clone, PartialEq, Error)]
105#[non_exhaustive]
106pub enum ArgumentError {
107    /// Invalid image dimensions (zero or exceeds limits).
108    #[error("invalid dimensions {width}x{height}: {reason}")]
109    InvalidDimensions {
110        width: u32,
111        height: u32,
112        reason: &'static str,
113    },
114
115    /// Invalid color space or pixel format combination.
116    #[error("invalid color format: {reason}")]
117    InvalidColorFormat { reason: &'static str },
118
119    /// Buffer size doesn't match expected size.
120    #[error("invalid buffer size: expected {expected} bytes, got {actual}")]
121    InvalidBufferSize { expected: usize, actual: usize },
122
123    /// Feature not supported by this codec.
124    #[error("unsupported feature: {feature}")]
125    UnsupportedFeature { feature: &'static str },
126
127    /// Pixel format not supported for this operation.
128    #[error("pixel format {format:?} not supported")]
129    UnsupportedPixelFormat { format: crate::types::PixelFormat },
130}
131
132/// Errors caused by resource exhaustion or I/O failures.
133///
134/// These are runtime failures, not bugs in calling code.
135#[derive(Debug, Clone, PartialEq, Error)]
136#[non_exhaustive]
137pub enum ResourceError {
138    /// Memory allocation failed.
139    #[error("allocation of {bytes} bytes failed while {context}")]
140    AllocationFailed { bytes: usize, context: &'static str },
141
142    /// Size calculation overflowed.
143    #[error("size calculation overflow while {context}")]
144    SizeOverflow { context: &'static str },
145
146    /// Image exceeds maximum pixel limit.
147    #[error("image too large: {pixels} pixels exceeds limit of {limit}")]
148    ImageTooLarge { pixels: u64, limit: u64 },
149
150    /// I/O operation failed.
151    #[error("I/O error: {reason}")]
152    IoError { reason: String },
153}
154
155// ============================================================================
156// Internal ErrorKind - Flat enum for internal use
157// ============================================================================
158
159/// The specific kind of error that occurred (internal flat enum).
160///
161/// This is used internally and by the `From` implementations.
162/// Public APIs use [`decoder::ErrorKind`](crate::decoder::ErrorKind) or
163/// [`encoder::ErrorKind`](crate::encoder::ErrorKind).
164#[derive(Debug, Clone, PartialEq)]
165#[non_exhaustive]
166pub enum ErrorKind {
167    // === Shared: Argument errors ===
168    /// Invalid input dimensions (zero or too large).
169    InvalidDimensions {
170        width: u32,
171        height: u32,
172        reason: &'static str,
173    },
174    /// Invalid color space or pixel format combination.
175    InvalidColorFormat { reason: &'static str },
176    /// Input buffer has wrong size.
177    InvalidBufferSize { expected: usize, actual: usize },
178    /// Unsupported JPEG feature.
179    UnsupportedFeature { feature: &'static str },
180    /// Pixel format not yet supported for this operation.
181    UnsupportedPixelFormat { format: crate::types::PixelFormat },
182
183    // === Shared: Resource errors ===
184    /// Memory allocation failed (OOM or limit exceeded).
185    AllocationFailed { bytes: usize, context: &'static str },
186    /// Size calculation overflowed.
187    SizeOverflow { context: &'static str },
188    /// Image exceeds maximum pixel limit.
189    ImageTooLarge { pixels: u64, limit: u64 },
190    /// I/O error during encoding/decoding.
191    IoError { reason: String },
192
193    // === Shared: Other ===
194    /// ICC color management error.
195    IccError(String),
196    /// Internal error (should not happen in correct usage).
197    InternalError { reason: &'static str },
198    /// Operation was cancelled via Stop trait.
199    Cancelled,
200
201    // === Decoder-specific: Datastream errors ===
202    /// Invalid JPEG data (corrupted or not a JPEG).
203    InvalidJpegData { reason: &'static str },
204    /// Input data is truncated or corrupted.
205    TruncatedData { context: &'static str },
206    /// Invalid marker or segment in JPEG stream.
207    InvalidMarker { marker: u8, context: &'static str },
208    /// Invalid Huffman table.
209    InvalidHuffmanTable { table_idx: u8, reason: &'static str },
210    /// Invalid quantization table.
211    InvalidQuantTable { table_idx: u8, reason: &'static str },
212    /// Too many progressive scans.
213    TooManyScans { count: usize, limit: usize },
214    /// Decode error from JPEG decoder.
215    DecodeError(String),
216
217    // === Encoder-specific: Argument errors ===
218    /// Invalid quality parameter.
219    InvalidQuality {
220        value: f32,
221        valid_range: &'static str,
222    },
223    /// Invalid scan script for progressive encoding.
224    InvalidScanScript(String),
225    /// Invalid encoder configuration.
226    InvalidConfig(String),
227    /// Stride too small for image width.
228    StrideTooSmall { width: u32, stride: usize },
229
230    // === Encoder-specific: State errors ===
231    /// Pushed more rows than image height.
232    TooManyRows { height: u32, pushed: u32 },
233    /// Encoding finished without all rows pushed.
234    IncompleteImage { height: u32, pushed: u32 },
235}
236
237impl ErrorKind {
238    /// Convert to ArgumentError if this is an argument error variant.
239    pub fn as_argument_error(&self) -> Option<ArgumentError> {
240        match self {
241            Self::InvalidDimensions {
242                width,
243                height,
244                reason,
245            } => Some(ArgumentError::InvalidDimensions {
246                width: *width,
247                height: *height,
248                reason,
249            }),
250            Self::InvalidColorFormat { reason } => {
251                Some(ArgumentError::InvalidColorFormat { reason })
252            }
253            Self::InvalidBufferSize { expected, actual } => {
254                Some(ArgumentError::InvalidBufferSize {
255                    expected: *expected,
256                    actual: *actual,
257                })
258            }
259            Self::UnsupportedFeature { feature } => {
260                Some(ArgumentError::UnsupportedFeature { feature })
261            }
262            Self::UnsupportedPixelFormat { format } => {
263                Some(ArgumentError::UnsupportedPixelFormat { format: *format })
264            }
265            _ => None,
266        }
267    }
268
269    /// Convert to ResourceError if this is a resource error variant.
270    pub fn as_resource_error(&self) -> Option<ResourceError> {
271        match self {
272            Self::AllocationFailed { bytes, context } => Some(ResourceError::AllocationFailed {
273                bytes: *bytes,
274                context,
275            }),
276            Self::SizeOverflow { context } => Some(ResourceError::SizeOverflow { context }),
277            Self::ImageTooLarge { pixels, limit } => Some(ResourceError::ImageTooLarge {
278                pixels: *pixels,
279                limit: *limit,
280            }),
281            Self::IoError { reason } => Some(ResourceError::IoError {
282                reason: reason.clone(),
283            }),
284            _ => None,
285        }
286    }
287}
288
289impl fmt::Display for ErrorKind {
290    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291        match self {
292            // Argument errors
293            Self::InvalidDimensions {
294                width,
295                height,
296                reason,
297            } => write!(f, "invalid dimensions {}x{}: {}", width, height, reason),
298            Self::InvalidColorFormat { reason } => write!(f, "invalid color format: {}", reason),
299            Self::InvalidBufferSize { expected, actual } => {
300                write!(
301                    f,
302                    "invalid buffer size: expected {} bytes, got {}",
303                    expected, actual
304                )
305            }
306            Self::UnsupportedFeature { feature } => write!(f, "unsupported feature: {}", feature),
307            Self::UnsupportedPixelFormat { format } => {
308                write!(f, "pixel format {:?} not supported", format)
309            }
310
311            // Resource errors
312            Self::AllocationFailed { bytes, context } => {
313                write!(f, "allocation of {} bytes failed while {}", bytes, context)
314            }
315            Self::SizeOverflow { context } => {
316                write!(f, "size calculation overflow while {}", context)
317            }
318            Self::ImageTooLarge { pixels, limit } => {
319                write!(
320                    f,
321                    "image too large: {} pixels exceeds limit of {}",
322                    pixels, limit
323                )
324            }
325            Self::IoError { reason } => write!(f, "I/O error: {}", reason),
326
327            // Other shared
328            Self::IccError(reason) => write!(f, "ICC error: {}", reason),
329            Self::InternalError { reason } => write!(f, "internal error: {}", reason),
330            Self::Cancelled => write!(f, "operation cancelled"),
331
332            // Decoder-specific
333            Self::InvalidJpegData { reason } => write!(f, "invalid JPEG data: {}", reason),
334            Self::TruncatedData { context } => write!(f, "truncated data while {}", context),
335            Self::InvalidMarker { marker, context } => {
336                write!(f, "invalid marker 0x{:02X} while {}", marker, context)
337            }
338            Self::InvalidHuffmanTable { table_idx, reason } => {
339                write!(f, "invalid Huffman table {}: {}", table_idx, reason)
340            }
341            Self::InvalidQuantTable { table_idx, reason } => {
342                write!(f, "invalid quantization table {}: {}", table_idx, reason)
343            }
344            Self::TooManyScans { count, limit } => {
345                write!(f, "too many scans: {} exceeds limit of {}", count, limit)
346            }
347            Self::DecodeError(reason) => write!(f, "decode error: {}", reason),
348
349            // Encoder-specific
350            Self::InvalidQuality { value, valid_range } => {
351                write!(f, "invalid quality {}: must be in {}", value, valid_range)
352            }
353            Self::InvalidScanScript(reason) => write!(f, "invalid scan script: {}", reason),
354            Self::InvalidConfig(reason) => write!(f, "invalid encoder configuration: {}", reason),
355            Self::StrideTooSmall { width, stride } => {
356                write!(
357                    f,
358                    "stride {} is too small for width {} pixels",
359                    stride, width
360                )
361            }
362            Self::TooManyRows { height, pushed } => {
363                write!(
364                    f,
365                    "pushed {} rows but image height is only {}",
366                    pushed, height
367                )
368            }
369            Self::IncompleteImage { height, pushed } => {
370                write!(
371                    f,
372                    "encoding finished after {} rows but image height is {}",
373                    pushed, height
374                )
375            }
376        }
377    }
378}
379
380// ============================================================================
381// From implementations for ErrorKind
382// ============================================================================
383
384impl From<ArgumentError> for ErrorKind {
385    fn from(err: ArgumentError) -> Self {
386        match err {
387            ArgumentError::InvalidDimensions {
388                width,
389                height,
390                reason,
391            } => Self::InvalidDimensions {
392                width,
393                height,
394                reason,
395            },
396            ArgumentError::InvalidColorFormat { reason } => Self::InvalidColorFormat { reason },
397            ArgumentError::InvalidBufferSize { expected, actual } => {
398                Self::InvalidBufferSize { expected, actual }
399            }
400            ArgumentError::UnsupportedFeature { feature } => Self::UnsupportedFeature { feature },
401            ArgumentError::UnsupportedPixelFormat { format } => {
402                Self::UnsupportedPixelFormat { format }
403            }
404        }
405    }
406}
407
408impl From<ResourceError> for ErrorKind {
409    fn from(err: ResourceError) -> Self {
410        match err {
411            ResourceError::AllocationFailed { bytes, context } => {
412                Self::AllocationFailed { bytes, context }
413            }
414            ResourceError::SizeOverflow { context } => Self::SizeOverflow { context },
415            ResourceError::ImageTooLarge { pixels, limit } => Self::ImageTooLarge { pixels, limit },
416            ResourceError::IoError { reason } => Self::IoError { reason },
417        }
418    }
419}
420
421// ============================================================================
422// Error - Main error type with location tracking
423// ============================================================================
424
425/// Errors that can occur during JPEG encoding/decoding.
426///
427/// Use [`Error::kind()`] to match on the specific error variant.
428#[derive(Debug)]
429pub struct Error {
430    kind: ErrorKind,
431    trace: AtTraceBoxed,
432}
433
434impl Error {
435    /// Create a new error with the given kind, capturing the current location.
436    #[track_caller]
437    pub fn new(kind: ErrorKind) -> Self {
438        Self {
439            kind,
440            trace: AtTraceBoxed::capture(),
441        }
442    }
443
444    /// Create a new error without capturing a trace (for hot paths).
445    #[inline]
446    pub const fn new_untraced(kind: ErrorKind) -> Self {
447        Self {
448            kind,
449            trace: AtTraceBoxed::new(),
450        }
451    }
452
453    /// Get the kind of error.
454    #[inline]
455    pub fn kind(&self) -> &ErrorKind {
456        &self.kind
457    }
458
459    /// Convert into the error kind, discarding the trace.
460    #[inline]
461    pub fn into_kind(self) -> ErrorKind {
462        self.kind
463    }
464
465    // ========================================================================
466    // Convenience constructors - Argument errors
467    // ========================================================================
468
469    /// Create an invalid dimensions error.
470    #[track_caller]
471    pub fn invalid_dimensions(width: u32, height: u32, reason: &'static str) -> Self {
472        Self::new(ErrorKind::InvalidDimensions {
473            width,
474            height,
475            reason,
476        })
477    }
478
479    /// Create an invalid color format error.
480    #[track_caller]
481    pub fn invalid_color_format(reason: &'static str) -> Self {
482        Self::new(ErrorKind::InvalidColorFormat { reason })
483    }
484
485    /// Create an invalid buffer size error.
486    #[track_caller]
487    pub fn invalid_buffer_size(expected: usize, actual: usize) -> Self {
488        Self::new(ErrorKind::InvalidBufferSize { expected, actual })
489    }
490
491    /// Create an unsupported feature error.
492    #[track_caller]
493    pub fn unsupported_feature(feature: &'static str) -> Self {
494        Self::new(ErrorKind::UnsupportedFeature { feature })
495    }
496
497    /// Create an unsupported pixel format error.
498    #[track_caller]
499    pub fn unsupported_pixel_format(format: crate::types::PixelFormat) -> Self {
500        Self::new(ErrorKind::UnsupportedPixelFormat { format })
501    }
502
503    // ========================================================================
504    // Convenience constructors - Resource errors
505    // ========================================================================
506
507    /// Create an allocation failed error.
508    #[track_caller]
509    pub fn allocation_failed(bytes: usize, context: &'static str) -> Self {
510        Self::new(ErrorKind::AllocationFailed { bytes, context })
511    }
512
513    /// Create a size overflow error.
514    #[track_caller]
515    pub fn size_overflow(context: &'static str) -> Self {
516        Self::new(ErrorKind::SizeOverflow { context })
517    }
518
519    /// Create an image too large error.
520    #[track_caller]
521    pub fn image_too_large(pixels: u64, limit: u64) -> Self {
522        Self::new(ErrorKind::ImageTooLarge { pixels, limit })
523    }
524
525    /// Create an I/O error.
526    #[track_caller]
527    pub fn io_error(reason: String) -> Self {
528        Self::new(ErrorKind::IoError { reason })
529    }
530
531    // ========================================================================
532    // Convenience constructors - Other shared errors
533    // ========================================================================
534
535    /// Create an ICC error.
536    #[track_caller]
537    pub fn icc_error(reason: String) -> Self {
538        Self::new(ErrorKind::IccError(reason))
539    }
540
541    /// Create an internal error.
542    #[track_caller]
543    pub fn internal(reason: &'static str) -> Self {
544        Self::new(ErrorKind::InternalError { reason })
545    }
546
547    /// Create a cancelled error.
548    #[track_caller]
549    pub fn cancelled() -> Self {
550        Self::new(ErrorKind::Cancelled)
551    }
552
553    // ========================================================================
554    // Convenience constructors - Decoder-specific errors
555    // ========================================================================
556
557    /// Create an invalid JPEG data error.
558    #[track_caller]
559    pub fn invalid_jpeg_data(reason: &'static str) -> Self {
560        Self::new(ErrorKind::InvalidJpegData { reason })
561    }
562
563    /// Create a truncated data error.
564    #[track_caller]
565    pub fn truncated_data(context: &'static str) -> Self {
566        Self::new(ErrorKind::TruncatedData { context })
567    }
568
569    /// Create an invalid marker error.
570    #[track_caller]
571    pub fn invalid_marker(marker: u8, context: &'static str) -> Self {
572        Self::new(ErrorKind::InvalidMarker { marker, context })
573    }
574
575    /// Create an invalid Huffman table error.
576    #[track_caller]
577    pub fn invalid_huffman_table(table_idx: u8, reason: &'static str) -> Self {
578        Self::new(ErrorKind::InvalidHuffmanTable { table_idx, reason })
579    }
580
581    /// Create an invalid quantization table error.
582    #[track_caller]
583    pub fn invalid_quant_table(table_idx: u8, reason: &'static str) -> Self {
584        Self::new(ErrorKind::InvalidQuantTable { table_idx, reason })
585    }
586
587    /// Create a too many scans error.
588    #[track_caller]
589    pub fn too_many_scans(count: usize, limit: usize) -> Self {
590        Self::new(ErrorKind::TooManyScans { count, limit })
591    }
592
593    /// Create a decode error.
594    #[track_caller]
595    pub fn decode_error(reason: String) -> Self {
596        Self::new(ErrorKind::DecodeError(reason))
597    }
598
599    // ========================================================================
600    // Convenience constructors - Encoder-specific errors
601    // ========================================================================
602
603    /// Create an invalid quality error.
604    #[track_caller]
605    pub fn invalid_quality(value: f32, valid_range: &'static str) -> Self {
606        Self::new(ErrorKind::InvalidQuality { value, valid_range })
607    }
608
609    /// Create an invalid scan script error.
610    #[track_caller]
611    pub fn invalid_scan_script(reason: String) -> Self {
612        Self::new(ErrorKind::InvalidScanScript(reason))
613    }
614
615    /// Create an invalid config error.
616    #[track_caller]
617    pub fn invalid_config(reason: String) -> Self {
618        Self::new(ErrorKind::InvalidConfig(reason))
619    }
620
621    /// Create a stride too small error.
622    #[track_caller]
623    pub fn stride_too_small(width: u32, stride: usize) -> Self {
624        Self::new(ErrorKind::StrideTooSmall { width, stride })
625    }
626
627    /// Create a too many rows error.
628    #[track_caller]
629    pub fn too_many_rows(height: u32, pushed: u32) -> Self {
630        Self::new(ErrorKind::TooManyRows { height, pushed })
631    }
632
633    /// Create an incomplete image error.
634    #[track_caller]
635    pub fn incomplete_image(height: u32, pushed: u32) -> Self {
636        Self::new(ErrorKind::IncompleteImage { height, pushed })
637    }
638}
639
640impl AtTraceable for Error {
641    fn trace_mut(&mut self) -> &mut AtTrace {
642        self.trace.get_or_insert_mut()
643    }
644
645    fn trace(&self) -> Option<&AtTrace> {
646        self.trace.as_ref()
647    }
648
649    fn fmt_message(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
650        fmt::Display::fmt(&self.kind, f)
651    }
652}
653
654impl fmt::Display for Error {
655    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
656        fmt::Display::fmt(&self.kind, f)
657    }
658}
659
660// ============================================================================
661// From implementations for Error
662// ============================================================================
663
664impl From<ArgumentError> for Error {
665    #[track_caller]
666    fn from(err: ArgumentError) -> Self {
667        Self::new(err.into())
668    }
669}
670
671impl From<ResourceError> for Error {
672    #[track_caller]
673    fn from(err: ResourceError) -> Self {
674        Self::new(err.into())
675    }
676}
677
678impl From<enough::StopReason> for Error {
679    #[track_caller]
680    fn from(_: enough::StopReason) -> Self {
681        Self::cancelled()
682    }
683}
684
685#[cfg(feature = "std")]
686impl std::error::Error for Error {}
687
688#[cfg(feature = "std")]
689impl From<std::io::Error> for Error {
690    #[track_caller]
691    fn from(err: std::io::Error) -> Self {
692        Self::io_error(err.to_string())
693    }
694}
695
696#[cfg(feature = "ultrahdr")]
697impl From<ultrahdr_core::Error> for Error {
698    #[track_caller]
699    fn from(err: ultrahdr_core::Error) -> Self {
700        use ultrahdr_core::Error as UhdrError;
701        match err {
702            UhdrError::Stopped(reason) => Self::from(reason),
703            UhdrError::InvalidDimensions(w, h) => {
704                Self::invalid_dimensions(w, h, "invalid dimensions for UltraHDR")
705            }
706            UhdrError::DimensionMismatch { .. } => Self::decode_error(err.to_string()),
707            UhdrError::AllocationFailed(bytes) => {
708                Self::allocation_failed(bytes, "UltraHDR operation")
709            }
710            UhdrError::LimitExceeded(msg) => Self::decode_error(msg),
711            _ => Self::decode_error(err.to_string()),
712        }
713    }
714}
715
716impl From<crate::foundation::aligned_alloc::AllocError> for Error {
717    #[track_caller]
718    fn from(err: crate::foundation::aligned_alloc::AllocError) -> Self {
719        match err {
720            crate::foundation::aligned_alloc::AllocError::OutOfMemory => {
721                Self::allocation_failed(0, "adaptive quantization")
722            }
723            crate::foundation::aligned_alloc::AllocError::Overflow => {
724                Self::size_overflow("adaptive quantization size calculation")
725            }
726        }
727    }
728}
729
730// ============================================================================
731// Clone and PartialEq for Error
732// ============================================================================
733
734impl Clone for Error {
735    fn clone(&self) -> Self {
736        Self {
737            kind: self.kind.clone(),
738            trace: AtTraceBoxed::new(), // Don't clone the trace
739        }
740    }
741}
742
743impl PartialEq for Error {
744    fn eq(&self, other: &Self) -> bool {
745        self.kind == other.kind
746    }
747}
748
749// ============================================================================
750// Tests
751// ============================================================================
752
753#[cfg(test)]
754mod tests {
755    use super::*;
756    use whereat::ResultAtTraceableExt;
757
758    #[test]
759    fn test_error_size() {
760        let size = core::mem::size_of::<Error>();
761        println!("\n=== ERROR SIZES ===");
762        println!("Error: {} bytes", size);
763        println!("ErrorKind: {} bytes", core::mem::size_of::<ErrorKind>());
764        println!(
765            "ArgumentError: {} bytes",
766            core::mem::size_of::<ArgumentError>()
767        );
768        println!(
769            "ResourceError: {} bytes",
770            core::mem::size_of::<ResourceError>()
771        );
772        assert!(size <= 48, "Error is {} bytes, consider optimizing", size);
773    }
774
775    #[test]
776    fn test_argument_error_display() {
777        let err = ArgumentError::InvalidDimensions {
778            width: 0,
779            height: 100,
780            reason: "width cannot be zero",
781        };
782        assert!(err.to_string().contains("width cannot be zero"));
783    }
784
785    #[test]
786    fn test_resource_error_display() {
787        let err = ResourceError::AllocationFailed {
788            bytes: 1024,
789            context: "allocating buffer",
790        };
791        assert!(err.to_string().contains("1024 bytes"));
792    }
793
794    #[test]
795    fn test_error_from_argument_error() {
796        let arg_err = ArgumentError::InvalidDimensions {
797            width: 0,
798            height: 100,
799            reason: "width cannot be zero",
800        };
801        let err: Error = arg_err.into();
802        assert!(matches!(err.kind(), ErrorKind::InvalidDimensions { .. }));
803    }
804
805    #[test]
806    fn test_error_has_trace() {
807        let err = Error::invalid_dimensions(0, 100, "width cannot be zero");
808        assert!(!err.trace.is_empty());
809    }
810
811    #[test]
812    fn test_error_trace_propagation() {
813        fn inner() -> Result<()> {
814            Err(Error::invalid_dimensions(0, 100, "width cannot be zero"))
815        }
816
817        fn outer() -> Result<()> {
818            inner().at()?;
819            Ok(())
820        }
821
822        let err = outer().unwrap_err();
823        assert!(
824            err.trace.frame_count() >= 1,
825            "trace should have at least 1 entry"
826        );
827    }
828
829    #[cfg(feature = "std")]
830    #[test]
831    fn test_io_error_conversion() {
832        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
833        let err: Error = io_err.into();
834        assert!(matches!(err.kind(), ErrorKind::IoError { .. }));
835    }
836}