use serde::Deserialize;
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::PathBuf;
use thiserror::Error;
pub const MAX_BYTES: u64 = 1024 * 1024;
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct Input {
path: PathBuf,
}
#[derive(Debug, Error)]
pub enum Error {
#[error("invalid input JSON: {0}")]
InvalidJson(#[source] serde_json::Error),
#[error("read input from stdin: {0}")]
StdinRead(#[source] io::Error),
#[error("open {path}: {source}", path = path.display())]
Open {
path: PathBuf,
#[source]
source: io::Error,
},
#[error(
"file {path} is {size} bytes (cap {cap}); use a streaming tool",
path = path.display()
)]
TooLarge { path: PathBuf, cap: u64, size: u64 },
#[error("read {path}: {source}", path = path.display())]
Read {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("write to stdout: {0}")]
Write(#[source] io::Error),
}
pub fn run<R: Read, W: Write>(stdin: &mut R, stdout: &mut W) -> Result<(), Error> {
let mut buf = Vec::new();
stdin.read_to_end(&mut buf).map_err(Error::StdinRead)?;
let input: Input = serde_json::from_slice(&buf).map_err(Error::InvalidJson)?;
let path = input.path;
let file = File::open(&path).map_err(|source| Error::Open {
path: path.clone(),
source,
})?;
let mut content = Vec::new();
let mut capped = file.take(MAX_BYTES + 1);
capped
.read_to_end(&mut content)
.map_err(|source| Error::Read {
path: path.clone(),
source,
})?;
let read = content.len() as u64;
if read > MAX_BYTES {
let size = capped
.get_ref()
.metadata()
.map_or(read, |m| m.len().max(read));
let cap = MAX_BYTES;
return Err(Error::TooLarge { path, cap, size });
}
stdout.write_all(&content).map_err(Error::Write)
}
#[cfg(test)]
mod tests;