#[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,
#[error("timeline render failed: {reason}")]
TimelineRenderFailed {
reason: String,
},
#[error("clip source not found: path={path}")]
ClipNotFound {
path: String,
},
}
#[cfg(test)]
mod tests {
use std::error::Error;
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_error_timeline_render_failed_should_display_correctly() {
let err = PipelineError::TimelineRenderFailed {
reason: "not implemented".to_string(),
};
assert_eq!(err.to_string(), "timeline render failed: not implemented");
}
#[test]
fn pipeline_error_clip_not_found_should_include_path_in_message() {
let err = PipelineError::ClipNotFound {
path: "/tmp/missing.mp4".to_string(),
};
assert_eq!(
err.to_string(),
"clip source not found: path=/tmp/missing.mp4"
);
}
}