mirror of
https://git.proxmox.com/git/proxmox
synced 2025-08-02 19:17:26 +00:00
api-macro: start actually extracting parameters
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
d02a8f4e42
commit
881df81976
@ -1,7 +1,6 @@
|
||||
extern crate proc_macro;
|
||||
extern crate proc_macro2;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::mem;
|
||||
|
||||
@ -435,7 +434,7 @@ enum ParameterType<'a> {
|
||||
|
||||
fn handle_function_signature(
|
||||
input_schema: &mut Schema,
|
||||
_returns_schema: &mut Schema,
|
||||
returns_schema: &mut Schema,
|
||||
func: &mut syn::ItemFn,
|
||||
wrapper_ts: &mut TokenStream,
|
||||
) -> Result<Ident, Error> {
|
||||
@ -447,9 +446,9 @@ fn handle_function_signature(
|
||||
|
||||
let mut api_method_param = None;
|
||||
let mut rpc_env_param = None;
|
||||
let mut value_param = None;
|
||||
|
||||
let mut inputs = HashMap::<SimpleIdent, ParameterType>::new();
|
||||
let mut need_wrapper = false;
|
||||
let mut param_list = Vec::<(SimpleIdent, ParameterType)>::new();
|
||||
|
||||
// Go through the function signature to figure out whether we need to create an internal
|
||||
// wrapping function.
|
||||
@ -496,61 +495,45 @@ fn handle_function_signature(
|
||||
let param_type = if let Some((optional, schema)) =
|
||||
input_schema.find_object_property(&pat.ident.to_string())
|
||||
{
|
||||
need_wrapper = true;
|
||||
// Found an explicit parameter: extract it:
|
||||
ParameterType::Other(&pat_type.ty, optional, schema)
|
||||
} else if is_api_method_type(&pat_type.ty) {
|
||||
if api_method_param.is_some() {
|
||||
bail!(pat_type => "multiple ApiMethod parameters found");
|
||||
}
|
||||
api_method_param = Some(inputs.len());
|
||||
api_method_param = Some(param_list.len());
|
||||
ParameterType::ApiMethod
|
||||
} else if is_rpc_env_type(&pat_type.ty) {
|
||||
if rpc_env_param.is_some() {
|
||||
bail!(pat_type => "multiple RpcEnvironment parameters found");
|
||||
}
|
||||
rpc_env_param = Some(inputs.len());
|
||||
rpc_env_param = Some(param_list.len());
|
||||
ParameterType::RpcEnv
|
||||
} else if is_value_type(&pat_type.ty) {
|
||||
if value_param.is_some() {
|
||||
bail!(pat_type => "multiple additional Value parameters found");
|
||||
}
|
||||
value_param = Some(param_list.len());
|
||||
ParameterType::Value
|
||||
} else {
|
||||
bail!(&pat.ident => "unexpected parameter");
|
||||
};
|
||||
|
||||
if inputs
|
||||
.insert(pat.ident.clone().into(), param_type)
|
||||
.is_some()
|
||||
{
|
||||
bail!(&pat.ident => "duplicate parameter");
|
||||
}
|
||||
param_list.push((pat.ident.clone().into(), param_type));
|
||||
}
|
||||
|
||||
let func_name = &sig.ident;
|
||||
|
||||
if (inputs.len(), api_method_param, rpc_env_param) == (3, Some(1), Some(2)) {
|
||||
// No wrapper needed:
|
||||
return Ok(func_name.clone());
|
||||
// If our function has the correct signature we may not even need a wrapper:
|
||||
if (
|
||||
param_list.len(),
|
||||
value_param,
|
||||
api_method_param,
|
||||
rpc_env_param,
|
||||
) == (3, Some(0), Some(1), Some(2))
|
||||
{
|
||||
return Ok(sig.ident.clone());
|
||||
}
|
||||
|
||||
let api_func_name = Ident::new(
|
||||
&format!("api_function_{}", func_name),
|
||||
func.sig.ident.span(),
|
||||
);
|
||||
|
||||
let mut args = TokenStream::new();
|
||||
|
||||
// build the wrapping function:
|
||||
wrapper_ts.extend(quote! {
|
||||
fn #api_func_name(
|
||||
param: ::serde_json::Value,
|
||||
info: &::proxmox::api::ApiMethod,
|
||||
rpcenv: &mut dyn ::proxmox::api::RpcEnvironment,
|
||||
) -> Result<::serde_json::Value, ::failure::Error> {
|
||||
#func_name(param, info, rpcenv)
|
||||
}
|
||||
});
|
||||
|
||||
return Ok(api_func_name);
|
||||
create_wrapper_function(input_schema, returns_schema, param_list, func, wrapper_ts)
|
||||
}
|
||||
|
||||
fn is_api_method_type(ty: &syn::Type) -> bool {
|
||||
@ -592,3 +575,77 @@ fn is_value_type(ty: &syn::Type) -> bool {
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn create_wrapper_function(
|
||||
_input_schema: &Schema,
|
||||
_returns_schema: &Schema,
|
||||
param_list: Vec<(SimpleIdent, ParameterType)>,
|
||||
func: &syn::ItemFn,
|
||||
wrapper_ts: &mut TokenStream,
|
||||
) -> Result<Ident, Error> {
|
||||
let api_func_name = Ident::new(
|
||||
&format!("api_function_{}", &func.sig.ident),
|
||||
func.sig.ident.span(),
|
||||
);
|
||||
|
||||
let mut body = TokenStream::new();
|
||||
let mut args = TokenStream::new();
|
||||
|
||||
for (name, param) in param_list {
|
||||
let span = name.span();
|
||||
match param {
|
||||
ParameterType::Value => args.extend(quote_spanned! { span => input_params, }),
|
||||
ParameterType::ApiMethod => args.extend(quote_spanned! { span => api_method_param, }),
|
||||
ParameterType::RpcEnv => args.extend(quote_spanned! { span => rpc_env_param, }),
|
||||
ParameterType::Other(_ty, optional, _schema) => {
|
||||
let name_str = syn::LitStr::new(&name.to_string(), span);
|
||||
let arg_name = Ident::new(&format!("input_arg_{}", name), span);
|
||||
|
||||
// Optional parameters are expected to be Option<> types in the real function
|
||||
// signature, so we can just keep the returned Option from `input_map.remove()`.
|
||||
body.extend(quote_spanned! { span =>
|
||||
let #arg_name = input_map
|
||||
.remove(#name_str)
|
||||
.map(::serde_json::from_value)
|
||||
.transpose()?;
|
||||
});
|
||||
if !optional {
|
||||
// Non-optional types need to be extracted out of the option though:
|
||||
//
|
||||
// Whether the parameter is optional should have been verified by the schema
|
||||
// verifier already, so here we just use failure::bail! instead of building a
|
||||
// proper http error!
|
||||
body.extend(quote_spanned! { span =>
|
||||
let #arg_name = match #arg_name {
|
||||
Some(v) => v,
|
||||
None => ::failure::bail!(
|
||||
"missing non-optional parameter: {}",
|
||||
#name_str,
|
||||
),
|
||||
};
|
||||
});
|
||||
}
|
||||
args.extend(quote_spanned! { span => #arg_name, });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// build the wrapping function:
|
||||
let func_name = &func.sig.ident;
|
||||
wrapper_ts.extend(quote! {
|
||||
fn #api_func_name(
|
||||
mut input_params: ::serde_json::Value,
|
||||
api_method_param: &::proxmox::api::ApiMethod,
|
||||
rpc_env_param: &mut dyn ::proxmox::api::RpcEnvironment,
|
||||
) -> Result<::serde_json::Value, ::failure::Error> {
|
||||
if let Value::Object(ref mut input_map) = &mut input_params {
|
||||
#body
|
||||
#func_name(#args)
|
||||
} else {
|
||||
::failure::bail!("api function wrapper called with a non-object json value");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Ok(api_func_name);
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use proxmox::api::{ApiMethod, RpcEnvironment};
|
||||
use proxmox_api_macro::api;
|
||||
|
||||
use failure::Error;
|
||||
@ -44,3 +43,44 @@ use serde_json::Value;
|
||||
fn create_ticket(_param: Value) -> Result<Value, Error> {
|
||||
panic!("implement me");
|
||||
}
|
||||
|
||||
#[api(
|
||||
input: {
|
||||
properties: {
|
||||
username: {
|
||||
type: String,
|
||||
description: "User name",
|
||||
max_length: 64,
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
description: "The secret password or a valid ticket.",
|
||||
},
|
||||
}
|
||||
},
|
||||
returns: {
|
||||
properties: {
|
||||
"username": {
|
||||
type: String,
|
||||
description: "User name.",
|
||||
},
|
||||
"ticket": {
|
||||
type: String,
|
||||
description: "Auth ticket.",
|
||||
},
|
||||
"CSRFPreventionToken": {
|
||||
type: String,
|
||||
description: "Cross Site Request Forgerty Prevention Token.",
|
||||
},
|
||||
},
|
||||
},
|
||||
protected: true,
|
||||
)]
|
||||
/// Create or verify authentication ticket.
|
||||
///
|
||||
/// Returns: A ticket.
|
||||
fn create_ticket_direct(username: String, password: String) -> Result<Value, Error> {
|
||||
let _ = username;
|
||||
let _ = password;
|
||||
panic!("implement me");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user