use std::ffi::OsString;
use std::{
io::{stdout, Write},
str::FromStr,
};
use clap::{Args, ValueEnum};
use crate::cli::{util, Channel};
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("unknown type {0}, choose one of {1:?}")]
UnknownType(String, &'static [&'static str]),
#[error("error decoding JSON: {0}")]
ReadJsonCurr(crate::curr::Error),
#[error("error decoding JSON: {0}")]
ReadJsonNext(crate::next::Error),
#[error("error reading file: {0}")]
ReadFile(std::io::Error),
#[error("error writing output: {0}")]
WriteOutput(std::io::Error),
#[error("error generating XDR: {0}")]
WriteXdrCurr(crate::curr::Error),
#[error("error generating XDR: {0}")]
WriteXdrNext(crate::next::Error),
}
impl From<crate::curr::Error> for Error {
fn from(e: crate::curr::Error) -> Self {
match e {
crate::curr::Error::Invalid
| crate::curr::Error::Unsupported
| crate::curr::Error::LengthExceedsMax
| crate::curr::Error::LengthMismatch
| crate::curr::Error::NonZeroPadding
| crate::curr::Error::Utf8Error(_)
| crate::curr::Error::InvalidHex
| crate::curr::Error::Io(_)
| crate::curr::Error::DepthLimitExceeded
| crate::curr::Error::LengthLimitExceeded
| crate::curr::Error::Arbitrary(_) => Error::WriteXdrCurr(e),
crate::curr::Error::Json(_) => Error::ReadJsonCurr(e),
}
}
}
impl From<crate::next::Error> for Error {
fn from(e: crate::next::Error) -> Self {
match e {
crate::next::Error::Invalid
| crate::next::Error::Unsupported
| crate::next::Error::LengthExceedsMax
| crate::next::Error::LengthMismatch
| crate::next::Error::NonZeroPadding
| crate::next::Error::Utf8Error(_)
| crate::next::Error::InvalidHex
| crate::next::Error::Io(_)
| crate::next::Error::DepthLimitExceeded
| crate::next::Error::LengthLimitExceeded
| crate::next::Error::Arbitrary(_) => Error::WriteXdrNext(e),
crate::next::Error::Json(_) => Error::ReadJsonNext(e),
}
}
}
#[derive(Args, Debug, Clone)]
#[command()]
pub struct Cmd {
#[arg()]
pub input: Vec<OsString>,
#[arg(long)]
pub r#type: String,
#[arg(long = "input", value_enum, default_value_t)]
pub input_format: InputFormat,
#[arg(long = "output", value_enum, default_value_t)]
pub output_format: OutputFormat,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
pub enum InputFormat {
Json,
}
impl Default for InputFormat {
fn default() -> Self {
Self::Json
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
pub enum OutputFormat {
Single,
SingleBase64,
Stream,
}
impl Default for OutputFormat {
fn default() -> Self {
Self::SingleBase64
}
}
macro_rules! run_x {
($f:ident, $m:ident) => {
fn $f(&self) -> Result<(), Error> {
use crate::$m::WriteXdr;
let mut input = util::parse_input(&self.input).map_err(Error::ReadFile)?;
let r#type = crate::$m::TypeVariant::from_str(&self.r#type).map_err(|_| {
Error::UnknownType(self.r#type.clone(), &crate::$m::TypeVariant::VARIANTS_STR)
})?;
for f in &mut input {
match self.input_format {
InputFormat::Json => match self.output_format {
OutputFormat::Single => {
let t = crate::$m::Type::from_json(r#type, f)?;
let l = crate::$m::Limits::none();
stdout()
.write_all(&t.to_xdr(l)?)
.map_err(Error::WriteOutput)?;
}
OutputFormat::SingleBase64 => {
let t = crate::$m::Type::from_json(r#type, f)?;
let l = crate::$m::Limits::none();
writeln!(stdout(), "{}", t.to_xdr_base64(l)?)
.map_err(Error::WriteOutput)?
}
OutputFormat::Stream => {
let mut de =
serde_json::Deserializer::new(serde_json::de::IoRead::new(f));
loop {
let t = match crate::$m::Type::deserialize_json(r#type, &mut de) {
Ok(t) => t,
Err(crate::$m::Error::Json(ref inner)) if inner.is_eof() => {
break;
}
Err(e) => Err(e)?,
};
let l = crate::$m::Limits::none();
stdout()
.write_all(&t.to_xdr(l)?)
.map_err(Error::WriteOutput)?;
}
}
},
};
}
Ok(())
}
};
}
impl Cmd {
pub fn run(&self, channel: &Channel) -> Result<(), Error> {
let result = match channel {
Channel::Curr => self.run_curr(),
Channel::Next => self.run_next(),
};
match result {
Ok(()) => Ok(()),
Err(Error::WriteOutput(e)) if e.kind() == std::io::ErrorKind::BrokenPipe => Ok(()),
Err(e) => Err(e),
}
}
run_x!(run_curr, curr);
run_x!(run_next, next);
}