From 934038150e49b3541290cbcd7f34ec6ec2b6fb47 Mon Sep 17 00:00:00 2001 From: sunli Date: Sun, 22 Mar 2020 09:34:32 +0800 Subject: [PATCH] v1.4.2 --- Cargo.toml | 4 +- README.md | 7 ++- async-graphql-actix-web/Cargo.toml | 4 +- async-graphql-derive/Cargo.toml | 2 +- async-graphql-derive/src/args.rs | 32 +++++------ async-graphql-derive/src/input_object.rs | 4 +- async-graphql-derive/src/interface.rs | 2 +- async-graphql-derive/src/object.rs | 4 +- async-graphql-derive/src/subscription.rs | 4 +- async-graphql-derive/src/utils.rs | 30 ++++++---- src/lib.rs | 8 +-- src/registry.rs | 2 +- src/schema.rs | 4 +- src/types/query_root.rs | 2 +- .../rules/arguments_of_correct_type.rs | 2 +- src/validators/int_validators.rs | 24 +++++++- src/validators/list_validators.rs | 50 +++++++++++++++++ src/validators/mod.rs | 14 +++-- src/validators/string_validators.rs | 56 +++++++++++++++++-- 19 files changed, 196 insertions(+), 59 deletions(-) create mode 100644 src/validators/list_validators.rs diff --git a/Cargo.toml b/Cargo.toml index b633c7b8..b05ad7fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql" -version = "1.4.1" +version = "1.4.2" authors = ["sunli "] edition = "2018" description = "The GraphQL server library implemented by rust" @@ -18,7 +18,7 @@ default = ["bson", "chrono", "uuid", "url", "validators"] validators = ["regex", "once_cell"] [dependencies] -async-graphql-derive = { path = "async-graphql-derive", version = "1.4.1" } +async-graphql-derive = { path = "async-graphql-derive", version = "1.4.2" } graphql-parser = "0.2.3" anyhow = "1.0.26" thiserror = "1.0.11" diff --git a/README.md b/README.md index b936ac5a..1fd48e82 100644 --- a/README.md +++ b/README.md @@ -109,10 +109,15 @@ Open `http://localhost:8000` in browser - [X] IntRange - [X] IntLessThan - [X] IntGreaterThan + - [X] IntNonZero - [X] String - [X] Email - [X] MAC - - [X] String + - [X] StringMinLength + - [X] StringMaxLength + - [X] List + - [X] ListMaxLength + - [X] ListMinLength - [X] Subscription - [X] Filter - [X] WebSocket transport diff --git a/async-graphql-actix-web/Cargo.toml b/async-graphql-actix-web/Cargo.toml index f5c54116..2579713e 100644 --- a/async-graphql-actix-web/Cargo.toml +++ b/async-graphql-actix-web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-actix-web" -version = "0.5.1" +version = "0.5.2" authors = ["sunli "] edition = "2018" description = "async-graphql for actix-web" @@ -13,7 +13,7 @@ keywords = ["futures", "async", "graphql"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "..", version = "1.4.1" } +async-graphql = { path = "..", version = "1.4.2" } actix-web = "2.0.0" actix-multipart = "0.2.0" actix-web-actors = "2.0.0" diff --git a/async-graphql-derive/Cargo.toml b/async-graphql-derive/Cargo.toml index 4a76868b..9ac534fa 100644 --- a/async-graphql-derive/Cargo.toml +++ b/async-graphql-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-derive" -version = "1.4.1" +version = "1.4.2" authors = ["sunli "] edition = "2018" description = "Macros for async-graphql" diff --git a/async-graphql-derive/src/args.rs b/async-graphql-derive/src/args.rs index 53d09147..13f4b6f8 100644 --- a/async-graphql-derive/src/args.rs +++ b/async-graphql-derive/src/args.rs @@ -1,4 +1,4 @@ -use crate::utils::{parse_validators, parse_value}; +use crate::utils::{parse_validator, parse_value}; use graphql_parser::query::Value; use proc_macro2::TokenStream; use quote::quote; @@ -60,7 +60,7 @@ pub struct Argument { pub name: Option, pub desc: Option, pub default: Option, - pub validators: TokenStream, + pub validator: TokenStream, } impl Argument { @@ -68,11 +68,11 @@ impl Argument { let mut name = None; let mut desc = None; let mut default = None; - let mut validators = quote! { Default::default() }; + let mut validator = quote! { None }; for attr in attrs { - match attr.parse_meta() { - Ok(Meta::List(ls)) if ls.path.is_ident("arg") => { + match attr.parse_meta()? { + Meta::List(ls) if ls.path.is_ident("arg") => { for meta in &ls.nested { if let NestedMeta::Meta(Meta::NameValue(nv)) = meta { if nv.path.is_ident("name") { @@ -120,7 +120,7 @@ impl Argument { } } - validators = parse_validators(crate_name, &ls)?; + validator = parse_validator(crate_name, &ls)?; } _ => {} } @@ -130,7 +130,7 @@ impl Argument { name, desc, default, - validators, + validator, }) } } @@ -150,11 +150,11 @@ impl Field { let mut deprecation = None; for attr in attrs { - match attr.parse_meta() { - Ok(Meta::Path(p)) if p.is_ident("field") => { + match attr.parse_meta()? { + Meta::Path(p) if p.is_ident("field") => { is_field = true; } - Ok(Meta::List(ls)) if ls.path.is_ident("field") => { + Meta::List(ls) if ls.path.is_ident("field") => { is_field = true; for meta in &ls.nested { if let NestedMeta::Meta(Meta::NameValue(nv)) = meta { @@ -271,7 +271,7 @@ impl EnumItem { for attr in attrs { if attr.path.is_ident("item") { - if let Ok(Meta::List(args)) = attr.parse_meta() { + if let Meta::List(args) = attr.parse_meta()? { for meta in args.nested { if let NestedMeta::Meta(Meta::NameValue(nv)) = meta { if nv.path.is_ident("name") { @@ -322,7 +322,7 @@ pub struct InputField { pub name: Option, pub desc: Option, pub default: Option, - pub validators: TokenStream, + pub validator: TokenStream, } impl InputField { @@ -331,11 +331,11 @@ impl InputField { let mut name = None; let mut desc = None; let mut default = None; - let mut validators = quote! { Default::default() }; + let mut validator = quote! { None }; for attr in attrs { if attr.path.is_ident("field") { - if let Ok(Meta::List(args)) = &attr.parse_meta() { + if let Meta::List(args) = &attr.parse_meta()? { for meta in &args.nested { match meta { NestedMeta::Meta(Meta::Path(p)) if p.is_ident("internal") => { @@ -389,7 +389,7 @@ impl InputField { } } - validators = parse_validators(crate_name, &args)?; + validator = parse_validator(crate_name, &args)?; } } } @@ -399,7 +399,7 @@ impl InputField { name, desc, default, - validators, + validator, }) } } diff --git a/async-graphql-derive/src/input_object.rs b/async-graphql-derive/src/input_object.rs index 559e98b0..7a260c12 100644 --- a/async-graphql-derive/src/input_object.rs +++ b/async-graphql-derive/src/input_object.rs @@ -51,7 +51,7 @@ pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result< let field_args = args::InputField::parse(&crate_name, &field.attrs)?; let ident = field.ident.as_ref().unwrap(); let ty = &field.ty; - let validators = &field_args.validators; + let validator = &field_args.validator; let name = field_args .name .unwrap_or_else(|| ident.to_string().to_camel_case()); @@ -95,7 +95,7 @@ pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result< description: #desc, ty: <#ty as #crate_name::Type>::create_type_info(registry), default_value: #default, - validators: #validators, + validator: #validator, } }) } diff --git a/async-graphql-derive/src/interface.rs b/async-graphql-derive/src/interface.rs index 6a8bea6a..06d0e261 100644 --- a/async-graphql-derive/src/interface.rs +++ b/async-graphql-derive/src/interface.rs @@ -137,7 +137,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result description: #desc, ty: <#ty as #crate_name::Type>::create_type_info(registry), default_value: #schema_default, - validators: Default::default(), + validator: None, }); }); } diff --git a/async-graphql-derive/src/object.rs b/async-graphql-derive/src/object.rs index 677a5b41..2b988f11 100644 --- a/async-graphql-derive/src/object.rs +++ b/async-graphql-derive/src/object.rs @@ -117,7 +117,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< name, desc, default, - validators, + validator, }, ) in args { @@ -142,7 +142,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< description: #desc, ty: <#ty as #crate_name::Type>::create_type_info(registry), default_value: #schema_default, - validators: #validators, + validator: #validator, }); }); diff --git a/async-graphql-derive/src/subscription.rs b/async-graphql-derive/src/subscription.rs index dd0147e5..bb2b8f1d 100644 --- a/async-graphql-derive/src/subscription.rs +++ b/async-graphql-derive/src/subscription.rs @@ -141,7 +141,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< name, desc, default, - validators, + validator, }, ) in args { @@ -166,7 +166,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< description: #desc, ty: <#ty as #crate_name::Type>::create_type_info(registry), default_value: #schema_default, - validators: #validators, + validator: #validator, }); }); diff --git a/async-graphql-derive/src/utils.rs b/async-graphql-derive/src/utils.rs index 5eb7e318..3280d8d3 100644 --- a/async-graphql-derive/src/utils.rs +++ b/async-graphql-derive/src/utils.rs @@ -97,7 +97,10 @@ pub fn check_reserved_name(name: &str, internal: bool) -> Result<()> { } } -pub fn parse_validator(crate_name: &TokenStream, nested_meta: &NestedMeta) -> Result { +pub fn parse_nested_validator( + crate_name: &TokenStream, + nested_meta: &NestedMeta, +) -> Result { let mut params = Vec::new(); match nested_meta { @@ -105,7 +108,7 @@ pub fn parse_validator(crate_name: &TokenStream, nested_meta: &NestedMeta) -> Re if ls.path.is_ident("and") { let mut validators = Vec::new(); for nested_meta in &ls.nested { - validators.push(parse_validator(crate_name, nested_meta)?); + validators.push(parse_nested_validator(crate_name, nested_meta)?); } Ok(validators .into_iter() @@ -117,7 +120,7 @@ pub fn parse_validator(crate_name: &TokenStream, nested_meta: &NestedMeta) -> Re } else if ls.path.is_ident("or") { let mut validators = Vec::new(); for nested_meta in &ls.nested { - validators.push(parse_validator(crate_name, nested_meta)?); + validators.push(parse_nested_validator(crate_name, nested_meta)?); } Ok(validators .into_iter() @@ -150,17 +153,24 @@ pub fn parse_validator(crate_name: &TokenStream, nested_meta: &NestedMeta) -> Re } } -pub fn parse_validators(crate_name: &TokenStream, args: &MetaList) -> Result { - let mut validators = Vec::new(); +pub fn parse_validator(crate_name: &TokenStream, args: &MetaList) -> Result { for arg in &args.nested { if let NestedMeta::Meta(Meta::List(ls)) = arg { - if ls.path.is_ident("validators") { - for meta in &ls.nested { - let validator = parse_validator(crate_name, meta)?; - validators.push(quote! { Box::new(#validator) }); + if ls.path.is_ident("validator") { + if ls.nested.len() > 1 { + return Err(Error::new_spanned(ls, + "Only one validator can be defined. You can connect combine validators with `and` or `or`")); } + if ls.nested.len() == 0 { + return Err(Error::new_spanned( + ls, + "At least one validator must be defined", + )); + } + let validator = parse_nested_validator(crate_name, &ls.nested[0])?; + return Ok(quote! { Some(std::sync::Arc::new(#validator)) }); } } } - Ok(quote! { std::sync::Arc::new(vec![#(#validators),*]) }) + Ok(quote! {None}) } diff --git a/src/lib.rs b/src/lib.rs index 1a4a643e..b79a61d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -152,7 +152,7 @@ pub use types::{EnumItem, EnumType}; /// | name | Argument name | string | Y | /// | desc | Argument description | string | Y | /// | default | Argument default value | string | Y | -/// | validators | Input value validators | `InputValueValidator` | Y | +/// | validator | Input value validator | [`InputValueValidator`](validators/trait.InputValueValidator.html) | Y | /// /// # The field returns the value type /// @@ -300,7 +300,7 @@ pub use async_graphql_derive::Enum; /// | name | Field name | string | Y | /// | desc | Field description | string | Y | /// | default | Field default value | string | Y | -/// | validators | Input value validators | `InputValueValidator` | Y | +/// | validator | Input value validator | [`InputValueValidator`](validators/trait.InputValueValidator.html) | Y | /// /// # Examples /// @@ -355,7 +355,7 @@ pub use async_graphql_derive::InputObject; /// | desc | Field description | string | Y | /// | context | Method with the context | string | Y | /// | deprecation | Field deprecation reason | string | Y | -/// | args | Field arguments | [Arg] | Y | +/// | args | Field arguments | | Y | /// /// # Field argument parameters /// @@ -485,7 +485,7 @@ pub use async_graphql_derive::Union; /// | name | Argument name | string | Y | /// | desc | Argument description | string | Y | /// | default | Argument default value | string | Y | -/// | validators | Input value validators | `InputValueValidator` | Y | +/// | validator | Input value validator | [`InputValueValidator`](validators/trait.InputValueValidator.html) | Y | /// /// # Examples /// diff --git a/src/registry.rs b/src/registry.rs index 7e0edeef..041a1a28 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -60,7 +60,7 @@ pub struct InputValue { pub description: Option<&'static str>, pub ty: String, pub default_value: Option<&'static str>, - pub validators: Arc>>, + pub validator: Option>, } #[derive(Clone)] diff --git a/src/schema.rs b/src/schema.rs index 174a02c4..495188d1 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -57,7 +57,7 @@ impl description: Some("Included when true."), ty: "Boolean!".to_string(), default_value: None, - validators: Default::default(), + validator: None, }); args } @@ -78,7 +78,7 @@ impl description: Some("Skipped when true."), ty: "Boolean!".to_string(), default_value: None, - validators: Default::default(), + validator: None, }); args } diff --git a/src/types/query_root.rs b/src/types/query_root.rs index df7030ec..74f0724d 100644 --- a/src/types/query_root.rs +++ b/src/types/query_root.rs @@ -47,7 +47,7 @@ impl Type for QueryRoot { description: None, ty: "String!".to_string(), default_value: None, - validators: Default::default(), + validator: None, }, ); args diff --git a/src/validation/rules/arguments_of_correct_type.rs b/src/validation/rules/arguments_of_correct_type.rs index d8d2eea1..47c98ee9 100644 --- a/src/validation/rules/arguments_of_correct_type.rs +++ b/src/validation/rules/arguments_of_correct_type.rs @@ -36,7 +36,7 @@ impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> { .current_args .and_then(|args| args.get(name).map(|input| input)) { - for validator in arg.validators.iter() { + if let Some(validator) = &arg.validator { if let Some(reason) = validator.is_valid(value) { ctx.report_error( vec![pos], diff --git a/src/validators/int_validators.rs b/src/validators/int_validators.rs index 3746a71f..e65b6c39 100644 --- a/src/validators/int_validators.rs +++ b/src/validators/int_validators.rs @@ -24,7 +24,7 @@ impl InputValueValidator for IntRange { None } } else { - Some("expected type \"Int\"".to_string()) + None } } } @@ -48,7 +48,7 @@ impl InputValueValidator for IntLessThan { None } } else { - Some("expected type \"Int\"".to_string()) + None } } } @@ -76,3 +76,23 @@ impl InputValueValidator for IntGreaterThan { } } } + +/// Integer nonzero validator +pub struct IntNonZero {} + +impl InputValueValidator for IntNonZero { + fn is_valid(&self, value: &Value) -> Option { + if let Value::Int(n) = value { + if n.as_i64().unwrap() == 0 { + Some(format!( + "the value is {}, but must be nonzero", + n.as_i64().unwrap(), + )) + } else { + None + } + } else { + Some("expected type \"Int\"".to_string()) + } + } +} diff --git a/src/validators/list_validators.rs b/src/validators/list_validators.rs new file mode 100644 index 00000000..0158030e --- /dev/null +++ b/src/validators/list_validators.rs @@ -0,0 +1,50 @@ +use crate::validators::InputValueValidator; +use graphql_parser::query::Value; + +/// List minimum length validator +pub struct ListMinLength { + /// Must be greater than or equal to this value. + pub length: usize, +} + +impl InputValueValidator for ListMinLength { + fn is_valid(&self, value: &Value) -> Option { + if let Value::List(values) = value { + if values.len() < self.length { + Some(format!( + "The value length is {}, but the length must be greater than or equal to {}", + values.len(), + self.length + )) + } else { + None + } + } else { + Some("expected type \"List\"".to_string()) + } + } +} + +/// List maximum length validator +pub struct ListMaxLength { + /// Must be less than or equal to this value. + pub length: usize, +} + +impl InputValueValidator for ListMaxLength { + fn is_valid(&self, value: &Value) -> Option { + if let Value::List(values) = value { + if values.len() > self.length { + Some(format!( + "The value length is {}, but the length must be less than or equal to {}", + values.len(), + self.length + )) + } else { + None + } + } else { + None + } + } +} diff --git a/src/validators/mod.rs b/src/validators/mod.rs index 28a742b7..ed09bd71 100644 --- a/src/validators/mod.rs +++ b/src/validators/mod.rs @@ -1,10 +1,12 @@ mod int_validators; +mod list_validators; mod string_validators; use graphql_parser::schema::Value; -pub use int_validators::{IntGreaterThan, IntLessThan, IntRange}; -pub use string_validators::{Email, MAC}; +pub use int_validators::{IntGreaterThan, IntLessThan, IntNonZero, IntRange}; +pub use list_validators::{ListMaxLength, ListMinLength}; +pub use string_validators::{Email, StringMaxLength, StringMinLength, MAC}; /// Input value validator /// @@ -20,19 +22,19 @@ pub use string_validators::{Email, MAC}; /// impl QueryRoot { /// // Input is email address /// #[field] -/// async fn value1(&self, #[arg(validators(Email))] email: String) -> i32 { +/// async fn value1(&self, #[arg(validator(Email))] email: String) -> i32 { /// unimplemented!() /// } /// /// // Input is email or MAC address /// #[field] -/// async fn value2(&self, #[arg(validators(or(Email, MAC(colon: false))))] email_or_mac: String) -> i32 { +/// async fn value2(&self, #[arg(validator(or(Email, MAC(colon = false))))] email_or_mac: String) -> i32 { /// unimplemented!() /// } /// /// // Input is integer between 100 and 200 /// #[field] -/// async fn value3(&self, #[arg(validators(IntRange(min = 100, max = 200)))] value: i32) -> i32 { +/// async fn value3(&self, #[arg(validator(IntRange(min = 100, max = 200)))] value: i32) -> i32 { /// unimplemented!() /// } /// } @@ -42,6 +44,8 @@ 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 None directly, and other validators will find this error. fn is_valid(&self, value: &Value) -> Option; } diff --git a/src/validators/string_validators.rs b/src/validators/string_validators.rs index 43336b21..82ef3fb0 100644 --- a/src/validators/string_validators.rs +++ b/src/validators/string_validators.rs @@ -3,6 +3,54 @@ use graphql_parser::schema::Value; use once_cell::sync::Lazy; use regex::Regex; +/// String minimum length validator +pub struct StringMinLength { + /// Must be greater than or equal to this value. + pub length: usize, +} + +impl InputValueValidator for StringMinLength { + fn is_valid(&self, value: &Value) -> Option { + if let Value::String(s) = value { + if s.len() < self.length { + Some(format!( + "The value length is {}, but the length must be greater than or equal to {}", + s.len(), + self.length + )) + } else { + None + } + } else { + None + } + } +} + +/// String maximum length validator +pub struct StringMaxLength { + /// Must be less than or equal to this value. + pub length: usize, +} + +impl InputValueValidator for StringMaxLength { + fn is_valid(&self, value: &Value) -> Option { + if let Value::String(s) = value { + if s.len() > self.length { + Some(format!( + "The value length is {}, but the length must be less than or equal to {}", + s.len(), + self.length + )) + } else { + None + } + } else { + None + } + } +} + static EMAIL_RE: Lazy = Lazy::new(|| { Regex::new("^(([0-9A-Za-z!#$%&'*+-/=?^_`{|}~&&[^@]]+)|(\"([0-9A-Za-z!#$%&'*+-/=?^_`{|}~ \"(),:;<>@\\[\\\\\\]]+)\"))@").unwrap() }); @@ -19,7 +67,7 @@ impl InputValueValidator for Email { None } } else { - Some("expected type \"String\"".to_string()) + None } } } @@ -31,8 +79,8 @@ static MAC_ADDRESS_NO_COLON_RE: Lazy = /// MAC address validator pub struct MAC { - /// Must include colon - colon: bool, + /// Must include colon. + pub colon: bool, } impl InputValueValidator for MAC { @@ -50,7 +98,7 @@ impl InputValueValidator for MAC { None } } else { - Some("expected type \"String\"".to_string()) + None } } }