forked from proxmox-mirrors/proxmox
first succesful CLI test
Signed-off-by: Wolfgang Bumiller <wry.git@bumiller.com>
This commit is contained in:
parent
691af5cade
commit
ed3b7de2fd
@ -49,7 +49,10 @@ impl Parameter {
|
|||||||
let info = (self.type_info)();
|
let info = (self.type_info)();
|
||||||
match info.parse_cli {
|
match info.parse_cli {
|
||||||
Some(func) => func(name, value),
|
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
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ use serde_json::Value;
|
|||||||
|
|
||||||
use super::{ApiMethodInfo, ApiOutput, Parameter};
|
use super::{ApiMethodInfo, ApiOutput, Parameter};
|
||||||
|
|
||||||
|
type MethodInfoRef<Body> = &'static (dyn ApiMethodInfo<Body> + Send + Sync);
|
||||||
|
|
||||||
/// A CLI root node.
|
/// A CLI root node.
|
||||||
pub struct App<Body: 'static> {
|
pub struct App<Body: 'static> {
|
||||||
name: &'static str,
|
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
|
self.command
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| format_err!("no commands available"))?
|
.ok_or_else(|| format_err!("no commands available"))?
|
||||||
.resolve(args.iter())
|
.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
|
/// A node in the CLI command router. This is either
|
||||||
@ -83,7 +93,7 @@ impl<Body: 'static> Command<Body> {
|
|||||||
Command::SubCommands(SubCommands::new())
|
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 {
|
match self {
|
||||||
Command::Method(method) => method.resolve(args),
|
Command::Method(method) => method.resolve(args),
|
||||||
Command::SubCommands(subcmd) => subcmd.resolve(args),
|
Command::SubCommands(subcmd) => subcmd.resolve(args),
|
||||||
@ -120,7 +130,10 @@ impl<Body: 'static> SubCommands<Body> {
|
|||||||
self
|
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() {
|
match args.next() {
|
||||||
None => bail!("missing subcommand"),
|
None => bail!("missing subcommand"),
|
||||||
Some(arg) => match self.commands.get(arg) {
|
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
|
// XXX: If we want optional positional parameters - should we make an enum or just say the
|
||||||
// parameter name should have brackets around it?
|
// parameter name should have brackets around it?
|
||||||
pub struct Method<Body: 'static> {
|
pub struct Method<Body: 'static> {
|
||||||
pub method: &'static (dyn ApiMethodInfo<Body> + Send + Sync),
|
pub method: MethodInfoRef<Body>,
|
||||||
pub positional_args: &'static [&'static str],
|
pub positional_args: &'static [&'static str],
|
||||||
//pub formatter: Option<()>, // TODO: output formatter
|
//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 params = serde_json::Map::new();
|
||||||
let mut positionals = self.positional_args.iter();
|
let mut positionals = self.positional_args.iter();
|
||||||
|
|
||||||
@ -210,7 +226,7 @@ impl<Body: 'static> Method<Body> {
|
|||||||
bail!("missing positional parameters: {}", missing);
|
bail!("missing positional parameters: {}", missing);
|
||||||
}
|
}
|
||||||
|
|
||||||
unreachable!();
|
Ok((self.method, Value::Object(params)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This should insert the parameter 'arg' with value 'value' into '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:
|
// Saves us another mass impl macro such as the one below:
|
||||||
impl<T> ParseCli for T {
|
impl<T> ParseCli for T {
|
||||||
default fn parse_cli(name: &str, _value: Option<&str>) -> Result<Value, Error> {
|
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 {
|
impl ParseCli for Value {
|
||||||
fn parse_cli(name: &str, _value: Option<&str>) -> Result<Value, Error> {
|
fn parse_cli(name: &str, _value: Option<&str>) -> Result<Value, Error> {
|
||||||
// FIXME: we could of course allow generic json parameters...?
|
// 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 {
|
impl ParseCli for &str {
|
||||||
fn parse_cli(name: &str, value: Option<&str>) -> Result<Value, Error> {
|
fn parse_cli(name: &str, value: Option<&str>) -> Result<Value, Error> {
|
||||||
Ok(Value::String(value
|
Ok(Value::String(
|
||||||
|
value
|
||||||
.ok_or_else(|| format_err!("missing value for parameter '{}'", name))?
|
.ok_or_else(|| format_err!("missing value for parameter '{}'", name))?
|
||||||
.to_string()
|
.to_string(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParseCli for String {
|
impl ParseCli for String {
|
||||||
fn parse_cli(name: &str, value: Option<&str>) -> Result<Value, Error> {
|
fn parse_cli(name: &str, value: Option<&str>) -> Result<Value, Error> {
|
||||||
Ok(Value::String(value
|
Ok(Value::String(
|
||||||
|
value
|
||||||
.ok_or_else(|| format_err!("missing value for parameter '{}'", name))?
|
.ok_or_else(|| format_err!("missing value for parameter '{}'", name))?
|
||||||
.to_string()
|
.to_string(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,19 +16,22 @@ fn simple() {
|
|||||||
"newboth",
|
"newboth",
|
||||||
cli::Command::method(simple_method, &["foo", "bar"]),
|
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 {
|
mod methods {
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use failure::{bail, Error};
|
|
||||||
use http::Response;
|
use http::Response;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use proxmox_api::{
|
use proxmox_api::{get_type_info, ApiFuture, ApiMethod, ApiOutput, ApiType, Parameter};
|
||||||
get_type_info, ApiFuture, ApiMethod, ApiOutput, ApiType, Parameter, TypeInfo,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn simple_method(value: Value) -> ApiOutput<Bytes> {
|
pub async fn simple_method(value: Value) -> ApiOutput<Bytes> {
|
||||||
let foo = value["foo"].as_str().unwrap();
|
let foo = value["foo"].as_str().unwrap();
|
||||||
|
Loading…
Reference in New Issue
Block a user