stellar_xdr/cli/generate/
arbitrary.rs

1use arbitrary::Unstructured;
2use clap::{Args, ValueEnum};
3use std::{
4    io::{stdout, Write},
5    str::FromStr,
6};
7
8use crate::cli::{util, Channel};
9
10#[derive(thiserror::Error, Debug)]
11pub enum Error {
12    #[error("unknown type {0}, choose one of {1:?}")]
13    UnknownType(String, &'static [&'static str]),
14    #[error("error reading file: {0}")]
15    ReadFile(#[from] std::io::Error),
16    #[error("error generating XDR: {0}")]
17    WriteXdrCurr(crate::curr::Error),
18    #[error("error generating XDR: {0}")]
19    WriteXdrNext(crate::next::Error),
20    #[error("error generating JSON: {0}")]
21    GenerateJson(#[from] serde_json::Error),
22    #[error("error generating arbitrary value: {0}")]
23    Arbitrary(#[from] arbitrary::Error),
24    #[error("type doesn't have a text representation, use 'json' as output")]
25    TextUnsupported,
26}
27
28impl From<crate::curr::Error> for Error {
29    fn from(e: crate::curr::Error) -> Self {
30        match e {
31            crate::curr::Error::Invalid
32            | crate::curr::Error::Unsupported
33            | crate::curr::Error::LengthExceedsMax
34            | crate::curr::Error::LengthMismatch
35            | crate::curr::Error::NonZeroPadding
36            | crate::curr::Error::Utf8Error(_)
37            | crate::curr::Error::InvalidHex
38            | crate::curr::Error::Io(_)
39            | crate::curr::Error::DepthLimitExceeded
40            | crate::curr::Error::LengthLimitExceeded
41            | crate::curr::Error::Arbitrary(_)
42            | crate::curr::Error::Json(_) => Error::WriteXdrCurr(e),
43        }
44    }
45}
46
47impl From<crate::next::Error> for Error {
48    fn from(e: crate::next::Error) -> Self {
49        match e {
50            crate::next::Error::Invalid
51            | crate::next::Error::Unsupported
52            | crate::next::Error::LengthExceedsMax
53            | crate::next::Error::LengthMismatch
54            | crate::next::Error::NonZeroPadding
55            | crate::next::Error::Utf8Error(_)
56            | crate::next::Error::InvalidHex
57            | crate::next::Error::Io(_)
58            | crate::next::Error::DepthLimitExceeded
59            | crate::next::Error::LengthLimitExceeded
60            | crate::next::Error::Arbitrary(_)
61            | crate::next::Error::Json(_) => Error::WriteXdrNext(e),
62        }
63    }
64}
65
66/// Generate arbitrary XDR values
67#[derive(Args, Debug, Clone)]
68#[command()]
69pub struct Cmd {
70    /// XDR type to generate
71    #[arg(long)]
72    pub r#type: String,
73
74    // Output format to encode to
75    #[arg(long = "output", value_enum, default_value_t)]
76    pub output_format: OutputFormat,
77}
78
79#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
80pub enum OutputFormat {
81    Single,
82    SingleBase64,
83    // TODO: Stream,
84    // TODO: StreamBase64,
85    // TODO: StreamFramed,
86    Json,
87    JsonFormatted,
88    Text,
89}
90
91impl Default for OutputFormat {
92    fn default() -> Self {
93        Self::SingleBase64
94    }
95}
96
97macro_rules! run_x {
98    ($f:ident, $m:ident) => {
99        fn $f(&self) -> Result<(), Error> {
100            use crate::$m::WriteXdr;
101            let r#type = crate::$m::TypeVariant::from_str(&self.r#type).map_err(|_| {
102                Error::UnknownType(self.r#type.clone(), &crate::$m::TypeVariant::VARIANTS_STR)
103            })?;
104            let r = rand::random::<[u8; 10_240]>();
105            let mut u = Unstructured::new(&r);
106            let v = crate::$m::Type::arbitrary(r#type, &mut u)?;
107            match self.output_format {
108                OutputFormat::Single => {
109                    let l = crate::$m::Limits::none();
110                    stdout().write_all(&v.to_xdr(l)?)?
111                }
112                OutputFormat::SingleBase64 => {
113                    let l = crate::$m::Limits::none();
114                    println!("{}", v.to_xdr_base64(l)?)
115                }
116                OutputFormat::Json => {
117                    println!("{}", serde_json::to_string(&v)?);
118                }
119                OutputFormat::JsonFormatted => {
120                    println!("{}", serde_json::to_string_pretty(&v)?);
121                }
122                OutputFormat::Text => {
123                    let v = serde_json::to_value(v)?;
124                    let text = util::serde_json_value_to_text(v).ok_or(Error::TextUnsupported)?;
125                    println!("{text}")
126                }
127            }
128            Ok(())
129        }
130    };
131}
132
133impl Cmd {
134    /// Run the CLIs generate arbitrary command.
135    ///
136    /// ## Errors
137    ///
138    /// If the command is configured with state that is invalid.
139    pub fn run(&self, channel: &Channel) -> Result<(), Error> {
140        match channel {
141            Channel::Curr => self.run_curr()?,
142            Channel::Next => self.run_next()?,
143        }
144        Ok(())
145    }
146
147    run_x!(run_curr, curr);
148    run_x!(run_next, next);
149}