api-macro: add api_get_default!() macro

When writing an #[api] function, one can now access default
values by parameter name (see test_default_option in
tests/options.rs):

    #[api(...)]
    pub fn func(value: Option<isize>) {
        println!(
            "value: {}",
            value.unwrap_or(api_get_default!("value")),
        );
    }

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2020-03-19 15:49:34 +01:00
parent 8beec0d6e6
commit f5d15872f4
2 changed files with 62 additions and 0 deletions

View File

@ -1,4 +1,5 @@
use std::convert::{TryFrom, TryInto};
use std::mem;
use failure::Error;
@ -6,6 +7,7 @@ use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::Ident;
use syn::visit_mut::{self, VisitMut};
use super::{Schema, SchemaItem};
use crate::util::{self, FieldName, JSONObject};
@ -55,6 +57,9 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result<T
&mut wrapper_ts,
)?;
// input schema is done, let's give the method body a chance to extract default parameters:
DefaultParameters(&input_schema).visit_item_fn_mut(&mut func);
let input_schema = {
let mut ts = TokenStream::new();
input_schema.to_typed_schema(&mut ts)?;
@ -433,3 +438,38 @@ fn create_wrapper_function(
Ok(api_func_name)
}
struct DefaultParameters<'a>(&'a Schema);
impl<'a> VisitMut for DefaultParameters<'a> {
fn visit_expr_mut(&mut self, i: &mut syn::Expr) {
if let syn::Expr::Macro(exprmac) = i {
if exprmac.mac.path.is_ident("api_get_default") {
// replace api_get_default macros with the actual default found in the #[api]
// macro.
match self.get_default(mem::take(&mut exprmac.mac.tokens)) {
Ok(expr) => *i = expr,
Err(err) => {
*i = syn::Expr::Verbatim(err.to_compile_error());
return;
}
}
}
}
visit_mut::visit_expr_mut(self, i)
}
}
impl<'a> DefaultParameters<'a> {
fn get_default(&self, param_tokens: TokenStream) -> Result<syn::Expr, syn::Error> {
let param_name: syn::LitStr = syn::parse2(param_tokens)?;
match self.0.find_obj_property_by_ident(&param_name.value()) {
Some((_ident, _optional, schema)) => match schema.find_schema_property("default") {
Some(def) => Ok(def.clone()),
None => bail!(param_name => "no default found in schema")
}
None => bail!(param_name => "todo"),
}
}
}

View File

@ -21,6 +21,24 @@ pub fn test_option(value: bool) -> Result<bool, Error> {
Ok(value)
}
#[api(
input: {
properties: {
value: {
description: "The optional value with default.",
optional: true,
default: 5,
}
}
}
)]
/// Print the given message.
///
/// Returns: the input.
pub fn test_default_macro(value: Option<isize>) -> Result<isize, Error> {
Ok(value.unwrap_or(api_get_default!("value")))
}
struct RpcEnv;
impl proxmox::api::RpcEnvironment for RpcEnv {
fn set_result_attrib(&mut self, name: &str, value: Value) {
@ -65,4 +83,8 @@ fn test_invocations() {
let value = api_function_test_option(json!({"value": false}), &API_METHOD_TEST_OPTION, &mut env)
.expect("func with option should work");
assert_eq!(value, false);
let value = api_function_test_default_macro(json!({}), &API_METHOD_TEST_DEFAULT_MACRO, &mut env)
.expect("func with option should work");
assert_eq!(value, 5);
}