diff --git a/proxmox-api/src/api_type.rs b/proxmox-api/src/api_type.rs index 23e12741..f5c238e5 100644 --- a/proxmox-api/src/api_type.rs +++ b/proxmox-api/src/api_type.rs @@ -49,7 +49,10 @@ impl Parameter { let info = (self.type_info)(); match info.parse_cli { Some(func) => func(name, value), - None => bail!("cannot parse parameter '{}' as command line parameter", name), + None => bail!( + "cannot parse parameter '{}' as command line parameter", + name + ), } } } diff --git a/proxmox-api/src/cli.rs b/proxmox-api/src/cli.rs index 11c84424..4ac4b6af 100644 --- a/proxmox-api/src/cli.rs +++ b/proxmox-api/src/cli.rs @@ -9,6 +9,8 @@ use serde_json::Value; use super::{ApiMethodInfo, ApiOutput, Parameter}; +type MethodInfoRef = &'static (dyn ApiMethodInfo + Send + Sync); + /// A CLI root node. pub struct App { name: &'static str, @@ -55,12 +57,20 @@ impl App { } } - pub fn resolve(&self, args: &[&str]) -> Result, Error> { + /// Resolve a list of parameters to a method and a parameter json value. + pub fn resolve(&self, args: &[&str]) -> Result<(MethodInfoRef, Value), Error> { self.command .as_ref() .ok_or_else(|| format_err!("no commands available"))? .resolve(args.iter()) } + + /// Run a command through this command line interface. + pub fn run(&self, args: &[&str]) -> ApiOutput { + let (method, params) = self.resolve(args)?; + let handler = method.handler(); + futures::executor::block_on(handler(params)) + } } /// A node in the CLI command router. This is either @@ -83,7 +93,7 @@ impl Command { Command::SubCommands(SubCommands::new()) } - fn resolve(&self, args: std::slice::Iter<&str>) -> Result, Error> { + fn resolve(&self, args: std::slice::Iter<&str>) -> Result<(MethodInfoRef, Value), Error> { match self { Command::Method(method) => method.resolve(args), Command::SubCommands(subcmd) => subcmd.resolve(args), @@ -120,7 +130,10 @@ impl SubCommands { self } - fn resolve(&self, mut args: std::slice::Iter<&str>) -> Result, Error> { + fn resolve( + &self, + mut args: std::slice::Iter<&str>, + ) -> Result<(MethodInfoRef, Value), Error> { match args.next() { None => bail!("missing subcommand"), Some(arg) => match self.commands.get(arg) { @@ -139,7 +152,7 @@ impl SubCommands { // XXX: If we want optional positional parameters - should we make an enum or just say the // parameter name should have brackets around it? pub struct Method { - pub method: &'static (dyn ApiMethodInfo + Send + Sync), + pub method: MethodInfoRef, pub positional_args: &'static [&'static str], //pub formatter: Option<()>, // TODO: output formatter } @@ -156,7 +169,10 @@ impl Method { } } - fn resolve(&self, mut args: std::slice::Iter<&str>) -> Result, Error> { + fn resolve( + &self, + mut args: std::slice::Iter<&str>, + ) -> Result<(MethodInfoRef, Value), Error> { let mut params = serde_json::Map::new(); let mut positionals = self.positional_args.iter(); @@ -210,7 +226,7 @@ impl Method { bail!("missing positional parameters: {}", missing); } - unreachable!(); + Ok((self.method, Value::Object(params))) } /// This should insert the parameter 'arg' with value 'value' into 'params'. @@ -276,7 +292,10 @@ pub trait ParseCli { // Saves us another mass impl macro such as the one below: impl ParseCli for T { default fn parse_cli(name: &str, _value: Option<&str>) -> Result { - bail!("invalid type for command line interface found for parameter '{}'", name); + bail!( + "invalid type for command line interface found for parameter '{}'", + name + ); } } @@ -300,24 +319,29 @@ impl_parse_cli_from_str! {isize, usize, i64, u64, i32, u32, i16, u16, i8, u8, f6 impl ParseCli for Value { fn parse_cli(name: &str, _value: Option<&str>) -> Result { // FIXME: we could of course allow generic json parameters...? - bail!("found generic json parameter ('{}') in command line...", name); + bail!( + "found generic json parameter ('{}') in command line...", + name + ); } } impl ParseCli for &str { fn parse_cli(name: &str, value: Option<&str>) -> Result { - Ok(Value::String(value - .ok_or_else(|| format_err!("missing value for parameter '{}'", name))? - .to_string() + Ok(Value::String( + value + .ok_or_else(|| format_err!("missing value for parameter '{}'", name))? + .to_string(), )) } } impl ParseCli for String { fn parse_cli(name: &str, value: Option<&str>) -> Result { - Ok(Value::String(value - .ok_or_else(|| format_err!("missing value for parameter '{}'", name))? - .to_string() + Ok(Value::String( + value + .ok_or_else(|| format_err!("missing value for parameter '{}'", name))? + .to_string(), )) } } diff --git a/proxmox-api/tests/cli.rs b/proxmox-api/tests/cli.rs index 0cb8809a..515623b2 100644 --- a/proxmox-api/tests/cli.rs +++ b/proxmox-api/tests/cli.rs @@ -16,19 +16,22 @@ fn simple() { "newboth", cli::Command::method(simple_method, &["foo", "bar"]), ); + + let result = cli + .run(&["new", "--foo=FOO", "--bar=BAR"]) + .expect("command should execute successfully"); + let body = + std::str::from_utf8(result.body().as_ref()).expect("expected a valid utf8 repsonse body"); + assert_eq!(body, "FOO:BAR"); } mod methods { use bytes::Bytes; - use failure::{bail, Error}; use http::Response; use lazy_static::lazy_static; - use serde_derive::{Deserialize, Serialize}; use serde_json::Value; - use proxmox_api::{ - get_type_info, ApiFuture, ApiMethod, ApiOutput, ApiType, Parameter, TypeInfo, - }; + use proxmox_api::{get_type_info, ApiFuture, ApiMethod, ApiOutput, ApiType, Parameter}; pub async fn simple_method(value: Value) -> ApiOutput { let foo = value["foo"].as_str().unwrap();