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}