stellar_xdr/cli/
compare.rs

1use std::{fmt::Debug, fs::File, path::PathBuf, str::FromStr};
2
3use clap::{Args, ValueEnum};
4
5use crate::cli::{skip_whitespace::SkipWhitespace, Channel};
6
7#[derive(thiserror::Error, Debug)]
8pub enum Error {
9    #[error("unknown type {0}, choose one of {1:?}")]
10    UnknownType(String, &'static [&'static str]),
11    #[error("error decoding XDR: {0}")]
12    ReadXdrCurr(#[from] crate::curr::Error),
13    #[error("error decoding XDR: {0}")]
14    ReadXdrNext(#[from] crate::next::Error),
15    #[error("error reading file: {0}")]
16    ReadFile(#[from] std::io::Error),
17}
18
19/// Compare two XDR values with each other
20///
21/// Outputs:
22///   `-1` when the left XDR value is less than the right XDR value,
23///   `0` when the left XDR value is equal to the right XDR value,
24///   `1` when the left XDR value is greater than the right XDR value
25#[derive(Args, Debug, Clone)]
26#[command()]
27pub struct Cmd {
28    /// XDR file to decode and compare with the right value
29    #[arg()]
30    pub left: PathBuf,
31
32    /// XDR file to decode and compare with the left value
33    #[arg()]
34    pub right: PathBuf,
35
36    /// XDR type of both inputs
37    #[arg(long)]
38    pub r#type: String,
39
40    // Input format of the XDR
41    #[arg(long, value_enum, default_value_t)]
42    pub input: InputFormat,
43}
44
45#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
46pub enum InputFormat {
47    Single,
48    SingleBase64,
49}
50
51impl Default for InputFormat {
52    fn default() -> Self {
53        Self::SingleBase64
54    }
55}
56
57macro_rules! run_x {
58    ($f:ident, $m:ident) => {
59        fn $f(&self) -> Result<(), Error> {
60            let f1 = File::open(&self.left)?;
61            let f2 = File::open(&self.right)?;
62            let r#type = crate::$m::TypeVariant::from_str(&self.r#type).map_err(|_| {
63                Error::UnknownType(self.r#type.clone(), &crate::$m::TypeVariant::VARIANTS_STR)
64            })?;
65            let (t1, t2) = match self.input {
66                InputFormat::Single => {
67                    let t1 = {
68                        let mut l1 = crate::$m::Limited::new(f1, crate::$m::Limits::none());
69                        crate::$m::Type::read_xdr_to_end(r#type, &mut l1)?
70                    };
71                    let t2 = {
72                        let mut l = crate::$m::Limited::new(f2, crate::$m::Limits::none());
73                        crate::$m::Type::read_xdr_to_end(r#type, &mut l)?
74                    };
75                    (t1, t2)
76                }
77                InputFormat::SingleBase64 => {
78                    let t1 = {
79                        let sw = SkipWhitespace::new(f1);
80                        let mut l = crate::$m::Limited::new(sw, crate::$m::Limits::none());
81                        crate::$m::Type::read_xdr_base64_to_end(r#type, &mut l)?
82                    };
83                    let t2 = {
84                        let sw = SkipWhitespace::new(f2);
85                        let mut l = crate::$m::Limited::new(sw, crate::$m::Limits::none());
86                        crate::$m::Type::read_xdr_base64_to_end(r#type, &mut l)?
87                    };
88                    (t1, t2)
89                }
90            };
91            let cmp = t1.cmp(&t2) as i8;
92            println!("{cmp}");
93            Ok(())
94        }
95    };
96}
97
98impl Cmd {
99    /// Run the CLIs decode command.
100    ///
101    /// ## Errors
102    ///
103    /// If the command is configured with state that is invalid.
104    pub fn run(&self, channel: &Channel) -> Result<(), Error> {
105        match channel {
106            Channel::Curr => self.run_curr()?,
107            Channel::Next => self.run_next()?,
108        }
109        Ok(())
110    }
111
112    run_x!(run_curr, curr);
113    run_x!(run_next, next);
114}