use std::io::{self, BufRead, Read};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InputMode {
Json(String),
Pipe(Vec<u8>),
}
pub fn detect_stdin(stdin: &mut impl Read) -> io::Result<InputMode> {
let mut buffer = Vec::new();
stdin.read_to_end(&mut buffer)?;
if buffer.is_empty() {
return Ok(InputMode::Pipe(buffer));
}
let first_nonws = buffer.iter().position(|&b| !b.is_ascii_whitespace());
match first_nonws {
Some(pos) if buffer[pos] == b'{' => {
if let Ok(text) = std::str::from_utf8(&buffer)
&& is_ripsed_json(text)
{
return Ok(InputMode::Json(text.to_string()));
}
Ok(InputMode::Pipe(buffer))
}
_ => Ok(InputMode::Pipe(buffer)),
}
}
fn is_ripsed_json(text: &str) -> bool {
let trimmed = text.trim_start();
trimmed.starts_with('{') && trimmed.contains("\"operations\"")
}
pub fn detect_buffered(reader: &mut impl BufRead) -> io::Result<InputMode> {
let buf = reader.fill_buf()?;
if buf.is_empty() {
return Ok(InputMode::Pipe(vec![]));
}
let first_nonws = buf.iter().position(|&b| !b.is_ascii_whitespace());
if first_nonws.is_some_and(|pos| buf[pos] == b'{') {
let mut full = Vec::new();
reader.read_to_end(&mut full)?;
if let Ok(text) = std::str::from_utf8(&full)
&& is_ripsed_json(text)
{
return Ok(InputMode::Json(text.to_string()));
}
Ok(InputMode::Pipe(full))
} else {
let mut full = Vec::new();
reader.read_to_end(&mut full)?;
Ok(InputMode::Pipe(full))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_json() {
let input = r#"{"operations": [{"op": "replace", "find": "a", "replace": "b"}]}"#;
let mut cursor = io::Cursor::new(input.as_bytes());
let mode = detect_stdin(&mut cursor).unwrap();
assert!(matches!(mode, InputMode::Json(_)));
}
#[test]
fn test_detect_plain_text() {
let input = "just some plain text\n";
let mut cursor = io::Cursor::new(input.as_bytes());
let mode = detect_stdin(&mut cursor).unwrap();
assert!(matches!(mode, InputMode::Pipe(_)));
}
#[test]
fn test_detect_json_without_operations() {
let input = r#"{"key": "value"}"#;
let mut cursor = io::Cursor::new(input.as_bytes());
let mode = detect_stdin(&mut cursor).unwrap();
assert!(matches!(mode, InputMode::Pipe(_)));
}
#[test]
fn test_detect_empty() {
let input = "";
let mut cursor = io::Cursor::new(input.as_bytes());
let mode = detect_stdin(&mut cursor).unwrap();
assert!(matches!(mode, InputMode::Pipe(_)));
}
}