mirror of
https://git.proxmox.com/git/proxmox
synced 2025-06-03 20:35:53 +00:00
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};
|
use crate::util::{JSONObject, JSONValue};
|
||||||
|
|
||||||
|
mod enums;
|
||||||
mod method;
|
mod method;
|
||||||
mod structs;
|
mod structs;
|
||||||
|
|
||||||
@ -356,6 +357,7 @@ pub(crate) fn api(attr: TokenStream, item: TokenStream) -> Result<TokenStream, E
|
|||||||
match item {
|
match item {
|
||||||
syn::Item::Fn(item) => method::handle_method(attribs, item),
|
syn::Item::Fn(item) => method::handle_method(attribs, item),
|
||||||
syn::Item::Struct(item) => structs::handle_struct(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"),
|
_ => 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 super::Schema;
|
||||||
use crate::util::JSONObject;
|
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> {
|
pub fn handle_struct(attribs: JSONObject, stru: syn::ItemStruct) -> Result<TokenStream, Error> {
|
||||||
let schema = {
|
let schema = {
|
||||||
let schema: Schema = attribs.try_into()?;
|
let schema: Schema = attribs.try_into()?;
|
||||||
|
@ -14,9 +14,9 @@ use syn::Token;
|
|||||||
pub struct SimpleIdent(Ident, String);
|
pub struct SimpleIdent(Ident, String);
|
||||||
|
|
||||||
impl SimpleIdent {
|
impl SimpleIdent {
|
||||||
//pub fn new(name: String, span: Span) -> Self {
|
pub fn new(name: String, span: Span) -> Self {
|
||||||
// Self(Ident::new(&name, span), name)
|
Self(Ident::new(&name, span), name)
|
||||||
//}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
@ -124,6 +124,38 @@ impl JSONValue {
|
|||||||
JSONValue::Expr(e) => bail!(e => "expected {}", expected),
|
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:
|
/// Expect a json value to be an expression, not an object:
|
||||||
|
@ -23,6 +23,13 @@ impl OkString {
|
|||||||
.schema();
|
.schema();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[api(description: "A selection of either A, B or C")]
|
||||||
|
pub enum Selection {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
C,
|
||||||
|
}
|
||||||
|
|
||||||
// Initial test:
|
// Initial test:
|
||||||
#[api(
|
#[api(
|
||||||
input: {
|
input: {
|
||||||
|
Loading…
Reference in New Issue
Block a user