From 56b3b8f4f1f2e7967b94a2f7d0c11810ff34dd95 Mon Sep 17 00:00:00 2001 From: Chip Senkbeil Date: Fri, 7 Jul 2023 00:21:06 -0500 Subject: [PATCH] Fix CLI commands with --format json not outputting errors in JSON --- CHANGELOG.md | 5 ++++ src/lib.rs | 60 +++++++++++++++++++++++++++++++++++++-------- src/main.rs | 16 +++++++++--- src/options.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91ac4f4..b5d3757 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- CLI commands like `distant manager select` will now output errors in a JSON + format when configured to communicate using JSON + ## [0.20.0-alpha.12] ### Changed diff --git a/src/lib.rs b/src/lib.rs index 95af545..81017da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,24 +17,54 @@ mod options; pub mod win_service; pub use cli::Cli; -pub use options::{Options, OptionsError}; +pub use options::{Format, Options, OptionsError}; -/// Wrapper around a [`CliResult`] that provides [`Termination`] support -pub struct MainResult(CliResult); +/// Wrapper around a [`CliResult`] that provides [`Termination`] support and [`Format`]ing. +pub struct MainResult { + inner: CliResult, + format: Format, +} impl MainResult { - pub const OK: MainResult = MainResult(Ok(())); + pub const OK: MainResult = MainResult { + inner: Ok(()), + format: Format::Shell, + }; + + /// Creates a new result that performs general shell formatting. + pub fn new(inner: CliResult) -> Self { + Self { + inner, + format: Format::Shell, + } + } + + /// Converts to shell formatting for errors. + pub fn shell(self) -> Self { + Self { + inner: self.inner, + format: Format::Shell, + } + } + + /// Converts to a JSON formatting for errors. + pub fn json(self) -> Self { + Self { + inner: self.inner, + format: Format::Json, + } + } } impl From for MainResult { fn from(res: CliResult) -> Self { - Self(res) + Self::new(res) } } impl From for MainResult { fn from(x: OptionsError) -> Self { - Self(match x { + Self::new(match x { OptionsError::Config(x) => Err(CliError::Error(x)), OptionsError::Options(x) => match x.kind() { // --help and --version should not actually exit with an error and instead display @@ -57,13 +87,13 @@ impl From for MainResult { impl From for MainResult { fn from(x: anyhow::Error) -> Self { - Self(Err(CliError::Error(x))) + Self::new(Err(CliError::Error(x))) } } impl From> for MainResult { fn from(res: anyhow::Result<()>) -> Self { - Self(res.map_err(CliError::Error)) + Self::new(res.map_err(CliError::Error)) } } @@ -86,12 +116,22 @@ impl CliError { impl Termination for MainResult { fn report(self) -> ExitCode { - match self.0 { + match self.inner { Ok(_) => ExitCode::SUCCESS, Err(x) => match x { CliError::Exit(code) => ExitCode::from(code), CliError::Error(x) => { - eprintln!("{x:?}"); + match self.format { + Format::Shell => eprintln!("{x}"), + Format::Json => println!( + "{}", + serde_json::to_string(&serde_json::json!({ + "type": "error", + "msg": x.to_string(), + }),) + .expect("Failed to format error to JSON") + ), + } ::log::error!("{x:?}"); ::log::logger().flush(); ExitCode::FAILURE diff --git a/src/main.rs b/src/main.rs index 1752f2e..6342a4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use distant::{Cli, MainResult}; +use distant::{Cli, Format, MainResult}; #[cfg(unix)] fn main() -> MainResult { @@ -8,7 +8,12 @@ fn main() -> MainResult { }; let _logger = cli.init_logger(); - MainResult::from(cli.run()) + let format = cli.options.command.format(); + let result = MainResult::from(cli.run()); + match format { + Format::Shell => result.shell(), + Format::Json => result.json(), + } } #[cfg(windows)] @@ -18,6 +23,7 @@ fn main() -> MainResult { Err(x) => return MainResult::from(x), }; let _logger = cli.init_logger(); + let format = cli.options.command.format(); // If we are trying to listen as a manager, try as a service first if cli.is_manager_listen_command() { @@ -36,5 +42,9 @@ fn main() -> MainResult { } // Otherwise, execute as a non-service CLI - MainResult::from(cli.run()) + let result = MainResult::from(cli.run()); + match format { + Format::Shell => result.shell(), + Format::Json => result.json(), + } } diff --git a/src/options.rs b/src/options.rs index 5b9163f..8ac9e54 100644 --- a/src/options.rs +++ b/src/options.rs @@ -281,6 +281,19 @@ pub enum DistantSubcommand { Generate(GenerateSubcommand), } +impl DistantSubcommand { + /// Format used by the subcommand. + #[inline] + pub fn format(&self) -> Format { + match self { + Self::Client(x) => x.format(), + Self::Manager(x) => x.format(), + Self::Server(x) => x.format(), + Self::Generate(x) => x.format(), + } + } +} + /// Subcommands for `distant client`. #[derive(Debug, PartialEq, Subcommand, IsVariant)] pub enum ClientSubcommand { @@ -539,6 +552,21 @@ impl ClientSubcommand { Self::Version { network, .. } => network, } } + + /// Format used by the subcommand. + #[inline] + pub fn format(&self) -> Format { + match self { + Self::Api { .. } => Format::Json, + Self::Connect { format, .. } => *format, + Self::FileSystem(fs) => fs.format(), + Self::Launch { format, .. } => *format, + Self::Shell { .. } => Format::Shell, + Self::Spawn { .. } => Format::Shell, + Self::SystemInfo { .. } => Format::Shell, + Self::Version { format, .. } => *format, + } + } } /// Subcommands for `distant fs`. @@ -936,6 +964,12 @@ impl ClientFileSystemSubcommand { Self::Write { network, .. } => network, } } + + /// Format used by the subcommand. + #[inline] + pub fn format(&self) -> Format { + Format::Shell + } } /// Subcommands for `distant generate`. @@ -960,6 +994,14 @@ pub enum GenerateSubcommand { }, } +impl GenerateSubcommand { + /// Format used by the subcommand. + #[inline] + pub fn format(&self) -> Format { + Format::Shell + } +} + /// Subcommands for `distant manager`. #[derive(Debug, PartialEq, Eq, Subcommand, IsVariant)] pub enum ManagerSubcommand { @@ -1056,6 +1098,22 @@ pub enum ManagerSubcommand { }, } +impl ManagerSubcommand { + /// Format used by the subcommand. + #[inline] + pub fn format(&self) -> Format { + match self { + Self::Select { format, .. } => *format, + Self::Service(_) => Format::Shell, + Self::Listen { .. } => Format::Shell, + Self::Capabilities { format, .. } => *format, + Self::Info { format, .. } => *format, + Self::List { format, .. } => *format, + Self::Kill { format, .. } => *format, + } + } +} + /// Subcommands for `distant manager service`. #[derive(Debug, PartialEq, Eq, Subcommand, IsVariant)] pub enum ManagerServiceSubcommand { @@ -1172,6 +1230,14 @@ pub enum ServerSubcommand { }, } +impl ServerSubcommand { + /// Format used by the subcommand. + #[inline] + pub fn format(&self) -> Format { + Format::Shell + } +} + #[derive(Args, Debug, PartialEq)] pub struct ServerListenWatchOptions { /// If specified, will use the polling-based watcher for filesystem changes