Skip to main content

stellar_xdr/cli/
compare.rs

1use std::{
2    fmt::Debug,
3    fs::File,
4    io::{stdout, Write},
5    path::PathBuf,
6    str::FromStr,
7};
8
9use clap::{Args, ValueEnum};
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 XDR: {0}")]
16    ReadXdr(#[from] 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}
22
23/// Compare two XDR values with each other
24///
25/// Outputs:
26///   `-1` when the left XDR value is less than the right XDR value,
27///   `0` when the left XDR value is equal to the right XDR value,
28///   `1` when the left XDR value is greater than the right XDR value
29#[derive(Args, Debug, Clone)]
30#[command()]
31pub struct Cmd {
32    /// XDR file to decode and compare with the right value
33    #[arg()]
34    pub left: PathBuf,
35
36    /// XDR file to decode and compare with the left value
37    #[arg()]
38    pub right: PathBuf,
39
40    /// XDR type of both inputs
41    #[arg(long)]
42    pub r#type: String,
43
44    // Input format of the XDR
45    #[arg(long, value_enum, default_value_t)]
46    pub input: InputFormat,
47}
48
49#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
50pub enum InputFormat {
51    Single,
52    SingleBase64,
53}
54
55impl Default for InputFormat {
56    fn default() -> Self {
57        Self::SingleBase64
58    }
59}
60
61// TODO: Remove run_x macro, it exists only to reduce the diff from when curr/next
62// channels existed and each had their own run_curr/run_next invocation.
63macro_rules! run_x {
64    ($f:ident) => {
65        fn $f(&self) -> Result<(), Error> {
66            let f1 = File::open(&self.left).map_err(Error::ReadFile)?;
67            let f2 = File::open(&self.right).map_err(Error::ReadFile)?;
68            let r#type = crate::TypeVariant::from_str(&self.r#type).map_err(|_| {
69                Error::UnknownType(self.r#type.clone(), &crate::TypeVariant::VARIANTS_STR)
70            })?;
71            let (t1, t2) = match self.input {
72                InputFormat::Single => {
73                    let t1 = {
74                        let mut l1 = crate::Limited::new(f1, crate::Limits::none());
75                        crate::Type::read_xdr_to_end(r#type, &mut l1)?
76                    };
77                    let t2 = {
78                        let mut l = crate::Limited::new(f2, crate::Limits::none());
79                        crate::Type::read_xdr_to_end(r#type, &mut l)?
80                    };
81                    (t1, t2)
82                }
83                InputFormat::SingleBase64 => {
84                    let t1 = {
85                        let mut l = crate::Limited::new(f1, crate::Limits::none());
86                        crate::Type::read_xdr_base64_to_end(r#type, &mut l)?
87                    };
88                    let t2 = {
89                        let mut l = crate::Limited::new(f2, crate::Limits::none());
90                        crate::Type::read_xdr_base64_to_end(r#type, &mut l)?
91                    };
92                    (t1, t2)
93                }
94            };
95            let cmp = t1.cmp(&t2) as i8;
96            writeln!(stdout(), "{cmp}").map_err(Error::WriteOutput)?;
97            Ok(())
98        }
99    };
100}
101
102impl Cmd {
103    /// Run the CLIs decode command.
104    ///
105    /// ## Errors
106    ///
107    /// If the command is configured with state that is invalid.
108    pub fn run(&self) -> Result<(), Error> {
109        let result = self.run_inner();
110        match result {
111            Ok(()) => Ok(()),
112            Err(Error::WriteOutput(e)) if e.kind() == std::io::ErrorKind::BrokenPipe => Ok(()),
113            Err(e) => Err(e),
114        }
115    }
116
117    run_x!(run_inner);
118}