api: add helper module for verifiers

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2019-07-29 14:16:04 +02:00
parent 30105c5743
commit 20b8c709c6
3 changed files with 134 additions and 5 deletions

View File

@ -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<TokenStream, Error> {
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(())
}
})
}

View File

@ -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<Body> = Result<Response<Body>, Error>;

80
proxmox-api/src/verify.rs Normal file
View File

@ -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<T> = Option<T>;
//!
//! #[api({
//! fields: {
//! foo: {
//! description: "Test",
//! default: 2,
//! minimum: 1,
//! maximum: 5,
//! },
//! bar: {
//! description: "Test",
//! default: 2,
//! minimum: 1,
//! maximum: 5,
//! },
//! },
//! })]
//! struct Foo {
//! foo: Option<usize>,
//! bar: Annoying<usize>,
//! }
//! ```
//!
//! 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<Other> {
fn test_minimum(&self, minimum: &Other) -> bool;
fn test_maximum(&self, maximum: &Other) -> bool;
}
impl<Other> Verify<Other> 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<Other> Verify<Other> for Option<Other>
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<U, T: Verify<U>>(value: &T, minimum: &U) -> bool {
value.test_minimum(minimum)
}