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, Channel};
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    ReadXdrCurr(#[from] crate::curr::Error),
16    #[error("error decoding XDR: {0}")]
17    ReadXdrNext(#[from] crate::next::Error),
18    #[error("error reading file: {0}")]
19    ReadFile(std::io::Error),
20    #[error("error writing output: {0}")]
21    WriteOutput(std::io::Error),
22    #[error("error generating JSON: {0}")]
23    GenerateJson(#[from] serde_json::Error),
24    #[error("type doesn't have a text representation, use 'json' as output")]
25    TextUnsupported,
26}
27
28#[derive(Args, Debug, Clone)]
29#[command()]
30pub struct Cmd {
31    /// XDR or files containing XDR to decode, or stdin if empty
32    #[arg()]
33    pub input: Vec<OsString>,
34
35    /// XDR type to decode
36    #[arg(long)]
37    pub r#type: String,
38
39    // Input format of the XDR
40    #[arg(long = "input", value_enum, default_value_t)]
41    pub input_format: InputFormat,
42
43    // Output format
44    #[arg(long = "output", value_enum, default_value_t)]
45    pub output_format: OutputFormat,
46}
47
48#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
49pub enum InputFormat {
50    Single,
51    SingleBase64,
52    Stream,
53    StreamBase64,
54    StreamFramed,
55}
56
57impl Default for InputFormat {
58    fn default() -> Self {
59        Self::StreamBase64
60    }
61}
62
63#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
64pub enum OutputFormat {
65    Json,
66    JsonFormatted,
67    Text,
68    RustDebug,
69    RustDebugFormatted,
70}
71
72impl Default for OutputFormat {
73    fn default() -> Self {
74        Self::Json
75    }
76}
77
78macro_rules! run_x {
79    ($f:ident, $m:ident) => {
80        fn $f(&self) -> Result<(), Error> {
81            let mut input = util::parse_input(&self.input).map_err(Error::ReadFile)?;
82            let r#type = crate::$m::TypeVariant::from_str(&self.r#type).map_err(|_| {
83                Error::UnknownType(self.r#type.clone(), &crate::$m::TypeVariant::VARIANTS_STR)
84            })?;
85            for f in &mut input {
86                match self.input_format {
87                    InputFormat::Single => {
88                        let mut l = crate::$m::Limited::new(f, crate::$m::Limits::none());
89                        let t = crate::$m::Type::read_xdr_to_end(r#type, &mut l)?;
90                        self.out(&t)?;
91                    }
92                    InputFormat::SingleBase64 => {
93                        let mut l = crate::$m::Limited::new(f, crate::$m::Limits::none());
94                        let t = crate::$m::Type::read_xdr_base64_to_end(r#type, &mut l)?;
95                        self.out(&t)?;
96                    }
97                    InputFormat::Stream => {
98                        let mut l = crate::$m::Limited::new(f, crate::$m::Limits::none());
99                        for t in crate::$m::Type::read_xdr_iter(r#type, &mut l) {
100                            self.out(&t?)?;
101                        }
102                    }
103                    InputFormat::StreamBase64 => {
104                        let mut l = crate::$m::Limited::new(f, crate::$m::Limits::none());
105                        for t in crate::$m::Type::read_xdr_base64_iter(r#type, &mut l) {
106                            self.out(&t?)?;
107                        }
108                    }
109                    InputFormat::StreamFramed => {
110                        let mut l = crate::$m::Limited::new(f, crate::$m::Limits::none());
111                        for t in crate::$m::Type::read_xdr_framed_iter(r#type, &mut l) {
112                            self.out(&t?)?;
113                        }
114                    }
115                };
116            }
117            Ok(())
118        }
119    };
120}
121
122impl Cmd {
123    /// Run the CLIs decode command.
124    ///
125    /// ## Errors
126    ///
127    /// If the command is configured with state that is invalid.
128    pub fn run(&self, channel: &Channel) -> Result<(), Error> {
129        let result = match channel {
130            Channel::Curr => self.run_curr(),
131            Channel::Next => self.run_next(),
132        };
133
134        match result {
135            Ok(()) => Ok(()),
136            Err(Error::WriteOutput(e)) if e.kind() == std::io::ErrorKind::BrokenPipe => Ok(()),
137            Err(e) => Err(e),
138        }
139    }
140
141    run_x!(run_curr, curr);
142    run_x!(run_next, next);
143
144    fn out(&self, v: &(impl Serialize + Debug)) -> Result<(), Error> {
145        let text = match self.output_format {
146            OutputFormat::Json => serde_json::to_string(v)?,
147            OutputFormat::JsonFormatted => serde_json::to_string_pretty(v)?,
148            OutputFormat::Text => {
149                let v = serde_json::to_value(v)?;
150                util::serde_json_value_to_text(v).ok_or(Error::TextUnsupported)?
151            }
152            OutputFormat::RustDebug => format!("{v:?}"),
153            OutputFormat::RustDebugFormatted => format!("{v:#?}"),
154        };
155        writeln!(stdout(), "{text}").map_err(Error::WriteOutput)?;
156        Ok(())
157    }
158}