1use thiserror::Error;
4
5#[derive(Error, Debug)]
7pub enum Error {
8 #[error("extraction error: {0}")]
10 Extraction(#[from] ExtractError),
11
12 #[error("chunking error: {0}")]
14 Chunking(#[from] ChunkError),
15
16 #[error("embedding error: {0}")]
18 Embedding(#[from] EmbedError),
19
20 #[error("store error: {0}")]
22 Store(#[from] StoreError),
23
24 #[error("io error: {0}")]
26 Io(#[from] std::io::Error),
27
28 #[error("serialization error: {0}")]
30 Serialization(#[from] serde_json::Error),
31
32 #[error("config error: {0}")]
34 Config(String),
35
36 #[error("{0}")]
38 Other(String),
39}
40
41#[derive(Error, Debug)]
43pub enum ExtractError {
44 #[error("unsupported file type: {0}")]
45 UnsupportedType(String),
46
47 #[error("parse error: {0}")]
48 Parse(String),
49
50 #[error("io error: {0}")]
51 Io(#[from] std::io::Error),
52
53 #[error("extraction failed: {0}")]
54 Failed(String),
55}
56
57#[derive(Error, Debug)]
59pub enum ChunkError {
60 #[error("chunking failed: {0}")]
61 Failed(String),
62
63 #[error("invalid configuration: {0}")]
64 InvalidConfig(String),
65}
66
67#[derive(Error, Debug)]
69pub enum EmbedError {
70 #[error("model loading failed: {0}")]
71 ModelLoad(String),
72
73 #[error("inference failed: {0}")]
74 Inference(String),
75
76 #[error("modality not supported: {0:?}")]
77 ModalityNotSupported(crate::types::Modality),
78
79 #[error("input too long: {tokens} tokens, max {max}")]
80 InputTooLong { tokens: usize, max: usize },
81}
82
83#[derive(Error, Debug)]
85pub enum StoreError {
86 #[error("store initialization failed: {0}")]
87 Init(String),
88
89 #[error("insert failed: {0}")]
90 Insert(String),
91
92 #[error("query failed: {0}")]
93 Query(String),
94
95 #[error("delete failed: {0}")]
96 Delete(String),
97
98 #[error("schema error: {0}")]
99 Schema(String),
100}
101
102pub type Result<T> = std::result::Result<T, Error>;
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::types::Modality;
109
110 #[test]
113 fn test_extract_error_unsupported_type_display() {
114 let err = ExtractError::UnsupportedType("application/octet-stream".to_string());
115 assert_eq!(
116 err.to_string(),
117 "unsupported file type: application/octet-stream"
118 );
119 }
120
121 #[test]
122 fn test_extract_error_parse_display() {
123 let err = ExtractError::Parse("invalid UTF-8".to_string());
124 assert_eq!(err.to_string(), "parse error: invalid UTF-8");
125 }
126
127 #[test]
128 fn test_extract_error_io_display() {
129 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
130 let err = ExtractError::Io(io_err);
131 assert!(err.to_string().contains("file not found"));
132 }
133
134 #[test]
135 fn test_extract_error_failed_display() {
136 let err = ExtractError::Failed("PDF parsing crashed".to_string());
137 assert_eq!(err.to_string(), "extraction failed: PDF parsing crashed");
138 }
139
140 #[test]
141 fn test_extract_error_from_io() {
142 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
143 let err: ExtractError = io_err.into();
144 assert!(matches!(err, ExtractError::Io(_)));
145 }
146
147 #[test]
150 fn test_chunk_error_failed_display() {
151 let err = ChunkError::Failed("empty content".to_string());
152 assert_eq!(err.to_string(), "chunking failed: empty content");
153 }
154
155 #[test]
156 fn test_chunk_error_invalid_config_display() {
157 let err = ChunkError::InvalidConfig("target_size must be > 0".to_string());
158 assert_eq!(
159 err.to_string(),
160 "invalid configuration: target_size must be > 0"
161 );
162 }
163
164 #[test]
167 fn test_embed_error_model_load_display() {
168 let err = EmbedError::ModelLoad("weights file not found".to_string());
169 assert_eq!(
170 err.to_string(),
171 "model loading failed: weights file not found"
172 );
173 }
174
175 #[test]
176 fn test_embed_error_inference_display() {
177 let err = EmbedError::Inference("CUDA out of memory".to_string());
178 assert_eq!(err.to_string(), "inference failed: CUDA out of memory");
179 }
180
181 #[test]
182 fn test_embed_error_modality_not_supported_display() {
183 let err = EmbedError::ModalityNotSupported(Modality::Audio);
184 assert_eq!(err.to_string(), "modality not supported: Audio");
185 }
186
187 #[test]
188 fn test_embed_error_input_too_long_display() {
189 let err = EmbedError::InputTooLong {
190 tokens: 10000,
191 max: 8192,
192 };
193 assert_eq!(err.to_string(), "input too long: 10000 tokens, max 8192");
194 }
195
196 #[test]
199 fn test_store_error_init_display() {
200 let err = StoreError::Init("database locked".to_string());
201 assert_eq!(
202 err.to_string(),
203 "store initialization failed: database locked"
204 );
205 }
206
207 #[test]
208 fn test_store_error_insert_display() {
209 let err = StoreError::Insert("duplicate key".to_string());
210 assert_eq!(err.to_string(), "insert failed: duplicate key");
211 }
212
213 #[test]
214 fn test_store_error_query_display() {
215 let err = StoreError::Query("invalid vector dimension".to_string());
216 assert_eq!(err.to_string(), "query failed: invalid vector dimension");
217 }
218
219 #[test]
220 fn test_store_error_delete_display() {
221 let err = StoreError::Delete("file not indexed".to_string());
222 assert_eq!(err.to_string(), "delete failed: file not indexed");
223 }
224
225 #[test]
226 fn test_store_error_schema_display() {
227 let err = StoreError::Schema("missing embedding column".to_string());
228 assert_eq!(err.to_string(), "schema error: missing embedding column");
229 }
230
231 #[test]
234 fn test_error_from_extract_error() {
235 let extract_err = ExtractError::UnsupportedType("video/mp4".to_string());
236 let err: Error = extract_err.into();
237 assert!(matches!(err, Error::Extraction(_)));
238 assert!(err.to_string().contains("video/mp4"));
239 }
240
241 #[test]
242 fn test_error_from_chunk_error() {
243 let chunk_err = ChunkError::Failed("too short".to_string());
244 let err: Error = chunk_err.into();
245 assert!(matches!(err, Error::Chunking(_)));
246 assert!(err.to_string().contains("too short"));
247 }
248
249 #[test]
250 fn test_error_from_embed_error() {
251 let embed_err = EmbedError::ModelLoad("missing model".to_string());
252 let err: Error = embed_err.into();
253 assert!(matches!(err, Error::Embedding(_)));
254 assert!(err.to_string().contains("missing model"));
255 }
256
257 #[test]
258 fn test_error_from_store_error() {
259 let store_err = StoreError::Query("timeout".to_string());
260 let err: Error = store_err.into();
261 assert!(matches!(err, Error::Store(_)));
262 assert!(err.to_string().contains("timeout"));
263 }
264
265 #[test]
266 fn test_error_from_io_error() {
267 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "missing");
268 let err: Error = io_err.into();
269 assert!(matches!(err, Error::Io(_)));
270 }
271
272 #[test]
273 fn test_error_config_display() {
274 let err = Error::Config("invalid path".to_string());
275 assert_eq!(err.to_string(), "config error: invalid path");
276 }
277
278 #[test]
279 fn test_error_other_display() {
280 let err = Error::Other("unexpected condition".to_string());
281 assert_eq!(err.to_string(), "unexpected condition");
282 }
283
284 #[test]
287 fn test_extract_error_debug() {
288 let err = ExtractError::Parse("bad format".to_string());
289 let debug_str = format!("{err:?}");
290 assert!(debug_str.contains("Parse"));
291 assert!(debug_str.contains("bad format"));
292 }
293
294 #[test]
295 fn test_chunk_error_debug() {
296 let err = ChunkError::InvalidConfig("negative size".to_string());
297 let debug_str = format!("{err:?}");
298 assert!(debug_str.contains("InvalidConfig"));
299 }
300
301 #[test]
302 fn test_embed_error_debug() {
303 let err = EmbedError::InputTooLong {
304 tokens: 5000,
305 max: 4096,
306 };
307 let debug_str = format!("{err:?}");
308 assert!(debug_str.contains("InputTooLong"));
309 assert!(debug_str.contains("5000"));
310 }
311
312 #[test]
313 fn test_store_error_debug() {
314 let err = StoreError::Schema("wrong type".to_string());
315 let debug_str = format!("{err:?}");
316 assert!(debug_str.contains("Schema"));
317 }
318
319 #[test]
320 fn test_main_error_debug() {
321 let err = Error::Config("missing key".to_string());
322 let debug_str = format!("{err:?}");
323 assert!(debug_str.contains("Config"));
324 }
325
326 #[test]
329 fn test_error_chain_io_to_extract_to_main() {
330 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file.txt not found");
331 let extract_err: ExtractError = io_err.into();
332 let main_err: Error = extract_err.into();
333
334 assert!(matches!(main_err, Error::Extraction(ExtractError::Io(_))));
335 assert!(main_err.to_string().contains("extraction error"));
336 }
337
338 #[test]
339 fn test_result_type_alias() {
340 fn example_function() -> Result<i32> {
341 Ok(42)
342 }
343
344 fn failing_function() -> Result<i32> {
345 Err(Error::Other("test failure".to_string()))
346 }
347
348 assert!(example_function().is_ok());
349 assert!(failing_function().is_err());
350 }
351
352 #[test]
355 fn test_embed_error_all_modalities() {
356 let text_err = EmbedError::ModalityNotSupported(Modality::Text);
357 assert!(text_err.to_string().contains("Text"));
358
359 let image_err = EmbedError::ModalityNotSupported(Modality::Image);
360 assert!(image_err.to_string().contains("Image"));
361
362 let audio_err = EmbedError::ModalityNotSupported(Modality::Audio);
363 assert!(audio_err.to_string().contains("Audio"));
364 }
365}