Rework validators

This commit is contained in:
Sunli 2021-11-14 21:09:14 +08:00
parent 08263394a8
commit 8750d8d34b
30 changed files with 317 additions and 2912 deletions

View File

@ -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,
}

View File

@ -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();

View File

@ -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,
});

View File

@ -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,
});

View File

@ -20,6 +20,7 @@ mod simple_object;
mod subscription;
mod union;
mod utils;
mod validators;
use darling::{FromDeriveInput, FromMeta};
use proc_macro::TokenStream;

View File

@ -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
});
}

View File

@ -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
});
}

View File

@ -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
View 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;)*)
}
}

View File

@ -179,6 +179,7 @@ pub mod guard;
pub mod http;
pub mod resolver_utils;
pub mod types;
#[doc(hidden)]
pub mod validators;
#[doc(hidden)]

View File

@ -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,
},

View File

@ -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,
});

View File

@ -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,
},

View File

@ -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),
);
}
}

View File

@ -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),
)
}
}

View File

@ -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));

View File

@ -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

View File

@ -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,
}
}
}

View File

@ -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(())
}
}

View File

@ -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(())
}
}

View 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())
}
}

View 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
View 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())
}
}

View 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())
}
}

View 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
View 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())
}
}

View File

@ -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;

View 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())
}
}

View File

@ -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