api, macro: implement 'format' verifier for structs

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2019-07-30 10:06:32 +02:00
parent b77733c8ef
commit fab0210287
4 changed files with 56 additions and 0 deletions

View File

@ -101,6 +101,12 @@ pub struct ParameterDefinition {
pub minimum_length: Option<syn::Expr>,
#[builder(default)]
pub validate: Option<syn::Expr>,
/// Formats are module paths. The module must contain a verify function:
/// `fn verify(Option<&str>) -> bool`, and a `NAME` constant used in error messages to refer to
/// the format name.
#[builder(default)]
pub format: Option<syn::Path>,
}
impl ParameterDefinition {
@ -135,6 +141,9 @@ impl ParameterDefinition {
"validate" => {
def.validate(Some(value.expect_expr()?));
}
"format" => {
def.format(Some(value.expect_path()?));
}
other => c_bail!(key.span(), "invalid key in type definition: {}", other),
}
}

View File

@ -635,6 +635,18 @@ fn named_struct_impl_verify(span: Span, fields: &[StructField]) -> Result<TokenS
}
});
}
if let Some(ref value) = field.def.format {
body.extend(quote_spanned! { value.span() =>
if !#value::verify(::proxmox::api::meta::AsOptionStr::as_option_str(
&self.#field_ident,
)) {
error_string.push_str(
&format!("field {} does not match format {}", #field_str, #value::NAME)
);
}
});
}
}
if !body.is_empty() {

View File

@ -257,6 +257,13 @@ impl Expression {
}
}
pub fn expect_path(self) -> Result<syn::Path, Error> {
match self {
Expression::Expr(Expr::Path(path)) => Ok(path.path),
other => c_bail!(other.span(), "expected expression, found {:?}", other),
}
}
pub fn expect_object(self) -> Result<Object, Error> {
match self {
Expression::Object(obj) => Ok(obj),

View File

@ -26,3 +26,31 @@ where
*self = Some(value);
}
}
pub trait AsOptionStr {
fn as_option_str(&self) -> Option<&str>;
}
impl AsOptionStr for String {
fn as_option_str(&self) -> Option<&str> {
Some(self.as_str())
}
}
impl AsOptionStr for str {
fn as_option_str(&self) -> Option<&str> {
Some(self)
}
}
impl AsOptionStr for Option<String> {
fn as_option_str(&self) -> Option<&str> {
self.as_ref().map(String::as_str)
}
}
impl AsOptionStr for Option<&str> {
fn as_option_str(&self) -> Option<&str> {
*self
}
}