first succesful CLI test

Signed-off-by: Wolfgang Bumiller <wry.git@bumiller.com>
This commit is contained in:
Wolfgang Bumiller 2019-06-16 00:07:20 +02:00 committed by Wolfgang Bumiller
parent 691af5cade
commit ed3b7de2fd
3 changed files with 50 additions and 20 deletions

View File

@ -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
),
}
}
}

View File

@ -9,6 +9,8 @@ use serde_json::Value;
use super::{ApiMethodInfo, ApiOutput, Parameter};
type MethodInfoRef<Body> = &'static (dyn ApiMethodInfo<Body> + Send + Sync);
/// A CLI root node.
pub struct App<Body: 'static> {
name: &'static str,
@ -55,12 +57,20 @@ impl<Body: 'static> App<Body> {
}
}
pub fn resolve(&self, args: &[&str]) -> Result<ApiOutput<Body>, Error> {
/// Resolve a list of parameters to a method and a parameter json value.
pub fn resolve(&self, args: &[&str]) -> Result<(MethodInfoRef<Body>, 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<Body> {
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<Body: 'static> Command<Body> {
Command::SubCommands(SubCommands::new())
}
fn resolve(&self, args: std::slice::Iter<&str>) -> Result<ApiOutput<Body>, Error> {
fn resolve(&self, args: std::slice::Iter<&str>) -> Result<(MethodInfoRef<Body>, Value), Error> {
match self {
Command::Method(method) => method.resolve(args),
Command::SubCommands(subcmd) => subcmd.resolve(args),
@ -120,7 +130,10 @@ impl<Body: 'static> SubCommands<Body> {
self
}
fn resolve(&self, mut args: std::slice::Iter<&str>) -> Result<ApiOutput<Body>, Error> {
fn resolve(
&self,
mut args: std::slice::Iter<&str>,
) -> Result<(MethodInfoRef<Body>, Value), Error> {
match args.next() {
None => bail!("missing subcommand"),
Some(arg) => match self.commands.get(arg) {
@ -139,7 +152,7 @@ impl<Body: 'static> SubCommands<Body> {
// 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<Body: 'static> {
pub method: &'static (dyn ApiMethodInfo<Body> + Send + Sync),
pub method: MethodInfoRef<Body>,
pub positional_args: &'static [&'static str],
//pub formatter: Option<()>, // TODO: output formatter
}
@ -156,7 +169,10 @@ impl<Body: 'static> Method<Body> {
}
}
fn resolve(&self, mut args: std::slice::Iter<&str>) -> Result<ApiOutput<Body>, Error> {
fn resolve(
&self,
mut args: std::slice::Iter<&str>,
) -> Result<(MethodInfoRef<Body>, Value), Error> {
let mut params = serde_json::Map::new();
let mut positionals = self.positional_args.iter();
@ -210,7 +226,7 @@ impl<Body: 'static> Method<Body> {
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<T> ParseCli for T {
default fn parse_cli(name: &str, _value: Option<&str>) -> Result<Value, Error> {
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<Value, Error> {
// 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<Value, Error> {
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<Value, Error> {
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(),
))
}
}

View File

@ -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<Bytes> {
let foo = value["foo"].as_str().unwrap();