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