v1.5.2
This commit is contained in:
parent
41fd8ed40e
commit
457e30ea2f
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
38
src/query.rs
38
src/query.rs
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
12
src/validation/visitors/complexity.rs
Normal file
12
src/validation/visitors/complexity.rs
Normal 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;
|
||||
}
|
||||
}
|
68
src/validation/visitors/depth.rs
Normal file
68
src/validation/visitors/depth.rs
Normal 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;
|
||||
}
|
||||
}
|
7
src/validation/visitors/mod.rs
Normal file
7
src/validation/visitors/mod.rs
Normal 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;
|
Loading…
Reference in New Issue
Block a user