stellar_xdr/cli/
decode.rs

1use std::{
2    fmt::Debug,
3    fs::File,
4    io::{stdin, Read},
5    path::PathBuf,
6    str::FromStr,
7};
8
9use clap::{Args, ValueEnum};
10use serde::Serialize;
11
12use crate::cli::{skip_whitespace::SkipWhitespace, Channel};
13
14#[derive(thiserror::Error, Debug)]
15pub enum Error {
16    #[error("unknown type {0}, choose one of {1:?}")]
17    UnknownType(String, &'static [&'static str]),
18    #[error("error decoding XDR: {0}")]
19    ReadXdrCurr(#[from] crate::curr::Error),
20    #[error("error decoding XDR: {0}")]
21    ReadXdrNext(#[from] crate::next::Error),
22    #[error("error reading file: {0}")]
23    ReadFile(#[from] std::io::Error),
24    #[error("error generating JSON: {0}")]
25    GenerateJson(#[from] serde_json::Error),
26}
27
28#[derive(Args, Debug, Clone)]
29#[command()]
30pub struct Cmd {
31    /// Files to decode, or stdin if omitted
32    #[arg()]
33    pub files: Vec<PathBuf>,
34
35    /// XDR type to decode
36    #[arg(long)]
37    pub r#type: String,
38
39    // Input format of the XDR
40    #[arg(long, value_enum, default_value_t)]
41    pub input: InputFormat,
42
43    // Output format
44    #[arg(long, value_enum, default_value_t)]
45    pub output: 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    RustDebug,
68    RustDebugFormatted,
69}
70
71impl Default for OutputFormat {
72    fn default() -> Self {
73        Self::Json
74    }
75}
76
77macro_rules! run_x {
78    ($f:ident, $m:ident) => {
79        fn $f(&self) -> Result<(), Error> {
80            let mut files = self.files()?;
81            let r#type = crate::$m::TypeVariant::from_str(&self.r#type).map_err(|_| {
82                Error::UnknownType(self.r#type.clone(), &crate::$m::TypeVariant::VARIANTS_STR)
83            })?;
84            for f in &mut files {
85                match self.input {
86                    InputFormat::Single => {
87                        let mut l = crate::$m::Limited::new(f, crate::$m::Limits::none());
88                        let t = crate::$m::Type::read_xdr_to_end(r#type, &mut l)?;
89                        self.out(&t)?;
90                    }
91                    InputFormat::SingleBase64 => {
92                        let sw = SkipWhitespace::new(f);
93                        let mut l = crate::$m::Limited::new(sw, 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 sw = SkipWhitespace::new(f);
105                        let mut l = crate::$m::Limited::new(sw, crate::$m::Limits::none());
106                        for t in crate::$m::Type::read_xdr_base64_iter(r#type, &mut l) {
107                            self.out(&t?)?;
108                        }
109                    }
110                    InputFormat::StreamFramed => {
111                        let mut l = crate::$m::Limited::new(f, crate::$m::Limits::none());
112                        for t in crate::$m::Type::read_xdr_framed_iter(r#type, &mut l) {
113                            self.out(&t?)?;
114                        }
115                    }
116                };
117            }
118            Ok(())
119        }
120    };
121}
122
123impl Cmd {
124    /// Run the CLIs decode command.
125    ///
126    /// ## Errors
127    ///
128    /// If the command is configured with state that is invalid.
129    pub fn run(&self, channel: &Channel) -> Result<(), Error> {
130        match channel {
131            Channel::Curr => self.run_curr()?,
132            Channel::Next => self.run_next()?,
133        }
134        Ok(())
135    }
136
137    run_x!(run_curr, curr);
138    run_x!(run_next, next);
139
140    fn files(&self) -> Result<Vec<Box<dyn Read>>, Error> {
141        if self.files.is_empty() {
142            Ok(vec![Box::new(stdin())])
143        } else {
144            Ok(self
145                .files
146                .iter()
147                .map(File::open)
148                .collect::<Result<Vec<_>, _>>()?
149                .into_iter()
150                .map(|f| -> Box<dyn Read> { Box::new(f) })
151                .collect())
152        }
153    }
154
155    fn out(&self, v: &(impl Serialize + Debug)) -> Result<(), Error> {
156        match self.output {
157            OutputFormat::Json => println!("{}", serde_json::to_string(v)?),
158            OutputFormat::JsonFormatted => println!("{}", serde_json::to_string_pretty(v)?),
159            OutputFormat::RustDebug => println!("{v:?}"),
160            OutputFormat::RustDebugFormatted => println!("{v:#?}"),
161        }
162        Ok(())
163    }
164}