1#[derive(Debug, thiserror::Error)]
19pub enum PipelineError {
20 #[error("decode failed: {0}")]
25 Decode(#[from] ff_decode::DecodeError),
26
27 #[error("filter failed: {0}")]
32 Filter(#[from] ff_filter::FilterError),
33
34 #[error("encode failed: {0}")]
39 Encode(#[from] ff_encode::EncodeError),
40
41 #[error("no input specified")]
46 NoInput,
47
48 #[error("no output specified")]
53 NoOutput,
54
55 #[error("secondary input provided without a filter graph")]
60 SecondaryInputWithoutFilter,
61
62 #[error("pipeline cancelled by caller")]
67 Cancelled,
68
69 #[error("i/o error: {0}")]
71 Io(#[from] std::io::Error),
72
73 #[error("no frame available at the requested position")]
79 FrameNotAvailable,
80
81 #[error("timeline render failed: {reason}")]
84 TimelineRenderFailed {
85 reason: String,
87 },
88
89 #[error("clip source not found: path={path}")]
94 ClipNotFound {
95 path: String,
97 },
98}
99
100#[cfg(test)]
101mod tests {
102 use std::error::Error;
103
104 use super::PipelineError;
105
106 #[test]
109 fn no_input_should_display_correct_message() {
110 let err = PipelineError::NoInput;
111 assert_eq!(err.to_string(), "no input specified");
112 }
113
114 #[test]
115 fn no_output_should_display_correct_message() {
116 let err = PipelineError::NoOutput;
117 assert_eq!(err.to_string(), "no output specified");
118 }
119
120 #[test]
121 fn cancelled_should_display_correct_message() {
122 let err = PipelineError::Cancelled;
123 assert_eq!(err.to_string(), "pipeline cancelled by caller");
124 }
125
126 #[test]
129 fn decode_should_prefix_inner_message() {
130 let err = PipelineError::Decode(ff_decode::DecodeError::decoding_failed("test error"));
131 assert!(err.to_string().starts_with("decode failed:"));
132 }
133
134 #[test]
135 fn filter_should_prefix_inner_message() {
136 let err = PipelineError::Filter(ff_filter::FilterError::BuildFailed);
137 assert_eq!(
138 err.to_string(),
139 "filter failed: failed to build filter graph"
140 );
141 }
142
143 #[test]
144 fn encode_should_prefix_inner_message() {
145 let err = PipelineError::Encode(ff_encode::EncodeError::Cancelled);
146 assert_eq!(err.to_string(), "encode failed: Encoding cancelled by user");
147 }
148
149 #[test]
152 fn decode_error_should_convert_into_pipeline_error() {
153 let inner = ff_decode::DecodeError::decoding_failed("test error");
154 let err: PipelineError = inner.into();
155 assert!(matches!(err, PipelineError::Decode(_)));
156 }
157
158 #[test]
159 fn filter_error_should_convert_into_pipeline_error() {
160 let inner = ff_filter::FilterError::BuildFailed;
161 let err: PipelineError = inner.into();
162 assert!(matches!(err, PipelineError::Filter(_)));
163 }
164
165 #[test]
166 fn encode_error_should_convert_into_pipeline_error() {
167 let inner = ff_encode::EncodeError::Cancelled;
168 let err: PipelineError = inner.into();
169 assert!(matches!(err, PipelineError::Encode(_)));
170 }
171
172 #[test]
175 fn decode_should_expose_source() {
176 let err = PipelineError::Decode(ff_decode::DecodeError::decoding_failed("test error"));
177 assert!(err.source().is_some());
178 }
179
180 #[test]
181 fn filter_should_expose_source() {
182 let err = PipelineError::Filter(ff_filter::FilterError::BuildFailed);
183 assert!(err.source().is_some());
184 }
185
186 #[test]
187 fn encode_should_expose_source() {
188 let err = PipelineError::Encode(ff_encode::EncodeError::Cancelled);
189 assert!(err.source().is_some());
190 }
191
192 #[test]
193 fn unit_variants_should_have_no_source() {
194 assert!(PipelineError::NoInput.source().is_none());
195 assert!(PipelineError::NoOutput.source().is_none());
196 assert!(PipelineError::Cancelled.source().is_none());
197 }
198
199 #[test]
202 fn io_error_should_convert_into_pipeline_error() {
203 let inner = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
204 let err: PipelineError = inner.into();
205 assert!(matches!(err, PipelineError::Io(_)));
206 }
207
208 #[test]
209 fn io_error_should_display_correct_message() {
210 let inner = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
211 let err: PipelineError = inner.into();
212 assert_eq!(err.to_string(), "i/o error: access denied");
213 }
214
215 #[test]
216 fn io_error_should_expose_source() {
217 let inner = std::io::Error::new(std::io::ErrorKind::Other, "some error");
218 let err: PipelineError = inner.into();
219 assert!(err.source().is_some());
220 }
221
222 #[test]
223 fn pipeline_error_timeline_render_failed_should_display_correctly() {
224 let err = PipelineError::TimelineRenderFailed {
225 reason: "not implemented".to_string(),
226 };
227 assert_eq!(err.to_string(), "timeline render failed: not implemented");
228 }
229
230 #[test]
231 fn pipeline_error_clip_not_found_should_include_path_in_message() {
232 let err = PipelineError::ClipNotFound {
233 path: "/tmp/missing.mp4".to_string(),
234 };
235 assert_eq!(
236 err.to_string(),
237 "clip source not found: path=/tmp/missing.mp4"
238 );
239 }
240}