stellar_xdr/cli/
decode.rs

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