use crossbeam_channel::{Receiver, unbounded};
use serde_json::Value;
use std::fs;
use std::io::{self, Read};
use std::path::PathBuf;
use std::thread::{self, JoinHandle};
pub struct LoadResult {
pub result: Result<Vec<Value>, String>,
}
pub struct Loader {
result_rx: Receiver<LoadResult>,
_handle: JoinHandle<()>,
}
impl Loader {
pub fn spawn_file(path: PathBuf) -> Self {
let (result_tx, result_rx) = unbounded();
let handle = thread::spawn(move || {
let result = load_file(&path);
let _ = result_tx.send(LoadResult { result });
});
Loader {
result_rx,
_handle: handle,
}
}
pub fn spawn_stdin() -> Self {
let (result_tx, result_rx) = unbounded();
let handle = thread::spawn(move || {
let result = load_stdin();
let _ = result_tx.send(LoadResult { result });
});
Loader {
result_rx,
_handle: handle,
}
}
pub fn try_recv(&self) -> Option<LoadResult> {
self.result_rx.try_recv().ok()
}
}
fn load_file(path: &PathBuf) -> Result<Vec<Value>, String> {
let content = fs::read_to_string(path).map_err(|e| e.to_string())?;
parse_json(&content)
}
fn load_stdin() -> Result<Vec<Value>, String> {
let mut content = String::new();
io::stdin()
.read_to_string(&mut content)
.map_err(|e| e.to_string())?;
if content.is_empty() {
return Err("Empty input is not valid JSON".to_string());
}
parse_json(&content)
}
fn parse_json(input: &str) -> Result<Vec<Value>, String> {
if let Ok(value) = serde_json::from_str::<Value>(input) {
return Ok(vec![value]);
}
let mut values = Vec::new();
for (line_num, line) in input.lines().enumerate() {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
let value = serde_json::from_str(trimmed)
.map_err(|e| format!("Invalid JSON on line {}: {}", line_num + 1, e))?;
values.push(value);
}
if values.is_empty() {
return Err("No valid JSON found in input".to_string());
}
Ok(values)
}