api-macro: experimental enum support
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
6818cf76c9
commit
30a1c0b9ae
@ -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"),
|
||||
}
|
||||
}
|
||||
|
92
proxmox-api-macro/src/api/enums.rs
Normal file
92
proxmox-api-macro/src/api/enums.rs
Normal 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();
|
||||
}
|
||||
})
|
||||
}
|
@ -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()?;
|
||||
|
@ -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:
|
||||
|
@ -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: {
|
||||
|
Loading…
Reference in New Issue
Block a user