pub mod asset;
pub mod conformance;
pub mod event;
pub mod manifest;
pub mod receipt;
pub mod telemetry;
use std::io::Read;
pub const MAX_STDIN_BYTES: u64 = 16 * 1024 * 1024;
#[derive(Debug)]
pub enum CliError {
Usage(String),
Validation(String),
Input(String),
}
impl CliError {
pub fn exit_code(&self) -> u8 {
match self {
CliError::Validation(_) => 1,
CliError::Usage(_) => 2,
CliError::Input(_) => 3,
}
}
pub fn message(&self) -> &str {
match self {
CliError::Usage(m) | CliError::Validation(m) | CliError::Input(m) => m,
}
}
}
pub fn read_stdin(label: &str) -> Result<String, CliError> {
read_text_bounded(std::io::stdin().lock(), label)
}
pub fn read_text_bounded<R: Read>(reader: R, label: &str) -> Result<String, CliError> {
let mut buf = Vec::new();
reader
.take(MAX_STDIN_BYTES + 1)
.read_to_end(&mut buf)
.map_err(|err| CliError::Input(format!("failed to read {label} from stdin: {err}")))?;
if buf.len() as u64 > MAX_STDIN_BYTES {
return Err(CliError::Input(format!(
"{label}: stdin exceeds {MAX_STDIN_BYTES} bytes"
)));
}
let buf = String::from_utf8(buf)
.map_err(|err| CliError::Input(format!("{label}: stdin is not UTF-8: {err}")))?;
if buf.trim().is_empty() {
return Err(CliError::Input(format!("{label}: stdin is empty")));
}
Ok(buf)
}
pub fn parse_stdin_json<T: serde::de::DeserializeOwned>(label: &str) -> Result<T, CliError> {
let buf = read_stdin(label)?;
serde_json::from_str::<T>(&buf)
.map_err(|err| CliError::Input(format!("{label}: invalid JSON: {err}")))
}
pub fn print_json<T: serde::Serialize>(value: &T) -> Result<(), CliError> {
let json = serde_json::to_string_pretty(value)
.map_err(|err| CliError::Input(format!("failed to serialize output: {err}")))?;
println!("{json}");
Ok(())
}