api-macro: experimental enum support

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2019-11-28 12:21:46 +01:00
parent 6818cf76c9
commit 30a1c0b9ae
5 changed files with 136 additions and 7 deletions

View File

@ -10,6 +10,7 @@ use syn::{ExprPath, Ident};
use crate::util::{JSONObject, JSONValue};
mod enums;
mod method;
mod structs;
@ -356,6 +357,7 @@ pub(crate) fn api(attr: TokenStream, item: TokenStream) -> Result<TokenStream, E
match item {
syn::Item::Fn(item) => method::handle_method(attribs, item),
syn::Item::Struct(item) => structs::handle_struct(attribs, item),
syn::Item::Enum(item) => enums::handle_enum(attribs, item),
_ => bail!(item => "api macro only works on functions"),
}
}

View File

@ -0,0 +1,92 @@
use std::convert::TryInto;
use std::mem;
use failure::Error;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, quote_spanned};
use syn::punctuated::Punctuated;
use syn::Token;
use super::Schema;
use crate::util::{JSONObject, JSONValue, SimpleIdent};
/// Enums, provided they're simple enums, simply get an enum string schema attached to them.
pub fn handle_enum(
mut attribs: JSONObject,
mut enum_ty: syn::ItemEnum,
) -> Result<TokenStream, Error> {
if !attribs.contains_key("type") {
attribs.insert(
SimpleIdent::new("type".to_string(), Span::call_site()),
JSONValue::new_ident(Ident::new("String", enum_ty.enum_token.span)),
);
}
if let Some(fmt) = attribs.get("format") {
bail!(fmt.span(), "illegal key 'format', will be autogenerated");
}
let schema = {
let schema: Schema = attribs.try_into()?;
let mut ts = TokenStream::new();
schema.to_typed_schema(&mut ts)?;
ts
};
// with_capacity(enum_ty.variants.len());
// doesn't exist O.o
let mut variants = Punctuated::<syn::LitStr, Token![,]>::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 mut renamed = false;
for attrib in &mut variant.attrs {
if !attrib.path.is_ident("api") {
continue;
}
attrib.path = syn::parse2(quote! { serde })?;
let mut obj: JSONObject =
syn::parse2(mem::replace(&mut attrib.tokens, TokenStream::new()))?;
match obj.remove("rename") {
Some(JSONValue::Expr(syn::Expr::Lit(lit))) => {
if let syn::Lit::Str(lit) = lit.lit {
attrib.tokens.extend(quote! { rename = #lit });
variants.push(lit);
renamed = true;
} else {
bail!(attrib => "'rename' must be a literal string");
}
}
Some(_) => bail!(attrib => "'rename' must be a literal string"),
None => (),
}
if !obj.is_empty() {
bail!(attrib => "unknown fields in attribute");
}
}
if !renamed {
let name = &variant.ident;
variants.push(syn::LitStr::new(&name.to_string(), name.span()));
}
}
let name = &enum_ty.ident;
Ok(quote_spanned! { name.span() =>
#enum_ty
impl #name {
pub const API_SCHEMA: &'static ::proxmox::api::schema::Schema =
& #schema
.format(&::proxmox::api::schema::ApiStringFormat::Enum(&[#variants]))
.schema();
}
})
}

View File

@ -8,10 +8,6 @@ use quote::quote_spanned;
use super::Schema;
use crate::util::JSONObject;
/// Parse `input`, `returns` and `protected` attributes out of an function annotated
/// with an `#[api]` attribute and produce a `const ApiMethod` named after the function.
///
/// See the top level macro documentation for a complete example.
pub fn handle_struct(attribs: JSONObject, stru: syn::ItemStruct) -> Result<TokenStream, Error> {
let schema = {
let schema: Schema = attribs.try_into()?;

View File

@ -14,9 +14,9 @@ use syn::Token;
pub struct SimpleIdent(Ident, String);
impl SimpleIdent {
//pub fn new(name: String, span: Span) -> Self {
// Self(Ident::new(&name, span), name)
//}
pub fn new(name: String, span: Span) -> Self {
Self(Ident::new(&name, span), name)
}
#[inline]
pub fn as_str(&self) -> &str {
@ -124,6 +124,38 @@ impl JSONValue {
JSONValue::Expr(e) => bail!(e => "expected {}", expected),
}
}
pub fn new_string(value: &str, span: Span) -> JSONValue {
JSONValue::Expr(syn::Expr::Lit(syn::ExprLit {
attrs: Vec::new(),
lit: syn::Lit::Str(syn::LitStr::new(value, span)),
}))
}
pub fn new_ident(ident: Ident) -> JSONValue {
JSONValue::Expr(syn::Expr::Path(syn::ExprPath {
attrs: Vec::new(),
qself: None,
path: syn::Path {
leading_colon: None,
segments: {
let mut p = Punctuated::new();
p.push(syn::PathSegment {
ident,
arguments: Default::default(),
});
p
},
},
}))
}
pub fn span(&self) -> Span {
match self {
JSONValue::Object(obj) => obj.brace_token.span,
JSONValue::Expr(expr) => expr.span(),
}
}
}
/// Expect a json value to be an expression, not an object:

View File

@ -23,6 +23,13 @@ impl OkString {
.schema();
}
#[api(description: "A selection of either A, B or C")]
pub enum Selection {
A,
B,
C,
}
// Initial test:
#[api(
input: {