diff --git a/proxmox-api-macro/src/api_macro.rs b/proxmox-api-macro/src/api_macro.rs index 1710b5fb..5e3f71ad 100644 --- a/proxmox-api-macro/src/api_macro.rs +++ b/proxmox-api-macro/src/api_macro.rs @@ -1,15 +1,12 @@ -use std::mem; - -use proc_macro2::{Delimiter, Ident, Span, TokenStream, TokenTree}; +use proc_macro2::{Delimiter, TokenStream, TokenTree}; use failure::Error; -use quote::{quote_spanned, ToTokens}; +use quote::ToTokens; use syn::spanned::Spanned; -use crate::api_def::{CommonTypeDefinition, ParameterDefinition}; -use crate::parsing::*; -use crate::util; +use crate::parsing::parse_object; +mod enum_types; mod function; mod struct_types; @@ -40,7 +37,7 @@ pub fn api_macro(attr: TokenStream, item: TokenStream) -> Result function::handle_function(def_span, definition, func), syn::Item::Enum(ref mut itemenum) => { - let extra = handle_enum(definition, itemenum)?; + let extra = enum_types::handle_enum(definition, itemenum)?; let mut output = item.into_token_stream(); output.extend(extra); Ok(output) @@ -48,112 +45,3 @@ pub fn api_macro(attr: TokenStream, item: TokenStream) -> Result c_bail!(item => "api macro currently only applies to structs and functions"), } } - -/// Enums are string types. Note that we usually use lower case enum values, but rust wants -/// CamelCase, so unless otherwise requested by the user (todo!), we convert CamelCase to -/// underscore_case automatically. -/// -/// For enums we automatically implement `ToString`, `FromStr`, and derive `Serialize` and -/// `Deserialize` via `serde_plain`. -fn handle_enum(mut definition: Object, item: &mut syn::ItemEnum) -> Result { - if item.generics.lt_token.is_some() { - c_bail!( - item.generics.span(), - "generic types are currently not supported" - ); - } - - let enum_ident = &item.ident; - let enum_name = enum_ident.to_string(); - let expected = format!("valid {}", enum_ident); - - let mut display_entries = TokenStream::new(); - let mut from_str_entries = TokenStream::new(); - - for variant in item.variants.iter_mut() { - if variant.fields != syn::Fields::Unit { - c_bail!(variant.span(), "#[api] enums cannot have fields"); - } - - let variant_ident = &variant.ident; - let span = variant_ident.span(); - let underscore_name = util::to_underscore_case(&variant_ident.to_string()); - let mut underscore_name = syn::LitStr::new(&underscore_name, variant_ident.span()); - - let cap = variant.attrs.len(); - for attr in mem::replace(&mut variant.attrs, Vec::with_capacity(cap)) { - if attr.path.is_ident(Ident::new("api", Span::call_site())) { - use util::ApiItem; - - let attrs: util::ApiAttr = syn::parse2(attr.tts)?; - - for attr in attrs.items { - match attr { - ApiItem::Rename(to) => underscore_name = to, - //other => c_bail!(other.span(), "unsupported attribute on enum variant"), - } - } - } else { - variant.attrs.push(attr); - } - } - - display_entries.extend(quote_spanned! { - span => #enum_ident::#variant_ident => write!(f, #underscore_name), - }); - - from_str_entries.extend(quote_spanned! { - span => #underscore_name => Ok(#enum_ident::#variant_ident), - }); - } - - let common = CommonTypeDefinition::from_object(&mut definition)?; - let apidef = ParameterDefinition::from_object(definition)?; - - if let Some(validate) = apidef.validate { - c_bail!(validate => "validators are not allowed on enum types"); - } - - let description = common.description; - let parse_cli = common.cli.quote(&enum_ident); - Ok(quote_spanned! { item.span() => - impl ::std::fmt::Display for #enum_ident { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - match self { - #display_entries - } - } - } - - impl ::std::str::FromStr for #enum_ident { - type Err = ::failure::Error; - - fn from_str(s: &str) -> ::std::result::Result { - match s { - #from_str_entries - _ => ::failure::bail!("expected {}", #expected), - } - } - } - - ::serde_plain::derive_deserialize_from_str!(#enum_ident, #expected); - ::serde_plain::derive_serialize_from_display!(#enum_ident); - ::proxmox::api::derive_parse_cli_from_str!(#enum_ident); - - impl ::proxmox::api::ApiType for #enum_ident { - fn type_info() -> &'static ::proxmox::api::TypeInfo { - const INFO: ::proxmox::api::TypeInfo = ::proxmox::api::TypeInfo { - name: #enum_name, - description: #description, - complete_fn: None, // FIXME! - parse_cli: #parse_cli, - }; - &INFO - } - - fn verify(&self) -> ::std::result::Result<(), ::failure::Error> { - Ok(()) - } - } - }) -} diff --git a/proxmox-api-macro/src/api_macro/enum_types.rs b/proxmox-api-macro/src/api_macro/enum_types.rs new file mode 100644 index 00000000..946632c4 --- /dev/null +++ b/proxmox-api-macro/src/api_macro/enum_types.rs @@ -0,0 +1,124 @@ +//! `#[api]` handler for enums. +//! +//! Simple enums without data are string types. Note that we usually use lower case enum values, +//! but rust wants CamelCase, so unless otherwise requested by the user, we convert `CamelCase` to +//! `underscore_case` automatically. +//! +//! For "string" enums we automatically implement `ToString`, `FromStr`, and derive `Serialize` and +//! `Deserialize` via `serde_plain`. + +use std::mem; + +use proc_macro2::{Ident, Span, TokenStream}; + +use failure::Error; +use quote::quote_spanned; +use syn::spanned::Spanned; + +use crate::api_def::{CommonTypeDefinition, ParameterDefinition}; +use crate::parsing::Object; + +use crate::util; + +pub fn handle_enum(mut definition: Object, item: &mut syn::ItemEnum) -> Result { + if item.generics.lt_token.is_some() { + c_bail!( + item.generics.span(), + "generic types are currently not supported" + ); + } + + let enum_ident = &item.ident; + let enum_name = enum_ident.to_string(); + let expected = format!("valid {}", enum_ident); + + let mut display_entries = TokenStream::new(); + let mut from_str_entries = TokenStream::new(); + + for variant in item.variants.iter_mut() { + if variant.fields != syn::Fields::Unit { + c_bail!(variant.span(), "#[api] enums cannot have fields"); + } + + let variant_ident = &variant.ident; + let span = variant_ident.span(); + let underscore_name = util::to_underscore_case(&variant_ident.to_string()); + let mut underscore_name = syn::LitStr::new(&underscore_name, variant_ident.span()); + + let cap = variant.attrs.len(); + for attr in mem::replace(&mut variant.attrs, Vec::with_capacity(cap)) { + if attr.path.is_ident(Ident::new("api", Span::call_site())) { + use util::ApiItem; + + let attrs: util::ApiAttr = syn::parse2(attr.tts)?; + + for attr in attrs.items { + match attr { + ApiItem::Rename(to) => underscore_name = to, + //other => c_bail!(other.span(), "unsupported attribute on enum variant"), + } + } + } else { + variant.attrs.push(attr); + } + } + + display_entries.extend(quote_spanned! { + span => #enum_ident::#variant_ident => write!(f, #underscore_name), + }); + + from_str_entries.extend(quote_spanned! { + span => #underscore_name => Ok(#enum_ident::#variant_ident), + }); + } + + let common = CommonTypeDefinition::from_object(&mut definition)?; + let apidef = ParameterDefinition::from_object(definition)?; + + if let Some(validate) = apidef.validate { + c_bail!(validate => "validators are not allowed on enum types"); + } + + let description = common.description; + let parse_cli = common.cli.quote(&enum_ident); + Ok(quote_spanned! { item.span() => + impl ::std::fmt::Display for #enum_ident { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + match self { + #display_entries + } + } + } + + impl ::std::str::FromStr for #enum_ident { + type Err = ::failure::Error; + + fn from_str(s: &str) -> ::std::result::Result { + match s { + #from_str_entries + _ => ::failure::bail!("expected {}", #expected), + } + } + } + + ::serde_plain::derive_deserialize_from_str!(#enum_ident, #expected); + ::serde_plain::derive_serialize_from_display!(#enum_ident); + ::proxmox::api::derive_parse_cli_from_str!(#enum_ident); + + impl ::proxmox::api::ApiType for #enum_ident { + fn type_info() -> &'static ::proxmox::api::TypeInfo { + const INFO: ::proxmox::api::TypeInfo = ::proxmox::api::TypeInfo { + name: #enum_name, + description: #description, + complete_fn: None, // FIXME! + parse_cli: #parse_cli, + }; + &INFO + } + + fn verify(&self) -> ::std::result::Result<(), ::failure::Error> { + Ok(()) + } + } + }) +} diff --git a/proxmox-api-macro/src/api_macro/struct_types.rs b/proxmox-api-macro/src/api_macro/struct_types.rs index 06418885..b56a0cec 100644 --- a/proxmox-api-macro/src/api_macro/struct_types.rs +++ b/proxmox-api-macro/src/api_macro/struct_types.rs @@ -16,7 +16,7 @@ mod newtype; mod unnamed; /// Commonly used items of a struct field. -struct StructField<'i, 't> { +pub struct StructField<'i, 't> { def: ParameterDefinition, ident: Option<&'i Ident>, access: syn::Member,