Skip to main content

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;
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    WriteXdr(#[from] crate::Error),
18    #[error("error generating JSON: {0}")]
19    GenerateJson(#[from] serde_json::Error),
20    #[error("error generating arbitrary value: {0}")]
21    Arbitrary(#[from] arbitrary::Error),
22    #[error("type doesn't have a text representation, use 'json' as output")]
23    TextUnsupported,
24}
25
26/// Generate arbitrary XDR values
27#[derive(Args, Debug, Clone)]
28#[command()]
29pub struct Cmd {
30    /// XDR type to generate
31    #[arg(long)]
32    pub r#type: String,
33
34    // Output format to encode to
35    #[arg(long = "output", value_enum, default_value_t)]
36    pub output_format: OutputFormat,
37}
38
39#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
40pub enum OutputFormat {
41    Single,
42    SingleBase64,
43    // TODO: Stream,
44    // TODO: StreamBase64,
45    // TODO: StreamFramed,
46    Json,
47    JsonFormatted,
48    Text,
49}
50
51impl Default for OutputFormat {
52    fn default() -> Self {
53        Self::SingleBase64
54    }
55}
56
57// TODO: Remove run_x macro, it exists only to reduce the diff from when curr/next
58// channels existed and each had their own run_curr/run_next invocation.
59macro_rules! run_x {
60    ($f:ident) => {
61        fn $f(&self) -> Result<(), Error> {
62            use crate::WriteXdr;
63            let r#type = crate::TypeVariant::from_str(&self.r#type).map_err(|_| {
64                Error::UnknownType(self.r#type.clone(), &crate::TypeVariant::VARIANTS_STR)
65            })?;
66            let r = rand::random::<[u8; 10_240]>();
67            let mut u = Unstructured::new(&r);
68            let v = crate::Type::arbitrary(r#type, &mut u)?;
69            match self.output_format {
70                OutputFormat::Single => {
71                    let l = crate::Limits::none();
72                    stdout().write_all(&v.to_xdr(l)?)?
73                }
74                OutputFormat::SingleBase64 => {
75                    let l = crate::Limits::none();
76                    println!("{}", v.to_xdr_base64(l)?)
77                }
78                OutputFormat::Json => {
79                    println!("{}", serde_json::to_string(&v)?);
80                }
81                OutputFormat::JsonFormatted => {
82                    println!("{}", serde_json::to_string_pretty(&v)?);
83                }
84                OutputFormat::Text => {
85                    let v = serde_json::to_value(v)?;
86                    let text = util::serde_json_value_to_text(v).ok_or(Error::TextUnsupported)?;
87                    println!("{text}")
88                }
89            }
90            Ok(())
91        }
92    };
93}
94
95impl Cmd {
96    /// Run the CLIs generate arbitrary command.
97    ///
98    /// ## Errors
99    ///
100    /// If the command is configured with state that is invalid.
101    pub fn run(&self) -> Result<(), Error> {
102        self.run_inner()
103    }
104
105    run_x!(run_inner);
106}