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