Skip to main content

stellar_xdr/cli/
decode.rs

1use 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    /// XDR or files containing XDR to decode, or stdin if empty
30    #[arg()]
31    pub input: Vec<OsString>,
32
33    /// XDR type to decode
34    #[arg(long)]
35    pub r#type: String,
36
37    // Input format of the XDR
38    #[arg(long = "input", value_enum, default_value_t)]
39    pub input_format: InputFormat,
40
41    // Output format
42    #[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
66// TODO: Remove run_x macro, it exists only to reduce the diff from when curr/next
67// channels existed and each had their own run_curr/run_next invocation.
68macro_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    /// Run the CLIs decode command.
114    ///
115    /// ## Errors
116    ///
117    /// If the command is configured with state that is invalid.
118    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}