1use super::{Document, DocumentLoader, LoaderError};
7use async_trait::async_trait;
8use csv::Reader;
9use std::fs::File;
10use std::io::BufReader;
11use std::path::PathBuf;
12
13pub struct CSVLoader {
15 pub path: PathBuf,
17
18 pub content_column: String,
20}
21
22impl CSVLoader {
23 pub fn new(path: impl Into<PathBuf>, content_column: impl Into<String>) -> Self {
29 Self {
30 path: path.into(),
31 content_column: content_column.into(),
32 }
33 }
34}
35
36#[async_trait]
37impl DocumentLoader for CSVLoader {
38 async fn load(&self) -> Result<Vec<Document>, LoaderError> {
39 if !self.path.exists() {
41 return Err(LoaderError::Other(format!(
42 "CSV file does not exist: {}",
43 self.path.display()
44 )));
45 }
46
47 let file = File::open(&self.path)?;
49 let buf_reader = BufReader::new(file);
50 let mut reader = Reader::from_reader(buf_reader);
51
52 let mut documents = Vec::new();
53
54 let headers_result = reader.headers();
56 let headers = match headers_result {
57 Ok(headers) => headers.clone(),
58 Err(e) => return Err(LoaderError::CsvError(e.to_string())),
59 };
60
61 for result in reader.records() {
62 let record = match result {
63 Ok(record) => record,
64 Err(e) => return Err(LoaderError::CsvError(e.to_string())),
65 };
66
67 let content_idx = headers
69 .iter()
70 .position(|h| h == self.content_column.as_str());
71
72 if let Some(idx) = content_idx {
73 let content = record.get(idx).unwrap_or_default().to_string();
75
76 if content.is_empty() {
78 continue;
79 }
80
81 let mut document = Document::new(content);
83
84 for (i, header) in headers.iter().enumerate() {
86 let value = record.get(i).unwrap_or_default().to_string();
87 document = document.with_metadata(header.to_string(), value);
88 }
89
90 document =
92 document.with_metadata("source".to_string(), self.path.display().to_string());
93 document = document.with_metadata("format".to_string(), "csv".to_string());
94 document = document
95 .with_metadata("content_column".to_string(), self.content_column.clone());
96
97 documents.push(document);
98 } else {
99 return Err(LoaderError::CsvError(format!(
101 "content column '{}' does not exist in CSV file",
102 self.content_column
103 )));
104 }
105 }
106
107 Ok(documents)
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use std::io::Write;
115 use tempfile::NamedTempFile;
116
117 #[tokio::test]
118 async fn test_csv_loader_nonexistent() {
119 let loader = CSVLoader::new("./nonexistent.csv", "content");
120 let result = loader.load().await;
121
122 assert!(result.is_err());
123 }
124
125 #[tokio::test]
126 async fn test_csv_loader_content_column_not_found() {
127 let mut temp_file = NamedTempFile::new().unwrap();
128 writeln!(temp_file, "col1,col2").unwrap();
129 writeln!(temp_file, "val1,val2").unwrap();
130
131 let loader = CSVLoader::new(temp_file.path(), "content");
132 let result = loader.load().await;
133
134 assert!(result.is_err());
135 match result.unwrap_err() {
136 LoaderError::CsvError(msg) => assert!(msg.contains("does not exist")),
137 _ => panic!("Expected CsvError"),
138 }
139 }
140
141 #[tokio::test]
142 async fn test_csv_loader_valid_data() {
143 let mut temp_file = NamedTempFile::new().unwrap();
144 writeln!(temp_file, "title,content,author").unwrap();
145 writeln!(temp_file, "Example Title,\"This is the content\",John Doe").unwrap();
146 writeln!(temp_file, "Another Title,\"More content\",Jane Smith").unwrap();
147
148 let loader = CSVLoader::new(temp_file.path(), "content");
149 let result = loader.load().await;
150
151 assert!(result.is_ok());
152 let docs = result.unwrap();
153 assert_eq!(docs.len(), 2);
154
155 if !docs.is_empty() {
157 let doc = &docs[0];
158 assert!(doc.content.contains("This is the content"));
159 assert_eq!(
160 doc.metadata.get("title"),
161 Some(&serde_json::Value::String("Example Title".to_string()))
162 );
163 assert_eq!(
164 doc.metadata.get("author"),
165 Some(&serde_json::Value::String("John Doe".to_string()))
166 );
167 assert_eq!(
168 doc.metadata.get("content"),
169 Some(&serde_json::Value::String(
170 "This is the content".to_string()
171 ))
172 );
173 assert_eq!(
174 doc.metadata.get("format"),
175 Some(&serde_json::Value::String("csv".to_string()))
176 );
177 assert_eq!(
178 doc.metadata.get("content_column"),
179 Some(&serde_json::Value::String("content".to_string()))
180 );
181 }
182 }
183}