macro: body type support for router and api macros

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2019-06-10 20:05:20 +02:00
parent 10451f65eb
commit 7db28deaff
3 changed files with 43 additions and 23 deletions

View File

@ -74,11 +74,14 @@ fn handle_function(
value: false,
});
//let mut body_type = definition
// .remove("body")
// .map(|v| v.expect_tt())
// .transpose()?
// .unwrap_or_else(|| quote! { ::hyper::Body });
let body_type = definition
.remove("body")
.map(|v| v.expect_type())
.transpose()?
.map_or_else(
|| quote! { ::hyper::Body },
|v| v.into_token_stream(),
);
let vis = std::mem::replace(&mut item.vis, syn::Visibility::Inherited);
let span = item.ident.span();
@ -173,8 +176,12 @@ fn handle_function(
// perform the serialization/http::Response-building automatically.
// (Alternatively we could do exactly that with a trait so we don't have to parse the
// return type?)
fn wrapped_api_handler(args: ::serde_json::Value) -> ::proxmox::api::ApiFuture {
async fn handler(mut args: ::serde_json::Value) -> ::proxmox::api::ApiOutput {
fn wrapped_api_handler(
args: ::serde_json::Value,
) -> ::proxmox::api::ApiFuture<#body_type> {
async fn handler(
mut args: ::serde_json::Value,
) -> ::proxmox::api::ApiOutput<#body_type> {
let mut empty_args = ::serde_json::map::Map::new();
let args = args.as_object_mut()
.unwrap_or(&mut empty_args);
@ -243,10 +250,10 @@ fn handle_function(
// Unfortunately we cannot return the actual function since that won't work for
// `async fn`, since an `async fn` cannot appear as a return type :(
impl ::std::ops::Deref for #struct_name {
type Target = fn(#inputs) -> ::proxmox::api::ApiFuture;
type Target = fn(#inputs) -> ::proxmox::api::ApiFuture<#body_type>;
fn deref(&self) -> &Self::Target {
const FUNC: fn(#inputs) -> ::proxmox::api::ApiFuture = |#inputs| {
const FUNC: fn(#inputs) -> ::proxmox::api::ApiFuture<#body_type> = |#inputs| {
#struct_name::#impl_ident(#passed_args)
};
&FUNC
@ -261,7 +268,7 @@ fn handle_function(
//
// Note that technically we don't need the `description` member in this trait, as this is
// mostly used at compile time for documentation!
impl ::proxmox::api::ApiMethodInfo for #struct_name {
impl ::proxmox::api::ApiMethodInfo<#body_type> for #struct_name {
fn description(&self) -> &'static str {
#fn_api_description
}
@ -283,7 +290,7 @@ fn handle_function(
#fn_api_reload_timezone
}
fn handler(&self) -> fn(::serde_json::Value) -> ::proxmox::api::ApiFuture {
fn handler(&self) -> fn(::serde_json::Value) -> ::proxmox::api::ApiFuture<#body_type> {
#struct_name::wrapped_api_handler
}
}

View File

@ -3,7 +3,7 @@ use std::collections::HashMap;
use proc_macro2::{Delimiter, Ident, Span, TokenStream, TokenTree};
use failure::{bail, Error};
use quote::quote;
use quote::{quote, quote_spanned};
use syn::LitStr;
use super::parsing::*;
@ -20,11 +20,20 @@ pub fn router_macro(input: TokenStream) -> Result<TokenStream, Error> {
match_keyword(&mut input, "static")?;
let router_name = need_ident(&mut input)?;
match_colon(&mut input)?;
match_keyword(&mut input, "Router");
match_punct(&mut input, '<')?;
let body_type = need_ident(&mut input)?;
match_punct(&mut input, '>')?;
match_punct(&mut input, '=')?;
let content = need_group(&mut input, Delimiter::Brace)?;
let router = parse_router(content.stream().into_iter().peekable())?;
let router = router.into_token_stream(Some(router_name));
let router = router.into_token_stream(&body_type, Some(router_name));
//eprintln!("{}", router.to_string());
out.extend(router);
@ -166,11 +175,11 @@ impl Router {
Ok(())
}
fn into_token_stream(self, name: Option<Ident>) -> TokenStream {
fn into_token_stream(self, body_type: &Ident, name: Option<Ident>) -> TokenStream {
use std::iter::FromIterator;
let mut out = quote! {
::proxmox::api::Router::new()
let mut out = quote_spanned! {
body_type.span() => ::proxmox::api::Router::<#body_type>::new()
};
fn add_method(out: &mut TokenStream, name: &'static str, func_name: Ident) {
@ -196,14 +205,14 @@ impl Router {
match self.subroute {
None => (),
Some(SubRoute::Parameter(name, router)) => {
let router = router.into_token_stream(None);
let router = router.into_token_stream(body_type, None);
out.extend(quote! {
.parameter_subdir(#name, #router)
});
}
Some(SubRoute::Directories(hash)) => {
for (name, router) in hash {
let router = router.into_token_stream(None);
let router = router.into_token_stream(body_type, None);
out.extend(quote! {
.subdir(#name, #router)
});
@ -224,12 +233,12 @@ impl Router {
quote! {
#[allow(non_camel_case_types)]
struct #type_name(
std::cell::Cell<Option<::proxmox::api::Router>>,
std::cell::Cell<Option<::proxmox::api::Router<#body_type>>>,
std::sync::Once,
);
unsafe impl Sync for #type_name {}
impl std::ops::Deref for #type_name {
type Target = ::proxmox::api::Router;
type Target = ::proxmox::api::Router<#body_type>;
fn deref(&self) -> &Self::Target {
self.1.call_once(|| unsafe {
self.0.set(Some(#router_expression));

View File

@ -1,5 +1,6 @@
#![feature(async_await)]
use bytes::Bytes;
use failure::{bail, Error};
use serde_derive::{Deserialize, Serialize};
use serde_json::Value;
@ -42,6 +43,7 @@ pub struct Person {
}
#[api({
body: Bytes,
description: "A test function returning a fixed text",
parameters: {},
})]
@ -50,6 +52,7 @@ async fn test_body() -> Result<&'static str, Error> {
}
#[api({
body: Bytes,
description: "Loopback the `input` parameter",
parameters: {
input: "the input",
@ -60,20 +63,21 @@ async fn get_loopback(param: String) -> Result<String, Error> {
}
#[api({
body: Bytes,
description: "Loopback the `input` parameter",
parameters: {
input: "the input",
},
returns: String
})]
fn non_async_test(param: String) -> proxmox::api::ApiFuture {
fn non_async_test(param: String) -> proxmox::api::ApiFuture<Bytes> {
Box::pin((async move || {
proxmox::api::IntoApiOutput::into_api_output(param)
})())
}
proxmox_api_macro::router! {
static TEST_ROUTER = {
static TEST_ROUTER: Router<Bytes> = {
GET: test_body,
/subdir: { GET: test_body },
@ -95,7 +99,7 @@ proxmox_api_macro::router! {
};
}
fn check_body(router: &Router, path: &str, expect: &'static str) {
fn check_body(router: &Router<Bytes>, path: &str, expect: &'static str) {
let (router, parameters) = router
.lookup(path)
.expect("expected method to exist on test router");