diff --git a/rust/utils/src/cli.rs b/rust/utils/src/cli.rs index bae1b378..defe5bbf 100644 --- a/rust/utils/src/cli.rs +++ b/rust/utils/src/cli.rs @@ -1,18 +1,136 @@ +use std::fmt::Display; // SPDX-License-Identifier: MIT // // Copyright IBM Corp. 2023, 2024 - use std::io::{Read, Write}; +use std::marker::PhantomData; use std::path::{Path, PathBuf}; use std::process::ExitCode; +use std::str::FromStr; -use clap::{ArgAction, ArgGroup, Args, Command, ValueHint}; +use clap::builder::{EnumValueParser, PossibleValue, TypedValueParser}; +use clap::{Arg, ArgAction, ArgGroup, Args, Command, ValueEnum, ValueHint}; use log::{info, warn, LevelFilter}; use pv::misc::{create_file, open_file, read_certs, read_file}; use pv::request::openssl::pkey::{PKey, Public}; use pv::request::HkdVerifier; use pv::{Error, Result}; +/// Generic version selection for CLI +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AutoOrExplicit { + Auto, + Explicit(T), +} + +impl Display for AutoOrExplicit +where + T: Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AutoOrExplicit::Auto => write!(f, "auto"), + AutoOrExplicit::Explicit(version) => write!(f, "{version}"), + } + } +} + +impl AutoOrExplicit { + pub fn map(self, f: F) -> AutoOrExplicit + where + F: FnOnce(T) -> U, + { + match self { + AutoOrExplicit::Explicit(v) => AutoOrExplicit::Explicit(f(v)), + AutoOrExplicit::Auto => AutoOrExplicit::Auto, + } + } +} + +impl FromStr for AutoOrExplicit +where + T: FromStr, +{ + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + match s { + "auto" => Ok(Self::Auto), + _ => { + let v = T::from_str(s)?; + Ok(Self::Explicit(v)) + } + } + } +} + +#[derive(Clone)] +pub struct AutoOrExplicitParser { + _marker: PhantomData, +} + +impl Default for AutoOrExplicitParser { + fn default() -> Self { + Self::new() + } +} + +impl AutoOrExplicitParser { + pub fn new() -> Self { + Self { + _marker: PhantomData, + } + } +} + +impl TypedValueParser for AutoOrExplicitParser +where + T: ValueEnum + FromStr + Clone + Send + Sync + Display + 'static, + T::Err: std::fmt::Display, +{ + type Value = AutoOrExplicit; + + fn parse_ref( + &self, + cmd: &Command, + arg: Option<&Arg>, + value: &std::ffi::OsStr, + ) -> Result { + let s = value + .to_str() + .ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8).with_cmd(cmd))?; + + if s == format!("{}", Self::Value::Auto) { + Ok(Self::Value::Auto) + } else { + let parsed = s.parse::().map_err(|_e| { + let mut err = + clap::error::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd); + if let Some(arg) = arg { + err.insert( + clap::error::ContextKind::InvalidArg, + clap::error::ContextValue::String(arg.to_string()), + ); + } + err.insert( + clap::error::ContextKind::InvalidValue, + clap::error::ContextValue::String(s.to_string()), + ); + err + })?; + + Ok(Self::Value::Explicit(parsed)) + } + } + fn possible_values(&self) -> Option + '_>> { + let enum_parser = EnumValueParser::::new(); + let mut values = vec![PossibleValue::new("auto")]; + values.extend(enum_parser.possible_values()?); + + Some(Box::new(values.into_iter())) + } +} + /// CLI Argument collection for handling host-keys, IBM signing keys, and certificates. #[derive(Args, Debug, Clone, PartialEq, Eq, Default)] #[command( diff --git a/rust/utils/src/lib.rs b/rust/utils/src/lib.rs index 98e3941e..e00d6d6c 100644 --- a/rust/utils/src/lib.rs +++ b/rust/utils/src/lib.rs @@ -18,8 +18,9 @@ pub use utils_macros::{ControlFlag, ValueEnumDisplay, ValueEnumFromStr}; pub use crate::cli::{ combined_path_opt, combined_path_req, get_reader_from_cli_file_arg, - get_writer_from_cli_file_arg, print_cli_error, print_error, CertificateOptions, - DeprecatedVerbosityOptions, VerbosityOptions, STDIN, STDOUT, + get_writer_from_cli_file_arg, print_cli_error, print_error, AutoOrExplicit, + AutoOrExplicitParser, CertificateOptions, DeprecatedVerbosityOptions, VerbosityOptions, STDIN, + STDOUT, }; pub use crate::exit_code::{docstring, ExitCodeDoc, ExitCodeTrait, ExitCodeVariantDoc}; pub use crate::file::{AtomicFile, AtomicFileOperation};