This commit is contained in:
sunli 2020-03-25 15:07:16 +08:00
parent 41fd8ed40e
commit 457e30ea2f
15 changed files with 201 additions and 38 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql"
version = "1.5.1"
version = "1.5.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.5.1" }
async-graphql-derive = { path = "async-graphql-derive", version = "1.5.2" }
graphql-parser = "0.2.3"
anyhow = "1.0.26"
thiserror = "1.0.11"

View File

@ -96,7 +96,9 @@ Open `http://localhost:8000` in browser
- [X] FIELD
- [X] FRAGMENT_SPREAD
- [X] INLINE_FRAGMENT
- [X] Schema
- [X] Introspection
- [X] Query
- [X] Disable introspection
- [X] Multipart Request (https://github.com/jaydenseric/graphql-multipart-request-spec)
- [X] Actix-web
- [X] Cursor Connections
@ -118,6 +120,8 @@ Open `http://localhost:8000` in browser
- [X] List
- [X] ListMaxLength
- [X] ListMinLength
- [ ] Limit query complexity
- [ ] Limit query depth
- [X] Subscription
- [X] Filter
- [X] WebSocket transport

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql-actix-web"
version = "0.6.1"
version = "0.6.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.5.1" }
async-graphql = { path = "..", version = "1.5.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.5.1"
version = "1.5.2"
authors = ["sunli <scott_s829@163.com>"]
edition = "2018"
description = "Macros for async-graphql"

View File

@ -148,6 +148,12 @@ pub enum QueryError {
/// Actual type
actual: String,
},
#[error("Too complex.")]
TooComplex,
#[error("Too deep.")]
TooDeep,
}
/// Creates a wrapper with an error location

View File

@ -1,8 +1,8 @@
use crate::context::Data;
use crate::registry::{CacheControl, Registry};
use crate::types::QueryRoot;
use crate::validation::check_rules;
use crate::{ContextBase, OutputValueType, Result};
use crate::validation::{check_rules, CheckResult};
use crate::{ContextBase, OutputValueType, Result, Schema};
use crate::{ObjectType, QueryError, QueryParseError, Variables};
use bytes::Bytes;
use graphql_parser::parse_query;
@ -17,17 +17,15 @@ enum Root<'a, Query, Mutation> {
}
/// Query builder
pub struct QueryBuilder<'a, Query, Mutation> {
pub(crate) query: &'a QueryRoot<Query>,
pub(crate) mutation: &'a Mutation,
pub(crate) registry: &'a Registry,
pub struct QueryBuilder<'a, Query, Mutation, Subscription> {
pub(crate) schema: &'a Schema<Query, Mutation, Subscription>,
pub(crate) source: &'a str,
pub(crate) operation_name: Option<&'a str>,
pub(crate) variables: Option<Variables>,
pub(crate) data: &'a Data,
}
impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
impl<'a, Query, Mutation, Subscription> QueryBuilder<'a, Query, Mutation, Subscription> {
/// Specify the operation name.
pub fn operator_name(self, name: &'a str) -> Self {
QueryBuilder {
@ -47,7 +45,23 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
/// Prepare query
pub fn prepare(self) -> Result<PreparedQuery<'a, Query, Mutation>> {
let document = parse_query(self.source).map_err(|err| QueryParseError(err.to_string()))?;
let cache_control = check_rules(self.registry, &document)?;
let CheckResult {
cache_control,
complexity,
depth,
} = check_rules(&self.schema.registry, &document)?;
if let Some(limit_complexity) = self.schema.complexity {
if complexity > limit_complexity {
return Err(QueryError::TooComplex.into());
}
}
if let Some(limit_depth) = self.schema.depth {
if depth > limit_depth {
return Err(QueryError::TooDeep.into());
}
}
let mut fragments = HashMap::new();
let mut selection_set = None;
@ -59,14 +73,14 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
Definition::Operation(operation_definition) => match operation_definition {
OperationDefinition::SelectionSet(s) => {
selection_set = Some(s);
root = Some(Root::Query(self.query));
root = Some(Root::Query(&self.schema.query));
}
OperationDefinition::Query(query)
if query.name.is_none() || query.name.as_deref() == self.operation_name =>
{
selection_set = Some(query.selection_set);
variable_definitions = Some(query.variable_definitions);
root = Some(Root::Query(self.query));
root = Some(Root::Query(&self.schema.query));
}
OperationDefinition::Mutation(mutation)
if mutation.name.is_none()
@ -74,7 +88,7 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
{
selection_set = Some(mutation.selection_set);
variable_definitions = Some(mutation.variable_definitions);
root = Some(Root::Mutation(self.mutation));
root = Some(Root::Mutation(&self.schema.mutation));
}
OperationDefinition::Subscription(subscription)
if subscription.name.is_none()
@ -91,7 +105,7 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
}
Ok(PreparedQuery {
registry: self.registry,
registry: &self.schema.registry,
variables: self.variables.unwrap_or_default(),
data: self.data,
fragments,

View File

@ -9,11 +9,13 @@ use std::collections::HashMap;
/// GraphQL schema
pub struct Schema<Query, Mutation, Subscription> {
query: QueryRoot<Query>,
mutation: Mutation,
pub(crate) query: QueryRoot<Query>,
pub(crate) mutation: Mutation,
pub(crate) subscription: Subscription,
pub(crate) registry: Registry,
pub(crate) data: Data,
pub(crate) complexity: Option<usize>,
pub(crate) depth: Option<usize>,
}
impl<Query: ObjectType, Mutation: ObjectType, Subscription: SubscriptionType>
@ -99,14 +101,37 @@ impl<Query: ObjectType, Mutation: ObjectType, Subscription: SubscriptionType>
}
Self {
query: QueryRoot { inner: query },
query: QueryRoot {
inner: query,
disable_introspection: false,
},
mutation,
subscription,
registry,
data: Default::default(),
complexity: None,
depth: None,
}
}
/// Disable introspection query
pub fn disable_introspection(mut self) -> Self {
self.query.disable_introspection = true;
self
}
/// Set limit complexity, Default no limit.
pub fn limit_complexity(mut self, complexity: usize) -> Self {
self.complexity = Some(complexity);
self
}
/// Set limit complexity, Default no limit.
pub fn limit_depth(mut self, depth: usize) -> Self {
self.depth = Some(depth);
self
}
/// Add a global data that can be accessed in the `Context`.
pub fn data<D: Any + Send + Sync>(mut self, data: D) -> Self {
self.data.insert(data);
@ -114,11 +139,9 @@ impl<Query: ObjectType, Mutation: ObjectType, Subscription: SubscriptionType>
}
/// Start a query and return `QueryBuilder`.
pub fn query<'a>(&'a self, source: &'a str) -> QueryBuilder<'a, Query, Mutation> {
pub fn query<'a>(&'a self, source: &'a str) -> QueryBuilder<'a, Query, Mutation, Subscription> {
QueryBuilder {
query: &self.query,
mutation: &self.mutation,
registry: &self.registry,
schema: self,
source,
operation_name: None,
variables: None,

View File

@ -1,7 +1,7 @@
use crate::model::{__Schema, __Type};
use crate::{
do_resolve, registry, Context, ContextSelectionSet, ErrorWithPosition, ObjectType,
OutputValueType, Result, Type, Value,
OutputValueType, QueryError, Result, Type, Value,
};
use graphql_parser::query::Field;
use std::borrow::Cow;
@ -9,6 +9,7 @@ use std::collections::HashMap;
pub struct QueryRoot<T> {
pub inner: T,
pub disable_introspection: bool,
}
impl<T: Type> Type for QueryRoot<T> {
@ -67,6 +68,14 @@ impl<T: Type> Type for QueryRoot<T> {
impl<T: ObjectType + Send + Sync> ObjectType for QueryRoot<T> {
async fn resolve_field(&self, ctx: &Context<'_>, field: &Field) -> Result<serde_json::Value> {
if field.name.as_str() == "__schema" {
if self.disable_introspection {
return Err(QueryError::FieldNotFound {
field_name: field.name.clone(),
object: Self::type_name().to_string(),
}
.into());
}
let ctx_obj = ctx.with_item(&field.selection_set);
return OutputValueType::resolve(
&__Schema {

View File

@ -1,6 +1,7 @@
mod rules;
mod utils;
mod visitor;
mod visitors;
use crate::error::RuleErrors;
use crate::registry::Registry;
@ -8,9 +9,18 @@ use crate::{CacheControl, Result};
use graphql_parser::query::Document;
use visitor::{visit, VisitorContext, VisitorNil};
pub fn check_rules(registry: &Registry, doc: &Document) -> Result<CacheControl> {
pub struct CheckResult {
pub cache_control: CacheControl,
pub complexity: usize,
pub depth: usize,
}
pub fn check_rules(registry: &Registry, doc: &Document) -> Result<CheckResult> {
let mut ctx = VisitorContext::new(registry, doc);
let mut cache_control = CacheControl::default();
let mut complexity = 0;
let mut depth = 0;
let mut visitor = VisitorNil
.with(rules::ArgumentsOfCorrectType::default())
.with(rules::DefaultValuesOfCorrectType)
@ -37,14 +47,21 @@ pub fn check_rules(registry: &Registry, doc: &Document) -> Result<CacheControl>
.with(rules::KnownDirectives::default())
.with(rules::OverlappingFieldsCanBeMerged)
.with(rules::UploadFile)
.with(rules::CacheControlCalculate {
.with(visitors::CacheControlCalculate {
cache_control: &mut cache_control,
});
})
.with(visitors::ComplexityCalculate {
complexity: &mut complexity,
})
.with(visitors::DepthCalculate::new(&mut depth));
visit(&mut visitor, &mut ctx, doc);
if !ctx.errors.is_empty() {
Err(RuleErrors { errors: ctx.errors }.into())
} else {
Ok(cache_control)
return Err(RuleErrors { errors: ctx.errors }.into());
}
Ok(CheckResult {
cache_control,
complexity,
depth: depth as usize,
})
}

View File

@ -1,5 +1,4 @@
mod arguments_of_correct_type;
mod cache_control;
mod default_values_of_correct_type;
mod fields_on_correct_type;
mod fragments_on_composite_types;
@ -26,7 +25,6 @@ mod variables_are_input_types;
mod variables_in_allowed_position;
pub use arguments_of_correct_type::ArgumentsOfCorrectType;
pub use cache_control::CacheControlCalculate;
pub use default_values_of_correct_type::DefaultValuesOfCorrectType;
pub use fields_on_correct_type::FieldsOnCorrectType;
pub use fragments_on_composite_types::FragmentsOnCompositeTypes;

View File

@ -463,11 +463,13 @@ fn visit_selection_set<'a, V: Visitor<'a>>(
ctx: &mut VisitorContext<'a>,
selection_set: &'a SelectionSet,
) {
v.enter_selection_set(ctx, selection_set);
for selection in &selection_set.items {
visit_selection(v, ctx, selection);
if !selection_set.items.is_empty() {
v.enter_selection_set(ctx, selection_set);
for selection in &selection_set.items {
visit_selection(v, ctx, selection);
}
v.exit_selection_set(ctx, selection_set);
}
v.exit_selection_set(ctx, selection_set);
}
fn visit_selection<'a, V: Visitor<'a>>(
@ -578,6 +580,9 @@ fn visit_fragment_spread<'a, V: Visitor<'a>>(
) {
v.enter_fragment_spread(ctx, fragment_spread);
visit_directives(v, ctx, &fragment_spread.directives);
if let Some(fragment) = ctx.fragment(fragment_spread.fragment_name.as_str()) {
visit_selection_set(v, ctx, &fragment.selection_set);
}
v.exit_fragment_spread(ctx, fragment_spread);
}

View File

@ -0,0 +1,12 @@
use crate::validation::visitor::{Visitor, VisitorContext};
use graphql_parser::query::Field;
pub struct ComplexityCalculate<'a> {
pub complexity: &'a mut usize,
}
impl<'ctx, 'a> Visitor<'ctx> for ComplexityCalculate<'a> {
fn enter_field(&mut self, _ctx: &mut VisitorContext<'_>, _field: &Field) {
*self.complexity += 1;
}
}

View File

@ -0,0 +1,68 @@
use crate::validation::visitor::{Visitor, VisitorContext};
use graphql_parser::query::{FragmentSpread, InlineFragment, SelectionSet};
pub struct DepthCalculate<'a> {
max_depth: &'a mut i32,
current_depth: i32,
}
impl<'a> DepthCalculate<'a> {
pub fn new(max_depth: &'a mut i32) -> Self {
*max_depth = -1;
Self {
max_depth,
current_depth: -1,
}
}
}
impl<'ctx, 'a> Visitor<'ctx> for DepthCalculate<'a> {
fn enter_selection_set(
&mut self,
_ctx: &mut VisitorContext<'ctx>,
_selection_set: &'ctx SelectionSet,
) {
self.current_depth += 1;
*self.max_depth = (*self.max_depth).max(self.current_depth);
}
fn exit_selection_set(
&mut self,
_ctx: &mut VisitorContext<'ctx>,
_selection_set: &'ctx SelectionSet,
) {
self.current_depth -= 1;
}
fn enter_fragment_spread(
&mut self,
_ctx: &mut VisitorContext<'ctx>,
_fragment_spread: &'ctx FragmentSpread,
) {
self.current_depth -= 1;
}
fn exit_fragment_spread(
&mut self,
_ctx: &mut VisitorContext<'ctx>,
_fragment_spread: &'ctx FragmentSpread,
) {
self.current_depth += 1;
}
fn enter_inline_fragment(
&mut self,
_ctx: &mut VisitorContext<'ctx>,
_inline_fragment: &'ctx InlineFragment,
) {
self.current_depth -= 1;
}
fn exit_inline_fragment(
&mut self,
_ctx: &mut VisitorContext<'ctx>,
_inline_fragment: &'ctx InlineFragment,
) {
self.current_depth += 1;
}
}

View File

@ -0,0 +1,7 @@
mod cache_control;
mod complexity;
mod depth;
pub use cache_control::CacheControlCalculate;
pub use complexity::ComplexityCalculate;
pub use depth::DepthCalculate;