1use crate::constants::buffer;
2use anyhow::{Context, Result};
3use serde_json::Value;
4use std::fs::File;
5use std::io::{BufRead, BufReader, Read, Write};
6use std::path::Path;
7
8pub fn read_jsonl<P: AsRef<Path>>(path: P) -> Result<Vec<Value>> {
21 let file = File::open(path.as_ref())
22 .with_context(|| format!("Failed to open file: {}", path.as_ref().display()))?;
23
24 let file_size = file.metadata().ok().map(|m| m.len() as usize).unwrap_or(0);
27 let estimated_lines = if file_size > 0 {
28 file_size / buffer::AVG_JSONL_LINE_SIZE
30 } else {
31 10 };
33 let mut results = Vec::with_capacity(estimated_lines);
34
35 let reader = BufReader::with_capacity(buffer::FILE_READ_BUFFER, file);
37
38 for (index, line) in reader.lines().enumerate() {
39 let line = line.with_context(|| format!("Failed to read line {}", index + 1))?;
40
41 if line.trim().is_empty() {
42 continue;
43 }
44
45 let obj: Value = serde_json::from_str(&line)
46 .with_context(|| format!("Failed to parse JSON at line {}", index + 1))?;
47
48 results.push(obj);
49 }
50
51 results.shrink_to_fit();
53
54 Ok(results)
55}
56
57pub fn write_json_atomic<T, P>(path: P, value: &T) -> Result<()>
69where
70 T: serde::Serialize,
71 P: AsRef<Path>,
72{
73 write_json_atomic_inner(path.as_ref(), value, false)
74}
75
76pub fn write_json_atomic_pretty<T, P>(path: P, value: &T) -> Result<()>
87where
88 T: serde::Serialize,
89 P: AsRef<Path>,
90{
91 write_json_atomic_inner(path.as_ref(), value, true)
92}
93
94fn write_json_atomic_inner<T>(path: &Path, value: &T, pretty: bool) -> Result<()>
96where
97 T: serde::Serialize,
98{
99 persist_atomic(path, |tmp| {
100 if pretty {
101 serde_json::to_writer_pretty(tmp, value).context("Failed to serialize JSON")
102 } else {
103 serde_json::to_writer(tmp, value).context("Failed to serialize JSON")
104 }
105 })
106}
107
108pub fn write_string_atomic<P: AsRef<Path>>(path: P, contents: &str) -> Result<()> {
119 persist_atomic(path.as_ref(), |tmp| {
120 tmp.write_all(contents.as_bytes())
121 .context("Failed to write file contents")
122 })
123}
124
125fn persist_atomic(
128 path: &Path,
129 write: impl FnOnce(&mut tempfile::NamedTempFile) -> Result<()>,
130) -> Result<()> {
131 let dir = path.parent().unwrap_or_else(|| Path::new("."));
132 std::fs::create_dir_all(dir)
133 .with_context(|| format!("Failed to create directory: {}", dir.display()))?;
134 let mut tmp = tempfile::NamedTempFile::new_in(dir)
135 .with_context(|| format!("Failed to create temp file in: {}", dir.display()))?;
136 write(&mut tmp)?;
137 tmp.as_file().sync_all().ok();
138 tmp.persist(path)
139 .with_context(|| format!("Failed to persist file: {}", path.display()))?;
140 Ok(())
141}
142
143pub fn read_json<P: AsRef<Path>>(path: P) -> Result<Vec<Value>> {
154 let file = File::open(path.as_ref())
155 .with_context(|| format!("Failed to open file: {}", path.as_ref().display()))?;
156
157 let file_size = file.metadata().ok().map(|m| m.len() as usize).unwrap_or(0);
159 let mut contents = String::with_capacity(file_size);
160
161 let mut reader = BufReader::with_capacity(buffer::FILE_READ_BUFFER, file);
163 reader
164 .read_to_string(&mut contents)
165 .with_context(|| format!("Failed to read file: {}", path.as_ref().display()))?;
166
167 let obj: Value = serde_json::from_str(&contents).with_context(|| {
168 format!(
169 "Failed to parse JSON from file: {}",
170 path.as_ref().display()
171 )
172 })?;
173
174 Ok(vec![obj])
175}
176
177pub fn count_lines(text: &str) -> usize {
195 if text.is_empty() {
196 return 0;
197 }
198 let newline_count = bytecount::count(text.as_bytes(), b'\n');
201 if text.ends_with('\n') {
202 newline_count
203 } else {
204 newline_count + 1
205 }
206}
207
208pub fn save_json_pretty<P: AsRef<Path>>(path: P, value: &Value) -> Result<()> {
217 let json_str = serde_json::to_string_pretty(value).context("Failed to serialize JSON")?;
218
219 std::fs::write(path.as_ref(), json_str)
220 .with_context(|| format!("Failed to write file: {}", path.as_ref().display()))?;
221
222 Ok(())
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use serde_json::json;
229 use std::fs::File;
230 use std::io::Write;
231 use tempfile::tempdir;
232
233 #[test]
234 fn test_count_lines_empty() {
235 assert_eq!(count_lines(""), 0);
237 }
238
239 #[test]
240 fn test_count_lines_single_line_no_newline() {
241 assert_eq!(count_lines("hello"), 1);
243 }
244
245 #[test]
246 fn test_count_lines_single_line_with_newline() {
247 assert_eq!(count_lines("hello\n"), 1);
249 }
250
251 #[test]
252 fn test_count_lines_multiple_lines() {
253 assert_eq!(count_lines("line1\nline2\nline3"), 3);
255 }
256
257 #[test]
258 fn test_count_lines_multiple_lines_with_newline() {
259 assert_eq!(count_lines("line1\nline2\nline3\n"), 3);
261 }
262
263 #[test]
264 fn test_count_lines_empty_lines() {
265 assert_eq!(count_lines("line1\n\nline3"), 3);
267 assert_eq!(count_lines("\n\n\n"), 3);
268 }
269
270 #[test]
271 fn test_read_jsonl_valid() {
272 let dir = tempdir().unwrap();
274 let file_path = dir.path().join("test.jsonl");
275
276 let mut file = File::create(&file_path).unwrap();
277 writeln!(file, r#"{{"key1": "value1"}}"#).unwrap();
278 writeln!(file, r#"{{"key2": "value2"}}"#).unwrap();
279 writeln!(file, r#"{{"key3": "value3"}}"#).unwrap();
280
281 let result = read_jsonl(&file_path).unwrap();
282 assert_eq!(result.len(), 3);
283 assert_eq!(result[0]["key1"], "value1");
284 assert_eq!(result[1]["key2"], "value2");
285 assert_eq!(result[2]["key3"], "value3");
286 }
287
288 #[test]
289 fn test_read_jsonl_with_empty_lines() {
290 let dir = tempdir().unwrap();
292 let file_path = dir.path().join("test.jsonl");
293
294 let mut file = File::create(&file_path).unwrap();
295 writeln!(file, r#"{{"key1": "value1"}}"#).unwrap();
296 writeln!(file).unwrap();
297 writeln!(file, r#"{{"key2": "value2"}}"#).unwrap();
298 writeln!(file, " ").unwrap();
299 writeln!(file, r#"{{"key3": "value3"}}"#).unwrap();
300
301 let result = read_jsonl(&file_path).unwrap();
302 assert_eq!(result.len(), 3);
303 }
304
305 #[test]
306 fn test_read_jsonl_empty_file() {
307 let dir = tempdir().unwrap();
309 let file_path = dir.path().join("empty.jsonl");
310
311 File::create(&file_path).unwrap();
312
313 let result = read_jsonl(&file_path).unwrap();
314 assert_eq!(result.len(), 0);
315 }
316
317 #[test]
318 fn test_read_jsonl_invalid_json() {
319 let dir = tempdir().unwrap();
321 let file_path = dir.path().join("invalid.jsonl");
322
323 let mut file = File::create(&file_path).unwrap();
324 writeln!(file, "not valid json").unwrap();
325
326 let result = read_jsonl(&file_path);
327 assert!(result.is_err());
328 }
329
330 #[test]
331 fn test_read_jsonl_nonexistent_file() {
332 let result = read_jsonl("/nonexistent/path/file.jsonl");
334 assert!(result.is_err());
335 }
336
337 #[test]
338 fn test_read_json_valid() {
339 let dir = tempdir().unwrap();
341 let file_path = dir.path().join("test.json");
342
343 let mut file = File::create(&file_path).unwrap();
344 write!(file, r#"{{"key": "value", "number": 42}}"#).unwrap();
345
346 let result = read_json(&file_path).unwrap();
347 assert_eq!(result.len(), 1);
348 assert_eq!(result[0]["key"], "value");
349 assert_eq!(result[0]["number"], 42);
350 }
351
352 #[test]
353 fn test_read_json_array() {
354 let dir = tempdir().unwrap();
356 let file_path = dir.path().join("array.json");
357
358 let mut file = File::create(&file_path).unwrap();
359 write!(file, r#"[1, 2, 3, 4, 5]"#).unwrap();
360
361 let result = read_json(&file_path).unwrap();
362 assert_eq!(result.len(), 1);
363 assert!(result[0].is_array());
364 assert_eq!(result[0].as_array().unwrap().len(), 5);
365 }
366
367 #[test]
368 fn test_read_json_invalid() {
369 let dir = tempdir().unwrap();
371 let file_path = dir.path().join("invalid.json");
372
373 let mut file = File::create(&file_path).unwrap();
374 write!(file, "not valid json").unwrap();
375
376 let result = read_json(&file_path);
377 assert!(result.is_err());
378 }
379
380 #[test]
381 fn test_read_json_nonexistent_file() {
382 let result = read_json("/nonexistent/path/file.json");
384 assert!(result.is_err());
385 }
386
387 #[test]
388 fn test_count_lines_unicode() {
389 assert_eq!(count_lines("こんにちは"), 1);
391 assert_eq!(count_lines("line1 😀\nline2 🎉\nline3 🚀"), 3);
392 }
393
394 #[test]
395 fn test_count_lines_windows_line_endings() {
396 assert_eq!(count_lines("line1\r\nline2\r\nline3"), 3);
399 }
400
401 #[test]
402 fn test_read_jsonl_large_objects() {
403 let dir = tempdir().unwrap();
405 let file_path = dir.path().join("large.jsonl");
406
407 let mut file = File::create(&file_path).unwrap();
408 let large_obj = json!({
409 "field1": "value1",
410 "field2": "value2",
411 "nested": {
412 "a": 1,
413 "b": 2,
414 "c": [1, 2, 3, 4, 5]
415 }
416 });
417 writeln!(file, "{}", serde_json::to_string(&large_obj).unwrap()).unwrap();
418
419 let result = read_jsonl(&file_path).unwrap();
420 assert_eq!(result.len(), 1);
421 assert_eq!(result[0]["field1"], "value1");
422 assert_eq!(result[0]["nested"]["c"].as_array().unwrap().len(), 5);
423 }
424
425 #[test]
426 fn test_read_json_nested_structure() {
427 let dir = tempdir().unwrap();
429 let file_path = dir.path().join("nested.json");
430
431 let nested = json!({
432 "level1": {
433 "level2": {
434 "level3": {
435 "value": "deep"
436 }
437 }
438 }
439 });
440
441 let mut file = File::create(&file_path).unwrap();
442 write!(file, "{}", serde_json::to_string(&nested).unwrap()).unwrap();
443
444 let result = read_json(&file_path).unwrap();
445 assert_eq!(result.len(), 1);
446 assert_eq!(result[0]["level1"]["level2"]["level3"]["value"], "deep");
447 }
448
449 #[test]
450 fn test_count_lines_only_newlines() {
451 assert_eq!(count_lines("\n"), 1);
453 assert_eq!(count_lines("\n\n"), 2);
454 assert_eq!(count_lines("\n\n\n"), 3);
455 }
456
457 #[test]
458 fn test_count_lines_mixed_content() {
459 assert_eq!(count_lines("a\n \nc"), 3);
461 assert_eq!(count_lines(" hello \n world "), 2);
462 }
463}