Skip to main content

ff_format/
media_error.rs

1//! Shared error classification for the `ff-*` crate family.
2//!
3//! Every `ff-*` error type implements [`MediaError`], which classifies an error
4//! by [`ErrorSeverity`] so a caller can branch on recoverability generically,
5//! without matching each crate's variants. This lives in `ff-format` (the lowest
6//! shared, `FFmpeg`-free type crate) and adds no `FFmpeg` dependency.
7
8/// Severity class of a media error: whether the failing operation can be retried
9/// without rebuilding the component that raised it.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[non_exhaustive]
12pub enum ErrorSeverity {
13    /// The component cannot continue; it must be discarded or reconfigured
14    /// (e.g. a missing file, an unsupported codec, an I/O failure).
15    Fatal,
16    /// Transient: the failing operation can be retried, or the stream reconnected,
17    /// without rebuilding (e.g. a corrupt frame, a network timeout).
18    Recoverable,
19    /// Neither strictly fatal nor retryable: a one-off condition the caller can
20    /// handle in context (e.g. a raw `FFmpeg` error, no frame at a timestamp).
21    Other,
22}
23
24/// Shared classification for the `ff-*` crate error types.
25///
26/// Implement [`severity`](MediaError::severity) on each error type; the boolean
27/// helpers are derived from it. This lets downstream code branch on recoverability
28/// generically:
29///
30/// ```
31/// use ff_format::{FormatError, MediaError, ErrorSeverity};
32///
33/// let err = FormatError::invalid_pixel_format("nope");
34/// assert_eq!(err.severity(), ErrorSeverity::Other);
35/// assert!(!err.is_recoverable());
36/// assert!(!err.is_fatal());
37/// ```
38pub trait MediaError {
39    /// Classifies this error.
40    fn severity(&self) -> ErrorSeverity;
41
42    /// Returns `true` if the failing operation can be retried without rebuilding.
43    fn is_recoverable(&self) -> bool {
44        matches!(self.severity(), ErrorSeverity::Recoverable)
45    }
46
47    /// Returns `true` if the component must be discarded or reconfigured.
48    fn is_fatal(&self) -> bool {
49        matches!(self.severity(), ErrorSeverity::Fatal)
50    }
51}
52
53impl MediaError for crate::FormatError {
54    fn severity(&self) -> ErrorSeverity {
55        // Format errors are validation / conversion failures on caller-provided
56        // data: not retryable, but a one-off input problem rather than a component
57        // that must be torn down.
58        ErrorSeverity::Other
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn severity_should_drive_is_recoverable_and_is_fatal() {
68        struct Fatal;
69        struct Recoverable;
70        struct Other;
71        impl MediaError for Fatal {
72            fn severity(&self) -> ErrorSeverity {
73                ErrorSeverity::Fatal
74            }
75        }
76        impl MediaError for Recoverable {
77            fn severity(&self) -> ErrorSeverity {
78                ErrorSeverity::Recoverable
79            }
80        }
81        impl MediaError for Other {
82            fn severity(&self) -> ErrorSeverity {
83                ErrorSeverity::Other
84            }
85        }
86
87        assert!(Fatal.is_fatal() && !Fatal.is_recoverable());
88        assert!(Recoverable.is_recoverable() && !Recoverable.is_fatal());
89        assert!(!Other.is_fatal() && !Other.is_recoverable());
90    }
91
92    #[test]
93    fn format_error_severity_should_be_other() {
94        let err = crate::FormatError::invalid_pixel_format("x");
95        assert_eq!(err.severity(), ErrorSeverity::Other);
96    }
97}