stellar_xdr/cli/
decode.rs1use std::ffi::OsString;
2use std::io::{stdout, Write};
3use std::{fmt::Debug, str::FromStr};
4
5use clap::{Args, ValueEnum};
6use serde::Serialize;
7
8use crate::cli::util;
9
10#[derive(thiserror::Error, Debug)]
11pub enum Error {
12 #[error("unknown type {0}, choose one of {1:?}")]
13 UnknownType(String, &'static [&'static str]),
14 #[error("error decoding XDR: {0}")]
15 ReadXdr(#[from] crate::Error),
16 #[error("error reading file: {0}")]
17 ReadFile(std::io::Error),
18 #[error("error writing output: {0}")]
19 WriteOutput(std::io::Error),
20 #[error("error generating JSON: {0}")]
21 GenerateJson(#[from] serde_json::Error),
22 #[error("type doesn't have a text representation, use 'json' as output")]
23 TextUnsupported,
24}
25
26#[derive(Args, Debug, Clone)]
27#[command()]
28pub struct Cmd {
29 #[arg()]
31 pub input: Vec<OsString>,
32
33 #[arg(long)]
35 pub r#type: String,
36
37 #[arg(long = "input", value_enum, default_value_t)]
39 pub input_format: InputFormat,
40
41 #[arg(long = "output", value_enum, default_value_t)]
43 pub output_format: OutputFormat,
44}
45
46#[derive(Default, Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
47pub enum InputFormat {
48 Single,
49 SingleBase64,
50 Stream,
51 #[default]
52 StreamBase64,
53 StreamFramed,
54}
55
56#[derive(Default, Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
57pub enum OutputFormat {
58 #[default]
59 Json,
60 JsonFormatted,
61 Text,
62 RustDebug,
63 RustDebugFormatted,
64}
65
66macro_rules! run_x {
69 ($f:ident) => {
70 fn $f(&self) -> Result<(), Error> {
71 let mut input = util::parse_input(&self.input).map_err(Error::ReadFile)?;
72 let r#type = crate::TypeVariant::from_str(&self.r#type).map_err(|_| {
73 Error::UnknownType(self.r#type.clone(), &crate::TypeVariant::VARIANTS_STR)
74 })?;
75 for f in &mut input {
76 match self.input_format {
77 InputFormat::Single => {
78 let mut l = crate::Limited::new(f, crate::Limits::none());
79 let t = crate::Type::read_xdr_to_end(r#type, &mut l)?;
80 self.out(&t)?;
81 }
82 InputFormat::SingleBase64 => {
83 let mut l = crate::Limited::new(f, crate::Limits::none());
84 let t = crate::Type::read_xdr_base64_to_end(r#type, &mut l)?;
85 self.out(&t)?;
86 }
87 InputFormat::Stream => {
88 let mut l = crate::Limited::new(f, crate::Limits::none());
89 for t in crate::Type::read_xdr_iter(r#type, &mut l) {
90 self.out(&t?)?;
91 }
92 }
93 InputFormat::StreamBase64 => {
94 let mut l = crate::Limited::new(f, crate::Limits::none());
95 for t in crate::Type::read_xdr_base64_iter(r#type, &mut l) {
96 self.out(&t?)?;
97 }
98 }
99 InputFormat::StreamFramed => {
100 let mut l = crate::Limited::new(f, crate::Limits::none());
101 for t in crate::Type::read_xdr_framed_iter(r#type, &mut l) {
102 self.out(&t?)?;
103 }
104 }
105 };
106 }
107 Ok(())
108 }
109 };
110}
111
112impl Cmd {
113 pub fn run(&self) -> Result<(), Error> {
119 let result = self.run_inner();
120 match result {
121 Ok(()) => Ok(()),
122 Err(Error::WriteOutput(e)) if e.kind() == std::io::ErrorKind::BrokenPipe => Ok(()),
123 Err(e) => Err(e),
124 }
125 }
126
127 run_x!(run_inner);
128
129 fn out(&self, v: &(impl Serialize + Debug)) -> Result<(), Error> {
130 let text = match self.output_format {
131 OutputFormat::Json => serde_json::to_string(v)?,
132 OutputFormat::JsonFormatted => serde_json::to_string_pretty(v)?,
133 OutputFormat::Text => {
134 let v = serde_json::to_value(v)?;
135 util::serde_json_value_to_text(v).ok_or(Error::TextUnsupported)?
136 }
137 OutputFormat::RustDebug => format!("{v:?}"),
138 OutputFormat::RustDebugFormatted => format!("{v:#?}"),
139 };
140 writeln!(stdout(), "{text}").map_err(Error::WriteOutput)?;
141 Ok(())
142 }
143}