From b4116cdcaa4226c70dfa9eda588b714a6b15055e Mon Sep 17 00:00:00 2001 From: smihica Date: Wed, 13 Oct 2021 15:16:14 +0900 Subject: [PATCH 1/4] Supported multibyte string in StringMinLength and StringMaxLength to count chars, not byte len. --- src/validators/string_validators.rs | 8 ++++---- tests/input_validators.rs | 14 ++++++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/validators/string_validators.rs b/src/validators/string_validators.rs index c2f83f32..da70a1d4 100644 --- a/src/validators/string_validators.rs +++ b/src/validators/string_validators.rs @@ -13,10 +13,10 @@ pub struct StringMinLength { 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 { + if s.chars().count() < self.length as usize { Err(format!( "the value length is {}, must be greater than or equal to {}", - s.len(), + s.chars().count(), self.length )) } else { @@ -37,10 +37,10 @@ pub struct StringMaxLength { 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 { + if s.chars().count() > self.length as usize { Err(format!( "the value length is {}, must be less than or equal to {}", - s.len(), + s.chars().count(), self.length )) } else { diff --git a/tests/input_validators.rs b/tests/input_validators.rs index 39d4060a..5fbd6428 100644 --- a/tests/input_validators.rs +++ b/tests/input_validators.rs @@ -31,18 +31,21 @@ pub async fn test_input_validator_string_min_length() { let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription); let test_cases = [ "abc", - "acbce", + "abcde", "abcdef", "abcdefghi", "abcdefghijkl", "abcdefghijklmnop", + "一二三四五", + "壹贰叁肆伍陆", + "一二三四五六七", ]; let validator_length = 6; for case in &test_cases { let field_query = format!("{{fieldParameter(id: \"{}\")}}", case); let object_query = format!("{{inputObject(input: {{id: \"{}\"}})}}", case); - let case_length = case.len(); + let case_length = case.chars().count(); if case_length < validator_length { let should_fail_msg = format!( @@ -147,18 +150,21 @@ pub async fn test_input_validator_string_max_length() { let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription); let test_cases = [ "abc", - "acbce", + "abcde", "abcdef", "abcdefghi", "abcdefghijkl", "abcdefghijklmnop", + "一二三四五", + "壹贰叁肆伍陆", + "一二三四五六七", ]; let validator_length = 6; for case in &test_cases { let field_query = format!("{{fieldParameter(id: \"{}\")}}", case); let object_query = format!("{{inputObject(input: {{id: \"{}\"}})}}", case); - let case_length = case.len(); + let case_length = case.chars().count(); if case_length > validator_length { let should_fail_msg = format!( From 4c3e5905a7221b13e3236080b7ab4a6b1db80850 Mon Sep 17 00:00:00 2001 From: smihica Date: Thu, 14 Oct 2021 00:33:20 +0900 Subject: [PATCH 2/4] Revert "Supported multibyte string in StringMinLength and StringMaxLength to count chars, not byte len." This reverts commit b4116cdcaa4226c70dfa9eda588b714a6b15055e. --- src/validators/string_validators.rs | 8 ++++---- tests/input_validators.rs | 14 ++++---------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/validators/string_validators.rs b/src/validators/string_validators.rs index da70a1d4..c2f83f32 100644 --- a/src/validators/string_validators.rs +++ b/src/validators/string_validators.rs @@ -13,10 +13,10 @@ pub struct StringMinLength { impl InputValueValidator for StringMinLength { fn is_valid(&self, value: &Value) -> Result<(), String> { if let Value::String(s) = value { - if s.chars().count() < self.length as usize { + if s.len() < self.length as usize { Err(format!( "the value length is {}, must be greater than or equal to {}", - s.chars().count(), + s.len(), self.length )) } else { @@ -37,10 +37,10 @@ pub struct StringMaxLength { impl InputValueValidator for StringMaxLength { fn is_valid(&self, value: &Value) -> Result<(), String> { if let Value::String(s) = value { - if s.chars().count() > self.length as usize { + if s.len() > self.length as usize { Err(format!( "the value length is {}, must be less than or equal to {}", - s.chars().count(), + s.len(), self.length )) } else { diff --git a/tests/input_validators.rs b/tests/input_validators.rs index 5fbd6428..39d4060a 100644 --- a/tests/input_validators.rs +++ b/tests/input_validators.rs @@ -31,21 +31,18 @@ pub async fn test_input_validator_string_min_length() { let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription); let test_cases = [ "abc", - "abcde", + "acbce", "abcdef", "abcdefghi", "abcdefghijkl", "abcdefghijklmnop", - "一二三四五", - "壹贰叁肆伍陆", - "一二三四五六七", ]; let validator_length = 6; for case in &test_cases { let field_query = format!("{{fieldParameter(id: \"{}\")}}", case); let object_query = format!("{{inputObject(input: {{id: \"{}\"}})}}", case); - let case_length = case.chars().count(); + let case_length = case.len(); if case_length < validator_length { let should_fail_msg = format!( @@ -150,21 +147,18 @@ pub async fn test_input_validator_string_max_length() { let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription); let test_cases = [ "abc", - "abcde", + "acbce", "abcdef", "abcdefghi", "abcdefghijkl", "abcdefghijklmnop", - "一二三四五", - "壹贰叁肆伍陆", - "一二三四五六七", ]; let validator_length = 6; for case in &test_cases { let field_query = format!("{{fieldParameter(id: \"{}\")}}", case); let object_query = format!("{{inputObject(input: {{id: \"{}\"}})}}", case); - let case_length = case.chars().count(); + let case_length = case.len(); if case_length > validator_length { let should_fail_msg = format!( From 7d9de1c0f847077a36bfd760939e54684b43b310 Mon Sep 17 00:00:00 2001 From: smihica Date: Thu, 14 Oct 2021 00:42:10 +0900 Subject: [PATCH 3/4] Supported CharsMinLength and CharsMaxLength to count chars --- src/validators/mod.rs | 4 +- src/validators/string_validators.rs | 48 ++++++ tests/input_validators.rs | 225 +++++++++++++++++++++++++++- 3 files changed, 274 insertions(+), 3 deletions(-) diff --git a/src/validators/mod.rs b/src/validators/mod.rs index c9ad8bb4..5e2062c3 100644 --- a/src/validators/mod.rs +++ b/src/validators/mod.rs @@ -8,7 +8,9 @@ use crate::{Error, Value}; pub use int_validators::{IntEqual, IntGreaterThan, IntLessThan, IntNonZero, IntRange}; pub use list_validators::{List, ListMaxLength, ListMinLength}; -pub use string_validators::{Email, StringMaxLength, StringMinLength, MAC}; +pub use string_validators::{ + CharsMaxLength, CharsMinLength, Email, StringMaxLength, StringMinLength, MAC, +}; /// Input value validator /// diff --git a/src/validators/string_validators.rs b/src/validators/string_validators.rs index c2f83f32..a6982a94 100644 --- a/src/validators/string_validators.rs +++ b/src/validators/string_validators.rs @@ -52,6 +52,54 @@ impl InputValueValidator for StringMaxLength { } } +/// Chars minimum length validator. This supports multibyte character. +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 maximum length validator. This supports multibyte characters. +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 = Lazy::new(|| { Regex::new("^(([0-9A-Za-z!#$%&'*+-/=?^_`{|}~&&[^@]]+)|(\"([0-9A-Za-z!#$%&'*+-/=?^_`{|}~ \"(),:;<>@\\[\\\\\\]]+)\"))@").unwrap() }); diff --git a/tests/input_validators.rs b/tests/input_validators.rs index 39d4060a..4807903e 100644 --- a/tests/input_validators.rs +++ b/tests/input_validators.rs @@ -1,6 +1,7 @@ use async_graphql::validators::{ - Email, InputValueValidator, IntEqual, IntGreaterThan, IntLessThan, IntNonZero, IntRange, - ListMaxLength, ListMinLength, StringMaxLength, StringMinLength, MAC, + CharsMaxLength, CharsMinLength, Email, InputValueValidator, IntEqual, IntGreaterThan, + IntLessThan, IntNonZero, IntRange, ListMaxLength, ListMinLength, StringMaxLength, + StringMinLength, MAC, }; use async_graphql::*; @@ -230,6 +231,226 @@ pub async fn test_input_validator_string_max_length() { } } +#[tokio::test] +pub async fn test_input_validator_chars_min_length() { + struct QueryRoot; + + #[derive(InputObject)] + struct InputMaxLength { + #[graphql(validator(CharsMinLength(length = "6")))] + pub id: String, + } + + #[Object] + impl QueryRoot { + async fn field_parameter( + &self, + #[graphql(validator(CharsMinLength(length = "6")))] _id: String, + ) -> bool { + true + } + + async fn input_object(&self, _input: InputMaxLength) -> bool { + true + } + } + + let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription); + let test_cases = ["一2三4五", "壹2叁4伍6", "一2三4五6七", "壹2叁4伍6Ⅶ8"]; + + let validator_length = 6; + for case in &test_cases { + let field_query = format!("{{fieldParameter(id: \"{}\")}}", case); + let object_query = format!("{{inputObject(input: {{id: \"{}\"}})}}", case); + let case_length = case.chars().count(); + + if case_length < validator_length { + let should_fail_msg = format!( + "CharsMinLength case {} should have failed, but did not", + case + ); + + let field_error_msg = format!( + "Invalid value for argument \"id\", the value chars count is {}, must be greater than or equal to {}", + case_length, validator_length + ); + let object_error_msg = format!( + "Invalid value for argument \"input.id\", the value chars count is {}, must be greater than or equal to {}", + case_length, validator_length + ); + assert_eq!( + schema + .execute(&field_query) + .await + .into_result() + .expect_err(&should_fail_msg), + vec![ServerError { + message: field_error_msg, + locations: vec!(Pos { + line: 1, + column: 17 + }), + path: Vec::new(), + extensions: None, + }] + ); + + assert_eq!( + schema + .execute(&object_query) + .await + .into_result() + .expect_err(&should_fail_msg[..]), + vec![ServerError { + message: object_error_msg, + locations: vec!(Pos { + line: 1, + column: 14 + }), + path: Vec::new(), + extensions: None, + }] + ); + } else { + let error_msg = format!("Schema returned error with test_string = {}", case); + assert_eq!( + schema + .execute(&field_query) + .await + .into_result() + .expect(&error_msg[..]) + .data, + value!({"fieldParameter": true}), + "Failed to validate {} with CharsMinLength", + case + ); + + assert_eq!( + schema + .execute(&object_query) + .await + .into_result() + .expect(&error_msg[..]) + .data, + value!({"inputObject": true}), + "Failed to validate {} with CharsMinLength", + case + ); + } + } +} + +#[tokio::test] +pub async fn test_input_validator_chars_max_length() { + struct QueryRoot; + + #[derive(InputObject)] + struct InputMaxLength { + #[graphql(validator(CharsMaxLength(length = "6")))] + pub id: String, + } + + #[Object] + impl QueryRoot { + async fn field_parameter( + &self, + #[graphql(validator(CharsMaxLength(length = "6")))] _id: String, + ) -> bool { + true + } + + async fn input_object(&self, _input: InputMaxLength) -> bool { + true + } + } + + let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription); + let test_cases = ["一2三4五", "壹2叁4伍6", "一2三4五6七", "壹2叁4伍6Ⅶ8"]; + + let validator_length = 6; + for case in &test_cases { + let field_query = format!("{{fieldParameter(id: \"{}\")}}", case); + let object_query = format!("{{inputObject(input: {{id: \"{}\"}})}}", case); + let case_length = case.chars().count(); + + if case_length > validator_length { + let should_fail_msg = format!( + "CharsMaxLength case {} should have failed, but did not", + case + ); + + let field_error_msg = format!( + "Invalid value for argument \"id\", the value chars count is {}, must be less than or equal to {}", + case_length, + validator_length + ); + let object_error_msg = format!( + "Invalid value for argument \"input.id\", the value chars count is {}, must be less than or equal to {}", + case_length, + validator_length + ); + assert_eq!( + schema + .execute(&field_query) + .await + .into_result() + .expect_err(&should_fail_msg[..]), + vec![ServerError { + message: field_error_msg, + locations: vec!(Pos { + line: 1, + column: 17 + }), + path: Vec::new(), + extensions: None, + }] + ); + + assert_eq!( + schema + .execute(&object_query) + .await + .into_result() + .expect_err(&should_fail_msg[..]), + vec![ServerError { + message: object_error_msg, + locations: vec!(Pos { + line: 1, + column: 14 + }), + path: Vec::new(), + extensions: None, + }] + ); + } else { + let error_msg = format!("Schema returned error with test_string = {}", case); + assert_eq!( + schema + .execute(&field_query) + .await + .into_result() + .expect(&error_msg[..]) + .data, + value!({"fieldParameter": true}), + "Failed to validate {} with CharsMaxLength", + case + ); + + assert_eq!( + schema + .execute(&object_query) + .await + .into_result() + .expect(&error_msg[..]) + .data, + value!({"inputObject": true}), + "Failed to validate {} with CharsMaxLength", + case + ); + } + } +} + #[tokio::test] pub async fn test_input_validator_string_email() { struct QueryRoot; From b4066d5f50de24b267cb586f2bf2f68fa872e489 Mon Sep 17 00:00:00 2001 From: smihica Date: Thu, 14 Oct 2021 00:49:47 +0900 Subject: [PATCH 4/4] Changed docs comment --- src/validators/string_validators.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/validators/string_validators.rs b/src/validators/string_validators.rs index a6982a94..c48b39b7 100644 --- a/src/validators/string_validators.rs +++ b/src/validators/string_validators.rs @@ -52,7 +52,7 @@ impl InputValueValidator for StringMaxLength { } } -/// Chars minimum length validator. This supports multibyte character. +/// Chars in string minimum length validator. pub struct CharsMinLength { /// Must be greater than or equal to this value. pub length: i32, @@ -76,7 +76,7 @@ impl InputValueValidator for CharsMinLength { } } -/// Chars maximum length validator. This supports multibyte characters. +/// Chars in string maximum length validator. pub struct CharsMaxLength { /// Must be less than or equal to this value. pub length: i32,