From 6a515c304f737fc9a06f1f81ec3d8bbd026b38bc Mon Sep 17 00:00:00 2001 From: Adam Leventhal Date: Wed, 26 Apr 2023 15:15:07 -0700 Subject: [PATCH] CLI: generate a fixed list of valid values for enum types (#442) --- progenitor-impl/src/cli.rs | 163 +++++++---- progenitor-impl/tests/output/nexus-cli.out | 313 ++++++++++++++++++--- 2 files changed, 376 insertions(+), 100 deletions(-) diff --git a/progenitor-impl/src/cli.rs b/progenitor-impl/src/cli.rs index 1e9981b..7ce6b4e 100644 --- a/progenitor-impl/src/cli.rs +++ b/progenitor-impl/src/cli.rs @@ -4,7 +4,7 @@ use heck::ToKebabCase; use openapiv3::OpenAPI; use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use typify::{TypeSpaceImpl, TypeStructPropInfo}; +use typify::{Type, TypeEnumVariant, TypeSpaceImpl, TypeStructPropInfo}; use crate::{ method::{ @@ -247,7 +247,6 @@ impl Generator { prop_type }; - let prop_type_ident = prop_type.ident(); let scalar = prop_type.has_impl(TypeSpaceImpl::FromStr); let prop_name = prop_name.to_kebab_case(); @@ -266,7 +265,7 @@ impl Generator { prop_name.clone(), required, description.map(str::to_string), - prop_type_ident.clone(), + prop_type, ) }) }) @@ -296,52 +295,34 @@ impl Generator { OperationParameterKind::Body(_) => unreachable!(), }; - let OperationParameterType::Type(arg_type_id) = ¶m.typ else { + let OperationParameterType::Type(arg_type_id) = ¶m.typ + else { panic!() }; let arg_type = self.type_space.get_type(arg_type_id).unwrap(); - let arg_details = arg_type.details(); - let arg_type_name = match &arg_details{ - typify::TypeDetails::Option(opt_id) => { - let inner_type = self.type_space.get_type(opt_id).unwrap(); - inner_type.ident() - } - _ => { - arg_type.ident() - } + + let maybe_inner_type = + if let typify::TypeDetails::Option(inner_type_id) = + arg_type.details() + { + let inner_type = + self.type_space.get_type(&inner_type_id).unwrap(); + Some(inner_type) + } else { + None + }; + let arg_type = if let Some(inner_type) = maybe_inner_type { + inner_type + } else { + arg_type }; - let help = param.description.as_ref().map(|description| { - quote! { - .help(#description) - } - }); - - quote! { - clap::Arg::new(#arg_name) - .long(#arg_name) - .required(#required) - .value_parser(clap::value_parser!(#arg_type_name)) - #help - } + clap_arg(&arg_name, required, ¶m.description, &arg_type) }); let body_args = body_params.iter().map( - |(prop_name, required, description, prop_type_ident)| { - let help = description.as_ref().map(|description| { - quote! { - .help(#description) - } - }); - quote! { - clap::Arg::new(#prop_name) - .long(#prop_name) - .required(#required) - .value_parser(clap::value_parser!( - #prop_type_ident - )) - #help - } + |(prop_name, required, description, prop_type)| { + clap_arg(prop_name, *required, description, prop_type) }, ); @@ -421,25 +402,24 @@ impl Generator { // Build up the iterator processing each body property we can handle. let body_args = - body_params - .iter() - .map(|(prop_name, _, _, prop_type_ident)| { - let prop_fn = - format_ident!("{}", sanitize(prop_name, Case::Snake)); - quote! { - if let Some(value) = - matches.get_one::<#prop_type_ident>( - #prop_name, - ) - { - // clone here in case the arg type - // doesn't impl TryFrom<&T> - request = request.body_map(|body| { - body.#prop_fn(value.clone()) - }) - } + body_params.iter().map(|(prop_name, _, _, prop_type)| { + let prop_fn = + format_ident!("{}", sanitize(prop_name, Case::Snake)); + let prop_type_ident = prop_type.ident(); + quote! { + if let Some(value) = + matches.get_one::<#prop_type_ident>( + #prop_name, + ) + { + // clone here in case the arg type + // doesn't impl TryFrom<&T> + request = request.body_map(|body| { + body.#prop_fn(value.clone()) + }) } - }); + } + }); let (_, success_type) = self.extract_responses( method, @@ -520,3 +500,68 @@ impl Generator { } } } + +fn clap_arg( + arg_name: &str, + required: bool, + description: &Option, + arg_type: &Type, +) -> TokenStream { + let help = description.as_ref().map(|description| { + quote! { + .help(#description) + } + }); + let arg_type_name = arg_type.ident(); + + // For enums that have **only** simple variants, we do some slightly + // fancier argument handling to expose the possible values. In particular, + // we use clap's `PossibleValuesParser` with each variant converted to a + // string. Then we use TypedValueParser::map to translate that into the + // actual type of the enum. + let maybe_enum_parser = + if let typify::TypeDetails::Enum(e) = arg_type.details() { + let maybe_var_names = e + .variants() + .map(|(var_name, var_details)| { + if let TypeEnumVariant::Simple = var_details { + Some(format_ident!("{}", var_name)) + } else { + None + } + }) + .collect::>>(); + + maybe_var_names.map(|var_names| { + quote! { + clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + #( #arg_type_name :: #var_names.to_string(), )* + ]), + |s| #arg_type_name :: try_from(s).unwrap() + ) + } + }) + } else { + None + }; + + let value_parser = if let Some(enum_parser) = maybe_enum_parser { + enum_parser + } else { + // Let clap pick a value parser for us. This has the benefit of + // allowing for override implementations. A generated client may + // implement ValueParserFactory for a type to create a custom parser. + quote! { + clap::value_parser!(#arg_type_name) + } + }; + + quote! { + clap::Arg::new(#arg_name) + .long(#arg_name) + .required(#required) + .value_parser(#value_parser) + #help + } +} diff --git a/progenitor-impl/tests/output/nexus-cli.out b/progenitor-impl/tests/output/nexus-cli.out index 52316a2..6fcc858 100644 --- a/progenitor-impl/tests/output/nexus-cli.out +++ b/progenitor-impl/tests/output/nexus-cli.out @@ -411,7 +411,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::IdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::IdSortMode::IdAscending.to_string() + ]), + |s| types::IdSortMode::try_from(s).unwrap(), + )), ) .about("List groups") } @@ -502,7 +507,14 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameOrIdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameOrIdSortMode::NameAscending.to_string(), + types::NameOrIdSortMode::NameDescending.to_string(), + types::NameOrIdSortMode::IdAscending.to_string(), + ]), + |s| types::NameOrIdSortMode::try_from(s).unwrap(), + )), ) .about("List organizations\n\nUse `GET /v1/organizations` instead") } @@ -624,7 +636,14 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameOrIdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameOrIdSortMode::NameAscending.to_string(), + types::NameOrIdSortMode::NameDescending.to_string(), + types::NameOrIdSortMode::IdAscending.to_string(), + ]), + |s| types::NameOrIdSortMode::try_from(s).unwrap(), + )), ) .about("List projects\n\nUse `GET /v1/projects` instead") } @@ -749,7 +768,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameSortMode::NameAscending.to_string(), + ]), + |s| types::NameSortMode::try_from(s).unwrap(), + )), ) .about("List disks\n\nUse `GET /v1/disks` instead") } @@ -862,7 +886,17 @@ impl Cli { clap::Arg::new("metric-name") .long("metric-name") .required(true) - .value_parser(clap::value_parser!(types::DiskMetricName)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::DiskMetricName::Activated.to_string(), + types::DiskMetricName::Flush.to_string(), + types::DiskMetricName::Read.to_string(), + types::DiskMetricName::ReadBytes.to_string(), + types::DiskMetricName::Write.to_string(), + types::DiskMetricName::WriteBytes.to_string(), + ]), + |s| types::DiskMetricName::try_from(s).unwrap(), + )), ) .arg( clap::Arg::new("end-time") @@ -915,7 +949,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameSortMode::NameAscending.to_string(), + ]), + |s| types::NameSortMode::try_from(s).unwrap(), + )), ) .about( "List images\n\nList images in a project. The images are returned sorted by \ @@ -1031,7 +1070,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameSortMode::NameAscending.to_string(), + ]), + |s| types::NameSortMode::try_from(s).unwrap(), + )), ) .about("List instances") } @@ -1180,7 +1224,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameSortMode::NameAscending.to_string(), + ]), + |s| types::NameSortMode::try_from(s).unwrap(), + )), ) .about("List an instance's disks\n\nUse `GET /v1/instances/{instance}/disks` instead") } @@ -1329,7 +1378,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameSortMode::NameAscending.to_string(), + ]), + |s| types::NameSortMode::try_from(s).unwrap(), + )), ) .about("List network interfaces") } @@ -1735,7 +1789,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameSortMode::NameAscending.to_string(), + ]), + |s| types::NameSortMode::try_from(s).unwrap(), + )), ) .about("List snapshots") } @@ -1851,7 +1910,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameSortMode::NameAscending.to_string(), + ]), + |s| types::NameSortMode::try_from(s).unwrap(), + )), ) .about("List VPCs") } @@ -2069,7 +2133,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameSortMode::NameAscending.to_string(), + ]), + |s| types::NameSortMode::try_from(s).unwrap(), + )), ) .about("List routers") } @@ -2245,7 +2314,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameSortMode::NameAscending.to_string(), + ]), + |s| types::NameSortMode::try_from(s).unwrap(), + )), ) .about("List routes\n\nList the routes associated with a router in a particular VPC.") } @@ -2439,7 +2513,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameSortMode::NameAscending.to_string(), + ]), + |s| types::NameSortMode::try_from(s).unwrap(), + )), ) .about("List subnets") } @@ -2638,7 +2717,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameSortMode::NameAscending.to_string(), + ]), + |s| types::NameSortMode::try_from(s).unwrap(), + )), ) .about("List network interfaces") } @@ -2692,7 +2776,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::IdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::IdSortMode::IdAscending.to_string() + ]), + |s| types::IdSortMode::try_from(s).unwrap(), + )), ) .about("Fetch the silo\u{a0}groups the current user belongs to") } @@ -2710,7 +2799,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameSortMode::NameAscending.to_string(), + ]), + |s| types::NameSortMode::try_from(s).unwrap(), + )), ) .about( "List SSH public keys\n\nLists SSH public keys for the currently authenticated \ @@ -2819,7 +2913,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameSortMode::NameAscending.to_string(), + ]), + |s| types::NameSortMode::try_from(s).unwrap(), + )), ) .about( "List system-wide certificates\n\nReturns a list of all the system-wide \ @@ -2846,7 +2945,12 @@ impl Cli { clap::Arg::new("service") .long("service") .required(true) - .value_parser(clap::value_parser!(types::ServiceUsingCertificate)) + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::ServiceUsingCertificate::ExternalApi.to_string(), + ]), + |s| types::ServiceUsingCertificate::try_from(s).unwrap(), + )) .help("The service using this certificate"), ) .about( @@ -2893,7 +2997,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::IdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::IdSortMode::IdAscending.to_string() + ]), + |s| types::IdSortMode::try_from(s).unwrap(), + )), ) .about("List physical disks") } @@ -2911,7 +3020,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::IdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::IdSortMode::IdAscending.to_string() + ]), + |s| types::IdSortMode::try_from(s).unwrap(), + )), ) .about("List racks") } @@ -2941,7 +3055,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::IdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::IdSortMode::IdAscending.to_string() + ]), + |s| types::IdSortMode::try_from(s).unwrap(), + )), ) .about("List sleds") } @@ -2978,7 +3097,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::IdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::IdSortMode::IdAscending.to_string() + ]), + |s| types::IdSortMode::try_from(s).unwrap(), + )), ) .about("List physical disks attached to sleds") } @@ -2996,7 +3120,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameSortMode::NameAscending.to_string(), + ]), + |s| types::NameSortMode::try_from(s).unwrap(), + )), ) .about( "List system-wide images\n\nReturns a list of all the system-wide images. \ @@ -3066,7 +3195,14 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameOrIdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameOrIdSortMode::NameAscending.to_string(), + types::NameOrIdSortMode::NameDescending.to_string(), + types::NameOrIdSortMode::IdAscending.to_string(), + ]), + |s| types::NameOrIdSortMode::try_from(s).unwrap(), + )), ) .about("List IP pools") } @@ -3206,7 +3342,14 @@ impl Cli { clap::Arg::new("metric-name") .long("metric-name") .required(true) - .value_parser(clap::value_parser!(types::SystemMetricName)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::SystemMetricName::VirtualDiskSpaceProvisioned.to_string(), + types::SystemMetricName::CpusProvisioned.to_string(), + types::SystemMetricName::RamProvisioned.to_string(), + ]), + |s| types::SystemMetricName::try_from(s).unwrap(), + )), ) .arg( clap::Arg::new("end-time") @@ -3267,7 +3410,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::IdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::IdSortMode::IdAscending.to_string() + ]), + |s| types::IdSortMode::try_from(s).unwrap(), + )), ) .about("List sagas") } @@ -3296,7 +3444,14 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameOrIdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameOrIdSortMode::NameAscending.to_string(), + types::NameOrIdSortMode::NameDescending.to_string(), + types::NameOrIdSortMode::IdAscending.to_string(), + ]), + |s| types::NameOrIdSortMode::try_from(s).unwrap(), + )), ) .about( "List silos\n\nLists silos that are discoverable based on the current permissions.", @@ -3335,7 +3490,13 @@ impl Cli { clap::Arg::new("identity-mode") .long("identity-mode") .required(true) - .value_parser(clap::value_parser!(types::SiloIdentityMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::SiloIdentityMode::SamlJit.to_string(), + types::SiloIdentityMode::LocalOnly.to_string(), + ]), + |s| types::SiloIdentityMode::try_from(s).unwrap(), + )), ) .arg( clap::Arg::new("name") @@ -3390,7 +3551,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameSortMode::NameAscending.to_string(), + ]), + |s| types::NameSortMode::try_from(s).unwrap(), + )), ) .about("List a silo's IDPs") } @@ -3592,7 +3758,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::IdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::IdSortMode::IdAscending.to_string() + ]), + |s| types::IdSortMode::try_from(s).unwrap(), + )), ) .about("List users in a silo") } @@ -3629,7 +3800,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameSortMode::NameAscending.to_string(), + ]), + |s| types::NameSortMode::try_from(s).unwrap(), + )), ) .about("List built-in users") } @@ -3671,7 +3847,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::IdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::IdSortMode::IdAscending.to_string() + ]), + |s| types::IdSortMode::try_from(s).unwrap(), + )), ) .about("List users") } @@ -3701,7 +3882,14 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameOrIdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameOrIdSortMode::NameAscending.to_string(), + types::NameOrIdSortMode::NameDescending.to_string(), + types::NameOrIdSortMode::IdAscending.to_string(), + ]), + |s| types::NameOrIdSortMode::try_from(s).unwrap(), + )), ) .about("List disks") } @@ -3813,7 +4001,14 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameOrIdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameOrIdSortMode::NameAscending.to_string(), + types::NameOrIdSortMode::NameDescending.to_string(), + types::NameOrIdSortMode::IdAscending.to_string(), + ]), + |s| types::NameOrIdSortMode::try_from(s).unwrap(), + )), ) .about("List instances") } @@ -3960,7 +4155,14 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameOrIdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameOrIdSortMode::NameAscending.to_string(), + types::NameOrIdSortMode::NameDescending.to_string(), + types::NameOrIdSortMode::IdAscending.to_string(), + ]), + |s| types::NameOrIdSortMode::try_from(s).unwrap(), + )), ) .about("List an instance's disks") } @@ -4214,7 +4416,14 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameOrIdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameOrIdSortMode::NameAscending.to_string(), + types::NameOrIdSortMode::NameDescending.to_string(), + types::NameOrIdSortMode::IdAscending.to_string(), + ]), + |s| types::NameOrIdSortMode::try_from(s).unwrap(), + )), ) .about("List organizations") } @@ -4322,7 +4531,14 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::NameOrIdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameOrIdSortMode::NameAscending.to_string(), + types::NameOrIdSortMode::NameDescending.to_string(), + types::NameOrIdSortMode::IdAscending.to_string(), + ]), + |s| types::NameOrIdSortMode::try_from(s).unwrap(), + )), ) .about("List projects") } @@ -4460,7 +4676,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::IdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::IdSortMode::IdAscending.to_string() + ]), + |s| types::IdSortMode::try_from(s).unwrap(), + )), ) .about("View version and update status of component tree") } @@ -4478,7 +4699,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::IdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::IdSortMode::IdAscending.to_string() + ]), + |s| types::IdSortMode::try_from(s).unwrap(), + )), ) .about("List all update deployments") } @@ -4527,7 +4753,12 @@ impl Cli { clap::Arg::new("sort-by") .long("sort-by") .required(false) - .value_parser(clap::value_parser!(types::IdSortMode)), + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::IdSortMode::IdAscending.to_string() + ]), + |s| types::IdSortMode::try_from(s).unwrap(), + )), ) .about("List all updates") }