This commit is contained in:
sunli 2020-03-22 09:34:32 +08:00
parent 646a7c06c3
commit 788a3b558b
19 changed files with 196 additions and 59 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql"
version = "1.4.1"
version = "1.4.2"
authors = ["sunli <scott_s829@163.com>"]
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"

View File

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

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql-actix-web"
version = "0.5.1"
version = "0.5.2"
authors = ["sunli <scott_s829@163.com>"]
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"

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql-derive"
version = "1.4.1"
version = "1.4.2"
authors = ["sunli <scott_s829@163.com>"]
edition = "2018"
description = "Macros for async-graphql"

View File

@ -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<String>,
pub desc: Option<String>,
pub default: Option<Value>,
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<String>,
pub desc: Option<String>,
pub default: Option<Value>,
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,
})
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<TokenStream> {
pub fn parse_nested_validator(
crate_name: &TokenStream,
nested_meta: &NestedMeta,
) -> Result<TokenStream> {
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<TokenStream> {
let mut validators = Vec::new();
pub fn parse_validator(crate_name: &TokenStream, args: &MetaList) -> Result<TokenStream> {
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})
}

View File

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

View File

@ -60,7 +60,7 @@ pub struct InputValue {
pub description: Option<&'static str>,
pub ty: String,
pub default_value: Option<&'static str>,
pub validators: Arc<Vec<Box<dyn InputValueValidator>>>,
pub validator: Option<Arc<dyn InputValueValidator>>,
}
#[derive(Clone)]

View File

@ -57,7 +57,7 @@ impl<Query: ObjectType, Mutation: ObjectType, Subscription: SubscriptionType>
description: Some("Included when true."),
ty: "Boolean!".to_string(),
default_value: None,
validators: Default::default(),
validator: None,
});
args
}
@ -78,7 +78,7 @@ impl<Query: ObjectType, Mutation: ObjectType, Subscription: SubscriptionType>
description: Some("Skipped when true."),
ty: "Boolean!".to_string(),
default_value: None,
validators: Default::default(),
validator: None,
});
args
}

View File

@ -47,7 +47,7 @@ impl<T: Type> Type for QueryRoot<T> {
description: None,
ty: "String!".to_string(),
default_value: None,
validators: Default::default(),
validator: None,
},
);
args

View File

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

View File

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

View File

@ -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<String> {
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<String> {
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
}
}
}

View File

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

View File

@ -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<String> {
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<String> {
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<Regex> = 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<Regex> =
/// 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
}
}
}