use ff_format::{ErrorSeverity, MediaError};
#[derive(Debug, thiserror::Error)]
pub enum PipelineError {
#[error("decode failed: {0}")]
Decode(#[from] ff_decode::DecodeError),
#[error("filter failed: {0}")]
Filter(#[from] ff_filter::FilterError),
#[error("encode failed: {0}")]
Encode(#[from] ff_encode::EncodeError),
#[error("no input specified")]
NoInput,
#[error("no output specified")]
NoOutput,
#[error("secondary input provided without a filter graph")]
SecondaryInputWithoutFilter,
#[error("pipeline cancelled by caller")]
Cancelled,
#[error("i/o error: {0}")]
Io(#[from] std::io::Error),
#[error("no frame available at the requested position")]
FrameNotAvailable,
}
impl MediaError for PipelineError {
fn severity(&self) -> ErrorSeverity {
match self {
Self::Decode(e) => e.severity(),
Self::Filter(e) => e.severity(),
Self::Encode(e) => e.severity(),
Self::Cancelled | Self::FrameNotAvailable => ErrorSeverity::Other,
Self::NoInput | Self::NoOutput | Self::SecondaryInputWithoutFilter | Self::Io(_) => {
ErrorSeverity::Fatal
}
}
}
}
#[cfg(test)]
mod tests {
use std::error::Error;
use ff_format::MediaError;
use super::PipelineError;
#[test]
fn no_input_should_display_correct_message() {
let err = PipelineError::NoInput;
assert_eq!(err.to_string(), "no input specified");
}
#[test]
fn no_output_should_display_correct_message() {
let err = PipelineError::NoOutput;
assert_eq!(err.to_string(), "no output specified");
}
#[test]
fn cancelled_should_display_correct_message() {
let err = PipelineError::Cancelled;
assert_eq!(err.to_string(), "pipeline cancelled by caller");
}
#[test]
fn decode_should_prefix_inner_message() {
let err = PipelineError::Decode(ff_decode::DecodeError::decoding_failed("test error"));
assert!(err.to_string().starts_with("decode failed:"));
}
#[test]
fn filter_should_prefix_inner_message() {
let err = PipelineError::Filter(ff_filter::FilterError::BuildFailed);
assert_eq!(
err.to_string(),
"filter failed: failed to build filter graph"
);
}
#[test]
fn encode_should_prefix_inner_message() {
let err = PipelineError::Encode(ff_encode::EncodeError::Cancelled);
assert_eq!(err.to_string(), "encode failed: Encoding cancelled by user");
}
#[test]
fn decode_error_should_convert_into_pipeline_error() {
let inner = ff_decode::DecodeError::decoding_failed("test error");
let err: PipelineError = inner.into();
assert!(matches!(err, PipelineError::Decode(_)));
}
#[test]
fn filter_error_should_convert_into_pipeline_error() {
let inner = ff_filter::FilterError::BuildFailed;
let err: PipelineError = inner.into();
assert!(matches!(err, PipelineError::Filter(_)));
}
#[test]
fn encode_error_should_convert_into_pipeline_error() {
let inner = ff_encode::EncodeError::Cancelled;
let err: PipelineError = inner.into();
assert!(matches!(err, PipelineError::Encode(_)));
}
#[test]
fn decode_should_expose_source() {
let err = PipelineError::Decode(ff_decode::DecodeError::decoding_failed("test error"));
assert!(err.source().is_some());
}
#[test]
fn filter_should_expose_source() {
let err = PipelineError::Filter(ff_filter::FilterError::BuildFailed);
assert!(err.source().is_some());
}
#[test]
fn encode_should_expose_source() {
let err = PipelineError::Encode(ff_encode::EncodeError::Cancelled);
assert!(err.source().is_some());
}
#[test]
fn unit_variants_should_have_no_source() {
assert!(PipelineError::NoInput.source().is_none());
assert!(PipelineError::NoOutput.source().is_none());
assert!(PipelineError::Cancelled.source().is_none());
}
#[test]
fn io_error_should_convert_into_pipeline_error() {
let inner = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let err: PipelineError = inner.into();
assert!(matches!(err, PipelineError::Io(_)));
}
#[test]
fn io_error_should_display_correct_message() {
let inner = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
let err: PipelineError = inner.into();
assert_eq!(err.to_string(), "i/o error: access denied");
}
#[test]
fn io_error_should_expose_source() {
let inner = std::io::Error::new(std::io::ErrorKind::Other, "some error");
let err: PipelineError = inner.into();
assert!(err.source().is_some());
}
#[test]
fn pipeline_io_should_be_fatal() {
let e: PipelineError = std::io::Error::other("x").into();
assert!(e.is_fatal() && !e.is_recoverable());
}
#[test]
fn pipeline_cancelled_should_be_other() {
let e = PipelineError::Cancelled;
assert!(!e.is_fatal() && !e.is_recoverable());
}
#[test]
fn pipeline_decode_should_delegate_recoverable() {
let e = PipelineError::Decode(ff_decode::DecodeError::decoding_failed("x"));
assert!(e.is_recoverable() && !e.is_fatal());
}
}