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