implement descriptions for enum variants

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2020-04-29 10:42:36 +02:00
parent 693d8d39be
commit 83d9d3e165
5 changed files with 97 additions and 53 deletions

View File

@ -4,8 +4,6 @@ use anyhow::Error;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote_spanned;
use syn::punctuated::Punctuated;
use syn::Token;
use super::Schema;
use crate::serde;
@ -42,25 +40,35 @@ pub fn handle_enum(
let container_attrs = serde::ContainerAttrib::try_from(&enum_ty.attrs[..])?;
// with_capacity(enum_ty.variants.len());
// doesn't exist O.o
let mut variants = Punctuated::<syn::LitStr, Token![,]>::new();
let mut variants = TokenStream::new();
for variant in &mut enum_ty.variants {
match &variant.fields {
syn::Fields::Unit => (),
_ => bail!(variant => "api macro does not support enums with fields"),
}
let (comment, _doc_span) = util::get_doc_comments(&variant.attrs)?;
if comment.is_empty() {
bail!(variant => "enum variant needs a description");
}
let attrs = serde::SerdeAttrib::try_from(&variant.attrs[..])?;
if let Some(renamed) = attrs.rename {
variants.push(renamed.into_lit_str());
let variant_string = if let Some(renamed) = attrs.rename {
renamed.into_lit_str()
} else if let Some(rename_all) = container_attrs.rename_all {
let name = rename_all.apply_to_variant(&variant.ident.to_string());
variants.push(syn::LitStr::new(&name, variant.ident.span()));
syn::LitStr::new(&name, variant.ident.span())
} else {
let name = &variant.ident;
variants.push(syn::LitStr::new(&name.to_string(), name.span()));
}
syn::LitStr::new(&name.to_string(), name.span())
};
variants.extend(quote_spanned! { variant.ident.span() =>
::proxmox::api::schema::EnumEntry {
value: #variant_string,
description: #comment,
},
});
}
let name = &enum_ty.ident;

View File

@ -3,7 +3,7 @@
#![allow(dead_code)]
use proxmox::api::schema;
use proxmox::api::schema::{self, EnumEntry};
use proxmox_api_macro::api;
use anyhow::Error;
@ -13,7 +13,10 @@ use serde_json::Value;
#[api(
type: String,
description: "A string",
format: &schema::ApiStringFormat::Enum(&["ok", "not-ok"]),
format: &schema::ApiStringFormat::Enum(&[
EnumEntry::new("ok", "Ok"),
EnumEntry::new("not-ok", "Not OK"),
]),
)]
//#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct OkString(String);
@ -22,7 +25,10 @@ pub struct OkString(String);
fn ok_string() {
const TEST_SCHEMA: &'static ::proxmox::api::schema::Schema =
&::proxmox::api::schema::StringSchema::new("A string")
.format(&schema::ApiStringFormat::Enum(&["ok", "not-ok"]))
.format(&schema::ApiStringFormat::Enum(&[
EnumEntry::new("ok", "Ok"),
EnumEntry::new("not-ok", "Not OK"),
]))
.schema();
assert_eq!(TEST_SCHEMA, OkString::API_SCHEMA);
}
@ -107,9 +113,12 @@ fn renamed_struct() {
#[serde(rename_all = "kebab-case")]
/// A selection of either 'onekind', 'another-kind' or 'selection-number-three'.
pub enum Selection {
/// The first kind.
#[serde(rename = "onekind")]
OneKind,
/// Some other kind.
AnotherKind,
/// And yet another.
SelectionNumberThree,
}
@ -120,9 +129,9 @@ fn selection_test() {
"A selection of either \'onekind\', \'another-kind\' or \'selection-number-three\'.",
)
.format(&::proxmox::api::schema::ApiStringFormat::Enum(&[
"onekind",
"another-kind",
"selection-number-three",
EnumEntry::new("onekind", "The first kind."),
EnumEntry::new("another-kind", "Some other kind."),
EnumEntry::new("selection-number-three", "And yet another."),
]))
.schema();

View File

@ -20,18 +20,19 @@ use super::{completion::*, CliCommand, CliCommandMap, CommandLineInterface};
/// - ``json-pretty``: JSON, human readable.
///
pub const OUTPUT_FORMAT: Schema = StringSchema::new("Output format.")
.format(&ApiStringFormat::Enum(&["text", "json", "json-pretty"]))
.format(&ApiStringFormat::Enum(&[
EnumEntry::new("text", "plain text output"),
EnumEntry::new("json", "single-line json formatted output"),
EnumEntry::new("json-pretty", "pretty-printed json output"),
]))
.schema();
fn parse_arguments(
prefix: &str,
cli_cmd: &CliCommand,
args: Vec<String>,
) -> Result<Value, Error> {
let (params, remaining) =
match getopts::parse_arguments(
&args, cli_cmd.arg_param, &cli_cmd.fixed_param, &cli_cmd.info.parameters
fn parse_arguments(prefix: &str, cli_cmd: &CliCommand, args: Vec<String>) -> Result<Value, Error> {
let (params, remaining) = match getopts::parse_arguments(
&args,
cli_cmd.arg_param,
&cli_cmd.fixed_param,
&cli_cmd.info.parameters,
) {
Ok((p, r)) => (p, r),
Err(err) => {
@ -365,7 +366,10 @@ pub async fn run_async_cli_command<C: Into<CommandLineInterface>>(def: C) {
let (prefix, args) = prepare_cli_command(&def);
if handle_command_future(Arc::new(def), &prefix, args).await.is_err() {
if handle_command_future(Arc::new(def), &prefix, args)
.await
.is_err()
{
std::process::exit(-1);
}
}

View File

@ -37,12 +37,15 @@ fn get_property_completion(
}
match schema {
Schema::String(StringSchema { format: Some(format), .. }) => {
if let ApiStringFormat::Enum(list) = format {
Schema::String(StringSchema {
format: Some(format),
..
}) => {
if let ApiStringFormat::Enum(variants) = format {
let mut completions = Vec::new();
for value in list.iter() {
if value.starts_with(arg) {
completions.push((*value).to_string());
for variant in variants.iter() {
if variant.value.starts_with(arg) {
completions.push(variant.value.to_string());
}
}
return completions;

View File

@ -322,8 +322,8 @@ impl StringSchema {
bail!("value does not match the regex pattern");
}
}
ApiStringFormat::Enum(stringvec) => {
if stringvec.iter().find(|&e| *e == value) == None {
ApiStringFormat::Enum(variants) => {
if variants.iter().find(|&e| e.value == value).is_none() {
bail!("value '{}' is not defined in the enumeration.", value);
}
}
@ -503,6 +503,21 @@ pub enum Schema {
Array(ArraySchema),
}
/// A string enum entry. An enum entry must have a value and a description.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
pub struct EnumEntry {
pub value: &'static str,
pub description: &'static str,
}
impl EnumEntry {
/// Convenience method as long as we only have 2 mandatory fields in an `EnumEntry`.
pub const fn new(value: &'static str, description: &'static str) -> Self {
Self { value, description }
}
}
/// String microformat definitions.
///
/// Strings are probably the most flexible data type, and there are
@ -514,7 +529,10 @@ pub enum Schema {
///
/// ```
/// # use proxmox::api::{*, schema::*};
/// const format: ApiStringFormat = ApiStringFormat::Enum(&["vm", "ct"]);
/// const format: ApiStringFormat = ApiStringFormat::Enum(&[
/// EnumEntry::new("vm", "A guest VM run via qemu"),
/// EnumEntry::new("ct", "A guest container run via lxc"),
/// ]);
/// ```
///
/// ## Regular Expressions
@ -566,7 +584,7 @@ pub enum Schema {
/// ```
pub enum ApiStringFormat {
/// Enumerate all valid strings
Enum(&'static [&'static str]),
Enum(&'static [EnumEntry]),
/// Use a regular expression to describe valid strings.
Pattern(&'static ConstRegexPattern),
/// Use a schema to describe complex types encoded as string.
@ -579,7 +597,7 @@ impl std::fmt::Debug for ApiStringFormat {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ApiStringFormat::VerifyFn(fnptr) => write!(f, "VerifyFn({:p}", fnptr),
ApiStringFormat::Enum(strvec) => write!(f, "Enum({:?}", strvec),
ApiStringFormat::Enum(variants) => write!(f, "Enum({:?}", variants),
ApiStringFormat::Pattern(regex) => write!(f, "Pattern({:?}", regex),
ApiStringFormat::PropertyString(schema) => write!(f, "PropertyString({:?}", schema),
}
@ -874,12 +892,8 @@ pub fn verify_json_object(data: &Value, schema: &ObjectSchema) -> Result<(), Err
for (key, value) in map {
if let Some((_optional, prop_schema)) = schema.lookup(&key) {
let result = match prop_schema {
Schema::Object(object_schema) => {
verify_json_object(value, object_schema)
}
Schema::Array(array_schema) => {
verify_json_array(value, array_schema)
}
Schema::Object(object_schema) => verify_json_object(value, object_schema),
Schema::Array(array_schema) => verify_json_array(value, array_schema),
_ => verify_json(value, prop_schema),
};
if let Err(err) = result {
@ -1018,7 +1032,10 @@ fn test_query_string() {
"name",
false,
&StringSchema::new("Name.")
.format(&ApiStringFormat::Enum(&["ev1", "ev2"]))
.format(&ApiStringFormat::Enum(&[
EnumEntry::new("ev1", "desc ev1"),
EnumEntry::new("ev2", "desc ev2"),
]))
.schema(),
)],
);
@ -1163,7 +1180,10 @@ fn test_verify_function() {
#[test]
fn test_verify_complex_object() {
const NIC_MODELS: ApiStringFormat = ApiStringFormat::Enum(&["e1000", "virtio"]);
const NIC_MODELS: ApiStringFormat = ApiStringFormat::Enum(&[
EnumEntry::new("e1000", "Intel E1000"),
EnumEntry::new("virtio", "Paravirtualized ethernet device"),
]);
const PARAM_SCHEMA: Schema = ObjectSchema::new(
"Properties.",