From 20b8c709c66838e6e627d77b009ba498ef6fcdc9 Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Mon, 29 Jul 2019 14:16:04 +0200 Subject: [PATCH] api: add helper module for verifiers Signed-off-by: Wolfgang Bumiller --- proxmox-api-macro/src/api_macro.rs | 58 ++++++++++++++++++++-- proxmox-api/src/lib.rs | 1 + proxmox-api/src/verify.rs | 80 ++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 proxmox-api/src/verify.rs diff --git a/proxmox-api-macro/src/api_macro.rs b/proxmox-api-macro/src/api_macro.rs index 319a0efd..190e78f0 100644 --- a/proxmox-api-macro/src/api_macro.rs +++ b/proxmox-api-macro/src/api_macro.rs @@ -540,12 +540,12 @@ 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 accessors = - named_struct_impl_accessors(item.span(), type_ident, &fields)?; + let accessors = named_struct_impl_accessors(item.span(), type_ident, &fields)?; let impl_default = if derive_default { named_struct_impl_default(item.span(), type_ident, &fields)? @@ -575,10 +575,58 @@ fn handle_struct_named( &INFO } - fn verify(&self) -> ::std::result::Result<(), ::failure::Error> { - // FIXME: #verifiers - Ok(()) + #impl_verify + } + }) +} + +fn named_struct_impl_verify( + span: Span, + fields: &[StructField], +) -> Result { + let mut body = TokenStream::new(); + for field in fields { + let field_ident = field.ident; + let field_str = &field.strlit; + + if let Some(ref minimum) = field.def.minimum { + body.extend(quote_spanned! { minimum.span() => + let minimum = #minimum; + if !::proxmox::api::verify::Verify::test_minimum(&self.#field_ident, &minimum) { + error_string.push_str( + &format!("field {} out of range, must be >= {}", #field_str, minimum) + ); + } + }); + } + + if let Some(ref maximum) = field.def.maximum { + body.extend(quote_spanned! { maximum.span() => + let maximum = #maximum; + if !::proxmox::api::verify::Verify::test_maximum(&self.#field_ident, &maximum) { + error_string.push_str( + &format!("field {} out of range, must be <= {}", #field_str, maximum) + ); + } + }); + } + } + + if !body.is_empty() { + body = quote_spanned! { span => + let mut error_string = String::new(); + #body + if !error_string.is_empty() { + return Err(::failure::format_err!("{}", error_string)); } + }; + } + + Ok(quote_spanned! { span => + fn verify(&self) -> ::std::result::Result<(), ::failure::Error> { + #body + + Ok(()) } }) } diff --git a/proxmox-api/src/lib.rs b/proxmox-api/src/lib.rs index ee75872f..0d0aa839 100644 --- a/proxmox-api/src/lib.rs +++ b/proxmox-api/src/lib.rs @@ -24,6 +24,7 @@ pub use router::*; pub mod cli; pub mod meta; +pub mod verify; /// Return type of an API method. pub type ApiOutput = Result, Error>; diff --git a/proxmox-api/src/verify.rs b/proxmox-api/src/verify.rs new file mode 100644 index 00000000..83f3b0f1 --- /dev/null +++ b/proxmox-api/src/verify.rs @@ -0,0 +1,80 @@ +//! Helper module for verifiers implemented via the api macro crate. +//! +//! We need this to seamlessly support verifying optional types. Consider this: +//! +//! ```ignore +//! type Annoying = Option; +//! +//! #[api({ +//! fields: { +//! foo: { +//! description: "Test", +//! default: 2, +//! minimum: 1, +//! maximum: 5, +//! }, +//! bar: { +//! description: "Test", +//! default: 2, +//! minimum: 1, +//! maximum: 5, +//! }, +//! }, +//! })] +//! struct Foo { +//! foo: Option, +//! bar: Annoying, +//! } +//! ``` +//! +//! The macro does not know that `foo` and `bar` have in fact the same type, and wouldn't know that +//! in order to check `bar` it needs to first check the `Option`. +//! +//! With OIBITs or specialization, we could implement a trait that always gives us "the value we +//! actually want to check", but those aren't stable and guarded by a feature gate. +//! +//! So instead, we implement checks another way. + +pub mod mark { + pub struct Default; + pub struct Special; +} + +pub trait Verify { + fn test_minimum(&self, minimum: &Other) -> bool; + fn test_maximum(&self, maximum: &Other) -> bool; +} + +impl Verify for Other +where + Other: Ord, +{ + #[inline] + fn test_minimum(&self, minimum: &Other) -> bool { + *self >= *minimum + } + + #[inline] + fn test_maximum(&self, maximum: &Other) -> bool { + *self <= *maximum + } +} + +impl Verify for Option +where + Other: Ord, +{ + #[inline] + fn test_minimum(&self, minimum: &Other) -> bool { + self.as_ref().map(|x| *x >= *minimum).unwrap_or(true) + } + + #[inline] + fn test_maximum(&self, maximum: &Other) -> bool { + self.as_ref().map(|x| *x <= *maximum).unwrap_or(true) + } +} + +pub fn test_minimum>(value: &T, minimum: &U) -> bool { + value.test_minimum(minimum) +}