From 3c31381fb2ed04efe7cc2397b181e9ccd517278f Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Wed, 31 Jul 2019 14:32:35 +0200 Subject: [PATCH] macro: support 'pattern' verifier in structs Signed-off-by: Wolfgang Bumiller --- proxmox-api-macro/src/api_def.rs | 9 +++++ proxmox-api-macro/src/api_macro.rs | 53 +++++++++++++++++++++++++++--- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/proxmox-api-macro/src/api_def.rs b/proxmox-api-macro/src/api_def.rs index f756d45d..b7afe8e2 100644 --- a/proxmox-api-macro/src/api_def.rs +++ b/proxmox-api-macro/src/api_def.rs @@ -108,6 +108,12 @@ pub struct ParameterDefinition { #[builder(default)] pub format: Option, + /// Patterns are regular expressions. When a literal string is provided, a `lazy_static` regex + /// is created for the verifier. Otherwise it is taken as an expression (i.e. a path) to an + /// existing regex variable/method. + #[builder(default)] + pub pattern: Option, + #[builder(default)] pub serialize_with: Option, #[builder(default)] @@ -149,6 +155,9 @@ impl ParameterDefinition { "format" => { def.format(Some(value.expect_path()?)); } + "pattern" => { + def.pattern(Some(value.expect_expr()?)); + } "serialize_with" => { def.serialize_with(Some(value.expect_path()?)); } diff --git a/proxmox-api-macro/src/api_macro.rs b/proxmox-api-macro/src/api_macro.rs index 108f72ab..71193502 100644 --- a/proxmox-api-macro/src/api_macro.rs +++ b/proxmox-api-macro/src/api_macro.rs @@ -510,6 +510,12 @@ fn handle_struct_named( bail!("derive_default is not finished"); } + let serialize_as_string = definition + .remove("serialize_as_string") + .map(|e| e.expect_lit_bool_direct()) + .transpose()? + .unwrap_or(false); + let type_s = type_ident.to_string(); let type_span = type_ident.span(); let type_str = syn::LitStr::new(&type_s, type_span); @@ -541,10 +547,23 @@ fn handle_struct_named( } let impl_verify = named_struct_impl_verify(item.span(), &fields)?; - let impl_serialize = - named_struct_derive_serialize(item.span(), type_ident, &type_str, &fields)?; - let impl_deserialize = - named_struct_derive_deserialize(item.span(), type_ident, &type_str, &fields)?; + let (impl_serialize, impl_deserialize) = if serialize_as_string { + let expected = format!("valid {}", type_ident); + ( + quote_spanned! { item.span() => + ::serde_plain::derive_serialize_from_display!(#type_ident); + }, + quote_spanned! { item.span() => + ::serde_plain::derive_deserialize_from_str!(#type_ident, #expected); + }, + ) + } else { + ( + named_struct_derive_serialize(item.span(), type_ident, &type_str, &fields)?, + named_struct_derive_deserialize(item.span(), type_ident, &type_str, &fields)?, + ) + }; + let accessors = named_struct_impl_accessors(item.span(), type_ident, &fields)?; let impl_default = if derive_default { @@ -645,6 +664,32 @@ fn named_struct_impl_verify(span: Span, fields: &[StructField]) -> Result body.extend(quote_spanned! { value.span() => + { + ::lazy_static::lazy_static! { + static ref RE: ::regex::Regex = ::regex::Regex::new(#regex).unwrap(); + } + if !RE.is_match(&self.#field_ident) { + error_string.push_str(&format!( + "field {} does not match the allowed pattern: {}", + #field_str, + #regex, + )); + } + } + }), + regex => body.extend(quote_spanned! { value.span() => + if !#regex.is_match(&self.#field_ident) { + error_string.push_str( + &format!("field {} does not match the allowed pattern", #field_str) + ); + } + }), + } + } } if !body.is_empty() {