stellar_xdr/cli/
encode.rs

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