diff --git a/Cargo.lock b/Cargo.lock index d2d1bac0..f08da150 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,8 +246,9 @@ dependencies = [ [[package]] name = "escape-bytes" -version = "0.0.0" -source = "git+https://github.com/stellar/escape-bytes?rev=457ee2147e84fcc969228e9b11c8431eb44f0df3#457ee2147e84fcc969228e9b11c8431eb44f0df3" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3871d161fec5b6fade5fe7afe2e196b86839c5526a8d256c7b1a04dbbe5241a4" [[package]] name = "fnv" diff --git a/Cargo.toml b/Cargo.toml index 86b005b3..fb11da3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ stellar-strkey = { version = "0.0.8", optional = true } base64 = { version = "0.13.0", optional = true } serde = { version = "1.0.139", features = ["derive"], optional = true } serde_with = { version = "3.0.0", optional = true } -escape-bytes = { version = "0.0.0", git = "https://github.com/stellar/escape-bytes", rev = "457ee2147e84fcc969228e9b11c8431eb44f0df3", default-features = false } +escape-bytes = { version = "0.1.0", default-features = false } hex = { version = "0.4.3", optional = true } arbitrary = {version = "1.1.3", features = ["derive"], optional = true} clap = { version = "4.2.4", default-features = false, features = ["std", "derive", "usage", "help"], optional = true } diff --git a/Makefile b/Makefile index 1c730c15..21ebcb93 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ CARGO_HACK_ARGS=--feature-powerset --exclude-features default --group-features b CARGO_DOC_ARGS?=--open -XDRGEN_VERSION=193ddc25bdcde71b860b17828dc3fb1e5655b27e +XDRGEN_VERSION=e90b9ee62a89f346a86ef66f889bcfd8e1a8fbcb XDRGEN_TYPES_CUSTOM_STR_IMPL=PublicKey,AccountId,MuxedAccount,MuxedAccountMed25519,SignerKey,SignerKeyEd25519SignedPayload,NodeId,ScAddress,AssetCode,AssetCode4,AssetCode12 all: build test diff --git a/src/bin/stellar-xdr/main.rs b/src/bin/stellar-xdr/main.rs index dcc2b2ff..78ff30f3 100644 --- a/src/bin/stellar-xdr/main.rs +++ b/src/bin/stellar-xdr/main.rs @@ -1,75 +1,14 @@ -mod decode; -mod encode; -mod guess; -mod types; -mod version; - -use clap::{CommandFactory, Parser, Subcommand, ValueEnum}; -use std::{error::Error, fmt::Debug}; - -#[derive(Parser, Debug, Clone)] -#[command( - author, - version, - about, - long_about = None, - disable_help_subcommand = true, - disable_version_flag = true, - disable_colored_help = true, - infer_subcommands = true, -)] -struct Root { - /// Channel of XDR to operate on - #[arg(value_enum, default_value_t)] - channel: Channel, - #[command(subcommand)] - cmd: Cmd, -} - -#[derive(ValueEnum, Debug, Clone)] -pub enum Channel { - #[value(name = "+curr")] - Curr, - #[value(name = "+next")] - Next, -} - -impl Default for Channel { - fn default() -> Self { - Self::Curr - } -} - -#[derive(Subcommand, Debug, Clone)] -enum Cmd { - /// View information about types - Types(types::Cmd), - /// Guess the XDR type - Guess(guess::Cmd), - /// Decode XDR - Decode(decode::Cmd), - /// Encode XDR - Encode(encode::Cmd), - /// Print version information - Version, -} - -fn run() -> Result<(), Box> { - let root = Root::parse(); - match root.cmd { - Cmd::Types(c) => c.run(&root.channel)?, - Cmd::Guess(c) => c.run(&root.channel)?, - Cmd::Decode(c) => c.run(&root.channel)?, - Cmd::Encode(c) => c.run(&root.channel)?, - Cmd::Version => version::Cmd::run(), - } - Ok(()) -} +use clap::Error; +use std::env; +use stellar_xdr::cli; fn main() { - if let Err(e) = run() { - Root::command() - .error(clap::error::ErrorKind::ValueValidation, e) - .exit() + if let Err(e) = cli::run(env::args_os()) { + match e { + cli::Error::Clap(e) => e.exit(), + cli::Error::Guess(_) | cli::Error::Decode(_) | cli::Error::Encode(_) => { + Error::raw(clap::error::ErrorKind::ValueValidation, e).exit() + } + } } } diff --git a/src/bin/stellar-xdr/decode.rs b/src/cli/decode.rs similarity index 78% rename from src/bin/stellar-xdr/decode.rs rename to src/cli/decode.rs index c90f6fb9..965b3f87 100644 --- a/src/bin/stellar-xdr/decode.rs +++ b/src/cli/decode.rs @@ -8,16 +8,16 @@ use std::{ use clap::{Args, ValueEnum}; use serde::Serialize; -use crate::Channel; +use crate::cli::Channel; #[derive(thiserror::Error, Debug)] pub enum Error { #[error("unknown type {0}, choose one of {1:?}")] UnknownType(String, &'static [&'static str]), #[error("error decoding XDR: {0}")] - ReadXdrCurr(#[from] stellar_xdr::curr::Error), + ReadXdrCurr(#[from] crate::curr::Error), #[error("error decoding XDR: {0}")] - ReadXdrNext(#[from] stellar_xdr::next::Error), + ReadXdrNext(#[from] crate::next::Error), #[error("error reading file: {0}")] ReadFile(#[from] std::io::Error), #[error("error generating JSON: {0}")] @@ -75,35 +75,32 @@ macro_rules! run_x { ($f:ident, $m:ident) => { fn $f(&self) -> Result<(), Error> { let mut files = self.files()?; - let r#type = stellar_xdr::$m::TypeVariant::from_str(&self.r#type).map_err(|_| { - Error::UnknownType( - self.r#type.clone(), - &stellar_xdr::$m::TypeVariant::VARIANTS_STR, - ) + let r#type = crate::$m::TypeVariant::from_str(&self.r#type).map_err(|_| { + Error::UnknownType(self.r#type.clone(), &crate::$m::TypeVariant::VARIANTS_STR) })?; for f in &mut files { - let mut f = stellar_xdr::$m::Limited::new(f, stellar_xdr::$m::Limits::none()); + let mut f = crate::$m::Limited::new(f, crate::$m::Limits::none()); match self.input { InputFormat::Single => { - let t = stellar_xdr::$m::Type::read_xdr_to_end(r#type, &mut f)?; + let t = crate::$m::Type::read_xdr_to_end(r#type, &mut f)?; self.out(&t)?; } InputFormat::SingleBase64 => { - let t = stellar_xdr::$m::Type::read_xdr_base64_to_end(r#type, &mut f)?; + let t = crate::$m::Type::read_xdr_base64_to_end(r#type, &mut f)?; self.out(&t)?; } InputFormat::Stream => { - for t in stellar_xdr::$m::Type::read_xdr_iter(r#type, &mut f) { + for t in crate::$m::Type::read_xdr_iter(r#type, &mut f) { self.out(&t?)?; } } InputFormat::StreamBase64 => { - for t in stellar_xdr::$m::Type::read_xdr_base64_iter(r#type, &mut f) { + for t in crate::$m::Type::read_xdr_base64_iter(r#type, &mut f) { self.out(&t?)?; } } InputFormat::StreamFramed => { - for t in stellar_xdr::$m::Type::read_xdr_framed_iter(r#type, &mut f) { + for t in crate::$m::Type::read_xdr_framed_iter(r#type, &mut f) { self.out(&t?)?; } } diff --git a/src/bin/stellar-xdr/encode.rs b/src/cli/encode.rs similarity index 56% rename from src/bin/stellar-xdr/encode.rs rename to src/cli/encode.rs index b8dff346..ba588790 100644 --- a/src/bin/stellar-xdr/encode.rs +++ b/src/cli/encode.rs @@ -7,56 +7,56 @@ use std::{ use clap::{Args, ValueEnum}; -use crate::Channel; +use crate::cli::Channel; #[derive(thiserror::Error, Debug)] pub enum Error { #[error("unknown type {0}, choose one of {1:?}")] UnknownType(String, &'static [&'static str]), #[error("error decoding JSON: {0}")] - ReadJsonCurr(stellar_xdr::curr::Error), + ReadJsonCurr(crate::curr::Error), #[error("error decoding JSON: {0}")] - ReadJsonNext(stellar_xdr::next::Error), + ReadJsonNext(crate::next::Error), #[error("error reading file: {0}")] ReadFile(#[from] std::io::Error), #[error("error generating XDR: {0}")] - WriteXdrCurr(stellar_xdr::curr::Error), + WriteXdrCurr(crate::curr::Error), #[error("error generating XDR: {0}")] - WriteXdrNext(stellar_xdr::next::Error), + WriteXdrNext(crate::next::Error), } -impl From for Error { - fn from(e: stellar_xdr::curr::Error) -> Self { +impl From for Error { + fn from(e: crate::curr::Error) -> Self { match e { - stellar_xdr::curr::Error::Invalid - | stellar_xdr::curr::Error::Unsupported - | stellar_xdr::curr::Error::LengthExceedsMax - | stellar_xdr::curr::Error::LengthMismatch - | stellar_xdr::curr::Error::NonZeroPadding - | stellar_xdr::curr::Error::Utf8Error(_) - | stellar_xdr::curr::Error::InvalidHex - | stellar_xdr::curr::Error::Io(_) - | stellar_xdr::curr::Error::DepthLimitExceeded - | stellar_xdr::curr::Error::LengthLimitExceeded => Error::WriteXdrCurr(e), - stellar_xdr::curr::Error::Json(_) => Error::ReadJsonCurr(e), + crate::curr::Error::Invalid + | crate::curr::Error::Unsupported + | crate::curr::Error::LengthExceedsMax + | crate::curr::Error::LengthMismatch + | crate::curr::Error::NonZeroPadding + | crate::curr::Error::Utf8Error(_) + | crate::curr::Error::InvalidHex + | crate::curr::Error::Io(_) + | crate::curr::Error::DepthLimitExceeded + | crate::curr::Error::LengthLimitExceeded => Error::WriteXdrCurr(e), + crate::curr::Error::Json(_) => Error::ReadJsonCurr(e), } } } -impl From for Error { - fn from(e: stellar_xdr::next::Error) -> Self { +impl From for Error { + fn from(e: crate::next::Error) -> Self { match e { - stellar_xdr::next::Error::Invalid - | stellar_xdr::next::Error::Unsupported - | stellar_xdr::next::Error::LengthExceedsMax - | stellar_xdr::next::Error::LengthMismatch - | stellar_xdr::next::Error::NonZeroPadding - | stellar_xdr::next::Error::Utf8Error(_) - | stellar_xdr::next::Error::InvalidHex - | stellar_xdr::next::Error::Io(_) - | stellar_xdr::next::Error::DepthLimitExceeded - | stellar_xdr::next::Error::LengthLimitExceeded => Error::WriteXdrNext(e), - stellar_xdr::next::Error::Json(_) => Error::ReadJsonNext(e), + crate::next::Error::Invalid + | crate::next::Error::Unsupported + | crate::next::Error::LengthExceedsMax + | crate::next::Error::LengthMismatch + | crate::next::Error::NonZeroPadding + | crate::next::Error::Utf8Error(_) + | crate::next::Error::InvalidHex + | crate::next::Error::Io(_) + | crate::next::Error::DepthLimitExceeded + | crate::next::Error::LengthLimitExceeded => Error::WriteXdrNext(e), + crate::next::Error::Json(_) => Error::ReadJsonNext(e), } } } @@ -107,19 +107,16 @@ impl Default for OutputFormat { macro_rules! run_x { ($f:ident, $m:ident) => { fn $f(&self) -> Result<(), Error> { - use stellar_xdr::$m::WriteXdr; + use crate::$m::WriteXdr; let mut files = self.files()?; - let r#type = stellar_xdr::$m::TypeVariant::from_str(&self.r#type).map_err(|_| { - Error::UnknownType( - self.r#type.clone(), - &stellar_xdr::$m::TypeVariant::VARIANTS_STR, - ) + let r#type = crate::$m::TypeVariant::from_str(&self.r#type).map_err(|_| { + Error::UnknownType(self.r#type.clone(), &crate::$m::TypeVariant::VARIANTS_STR) })?; for f in &mut files { match self.input { InputFormat::Json => { - let t = stellar_xdr::$m::Type::read_json(r#type, f)?; - let l = stellar_xdr::$m::Limits::none(); + let t = crate::$m::Type::read_json(r#type, f)?; + let l = crate::$m::Limits::none(); match self.output { OutputFormat::Single => stdout().write_all(&t.to_xdr(l)?)?, diff --git a/src/bin/stellar-xdr/guess.rs b/src/cli/guess.rs similarity index 82% rename from src/bin/stellar-xdr/guess.rs rename to src/cli/guess.rs index 9ef9b574..4f48820b 100644 --- a/src/bin/stellar-xdr/guess.rs +++ b/src/cli/guess.rs @@ -7,15 +7,15 @@ use std::{ use clap::{Args, ValueEnum}; -use crate::Channel; +use crate::cli::Channel; #[derive(thiserror::Error, Debug)] #[allow(clippy::enum_variant_names)] pub enum Error { #[error("error decoding XDR: {0}")] - ReadXdrCurr(#[from] stellar_xdr::curr::Error), + ReadXdrCurr(#[from] crate::curr::Error), #[error("error decoding XDR: {0}")] - ReadXdrNext(#[from] stellar_xdr::next::Error), + ReadXdrNext(#[from] crate::next::Error), #[error("error reading file: {0}")] ReadFile(#[from] std::io::Error), } @@ -69,26 +69,21 @@ impl Default for OutputFormat { macro_rules! run_x { ($f:ident, $m:ident) => { fn $f(&self) -> Result<(), Error> { - let mut f = stellar_xdr::$m::Limited::new( - ResetRead::new(self.file()?), - stellar_xdr::$m::Limits::none(), - ); - 'variants: for v in stellar_xdr::$m::TypeVariant::VARIANTS { + let mut f = + crate::$m::Limited::new(ResetRead::new(self.file()?), crate::$m::Limits::none()); + 'variants: for v in crate::$m::TypeVariant::VARIANTS { f.inner.reset(); let count: usize = match self.input { - InputFormat::Single => stellar_xdr::$m::Type::read_xdr_to_end(v, &mut f) + InputFormat::Single => crate::$m::Type::read_xdr_to_end(v, &mut f) + .ok() + .map(|_| 1) + .unwrap_or_default(), + InputFormat::SingleBase64 => crate::$m::Type::read_xdr_base64_to_end(v, &mut f) .ok() .map(|_| 1) .unwrap_or_default(), - InputFormat::SingleBase64 => { - stellar_xdr::$m::Type::read_xdr_base64_to_end(v, &mut f) - .ok() - .map(|_| 1) - .unwrap_or_default() - } InputFormat::Stream => { - let iter = - stellar_xdr::$m::Type::read_xdr_iter(v, &mut f).take(self.certainty); + let iter = crate::$m::Type::read_xdr_iter(v, &mut f).take(self.certainty); let mut count = 0; for v in iter { match v { @@ -99,8 +94,8 @@ macro_rules! run_x { count } InputFormat::StreamBase64 => { - let iter = stellar_xdr::$m::Type::read_xdr_base64_iter(v, &mut f) - .take(self.certainty); + let iter = + crate::$m::Type::read_xdr_base64_iter(v, &mut f).take(self.certainty); let mut count = 0; for v in iter { match v { @@ -111,8 +106,8 @@ macro_rules! run_x { count } InputFormat::StreamFramed => { - let iter = stellar_xdr::$m::Type::read_xdr_framed_iter(v, &mut f) - .take(self.certainty); + let iter = + crate::$m::Type::read_xdr_framed_iter(v, &mut f).take(self.certainty); let mut count = 0; for v in iter { match v { diff --git a/src/cli/mod.rs b/src/cli/mod.rs new file mode 100644 index 00000000..c51bb95d --- /dev/null +++ b/src/cli/mod.rs @@ -0,0 +1,89 @@ +mod decode; +mod encode; +mod guess; +mod types; +mod version; + +use clap::{Parser, Subcommand, ValueEnum}; +use std::{ffi::OsString, fmt::Debug}; + +#[derive(Parser, Debug, Clone)] +#[command( + author, + version, + about, + long_about = None, + disable_help_subcommand = true, + disable_version_flag = true, + disable_colored_help = true, + infer_subcommands = true, +)] +pub struct Root { + /// Channel of XDR to operate on + #[arg(value_enum, default_value_t)] + channel: Channel, + #[command(subcommand)] + cmd: Cmd, +} + +#[derive(ValueEnum, Debug, Clone)] +pub enum Channel { + #[value(name = "+curr")] + Curr, + #[value(name = "+next")] + Next, +} + +impl Default for Channel { + fn default() -> Self { + Self::Curr + } +} + +#[derive(Subcommand, Debug, Clone)] +pub enum Cmd { + /// View information about types + Types(types::Cmd), + /// Guess the XDR type + Guess(guess::Cmd), + /// Decode XDR + Decode(decode::Cmd), + /// Encode XDR + Encode(encode::Cmd), + /// Print version information + Version, +} + +#[derive(thiserror::Error, Debug)] +#[allow(clippy::enum_variant_names)] +pub enum Error { + #[error("{0}")] + Clap(#[from] clap::Error), + #[error("error decoding XDR: {0}")] + Guess(#[from] guess::Error), + #[error("error reading file: {0}")] + Decode(#[from] decode::Error), + #[error("error reading file: {0}")] + Encode(#[from] encode::Error), +} + +/// Run the CLI with the given args. +/// +/// ## Errors +/// +/// If the input cannot be parsed. +pub fn run(args: I) -> Result<(), Error> +where + I: IntoIterator, + T: Into + Clone, +{ + let root = Root::try_parse_from(args)?; + match root.cmd { + Cmd::Types(c) => c.run(&root.channel), + Cmd::Guess(c) => c.run(&root.channel)?, + Cmd::Decode(c) => c.run(&root.channel)?, + Cmd::Encode(c) => c.run(&root.channel)?, + Cmd::Version => version::Cmd::run(), + } + Ok(()) +} diff --git a/src/bin/stellar-xdr/types.rs b/src/cli/types.rs similarity index 61% rename from src/bin/stellar-xdr/types.rs rename to src/cli/types.rs index 911401b6..a0ad2790 100644 --- a/src/bin/stellar-xdr/types.rs +++ b/src/cli/types.rs @@ -1,10 +1,8 @@ mod list; -use std::error::Error; - use clap::{Args, Subcommand}; -use crate::Channel; +use crate::cli::Channel; #[derive(Args, Debug, Clone)] #[command()] @@ -19,10 +17,9 @@ pub enum Sub { } impl Cmd { - pub fn run(&self, channel: &Channel) -> Result<(), Box> { + pub fn run(&self, channel: &Channel) { match &self.sub { - Sub::List(c) => c.run(channel)?, + Sub::List(c) => c.run(channel), } - Ok(()) } } diff --git a/src/bin/stellar-xdr/types/list.rs b/src/cli/types/list.rs similarity index 74% rename from src/bin/stellar-xdr/types/list.rs rename to src/cli/types/list.rs index 0f6ac4b2..30054ac4 100644 --- a/src/bin/stellar-xdr/types/list.rs +++ b/src/cli/types/list.rs @@ -1,7 +1,6 @@ use clap::{Args, ValueEnum}; -use std::error::Error; -use crate::Channel; +use crate::cli::Channel; #[derive(Args, Debug, Clone)] #[command()] @@ -25,7 +24,7 @@ impl Default for OutputFormat { } impl Cmd { - pub fn run(&self, channel: &Channel) -> Result<(), Box> { + pub fn run(&self, channel: &Channel) { let types = Self::types(channel); match self.output { OutputFormat::Plain => { @@ -34,19 +33,18 @@ impl Cmd { } } OutputFormat::Json => { - println!("{}", serde_json::to_string(&types)?); + println!("{}", serde_json::to_string(&types).unwrap()); } OutputFormat::JsonFormatted => { - println!("{}", serde_json::to_string_pretty(&types)?); + println!("{}", serde_json::to_string_pretty(&types).unwrap()); } } - Ok(()) } fn types(channel: &Channel) -> Vec<&'static str> { let types: &[&str] = match channel { - Channel::Curr => &stellar_xdr::curr::TypeVariant::VARIANTS_STR, - Channel::Next => &stellar_xdr::next::TypeVariant::VARIANTS_STR, + Channel::Curr => &crate::curr::TypeVariant::VARIANTS_STR, + Channel::Next => &crate::next::TypeVariant::VARIANTS_STR, }; let mut types: Vec<&'static str> = types.to_vec(); types.sort_unstable(); diff --git a/src/bin/stellar-xdr/version.rs b/src/cli/version.rs similarity index 85% rename from src/bin/stellar-xdr/version.rs rename to src/cli/version.rs index 1b18cbe6..7a18c186 100644 --- a/src/bin/stellar-xdr/version.rs +++ b/src/cli/version.rs @@ -1,12 +1,14 @@ use clap::Parser; +use crate::VERSION; + #[derive(Parser, Debug, Clone)] #[command()] pub struct Cmd; impl Cmd { pub fn run() { - let v = stellar_xdr::VERSION; + let v = VERSION; println!( "stellar-xdr {} ({}) xdr (+curr): {} diff --git a/src/lib.rs b/src/lib.rs index ef1e3b65..475a715f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,3 +123,6 @@ pub mod curr; #[cfg(feature = "next")] pub mod next; + +#[cfg(feature = "cli")] +pub mod cli;