Rework validators
This commit is contained in:
parent
08263394a8
commit
8750d8d34b
|
@ -6,6 +6,8 @@ use syn::{
|
|||
Attribute, Generics, Ident, Lit, LitBool, LitStr, Meta, NestedMeta, Path, Type, Visibility,
|
||||
};
|
||||
|
||||
use crate::validators::Validators;
|
||||
|
||||
#[derive(FromMeta, Clone)]
|
||||
#[darling(default)]
|
||||
pub struct CacheControl {
|
||||
|
@ -151,7 +153,7 @@ pub struct SimpleObjectField {
|
|||
#[darling(default)]
|
||||
pub default_with: Option<LitStr>,
|
||||
#[darling(default)]
|
||||
pub validator: Option<Meta>,
|
||||
pub validator: Option<Validators>,
|
||||
#[darling(default)]
|
||||
pub flatten: bool,
|
||||
#[darling(default)]
|
||||
|
@ -200,7 +202,7 @@ pub struct Argument {
|
|||
pub desc: Option<String>,
|
||||
pub default: Option<DefaultValue>,
|
||||
pub default_with: Option<LitStr>,
|
||||
pub validator: Option<Meta>,
|
||||
pub validator: Option<Validators>,
|
||||
pub key: bool, // for entity
|
||||
pub visible: Option<Visible>,
|
||||
pub secret: bool,
|
||||
|
@ -350,7 +352,7 @@ pub struct InputObjectField {
|
|||
#[darling(default)]
|
||||
pub default_with: Option<LitStr>,
|
||||
#[darling(default)]
|
||||
pub validator: Option<Meta>,
|
||||
pub validator: Option<Validators>,
|
||||
#[darling(default)]
|
||||
pub flatten: bool,
|
||||
#[darling(default)]
|
||||
|
@ -482,7 +484,7 @@ pub struct SubscriptionFieldArgument {
|
|||
pub desc: Option<String>,
|
||||
pub default: Option<DefaultValue>,
|
||||
pub default_with: Option<LitStr>,
|
||||
pub validator: Option<Meta>,
|
||||
pub validator: Option<Validators>,
|
||||
pub visible: Option<Visible>,
|
||||
pub secret: bool,
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@ use syn::{
|
|||
use crate::args::{self, ComplexityType, RenameRuleExt, RenameTarget};
|
||||
use crate::output_type::OutputType;
|
||||
use crate::utils::{
|
||||
extract_input_args, gen_deprecation, generate_default, generate_guards, generate_validator,
|
||||
get_cfg_attrs, get_crate_name, get_param_getter_ident, get_rustdoc, get_type_path_and_name,
|
||||
extract_input_args, gen_deprecation, generate_default, generate_guards, get_cfg_attrs,
|
||||
get_crate_name, get_param_getter_ident, get_rustdoc, get_type_path_and_name,
|
||||
parse_complexity_expr, parse_graphql_attrs, remove_graphql_attrs, visible_fn, GeneratorResult,
|
||||
};
|
||||
|
||||
|
@ -205,14 +205,6 @@ pub fn generate(
|
|||
})
|
||||
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
||||
|
||||
let validator = match &validator {
|
||||
Some(meta) => {
|
||||
let stream = generate_validator(&crate_name, meta)?;
|
||||
quote!(::std::option::Option::Some(#stream))
|
||||
}
|
||||
None => quote!(::std::option::Option::None),
|
||||
};
|
||||
|
||||
let visible = visible_fn(visible);
|
||||
schema_args.push(quote! {
|
||||
args.insert(#name, #crate_name::registry::MetaInputValue {
|
||||
|
@ -220,7 +212,6 @@ pub fn generate(
|
|||
description: #desc,
|
||||
ty: <#ty as #crate_name::InputType>::create_type_info(registry),
|
||||
default_value: #schema_default,
|
||||
validator: #validator,
|
||||
visible: #visible,
|
||||
is_secret: #secret,
|
||||
});
|
||||
|
@ -235,15 +226,23 @@ pub fn generate(
|
|||
}
|
||||
None => quote! { ::std::option::Option::None },
|
||||
};
|
||||
|
||||
let validators = validator.clone().unwrap_or_default().create_validators(
|
||||
&crate_name,
|
||||
quote!(&#ident),
|
||||
quote!(),
|
||||
);
|
||||
|
||||
// We're generating a new identifier,
|
||||
// so remove the 'r#` prefix if present
|
||||
let param_getter_name = get_param_getter_ident(&ident.ident.unraw().to_string());
|
||||
get_params.push(quote! {
|
||||
#[allow(non_snake_case)]
|
||||
let #param_getter_name = || -> #crate_name::ServerResult<#ty> { ctx.param_value(#name, #default) };
|
||||
#[allow(non_snake_case)]
|
||||
let #ident: #ty = #param_getter_name()?;
|
||||
});
|
||||
#[allow(non_snake_case)]
|
||||
let #param_getter_name = || -> #crate_name::ServerResult<#ty> { ctx.param_value(#name, #default) };
|
||||
#[allow(non_snake_case)]
|
||||
let #ident: #ty = #param_getter_name()?;
|
||||
#validators
|
||||
});
|
||||
}
|
||||
|
||||
let schema_ty = ty.value_type();
|
||||
|
|
|
@ -5,9 +5,7 @@ use syn::ext::IdentExt;
|
|||
use syn::Error;
|
||||
|
||||
use crate::args::{self, RenameRuleExt, RenameTarget};
|
||||
use crate::utils::{
|
||||
generate_default, generate_validator, get_crate_name, get_rustdoc, visible_fn, GeneratorResult,
|
||||
};
|
||||
use crate::utils::{generate_default, get_crate_name, get_rustdoc, visible_fn, GeneratorResult};
|
||||
|
||||
pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream> {
|
||||
let crate_name = get_crate_name(object_args.internal);
|
||||
|
@ -77,6 +75,16 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
|
|||
|
||||
federation_fields.push((ty, name.clone()));
|
||||
|
||||
let validators = field
|
||||
.validator
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.create_validators(
|
||||
&crate_name,
|
||||
quote!(&#ident),
|
||||
quote!(.map_err(#crate_name::InputValueError::propagate)?),
|
||||
);
|
||||
|
||||
if field.flatten {
|
||||
flatten_fields.push((ident, ty));
|
||||
|
||||
|
@ -93,6 +101,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
|
|||
let #ident: #ty = #crate_name::InputType::parse(
|
||||
::std::option::Option::Some(#crate_name::Value::Object(::std::clone::Clone::clone(&obj)))
|
||||
).map_err(#crate_name::InputValueError::propagate)?;
|
||||
#validators
|
||||
});
|
||||
|
||||
fields.push(ident);
|
||||
|
@ -105,13 +114,6 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
|
|||
continue;
|
||||
}
|
||||
|
||||
let validator = match &field.validator {
|
||||
Some(meta) => {
|
||||
let stream = generate_validator(&crate_name, meta)?;
|
||||
quote!(::std::option::Option::Some(#stream))
|
||||
}
|
||||
None => quote!(::std::option::Option::None),
|
||||
};
|
||||
let desc = get_rustdoc(&field.attrs)?
|
||||
.map(|s| quote! { ::std::option::Option::Some(#s) })
|
||||
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
||||
|
@ -140,12 +142,14 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
|
|||
::std::option::Option::None => #default,
|
||||
}
|
||||
};
|
||||
#validators
|
||||
});
|
||||
} else {
|
||||
get_fields.push(quote! {
|
||||
#[allow(non_snake_case)]
|
||||
let #ident: #ty = #crate_name::InputType::parse(obj.get(#name).cloned())
|
||||
.map_err(#crate_name::InputValueError::propagate)?;
|
||||
#validators
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -164,7 +168,6 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
|
|||
description: #desc,
|
||||
ty: <#ty as #crate_name::InputType>::create_type_info(registry),
|
||||
default_value: #schema_default,
|
||||
validator: #validator,
|
||||
visible: #visible,
|
||||
is_secret: #secret,
|
||||
});
|
||||
|
|
|
@ -228,7 +228,6 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
|
|||
description: #desc,
|
||||
ty: <#ty as #crate_name::InputType>::create_type_info(registry),
|
||||
default_value: #schema_default,
|
||||
validator: ::std::option::Option::None,
|
||||
visible: #visible,
|
||||
is_secret: #secret,
|
||||
});
|
||||
|
|
|
@ -20,6 +20,7 @@ mod simple_object;
|
|||
mod subscription;
|
||||
mod union;
|
||||
mod utils;
|
||||
mod validators;
|
||||
|
||||
use darling::{FromDeriveInput, FromMeta};
|
||||
use proc_macro::TokenStream;
|
||||
|
|
|
@ -12,8 +12,8 @@ use syn::{
|
|||
use crate::args::{self, ComplexityType, RenameRuleExt, RenameTarget};
|
||||
use crate::output_type::OutputType;
|
||||
use crate::utils::{
|
||||
extract_input_args, gen_deprecation, generate_default, generate_guards, generate_validator,
|
||||
get_cfg_attrs, get_crate_name, get_param_getter_ident, get_rustdoc, get_type_path_and_name,
|
||||
extract_input_args, gen_deprecation, generate_default, generate_guards, get_cfg_attrs,
|
||||
get_crate_name, get_param_getter_ident, get_rustdoc, get_type_path_and_name,
|
||||
parse_complexity_expr, parse_graphql_attrs, remove_graphql_attrs, visible_fn, GeneratorResult,
|
||||
};
|
||||
|
||||
|
@ -340,14 +340,6 @@ pub fn generate(
|
|||
})
|
||||
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
||||
|
||||
let validator = match &validator {
|
||||
Some(meta) => {
|
||||
let stream = generate_validator(&crate_name, meta)?;
|
||||
quote!(::std::option::Option::Some(#stream))
|
||||
}
|
||||
None => quote!(::std::option::Option::None),
|
||||
};
|
||||
|
||||
let visible = visible_fn(visible);
|
||||
schema_args.push(quote! {
|
||||
args.insert(#name, #crate_name::registry::MetaInputValue {
|
||||
|
@ -355,7 +347,6 @@ pub fn generate(
|
|||
description: #desc,
|
||||
ty: <#ty as #crate_name::InputType>::create_type_info(registry),
|
||||
default_value: #schema_default,
|
||||
validator: #validator,
|
||||
visible: #visible,
|
||||
is_secret: #secret,
|
||||
});
|
||||
|
@ -370,6 +361,13 @@ pub fn generate(
|
|||
}
|
||||
None => quote! { ::std::option::Option::None },
|
||||
};
|
||||
|
||||
let validators = validator.clone().unwrap_or_default().create_validators(
|
||||
&crate_name,
|
||||
quote!(&#ident),
|
||||
quote!(),
|
||||
);
|
||||
|
||||
// We're generating a new identifier,
|
||||
// so remove the 'r#` prefix if present
|
||||
let param_getter_name =
|
||||
|
@ -379,6 +377,7 @@ pub fn generate(
|
|||
let #param_getter_name = || -> #crate_name::ServerResult<#ty> { ctx.param_value(#name, #default) };
|
||||
#[allow(non_snake_case)]
|
||||
let #ident: #ty = #param_getter_name()?;
|
||||
#validators
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -9,9 +9,9 @@ use syn::{
|
|||
use crate::args::{self, ComplexityType, RenameRuleExt, RenameTarget, SubscriptionField};
|
||||
use crate::output_type::OutputType;
|
||||
use crate::utils::{
|
||||
gen_deprecation, generate_default, generate_guards, generate_validator, get_cfg_attrs,
|
||||
get_crate_name, get_param_getter_ident, get_rustdoc, get_type_path_and_name,
|
||||
parse_complexity_expr, parse_graphql_attrs, remove_graphql_attrs, visible_fn, GeneratorResult,
|
||||
gen_deprecation, generate_default, generate_guards, get_cfg_attrs, get_crate_name,
|
||||
get_param_getter_ident, get_rustdoc, get_type_path_and_name, parse_complexity_expr,
|
||||
parse_graphql_attrs, remove_graphql_attrs, visible_fn, GeneratorResult,
|
||||
};
|
||||
|
||||
pub fn generate(
|
||||
|
@ -167,14 +167,6 @@ pub fn generate(
|
|||
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
||||
let default = generate_default(default, default_with)?;
|
||||
|
||||
let validator = match &validator {
|
||||
Some(meta) => {
|
||||
let stream = generate_validator(&crate_name, meta)?;
|
||||
quote!(::std::option::Option::Some(#stream))
|
||||
}
|
||||
None => quote!(::std::option::Option::None),
|
||||
};
|
||||
|
||||
let schema_default = default
|
||||
.as_ref()
|
||||
.map(|value| {
|
||||
|
@ -193,7 +185,6 @@ pub fn generate(
|
|||
description: #desc,
|
||||
ty: <#ty as #crate_name::InputType>::create_type_info(registry),
|
||||
default_value: #schema_default,
|
||||
validator: #validator,
|
||||
visible: #visible,
|
||||
is_secret: #secret,
|
||||
});
|
||||
|
@ -205,12 +196,18 @@ pub fn generate(
|
|||
Some(default) => quote! { ::std::option::Option::Some(|| -> #ty { #default }) },
|
||||
None => quote! { ::std::option::Option::None },
|
||||
};
|
||||
let validators = validator.clone().unwrap_or_default().create_validators(
|
||||
&crate_name,
|
||||
quote!(&#ident),
|
||||
quote!(),
|
||||
);
|
||||
let param_getter_name = get_param_getter_ident(&ident.ident.to_string());
|
||||
get_params.push(quote! {
|
||||
#[allow(non_snake_case)]
|
||||
let #param_getter_name = || -> #crate_name::ServerResult<#ty> { ctx.param_value(#name, #default) };
|
||||
#[allow(non_snake_case)]
|
||||
let #ident: #ty = ctx.param_value(#name, #default)?;
|
||||
#validators
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -46,105 +46,6 @@ pub fn get_crate_name(internal: bool) -> TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
fn generate_nested_validator(
|
||||
crate_name: &TokenStream,
|
||||
nested_meta: &NestedMeta,
|
||||
) -> GeneratorResult<TokenStream> {
|
||||
let mut params = Vec::new();
|
||||
match nested_meta {
|
||||
NestedMeta::Meta(Meta::List(ls)) => {
|
||||
if ls.path.is_ident("and") {
|
||||
let mut validators = Vec::new();
|
||||
for nested_meta in &ls.nested {
|
||||
validators.push(generate_nested_validator(crate_name, nested_meta)?);
|
||||
}
|
||||
Ok(validators
|
||||
.into_iter()
|
||||
.fold(None, |acc, item| match acc {
|
||||
Some(prev) => Some(quote! { #crate_name::validators::InputValueValidatorExt::and(#prev, #item) }),
|
||||
None => Some(item),
|
||||
})
|
||||
.unwrap())
|
||||
} else if ls.path.is_ident("or") {
|
||||
let mut validators = Vec::new();
|
||||
for nested_meta in &ls.nested {
|
||||
validators.push(generate_nested_validator(crate_name, nested_meta)?);
|
||||
}
|
||||
Ok(validators
|
||||
.into_iter()
|
||||
.fold(None, |acc, item| match acc {
|
||||
Some(prev) => Some(quote! { #crate_name::validators::InputValueValidatorExt::or(#prev, #item) }),
|
||||
None => Some(item),
|
||||
})
|
||||
.unwrap())
|
||||
} else if ls.path.is_ident("list") {
|
||||
if ls.nested.len() > 1 {
|
||||
return Err(Error::new_spanned(
|
||||
ls,
|
||||
"Only one validator can be wrapped with list.",
|
||||
)
|
||||
.into());
|
||||
}
|
||||
if ls.nested.is_empty() {
|
||||
return Err(
|
||||
Error::new_spanned(ls, "At least one validator must be defined").into(),
|
||||
);
|
||||
}
|
||||
let validator = generate_nested_validator(crate_name, &ls.nested[0])?;
|
||||
Ok(quote! {
|
||||
#crate_name::validators::List(#validator)
|
||||
})
|
||||
} else {
|
||||
let ty = &ls.path;
|
||||
for item in &ls.nested {
|
||||
if let NestedMeta::Meta(Meta::NameValue(nv)) = item {
|
||||
let name = &nv.path;
|
||||
if let Lit::Str(value) = &nv.lit {
|
||||
let expr = syn::parse_str::<Expr>(&value.value())?;
|
||||
params.push(quote! { #name: (#expr).into() });
|
||||
} else {
|
||||
return Err(Error::new_spanned(
|
||||
&nv.lit,
|
||||
"Value must be string literal",
|
||||
)
|
||||
.into());
|
||||
}
|
||||
} else {
|
||||
return Err(Error::new_spanned(
|
||||
nested_meta,
|
||||
"Invalid property for validator",
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
Ok(quote! { #ty { #(#params),* } })
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(ty)) => Ok(quote! { #ty {} }),
|
||||
NestedMeta::Meta(Meta::NameValue(_)) | NestedMeta::Lit(_) => {
|
||||
Err(Error::new_spanned(nested_meta, "Invalid validator").into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_validator(crate_name: &TokenStream, args: &Meta) -> GeneratorResult<TokenStream> {
|
||||
match args {
|
||||
Meta::List(args) => {
|
||||
if args.nested.len() > 1 {
|
||||
return Err(Error::new_spanned(args, "Only one validator can be defined. You can connect combine validators with `and` or `or`").into());
|
||||
}
|
||||
if args.nested.is_empty() {
|
||||
return Err(
|
||||
Error::new_spanned(args, "At least one validator must be defined").into(),
|
||||
);
|
||||
}
|
||||
let validator = generate_nested_validator(crate_name, &args.nested[0])?;
|
||||
Ok(quote! { ::std::sync::Arc::new(#validator) })
|
||||
}
|
||||
_ => Err(Error::new_spanned(args, "Invalid validator").into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_guards(
|
||||
crate_name: &TokenStream,
|
||||
args: &Meta,
|
||||
|
|
78
derive/src/validators.rs
Normal file
78
derive/src/validators.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
use darling::FromMeta;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
#[derive(FromMeta, Default, Clone)]
|
||||
pub struct Validators {
|
||||
#[darling(default)]
|
||||
multiple_of: Option<f64>,
|
||||
#[darling(default)]
|
||||
maximum: Option<f64>,
|
||||
#[darling(default)]
|
||||
minimum: Option<f64>,
|
||||
#[darling(default)]
|
||||
max_length: Option<usize>,
|
||||
#[darling(default)]
|
||||
min_length: Option<usize>,
|
||||
#[darling(default)]
|
||||
max_items: Option<usize>,
|
||||
#[darling(default)]
|
||||
min_items: Option<usize>,
|
||||
#[darling(default, multiple)]
|
||||
custom: Vec<String>,
|
||||
}
|
||||
|
||||
impl Validators {
|
||||
pub fn create_validators(
|
||||
&self,
|
||||
crate_name: &TokenStream,
|
||||
value: TokenStream,
|
||||
map_err: TokenStream,
|
||||
) -> TokenStream {
|
||||
let mut codes = Vec::new();
|
||||
|
||||
if let Some(n) = &self.multiple_of {
|
||||
codes.push(quote! {
|
||||
#crate_name::validators::multiple_of(#value, #n) #map_err
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(n) = &self.maximum {
|
||||
codes.push(quote! {
|
||||
#crate_name::validators::maximum(#value, #n) #map_err
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(n) = &self.minimum {
|
||||
codes.push(quote! {
|
||||
#crate_name::validators::minimum(#value, #n) #map_err
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(n) = &self.max_length {
|
||||
codes.push(quote! {
|
||||
#crate_name::validators::max_length(#value, #n) #map_err
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(n) = &self.min_length {
|
||||
codes.push(quote! {
|
||||
#crate_name::validators::min_length(#value, #n) #map_err
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(n) = &self.max_items {
|
||||
codes.push(quote! {
|
||||
#crate_name::validators::max_items(#value, #n) #map_err
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(n) = &self.min_items {
|
||||
codes.push(quote! {
|
||||
#crate_name::validators::min_items(#value, #n) #map_err
|
||||
});
|
||||
}
|
||||
|
||||
quote!(#(#codes;)*)
|
||||
}
|
||||
}
|
|
@ -179,6 +179,7 @@ pub mod guard;
|
|||
pub mod http;
|
||||
pub mod resolver_utils;
|
||||
pub mod types;
|
||||
#[doc(hidden)]
|
||||
pub mod validators;
|
||||
|
||||
#[doc(hidden)]
|
||||
|
|
|
@ -3,7 +3,6 @@ mod export_sdl;
|
|||
mod stringify_exec_doc;
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
use indexmap::map::IndexMap;
|
||||
use indexmap::set::IndexSet;
|
||||
|
@ -11,7 +10,6 @@ use indexmap::set::IndexSet;
|
|||
use crate::parser::types::{
|
||||
BaseType as ParsedBaseType, Field, Type as ParsedType, VariableDefinition,
|
||||
};
|
||||
use crate::validators::InputValueValidator;
|
||||
use crate::{
|
||||
model, Any, Context, InputType, OutputType, Positioned, ServerResult, SubscriptionType, Value,
|
||||
VisitorContext,
|
||||
|
@ -109,7 +107,6 @@ pub struct MetaInputValue {
|
|||
pub description: Option<&'static str>,
|
||||
pub ty: String,
|
||||
pub default_value: Option<String>,
|
||||
pub validator: Option<Arc<dyn InputValueValidator>>,
|
||||
pub visible: Option<MetaVisibleFn>,
|
||||
pub is_secret: bool,
|
||||
}
|
||||
|
@ -615,7 +612,6 @@ impl Registry {
|
|||
description: None,
|
||||
ty: "[_Any!]!".to_string(),
|
||||
default_value: None,
|
||||
validator: None,
|
||||
visible: None,
|
||||
is_secret: false,
|
||||
},
|
||||
|
|
|
@ -283,7 +283,6 @@ where
|
|||
description: Some("Included when true."),
|
||||
ty: "Boolean!".to_string(),
|
||||
default_value: None,
|
||||
validator: None,
|
||||
visible: None,
|
||||
is_secret: false,
|
||||
});
|
||||
|
@ -306,7 +305,6 @@ where
|
|||
description: Some("Skipped when true."),
|
||||
ty: "Boolean!".to_string(),
|
||||
default_value: None,
|
||||
validator: None,
|
||||
visible: None,
|
||||
is_secret: false,
|
||||
});
|
||||
|
|
|
@ -129,7 +129,6 @@ impl<T: ObjectType> OutputType for QueryRoot<T> {
|
|||
description: None,
|
||||
ty: "String!".to_string(),
|
||||
default_value: None,
|
||||
validator: None,
|
||||
visible: None,
|
||||
is_secret: false,
|
||||
},
|
||||
|
|
|
@ -55,20 +55,7 @@ impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> {
|
|||
})
|
||||
.ok();
|
||||
|
||||
if let Some(validator) = &arg.validator {
|
||||
if let Some(value) = &value {
|
||||
if let Err(e) = validator.is_valid_with_extensions(value) {
|
||||
ctx.report_error_with_extensions(
|
||||
vec![name.pos],
|
||||
format!("Invalid value for argument \"{}\", {}", arg.name, e.message),
|
||||
e.extensions,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(e) = value.and_then(|value| {
|
||||
if let Some(reason) = value.and_then(|value| {
|
||||
is_valid_input_value(
|
||||
ctx.registry,
|
||||
&arg.ty,
|
||||
|
@ -79,10 +66,9 @@ impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> {
|
|||
},
|
||||
)
|
||||
}) {
|
||||
ctx.report_error_with_extensions(
|
||||
ctx.report_error(
|
||||
vec![name.pos],
|
||||
format!("Invalid value for argument {}", e.message),
|
||||
e.extensions,
|
||||
format!("Invalid value for argument {}", reason),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ impl<'a> Visitor<'a> for DefaultValuesOfCorrectType {
|
|||
"Argument \"{}\" has type \"{}\" and is not nullable, so it can't have a default value",
|
||||
variable_definition.node.name, variable_definition.node.var_type,
|
||||
));
|
||||
} else if let Some(e) = is_valid_input_value(
|
||||
} else if let Some(reason) = is_valid_input_value(
|
||||
ctx.registry,
|
||||
&variable_definition.node.var_type.to_string(),
|
||||
&value.node,
|
||||
|
@ -27,10 +27,9 @@ impl<'a> Visitor<'a> for DefaultValuesOfCorrectType {
|
|||
segment: QueryPathSegment::Name(&variable_definition.node.name.node),
|
||||
},
|
||||
) {
|
||||
ctx.report_error_with_extensions(
|
||||
ctx.report_error(
|
||||
vec![variable_definition.pos],
|
||||
format!("Invalid default value for argument {}", e.message),
|
||||
e.extensions,
|
||||
format!("Invalid default value for argument: {}", reason),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,6 @@ impl<'a> CycleDetector<'a> {
|
|||
self.errors.push(RuleError::new(
|
||||
vec![err_pos],
|
||||
format!("Cannot spread fragment \"{}\"", name),
|
||||
None,
|
||||
));
|
||||
} else if !self.visited.contains(name) {
|
||||
path.push((name, *pos));
|
||||
|
|
|
@ -3,7 +3,6 @@ use std::collections::HashSet;
|
|||
use async_graphql_value::{ConstValue, Value};
|
||||
|
||||
use crate::context::QueryPathNode;
|
||||
use crate::error::Error;
|
||||
use crate::{registry, QueryPathSegment};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
|
@ -42,12 +41,13 @@ pub fn is_valid_input_value(
|
|||
type_name: &str,
|
||||
value: &ConstValue,
|
||||
path_node: QueryPathNode,
|
||||
) -> Option<Error> {
|
||||
) -> Option<String> {
|
||||
match registry::MetaTypeName::create(type_name) {
|
||||
registry::MetaTypeName::NonNull(type_name) => match value {
|
||||
ConstValue::Null => {
|
||||
Some(valid_error(&path_node, format!("expected type \"{}\"", type_name)).into())
|
||||
}
|
||||
ConstValue::Null => Some(valid_error(
|
||||
&path_node,
|
||||
format!("expected type \"{}\"", type_name),
|
||||
)),
|
||||
_ => is_valid_input_value(registry, type_name, value, path_node),
|
||||
},
|
||||
registry::MetaTypeName::List(type_name) => match value {
|
||||
|
@ -75,10 +75,10 @@ pub fn is_valid_input_value(
|
|||
if is_valid(&value) {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
valid_error(&path_node, format!("expected type \"{}\"", type_name))
|
||||
.into(),
|
||||
)
|
||||
Some(valid_error(
|
||||
&path_node,
|
||||
format!("expected type \"{}\"", type_name),
|
||||
))
|
||||
}
|
||||
}
|
||||
registry::MetaType::Enum {
|
||||
|
@ -88,39 +88,34 @@ pub fn is_valid_input_value(
|
|||
} => match value {
|
||||
ConstValue::Enum(name) => {
|
||||
if !enum_values.contains_key(name.as_str()) {
|
||||
Some(
|
||||
valid_error(
|
||||
&path_node,
|
||||
format!(
|
||||
"enumeration type \"{}\" does not contain the value \"{}\"",
|
||||
enum_name, name
|
||||
),
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
Some(valid_error(
|
||||
&path_node,
|
||||
format!(
|
||||
"enumeration type \"{}\" does not contain the value \"{}\"",
|
||||
enum_name, name
|
||||
),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
ConstValue::String(name) => {
|
||||
if !enum_values.contains_key(name.as_str()) {
|
||||
Some(
|
||||
valid_error(
|
||||
&path_node,
|
||||
format!(
|
||||
"enumeration type \"{}\" does not contain the value \"{}\"",
|
||||
enum_name, name
|
||||
),
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
Some(valid_error(
|
||||
&path_node,
|
||||
format!(
|
||||
"enumeration type \"{}\" does not contain the value \"{}\"",
|
||||
enum_name, name
|
||||
),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => Some(
|
||||
valid_error(&path_node, format!("expected type \"{}\"", type_name)).into(),
|
||||
),
|
||||
_ => Some(valid_error(
|
||||
&path_node,
|
||||
format!("expected type \"{}\"", type_name),
|
||||
)),
|
||||
},
|
||||
registry::MetaType::InputObject {
|
||||
input_fields,
|
||||
|
@ -134,19 +129,6 @@ pub fn is_valid_input_value(
|
|||
for field in input_fields.values() {
|
||||
input_names.remove(field.name);
|
||||
if let Some(value) = values.get(field.name) {
|
||||
if let Some(validator) = &field.validator {
|
||||
if let Err(mut e) = validator.is_valid_with_extensions(value) {
|
||||
e.message = valid_error(
|
||||
&QueryPathNode {
|
||||
parent: Some(&path_node),
|
||||
segment: QueryPathSegment::Name(field.name),
|
||||
},
|
||||
e.message,
|
||||
);
|
||||
return Some(e);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(reason) = is_valid_input_value(
|
||||
registry,
|
||||
&field.ty,
|
||||
|
@ -161,30 +143,21 @@ pub fn is_valid_input_value(
|
|||
} else if registry::MetaTypeName::create(&field.ty).is_non_null()
|
||||
&& field.default_value.is_none()
|
||||
{
|
||||
return Some(
|
||||
valid_error(
|
||||
&path_node,
|
||||
format!(
|
||||
return Some(valid_error(
|
||||
&path_node,
|
||||
format!(
|
||||
"field \"{}\" of type \"{}\" is required but not provided",
|
||||
field.name, object_name,
|
||||
),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(name) = input_names.iter().next() {
|
||||
return Some(
|
||||
valid_error(
|
||||
&path_node,
|
||||
format!(
|
||||
"unknown field \"{}\" of type \"{}\"",
|
||||
name, object_name
|
||||
),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
return Some(valid_error(
|
||||
&path_node,
|
||||
format!("unknown field \"{}\" of type \"{}\"", name, object_name),
|
||||
));
|
||||
}
|
||||
|
||||
None
|
||||
|
|
|
@ -8,9 +8,7 @@ use crate::parser::types::{
|
|||
OperationDefinition, OperationType, Selection, SelectionSet, TypeCondition, VariableDefinition,
|
||||
};
|
||||
use crate::registry::{self, MetaType, MetaTypeName};
|
||||
use crate::{
|
||||
ErrorExtensionValues, InputType, Name, Pos, Positioned, ServerError, ServerResult, Variables,
|
||||
};
|
||||
use crate::{InputType, Name, Pos, Positioned, ServerError, ServerResult, Variables};
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct VisitorContext<'a> {
|
||||
|
@ -39,16 +37,7 @@ impl<'a> VisitorContext<'a> {
|
|||
}
|
||||
|
||||
pub(crate) fn report_error<T: Into<String>>(&mut self, locations: Vec<Pos>, msg: T) {
|
||||
self.errors.push(RuleError::new(locations, msg, None));
|
||||
}
|
||||
|
||||
pub(crate) fn report_error_with_extensions<T: Into<String>>(
|
||||
&mut self,
|
||||
locations: Vec<Pos>,
|
||||
msg: T,
|
||||
extensions: Option<ErrorExtensionValues>,
|
||||
) {
|
||||
self.errors.push(RuleError::new(locations, msg, extensions));
|
||||
self.errors.push(RuleError::new(locations, msg));
|
||||
}
|
||||
|
||||
pub(crate) fn append_errors(&mut self, errors: Vec<RuleError>) {
|
||||
|
@ -809,19 +798,13 @@ fn visit_inline_fragment<'a, V: Visitor<'a>>(
|
|||
pub(crate) struct RuleError {
|
||||
pub(crate) locations: Vec<Pos>,
|
||||
pub(crate) message: String,
|
||||
pub(crate) extensions: Option<ErrorExtensionValues>,
|
||||
}
|
||||
|
||||
impl RuleError {
|
||||
pub(crate) fn new(
|
||||
locations: Vec<Pos>,
|
||||
msg: impl Into<String>,
|
||||
extensions: Option<ErrorExtensionValues>,
|
||||
) -> Self {
|
||||
pub(crate) fn new(locations: Vec<Pos>, msg: impl Into<String>) -> Self {
|
||||
Self {
|
||||
locations,
|
||||
message: msg.into(),
|
||||
extensions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -854,7 +837,7 @@ impl From<RuleError> for ServerError {
|
|||
source: None,
|
||||
locations: e.locations,
|
||||
path: Vec::new(),
|
||||
extensions: e.extensions,
|
||||
extensions: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
use crate::validators::InputValueValidator;
|
||||
use crate::Value;
|
||||
|
||||
/// Integer range validator
|
||||
pub struct IntRange {
|
||||
/// Minimum value, including this value.
|
||||
pub min: i64,
|
||||
|
||||
/// Maximum value, including this value.
|
||||
pub max: i64,
|
||||
}
|
||||
|
||||
impl InputValueValidator for IntRange {
|
||||
fn is_valid(&self, value: &Value) -> Result<(), String> {
|
||||
if let Value::Number(n) = value {
|
||||
if let Some(n) = n.as_i64() {
|
||||
if n < self.min || n > self.max {
|
||||
return Err(format!(
|
||||
"the value is {}, must be between {} and {}",
|
||||
n, self.min, self.max
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Integer less than validator
|
||||
pub struct IntLessThan {
|
||||
/// Less than this value.
|
||||
pub value: i64,
|
||||
}
|
||||
|
||||
impl InputValueValidator for IntLessThan {
|
||||
fn is_valid(&self, value: &Value) -> Result<(), String> {
|
||||
if let Value::Number(n) = value {
|
||||
if let Some(n) = n.as_i64() {
|
||||
if n >= self.value {
|
||||
return Err(format!(
|
||||
"the value is {}, must be less than {}",
|
||||
n, self.value
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Integer greater than validator
|
||||
pub struct IntGreaterThan {
|
||||
/// Greater than this value.
|
||||
pub value: i64,
|
||||
}
|
||||
|
||||
impl InputValueValidator for IntGreaterThan {
|
||||
fn is_valid(&self, value: &Value) -> Result<(), String> {
|
||||
if let Value::Number(n) = value {
|
||||
if let Some(n) = n.as_i64() {
|
||||
if n <= self.value {
|
||||
return Err(format!(
|
||||
"the value is {}, must be greater than {}",
|
||||
n, self.value
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Integer nonzero validator
|
||||
pub struct IntNonZero {}
|
||||
|
||||
impl InputValueValidator for IntNonZero {
|
||||
fn is_valid(&self, value: &Value) -> Result<(), String> {
|
||||
if let Value::Number(n) = value {
|
||||
if let Some(n) = n.as_i64() {
|
||||
if n == 0 {
|
||||
return Err(format!("the value is {}, must be nonzero", n));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Integer equal validator
|
||||
pub struct IntEqual {
|
||||
/// equal this value.
|
||||
pub value: i64,
|
||||
}
|
||||
|
||||
impl InputValueValidator for IntEqual {
|
||||
fn is_valid(&self, value: &Value) -> Result<(), String> {
|
||||
if let Value::Number(n) = value {
|
||||
if let Some(n) = n.as_i64() {
|
||||
if n != self.value {
|
||||
return Err(format!(
|
||||
"the value is {}, must be equal to {}",
|
||||
n, self.value
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
use crate::validators::InputValueValidator;
|
||||
use crate::Value;
|
||||
|
||||
/// List minimum length validator
|
||||
pub struct ListMinLength {
|
||||
/// Must be greater than or equal to this value.
|
||||
pub length: i32,
|
||||
}
|
||||
|
||||
impl InputValueValidator for ListMinLength {
|
||||
fn is_valid(&self, value: &Value) -> Result<(), String> {
|
||||
if let Value::List(values) = value {
|
||||
if values.len() < self.length as usize {
|
||||
Err(format!(
|
||||
"the value length is {}, must be greater than or equal to {}",
|
||||
values.len(),
|
||||
self.length
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// List maximum length validator
|
||||
pub struct ListMaxLength {
|
||||
/// Must be less than or equal to this value.
|
||||
pub length: i32,
|
||||
}
|
||||
|
||||
impl InputValueValidator for ListMaxLength {
|
||||
fn is_valid(&self, value: &Value) -> Result<(), String> {
|
||||
if let Value::List(values) = value {
|
||||
if values.len() > self.length as usize {
|
||||
Err(format!(
|
||||
"the value length is {}, must be less than or equal to {}",
|
||||
values.len(),
|
||||
self.length
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct List<T>(pub T);
|
||||
|
||||
impl<T: InputValueValidator> InputValueValidator for List<T> {
|
||||
fn is_valid(&self, value: &Value) -> Result<(), String> {
|
||||
if let Value::List(elems) = value {
|
||||
for elem in elems {
|
||||
self.0.is_valid(elem)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
19
src/validators/max_items.rs
Normal file
19
src/validators/max_items.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use crate::{InputType, InputValueError};
|
||||
|
||||
pub async fn max_items<T: Deref<Target = [E]> + InputType, E>(
|
||||
value: &T,
|
||||
len: usize,
|
||||
) -> Result<(), InputValueError<T>> {
|
||||
if value.deref().len() <= len {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!(
|
||||
"the value length is {}, must be less than or equal to {}",
|
||||
value.deref().len(),
|
||||
len
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
17
src/validators/max_length.rs
Normal file
17
src/validators/max_length.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
use crate::{InputType, InputValueError};
|
||||
|
||||
pub async fn max_length<T: AsRef<str> + InputType>(
|
||||
value: &T,
|
||||
len: usize,
|
||||
) -> Result<(), InputValueError<T>> {
|
||||
if value.as_ref().len() <= len {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!(
|
||||
"the string length is {}, must be less than or equal to {}",
|
||||
value.as_ref().len(),
|
||||
len
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
19
src/validators/maximum.rs
Normal file
19
src/validators/maximum.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
use num_traits::AsPrimitive;
|
||||
|
||||
use crate::{InputType, InputValueError};
|
||||
|
||||
pub async fn maximum<T: AsPrimitive<f64> + InputType>(
|
||||
value: &T,
|
||||
n: f64,
|
||||
) -> Result<(), InputValueError<T>> {
|
||||
if value.as_() <= n {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!(
|
||||
"the value is {}, must be less than or equal to {}",
|
||||
value.as_(),
|
||||
n
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
19
src/validators/min_items.rs
Normal file
19
src/validators/min_items.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use crate::{InputType, InputValueError};
|
||||
|
||||
pub async fn min_items<T: Deref<Target = [E]> + InputType, E>(
|
||||
value: &T,
|
||||
len: usize,
|
||||
) -> Result<(), InputValueError<T>> {
|
||||
if value.deref().len() >= len {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!(
|
||||
"the value length is {}, must be greater than or equal to {}",
|
||||
value.deref().len(),
|
||||
len
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
17
src/validators/min_length.rs
Normal file
17
src/validators/min_length.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
use crate::{InputType, InputValueError};
|
||||
|
||||
pub async fn min_length<T: AsRef<str> + InputType>(
|
||||
value: &T,
|
||||
len: usize,
|
||||
) -> Result<(), InputValueError<T>> {
|
||||
if value.as_ref().len() >= len {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!(
|
||||
"the string length is {}, must be greater than or equal to {}",
|
||||
value.as_ref().len(),
|
||||
len
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
19
src/validators/minimum.rs
Normal file
19
src/validators/minimum.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
use num_traits::AsPrimitive;
|
||||
|
||||
use crate::{InputType, InputValueError};
|
||||
|
||||
pub async fn minimum<T: AsPrimitive<f64> + InputType>(
|
||||
value: &T,
|
||||
n: f64,
|
||||
) -> Result<(), InputValueError<T>> {
|
||||
if value.as_() >= n {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!(
|
||||
"the value is {}, must be greater than or equal to {}",
|
||||
value.as_(),
|
||||
n
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
|
@ -1,171 +1,15 @@
|
|||
//! Input value validators
|
||||
mod max_items;
|
||||
mod max_length;
|
||||
mod maximum;
|
||||
mod min_items;
|
||||
mod min_length;
|
||||
mod minimum;
|
||||
mod multiple_of;
|
||||
|
||||
mod int_validators;
|
||||
mod list_validators;
|
||||
mod string_validators;
|
||||
|
||||
use crate::{Error, Value};
|
||||
|
||||
pub use int_validators::{IntEqual, IntGreaterThan, IntLessThan, IntNonZero, IntRange};
|
||||
pub use list_validators::{List, ListMaxLength, ListMinLength};
|
||||
pub use string_validators::{
|
||||
CharsMaxLength, CharsMinLength, Email, StringMaxLength, StringMinLength, MAC,
|
||||
};
|
||||
|
||||
/// Input value validator
|
||||
///
|
||||
/// You can create your own input value validator by implementing this trait.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use async_graphql::*;
|
||||
/// use async_graphql::validators::{Email, MAC, IntRange};
|
||||
///
|
||||
/// struct QueryRoot;
|
||||
///
|
||||
/// #[Object]
|
||||
/// impl QueryRoot {
|
||||
/// // Input is email address
|
||||
/// async fn value1(&self, #[graphql(validator(Email))] email: String) -> i32 {
|
||||
/// unimplemented!()
|
||||
/// }
|
||||
///
|
||||
/// // Input is email or MAC address
|
||||
/// async fn value2(&self, #[graphql(validator(or(Email, MAC(colon = "false"))))] email_or_mac: String) -> i32 {
|
||||
/// unimplemented!()
|
||||
/// }
|
||||
///
|
||||
/// // Input is integer between 100 and 200
|
||||
/// async fn value3(&self, #[graphql(validator(IntRange(min = "100", max = "200")))] value: i32) -> i32 {
|
||||
/// unimplemented!()
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait InputValueValidator
|
||||
where
|
||||
Self: Sync + Send,
|
||||
{
|
||||
/// Check value is valid, returns the reason for the error if it fails, otherwise None.
|
||||
///
|
||||
/// If the input type is different from the required type, return `Ok(())` directly, and other validators will find this error.
|
||||
fn is_valid(&self, _value: &Value) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check value is valid, returns the reason include extensions for the error if it fails, otherwise None.
|
||||
///
|
||||
/// If the input type is different from the required type, return `Ok(())` directly, and other validators will find this error.
|
||||
///
|
||||
/// # Examples:
|
||||
///
|
||||
/// ```no_run
|
||||
/// use async_graphql::validators::InputValueValidator;
|
||||
/// use async_graphql::{Value, Error, ErrorExtensions};
|
||||
///
|
||||
/// pub struct IntGreaterThanZero;
|
||||
///
|
||||
/// impl InputValueValidator for IntGreaterThanZero {
|
||||
/// fn is_valid_with_extensions(&self, value: &Value) -> Result<(), Error> {
|
||||
/// if let Value::Number(n) = value {
|
||||
/// if let Some(n) = n.as_i64() {
|
||||
/// if n <= 0 {
|
||||
/// return Err(
|
||||
/// Error::new("Value must be greater than 0").extend_with(|_, e| e.set("code", 400))
|
||||
/// );
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn is_valid_with_extensions(&self, value: &Value) -> Result<(), Error> {
|
||||
// By default, use is_valid method to keep compatible with previous version
|
||||
self.is_valid(value).map_err(Error::new)
|
||||
}
|
||||
}
|
||||
|
||||
/// An extension trait for `InputValueValidator`
|
||||
pub trait InputValueValidatorExt: InputValueValidator + Sized {
|
||||
/// Merge the two validators and return None only if both validators are successful.
|
||||
fn and<R: InputValueValidator>(self, other: R) -> And<Self, R> {
|
||||
And(self, other)
|
||||
}
|
||||
|
||||
/// Merge two validators, and return None when either validator verifies successfully.
|
||||
fn or<R: InputValueValidator>(self, other: R) -> Or<Self, R> {
|
||||
Or(self, other)
|
||||
}
|
||||
|
||||
/// Changes the error message
|
||||
fn map_err<F: Fn(String) -> String>(self, f: F) -> MapErr<Self, F> {
|
||||
MapErr(self, f)
|
||||
}
|
||||
|
||||
/// Changes the error struct
|
||||
fn map_err_ext<F: Fn(Error) -> Error>(self, f: F) -> MapErrExt<Self, F> {
|
||||
MapErrExt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: InputValueValidator> InputValueValidatorExt for I {}
|
||||
|
||||
/// Invalidator for `InputValueValidatorExt::and`
|
||||
pub struct And<A, B>(A, B);
|
||||
|
||||
impl<A, B> InputValueValidator for And<A, B>
|
||||
where
|
||||
A: InputValueValidator,
|
||||
B: InputValueValidator,
|
||||
{
|
||||
fn is_valid_with_extensions(&self, value: &Value) -> Result<(), Error> {
|
||||
// By default, use is_valid method to keep compatible with previous version
|
||||
self.0.is_valid_with_extensions(value)?;
|
||||
self.1.is_valid_with_extensions(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Invalidator for `InputValueValidator::or`
|
||||
pub struct Or<A, B>(A, B);
|
||||
|
||||
impl<A, B> InputValueValidator for Or<A, B>
|
||||
where
|
||||
A: InputValueValidator,
|
||||
B: InputValueValidator,
|
||||
{
|
||||
fn is_valid_with_extensions(&self, value: &Value) -> Result<(), Error> {
|
||||
// By default, use is_valid method to keep compatible with previous version
|
||||
if self.0.is_valid_with_extensions(value).is_err() {
|
||||
self.1.is_valid_with_extensions(value)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Invalidator for `InputValueValidator::map_err`
|
||||
pub struct MapErr<I, F>(I, F);
|
||||
|
||||
impl<I, F> InputValueValidator for MapErr<I, F>
|
||||
where
|
||||
I: InputValueValidator,
|
||||
F: Fn(String) -> String + Send + Sync,
|
||||
{
|
||||
fn is_valid(&self, value: &Value) -> Result<(), String> {
|
||||
self.0.is_valid(value).map_err(&self.1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Invalidator for `InputValueValidator::map_err_ext`
|
||||
pub struct MapErrExt<I, F>(I, F);
|
||||
|
||||
impl<I, F> InputValueValidator for MapErrExt<I, F>
|
||||
where
|
||||
I: InputValueValidator,
|
||||
F: Fn(Error) -> Error + Send + Sync,
|
||||
{
|
||||
fn is_valid_with_extensions(&self, value: &Value) -> Result<(), Error> {
|
||||
self.0.is_valid_with_extensions(value).map_err(&self.1)
|
||||
}
|
||||
}
|
||||
pub use max_items::max_items;
|
||||
pub use max_length::max_length;
|
||||
pub use maximum::maximum;
|
||||
pub use min_items::min_items;
|
||||
pub use min_length::min_length;
|
||||
pub use minimum::minimum;
|
||||
pub use multiple_of::multiple_of;
|
||||
|
|
14
src/validators/multiple_of.rs
Normal file
14
src/validators/multiple_of.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
use num_traits::AsPrimitive;
|
||||
|
||||
use crate::{InputType, InputValueError};
|
||||
|
||||
pub async fn multiple_of<T: AsPrimitive<f64> + InputType>(
|
||||
value: &T,
|
||||
n: f64,
|
||||
) -> Result<(), InputValueError<T>> {
|
||||
if value.as_() % n as f64 == 0.0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("the value must be a multiple of {}.", n).into())
|
||||
}
|
||||
}
|
|
@ -1,153 +0,0 @@
|
|||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::validators::InputValueValidator;
|
||||
use crate::Value;
|
||||
|
||||
/// String minimum length validator
|
||||
pub struct StringMinLength {
|
||||
/// Must be greater than or equal to this value.
|
||||
pub length: i32,
|
||||
}
|
||||
|
||||
impl InputValueValidator for StringMinLength {
|
||||
fn is_valid(&self, value: &Value) -> Result<(), String> {
|
||||
if let Value::String(s) = value {
|
||||
if s.len() < self.length as usize {
|
||||
Err(format!(
|
||||
"the value length is {}, must be greater than or equal to {}",
|
||||
s.len(),
|
||||
self.length
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// String maximum length validator
|
||||
pub struct StringMaxLength {
|
||||
/// Must be less than or equal to this value.
|
||||
pub length: i32,
|
||||
}
|
||||
|
||||
impl InputValueValidator for StringMaxLength {
|
||||
fn is_valid(&self, value: &Value) -> Result<(), String> {
|
||||
if let Value::String(s) = value {
|
||||
if s.len() > self.length as usize {
|
||||
Err(format!(
|
||||
"the value length is {}, must be less than or equal to {}",
|
||||
s.len(),
|
||||
self.length
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Chars in string minimum length validator.
|
||||
pub struct CharsMinLength {
|
||||
/// Must be greater than or equal to this value.
|
||||
pub length: i32,
|
||||
}
|
||||
|
||||
impl InputValueValidator for CharsMinLength {
|
||||
fn is_valid(&self, value: &Value) -> Result<(), String> {
|
||||
if let Value::String(s) = value {
|
||||
if s.chars().count() < self.length as usize {
|
||||
Err(format!(
|
||||
"the value chars count is {}, must be greater than or equal to {}",
|
||||
s.chars().count(),
|
||||
self.length
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Chars in string maximum length validator.
|
||||
pub struct CharsMaxLength {
|
||||
/// Must be less than or equal to this value.
|
||||
pub length: i32,
|
||||
}
|
||||
|
||||
impl InputValueValidator for CharsMaxLength {
|
||||
fn is_valid(&self, value: &Value) -> Result<(), String> {
|
||||
if let Value::String(s) = value {
|
||||
if s.chars().count() > self.length as usize {
|
||||
Err(format!(
|
||||
"the value chars count is {}, must be less than or equal to {}",
|
||||
s.chars().count(),
|
||||
self.length
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static EMAIL_RE: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new("^(([0-9A-Za-z!#$%&'*+-/=?^_`{|}~&&[^@]]+)|(\"([0-9A-Za-z!#$%&'*+-/=?^_`{|}~ \"(),:;<>@\\[\\\\\\]]+)\"))@").unwrap()
|
||||
});
|
||||
|
||||
/// Email validator
|
||||
pub struct Email {}
|
||||
|
||||
impl InputValueValidator for Email {
|
||||
fn is_valid(&self, value: &Value) -> Result<(), String> {
|
||||
if let Value::String(s) = value {
|
||||
if !EMAIL_RE.is_match(s) {
|
||||
Err("invalid email format".to_string())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static MAC_ADDRESS_RE: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new("^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$").unwrap());
|
||||
static MAC_ADDRESS_NO_COLON_RE: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new("^[0-9a-fA-F]{12}$").unwrap());
|
||||
|
||||
/// MAC address validator
|
||||
pub struct MAC {
|
||||
/// Must include colon.
|
||||
pub colon: bool,
|
||||
}
|
||||
|
||||
impl InputValueValidator for MAC {
|
||||
fn is_valid(&self, value: &Value) -> Result<(), String> {
|
||||
if let Value::String(s) = value {
|
||||
if self.colon {
|
||||
if !MAC_ADDRESS_RE.is_match(s) {
|
||||
Err("invalid MAC format".to_string())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else if !MAC_ADDRESS_NO_COLON_RE.is_match(s) {
|
||||
Err("invalid MAC format".to_string())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user