stellar_xdr/cli/
encode.rs

1use std::ffi::OsString;
2use std::{
3    io::{stdout, Write},
4    str::FromStr,
5};
6
7use clap::{Args, ValueEnum};
8
9use crate::cli::{util, Channel};
10
11#[derive(thiserror::Error, Debug)]
12pub enum Error {
13    #[error("unknown type {0}, choose one of {1:?}")]
14    UnknownType(String, &'static [&'static str]),
15    #[error("error decoding JSON: {0}")]
16    ReadJsonCurr(crate::curr::Error),
17    #[error("error decoding JSON: {0}")]
18    ReadJsonNext(crate::next::Error),
19    #[error("error reading file: {0}")]
20    ReadFile(std::io::Error),
21    #[error("error writing output: {0}")]
22    WriteOutput(std::io::Error),
23    #[error("error generating XDR: {0}")]
24    WriteXdrCurr(crate::curr::Error),
25    #[error("error generating XDR: {0}")]
26    WriteXdrNext(crate::next::Error),
27}
28
29impl From<crate::curr::Error> for Error {
30    fn from(e: crate::curr::Error) -> Self {
31        match e {
32            crate::curr::Error::Invalid
33            | crate::curr::Error::Unsupported
34            | crate::curr::Error::LengthExceedsMax
35            | crate::curr::Error::LengthMismatch
36            | crate::curr::Error::NonZeroPadding
37            | crate::curr::Error::Utf8Error(_)
38            | crate::curr::Error::InvalidHex
39            | crate::curr::Error::Io(_)
40            | crate::curr::Error::DepthLimitExceeded
41            | crate::curr::Error::LengthLimitExceeded
42            | crate::curr::Error::Arbitrary(_) => Error::WriteXdrCurr(e),
43            crate::curr::Error::Json(_) => Error::ReadJsonCurr(e),
44        }
45    }
46}
47
48impl From<crate::next::Error> for Error {
49    fn from(e: crate::next::Error) -> Self {
50        match e {
51            crate::next::Error::Invalid
52            | crate::next::Error::Unsupported
53            | crate::next::Error::LengthExceedsMax
54            | crate::next::Error::LengthMismatch
55            | crate::next::Error::NonZeroPadding
56            | crate::next::Error::Utf8Error(_)
57            | crate::next::Error::InvalidHex
58            | crate::next::Error::Io(_)
59            | crate::next::Error::DepthLimitExceeded
60            | crate::next::Error::LengthLimitExceeded
61            | crate::next::Error::Arbitrary(_) => Error::WriteXdrNext(e),
62            crate::next::Error::Json(_) => Error::ReadJsonNext(e),
63        }
64    }
65}
66
67#[derive(Args, Debug, Clone)]
68#[command()]
69pub struct Cmd {
70    /// XDR or files containing XDR to decode, or stdin if empty
71    #[arg()]
72    pub input: Vec<OsString>,
73
74    /// XDR type to encode
75    #[arg(long)]
76    pub r#type: String,
77
78    // Input format
79    #[arg(long = "input", value_enum, default_value_t)]
80    pub input_format: InputFormat,
81
82    // Output format to encode to
83    #[arg(long = "output", value_enum, default_value_t)]
84    pub output_format: OutputFormat,
85}
86
87#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
88pub enum InputFormat {
89    Json,
90}
91
92impl Default for InputFormat {
93    fn default() -> Self {
94        Self::Json
95    }
96}
97
98#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
99pub enum OutputFormat {
100    Single,
101    SingleBase64,
102    Stream,
103    // TODO: StreamBase64,
104    // TODO: StreamFramed,
105}
106
107impl Default for OutputFormat {
108    fn default() -> Self {
109        Self::SingleBase64
110    }
111}
112
113macro_rules! run_x {
114    ($f:ident, $m:ident) => {
115        fn $f(&self) -> Result<(), Error> {
116            use crate::$m::WriteXdr;
117            let mut input = util::parse_input(&self.input).map_err(Error::ReadFile)?;
118            let r#type = crate::$m::TypeVariant::from_str(&self.r#type).map_err(|_| {
119                Error::UnknownType(self.r#type.clone(), &crate::$m::TypeVariant::VARIANTS_STR)
120            })?;
121            for f in &mut input {
122                match self.input_format {
123                    InputFormat::Json => match self.output_format {
124                        OutputFormat::Single => {
125                            let t = crate::$m::Type::from_json(r#type, f)?;
126                            let l = crate::$m::Limits::none();
127                            stdout()
128                                .write_all(&t.to_xdr(l)?)
129                                .map_err(Error::WriteOutput)?;
130                        }
131                        OutputFormat::SingleBase64 => {
132                            let t = crate::$m::Type::from_json(r#type, f)?;
133                            let l = crate::$m::Limits::none();
134                            writeln!(stdout(), "{}", t.to_xdr_base64(l)?)
135                                .map_err(Error::WriteOutput)?
136                        }
137                        OutputFormat::Stream => {
138                            let mut de =
139                                serde_json::Deserializer::new(serde_json::de::IoRead::new(f));
140                            loop {
141                                let t = match crate::$m::Type::deserialize_json(r#type, &mut de) {
142                                    Ok(t) => t,
143                                    Err(crate::$m::Error::Json(ref inner)) if inner.is_eof() => {
144                                        break;
145                                    }
146                                    Err(e) => Err(e)?,
147                                };
148                                let l = crate::$m::Limits::none();
149                                stdout()
150                                    .write_all(&t.to_xdr(l)?)
151                                    .map_err(Error::WriteOutput)?;
152                            }
153                        }
154                    },
155                };
156            }
157            Ok(())
158        }
159    };
160}
161
162impl Cmd {
163    /// Run the CLIs encode command.
164    ///
165    /// ## Errors
166    ///
167    /// If the command is configured with state that is invalid.
168    pub fn run(&self, channel: &Channel) -> Result<(), Error> {
169        let result = match channel {
170            Channel::Curr => self.run_curr(),
171            Channel::Next => self.run_next(),
172        };
173
174        match result {
175            Ok(()) => Ok(()),
176            Err(Error::WriteOutput(e)) if e.kind() == std::io::ErrorKind::BrokenPipe => Ok(()),
177            Err(e) => Err(e),
178        }
179    }
180
181    run_x!(run_curr, curr);
182    run_x!(run_next, next);
183}