Skip to main content

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