graphql 0.15.3 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql.rb +4 -1
- data/lib/graphql/analysis.rb +5 -0
- data/lib/graphql/analysis/analyze_query.rb +73 -0
- data/lib/graphql/analysis/max_query_complexity.rb +25 -0
- data/lib/graphql/analysis/max_query_depth.rb +25 -0
- data/lib/graphql/analysis/query_complexity.rb +122 -0
- data/lib/graphql/analysis/query_depth.rb +54 -0
- data/lib/graphql/analysis_error.rb +4 -0
- data/lib/graphql/base_type.rb +7 -0
- data/lib/graphql/define/assign_object_field.rb +2 -1
- data/lib/graphql/field.rb +25 -3
- data/lib/graphql/input_object_type.rb +1 -1
- data/lib/graphql/internal_representation.rb +2 -0
- data/lib/graphql/internal_representation/node.rb +81 -0
- data/lib/graphql/internal_representation/rewrite.rb +177 -0
- data/lib/graphql/language/visitor.rb +15 -9
- data/lib/graphql/object_type.rb +1 -1
- data/lib/graphql/query.rb +66 -7
- data/lib/graphql/query/context.rb +10 -3
- data/lib/graphql/query/directive_resolution.rb +5 -5
- data/lib/graphql/query/serial_execution.rb +5 -3
- data/lib/graphql/query/serial_execution/field_resolution.rb +22 -15
- data/lib/graphql/query/serial_execution/operation_resolution.rb +7 -5
- data/lib/graphql/query/serial_execution/selection_resolution.rb +20 -105
- data/lib/graphql/query/serial_execution/value_resolution.rb +15 -12
- data/lib/graphql/schema.rb +7 -2
- data/lib/graphql/schema/timeout_middleware.rb +67 -0
- data/lib/graphql/static_validation/all_rules.rb +0 -1
- data/lib/graphql/static_validation/type_stack.rb +7 -11
- data/lib/graphql/static_validation/validation_context.rb +11 -1
- data/lib/graphql/static_validation/validator.rb +14 -4
- data/lib/graphql/version.rb +1 -1
- data/readme.md +10 -9
- data/spec/graphql/analysis/analyze_query_spec.rb +50 -0
- data/spec/graphql/analysis/max_query_complexity_spec.rb +62 -0
- data/spec/graphql/{static_validation/rules/document_does_not_exceed_max_depth_spec.rb → analysis/max_query_depth_spec.rb} +20 -21
- data/spec/graphql/analysis/query_complexity_spec.rb +235 -0
- data/spec/graphql/analysis/query_depth_spec.rb +80 -0
- data/spec/graphql/directive_spec.rb +1 -0
- data/spec/graphql/internal_representation/rewrite_spec.rb +120 -0
- data/spec/graphql/introspection/schema_type_spec.rb +1 -0
- data/spec/graphql/language/visitor_spec.rb +14 -4
- data/spec/graphql/non_null_type_spec.rb +31 -0
- data/spec/graphql/query/context_spec.rb +24 -1
- data/spec/graphql/query_spec.rb +6 -2
- data/spec/graphql/schema/timeout_middleware_spec.rb +180 -0
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +1 -1
- data/spec/graphql/static_validation/validator_spec.rb +1 -1
- data/spec/support/dairy_app.rb +22 -1
- metadata +29 -5
- data/lib/graphql/static_validation/rules/document_does_not_exceed_max_depth.rb +0 -79
@@ -23,7 +23,6 @@ module GraphQL
|
|
23
23
|
GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTyped,
|
24
24
|
GraphQL::StaticValidation::VariablesAreUsedAndDefined,
|
25
25
|
GraphQL::StaticValidation::VariableUsagesAreAllowed,
|
26
|
-
GraphQL::StaticValidation::DocumentDoesNotExceedMaxDepth,
|
27
26
|
]
|
28
27
|
end
|
29
28
|
end
|
@@ -89,18 +89,14 @@ module GraphQL
|
|
89
89
|
def push(stack, node)
|
90
90
|
parent_type = stack.object_types.last
|
91
91
|
parent_type = parent_type.unwrap
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
else
|
99
|
-
stack.object_types.push(nil)
|
100
|
-
end
|
92
|
+
|
93
|
+
field_definition = stack.schema.get_field(parent_type, node.name)
|
94
|
+
stack.field_definitions.push(field_definition)
|
95
|
+
if !field_definition.nil?
|
96
|
+
next_object_type = field_definition.type
|
97
|
+
stack.object_types.push(next_object_type)
|
101
98
|
else
|
102
|
-
stack.
|
103
|
-
stack.object_types.push(parent_type)
|
99
|
+
stack.object_types.push(nil)
|
104
100
|
end
|
105
101
|
end
|
106
102
|
|
@@ -29,7 +29,7 @@ module GraphQL
|
|
29
29
|
end
|
30
30
|
|
31
31
|
@errors = []
|
32
|
-
@visitor = GraphQL::Language::Visitor.new
|
32
|
+
@visitor = GraphQL::Language::Visitor.new(document)
|
33
33
|
@type_stack = GraphQL::StaticValidation::TypeStack.new(schema, visitor)
|
34
34
|
end
|
35
35
|
|
@@ -37,6 +37,16 @@ module GraphQL
|
|
37
37
|
@type_stack.object_types
|
38
38
|
end
|
39
39
|
|
40
|
+
# @return [GraphQL::BaseType] The current object type
|
41
|
+
def type_definition
|
42
|
+
object_types.last
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [GraphQL::BaseType] The type which the current type came from
|
46
|
+
def parent_type_definition
|
47
|
+
object_types[-2]
|
48
|
+
end
|
49
|
+
|
40
50
|
# @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
|
41
51
|
def field_definition
|
42
52
|
@type_stack.field_definitions.last
|
@@ -17,16 +17,26 @@ module GraphQL
|
|
17
17
|
@rules = rules
|
18
18
|
end
|
19
19
|
|
20
|
-
# Validate `
|
21
|
-
# @param
|
20
|
+
# Validate `query` against the schema. Returns an array of message hashes.
|
21
|
+
# @param query [GraphQL::Query]
|
22
22
|
# @return [Array<Hash>]
|
23
23
|
def validate(query)
|
24
24
|
context = GraphQL::StaticValidation::ValidationContext.new(query)
|
25
|
+
rewrite = GraphQL::InternalRepresentation::Rewrite.new
|
26
|
+
|
27
|
+
# Put this first so its enters and exits are always called
|
28
|
+
rewrite.validate(context)
|
25
29
|
@rules.each do |rules|
|
26
30
|
rules.new.validate(context)
|
27
31
|
end
|
28
|
-
|
29
|
-
context.
|
32
|
+
|
33
|
+
context.visitor.visit
|
34
|
+
|
35
|
+
{
|
36
|
+
errors: context.errors.map(&:to_h),
|
37
|
+
# If there were errors, the irep is garbage
|
38
|
+
irep: context.errors.none? ? rewrite.operations : nil,
|
39
|
+
}
|
30
40
|
end
|
31
41
|
end
|
32
42
|
end
|
data/lib/graphql/version.rb
CHANGED
data/readme.md
CHANGED
@@ -13,6 +13,9 @@ A Ruby implementation of [GraphQL](http://graphql.org/).
|
|
13
13
|
- [Defining Your Schema](http://www.rubydoc.info/github/rmosolgo/graphql-ruby/file/guides/defining_your_schema.md)
|
14
14
|
- [Executing Queries](http://www.rubydoc.info/github/rmosolgo/graphql-ruby/file/guides/executing_queries.md)
|
15
15
|
- [Testing](http://www.rubydoc.info/github/rmosolgo/graphql-ruby/file/guides/testing.md)
|
16
|
+
- [Code Reuse](http://www.rubydoc.info/github/rmosolgo/graphql-ruby/file/guides/code_reuse.md)
|
17
|
+
- [Security](http://www.rubydoc.info/github/rmosolgo/graphql-ruby/file/guides/security.md)
|
18
|
+
|
16
19
|
|
17
20
|
- [API Documentation](http://www.rubydoc.info/github/rmosolgo/graphql-ruby)
|
18
21
|
|
@@ -119,16 +122,14 @@ If you're building a backend for [Relay](http://facebook.github.io/relay/), you'
|
|
119
122
|
|
120
123
|
## To Do
|
121
124
|
|
122
|
-
- __1.0 items:__
|
123
|
-
- Non-nulls should _propagate_ to the next non-null field (all the way up to data, if need be)
|
124
|
-
- Add docs for shared behaviors & DRY code
|
125
|
-
- Subscriptions
|
126
|
-
- Is there something to do at the graphql-ruby level to make this easier for specific implementations?
|
127
125
|
- Accept type name as `type` argument?
|
128
126
|
- Goal: accept `field :post, "Post"` to look up a type named `"Post"` in the schema
|
129
127
|
- Problem: how does a field know which schema to look up the name from?
|
130
128
|
- Problem: how can we load types in Rails without accessing the constant?
|
131
|
-
-
|
132
|
-
|
133
|
-
|
134
|
-
-
|
129
|
+
- Maybe support by third-party library? `type("Post!")` could implement "type_missing", keeps `graphql-ruby` very simple
|
130
|
+
- If we eval'd `define { ... }` blocks _lazily_, would that work around all circular dependency issues?
|
131
|
+
- `QueryComplexity` improvements:
|
132
|
+
- Better detection of union / interface possibilities? Right now they're summed
|
133
|
+
- Type check improvements:
|
134
|
+
- Use catch-all type/field/argument definitions instead of terminating traversal
|
135
|
+
- Reduce ad-hoc traversals?
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GraphQL::Analysis do
|
4
|
+
class TypeCollector
|
5
|
+
def initial_value(query)
|
6
|
+
[]
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(memo, visit_type, irep_node)
|
10
|
+
if visit_type == :enter
|
11
|
+
memo + [irep_node.return_type]
|
12
|
+
else
|
13
|
+
memo
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe ".analyze_query" do
|
19
|
+
let(:node_counter) {
|
20
|
+
-> (memo, visit_type, irep_node) {
|
21
|
+
memo ||= Hash.new { |h,k| h[k] = 0 }
|
22
|
+
visit_type == :enter && memo[irep_node.ast_node.class] += 1
|
23
|
+
memo
|
24
|
+
}
|
25
|
+
}
|
26
|
+
let(:type_collector) { TypeCollector.new }
|
27
|
+
let(:analyzers) { [type_collector, node_counter] }
|
28
|
+
let(:reduce_result) { GraphQL::Analysis.analyze_query(query, analyzers) }
|
29
|
+
let(:query) { GraphQL::Query.new(DummySchema, query_string) }
|
30
|
+
let(:query_string) {%|
|
31
|
+
{
|
32
|
+
cheese(id: 1) {
|
33
|
+
id
|
34
|
+
flavor
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|}
|
38
|
+
|
39
|
+
it "calls the defined analyzers" do
|
40
|
+
collected_types, node_counts = reduce_result
|
41
|
+
expected_visited_types = [QueryType, CheeseType, GraphQL::INT_TYPE, GraphQL::STRING_TYPE]
|
42
|
+
assert_equal expected_visited_types, collected_types
|
43
|
+
expected_node_counts = {
|
44
|
+
GraphQL::Language::Nodes::OperationDefinition => 1,
|
45
|
+
GraphQL::Language::Nodes::Field => 3,
|
46
|
+
}
|
47
|
+
assert_equal expected_node_counts, node_counts
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GraphQL::Analysis::MaxQueryComplexity do
|
4
|
+
before do
|
5
|
+
@prev_max_complexity = DummySchema.max_complexity
|
6
|
+
end
|
7
|
+
|
8
|
+
after do
|
9
|
+
DummySchema.max_complexity = @prev_max_complexity
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
let(:result) { DummySchema.execute(query_string) }
|
14
|
+
let(:query_string) {%|
|
15
|
+
{
|
16
|
+
a: cheese(id: 1) { id }
|
17
|
+
b: cheese(id: 1) { id }
|
18
|
+
c: cheese(id: 1) { id }
|
19
|
+
d: cheese(id: 1) { id }
|
20
|
+
e: cheese(id: 1) { id }
|
21
|
+
}
|
22
|
+
|}
|
23
|
+
|
24
|
+
describe "when a query goes over max complexity" do
|
25
|
+
before do
|
26
|
+
DummySchema.max_complexity = 9
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns an error" do
|
30
|
+
assert_equal "Query has complexity of 10, which exceeds max complexity of 9", result["errors"][0]["message"]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "when there is no max complexity" do
|
35
|
+
before do
|
36
|
+
DummySchema.max_complexity = nil
|
37
|
+
end
|
38
|
+
it "doesn't error" do
|
39
|
+
assert_equal nil, result["errors"]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "when the query is less than the max complexity" do
|
44
|
+
before do
|
45
|
+
DummySchema.max_complexity = 99
|
46
|
+
end
|
47
|
+
it "doesn't error" do
|
48
|
+
assert_equal nil, result["errors"]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "when complexity is overriden at query-level" do
|
53
|
+
before do
|
54
|
+
DummySchema.max_complexity = 100
|
55
|
+
end
|
56
|
+
let(:result) { DummySchema.execute(query_string, max_complexity: 7) }
|
57
|
+
|
58
|
+
it "is applied" do
|
59
|
+
assert_equal "Query has complexity of 10, which exceeds max complexity of 7", result["errors"][0]["message"]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,11 +1,15 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
-
describe GraphQL::
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
describe GraphQL::Analysis::MaxQueryDepth do
|
4
|
+
before do
|
5
|
+
@prev_max_depth = DummySchema.max_depth
|
6
|
+
end
|
7
|
+
|
8
|
+
after do
|
9
|
+
DummySchema.max_depth = @prev_max_depth
|
10
|
+
end
|
8
11
|
|
12
|
+
let(:result) { DummySchema.execute(query_string) }
|
9
13
|
let(:query_string) { "
|
10
14
|
{
|
11
15
|
cheese(id: 1) {
|
@@ -26,48 +30,43 @@ describe GraphQL::StaticValidation::DocumentDoesNotExceedMaxDepth do
|
|
26
30
|
|
27
31
|
describe "when the query is deeper than max depth" do
|
28
32
|
it "adds an error message for a too-deep query" do
|
29
|
-
assert_equal
|
33
|
+
assert_equal "Query has depth of 7, which exceeds max depth of 5", result["errors"][0]["message"]
|
30
34
|
end
|
31
35
|
end
|
32
36
|
|
33
37
|
describe "when the query specifies a different max_depth" do
|
34
|
-
let(:
|
38
|
+
let(:result) { DummySchema.execute(query_string, max_depth: 100) }
|
39
|
+
|
35
40
|
it "obeys that max_depth" do
|
36
|
-
assert_equal
|
41
|
+
assert_equal nil, result["errors"]
|
37
42
|
end
|
38
43
|
end
|
39
44
|
|
40
45
|
describe "When the query is not deeper than max_depth" do
|
41
46
|
before do
|
42
|
-
@prev_max_depth = DummySchema.max_depth
|
43
47
|
DummySchema.max_depth = 100
|
44
48
|
end
|
45
49
|
|
46
|
-
after do
|
47
|
-
DummySchema.max_depth = @prev_max_depth
|
48
|
-
end
|
49
|
-
|
50
50
|
it "doesn't add an error" do
|
51
|
-
assert_equal
|
51
|
+
assert_equal nil, result["errors"]
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
55
|
describe "when the max depth isn't set" do
|
56
56
|
before do
|
57
|
-
@prev_max_depth = DummySchema.max_depth
|
58
57
|
DummySchema.max_depth = nil
|
59
58
|
end
|
60
59
|
|
61
|
-
after do
|
62
|
-
DummySchema.max_depth = @prev_max_depth
|
63
|
-
end
|
64
|
-
|
65
60
|
it "doesn't add an error message" do
|
66
|
-
assert_equal
|
61
|
+
assert_equal nil, result["errors"]
|
67
62
|
end
|
68
63
|
end
|
69
64
|
|
70
65
|
describe "when a fragment exceeds max depth" do
|
66
|
+
before do
|
67
|
+
DummySchema.max_depth = 4
|
68
|
+
end
|
69
|
+
|
71
70
|
let(:query_string) { "
|
72
71
|
{
|
73
72
|
cheese(id: 1) {
|
@@ -95,7 +94,7 @@ describe GraphQL::StaticValidation::DocumentDoesNotExceedMaxDepth do
|
|
95
94
|
"}
|
96
95
|
|
97
96
|
it "adds an error message for a too-deep query" do
|
98
|
-
assert_equal 1, errors.length
|
97
|
+
assert_equal 1, result["errors"].length
|
99
98
|
end
|
100
99
|
end
|
101
100
|
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GraphQL::Analysis::QueryComplexity do
|
4
|
+
let(:complexities) { [] }
|
5
|
+
let(:query_complexity) { GraphQL::Analysis::QueryComplexity.new { |this_query, complexity| complexities << this_query << complexity } }
|
6
|
+
let(:reduce_result) { GraphQL::Analysis.analyze_query(query, [query_complexity]) }
|
7
|
+
let(:variables) { {} }
|
8
|
+
let(:query) { GraphQL::Query.new(DummySchema, query_string, variables: variables) }
|
9
|
+
|
10
|
+
describe "simple queries" do
|
11
|
+
let(:query_string) {%|
|
12
|
+
query cheeses($isSkipped: Boolean = false){
|
13
|
+
# complexity of 3
|
14
|
+
cheese1: cheese(id: 1) {
|
15
|
+
id
|
16
|
+
flavor
|
17
|
+
}
|
18
|
+
|
19
|
+
# complexity of 4
|
20
|
+
cheese2: cheese(id: 2) @skip(if: $isSkipped) {
|
21
|
+
similarCheese(source: SHEEP) {
|
22
|
+
... on Cheese {
|
23
|
+
similarCheese(source: SHEEP) {
|
24
|
+
id
|
25
|
+
}
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|}
|
31
|
+
|
32
|
+
it "sums the complexity" do
|
33
|
+
reduce_result
|
34
|
+
assert_equal complexities, [query, 7]
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "when skipped by directives" do
|
38
|
+
let(:variables) { { "isSkipped" => true } }
|
39
|
+
it "doesn't include skipped fields" do
|
40
|
+
reduce_result
|
41
|
+
assert_equal complexities, [query, 3]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "query with fragments" do
|
47
|
+
let(:query_string) {%|
|
48
|
+
{
|
49
|
+
# complexity of 3
|
50
|
+
cheese1: cheese(id: 1) {
|
51
|
+
id
|
52
|
+
flavor
|
53
|
+
}
|
54
|
+
|
55
|
+
# complexity of 7
|
56
|
+
cheese2: cheese(id: 2) {
|
57
|
+
... cheeseFields1
|
58
|
+
... cheeseFields2
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
fragment cheeseFields1 on Cheese {
|
63
|
+
similarCow: similarCheese(source: COW) {
|
64
|
+
id
|
65
|
+
... cheeseFields2
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
fragment cheeseFields2 on Cheese {
|
70
|
+
similarSheep: similarCheese(source: SHEEP) {
|
71
|
+
id
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|}
|
75
|
+
|
76
|
+
it "counts all fragment usages, not the definitions" do
|
77
|
+
reduce_result
|
78
|
+
assert_equal complexities, [query, 10]
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "mutually exclusive types" do
|
82
|
+
let(:query_string) {%|
|
83
|
+
{
|
84
|
+
favoriteEdible {
|
85
|
+
# 1 for everybody
|
86
|
+
fatContent
|
87
|
+
|
88
|
+
# 1 for everybody
|
89
|
+
... on Edible {
|
90
|
+
origin
|
91
|
+
}
|
92
|
+
|
93
|
+
# 1 for honey
|
94
|
+
... on Sweetener {
|
95
|
+
sweetness
|
96
|
+
}
|
97
|
+
|
98
|
+
# 2 for milk
|
99
|
+
... milkFields
|
100
|
+
# 1 for cheese
|
101
|
+
... cheeseFields
|
102
|
+
# 1 for honey
|
103
|
+
... honeyFields
|
104
|
+
# 1 for milk + cheese
|
105
|
+
... dairyProductFields
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
109
|
+
fragment milkFields on Milk {
|
110
|
+
id
|
111
|
+
source
|
112
|
+
}
|
113
|
+
|
114
|
+
fragment cheeseFields on Cheese {
|
115
|
+
source
|
116
|
+
}
|
117
|
+
|
118
|
+
fragment honeyFields on Honey {
|
119
|
+
flowerType
|
120
|
+
}
|
121
|
+
|
122
|
+
fragment dairyProductFields on DairyProduct {
|
123
|
+
... on Cheese {
|
124
|
+
flavor
|
125
|
+
}
|
126
|
+
|
127
|
+
... on Milk {
|
128
|
+
flavors
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|}
|
132
|
+
|
133
|
+
it "gets the max among options" do
|
134
|
+
reduce_result
|
135
|
+
assert_equal 5, complexities.last
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
describe "when there are no selections on any object types" do
|
141
|
+
let(:query_string) {%|
|
142
|
+
{
|
143
|
+
favoriteEdible {
|
144
|
+
# 1 for everybody
|
145
|
+
fatContent
|
146
|
+
|
147
|
+
# 1 for everybody
|
148
|
+
... on Edible { origin }
|
149
|
+
|
150
|
+
# 1 for honey
|
151
|
+
... on Sweetener { sweetness }
|
152
|
+
}
|
153
|
+
}
|
154
|
+
|}
|
155
|
+
|
156
|
+
it "gets the max among interface types" do
|
157
|
+
reduce_result
|
158
|
+
assert_equal 3, complexities.last
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
describe "redundant fields" do
|
163
|
+
let(:query_string) {%|
|
164
|
+
{
|
165
|
+
favoriteEdible {
|
166
|
+
fatContent
|
167
|
+
# this is executed separately and counts separately:
|
168
|
+
aliasedFatContent: fatContent
|
169
|
+
|
170
|
+
... on Edible {
|
171
|
+
fatContent
|
172
|
+
}
|
173
|
+
|
174
|
+
... edibleFields
|
175
|
+
}
|
176
|
+
}
|
177
|
+
|
178
|
+
fragment edibleFields on Edible {
|
179
|
+
fatContent
|
180
|
+
}
|
181
|
+
|}
|
182
|
+
|
183
|
+
it "only counts them once" do
|
184
|
+
reduce_result
|
185
|
+
assert_equal 3, complexities.last
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
describe "custom complexities" do
|
192
|
+
let(:query) { GraphQL::Query.new(complexity_schema, query_string) }
|
193
|
+
let(:complexity_schema) {
|
194
|
+
complexity_type = GraphQL::ObjectType.define do
|
195
|
+
name "Complexity"
|
196
|
+
field :value, types.Int, complexity: 0.1 do
|
197
|
+
resolve -> (obj, args, ctx) { obj }
|
198
|
+
end
|
199
|
+
field :complexity, -> { complexity_type } do
|
200
|
+
argument :value, types.Int
|
201
|
+
complexity -> (ctx, args, child_complexity) { args[:value] + child_complexity }
|
202
|
+
resolve -> (obj, args, ctx) { args[:value] }
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
query_type = GraphQL::ObjectType.define do
|
207
|
+
name "Query"
|
208
|
+
field :complexity, -> { complexity_type } do
|
209
|
+
argument :value, types.Int
|
210
|
+
complexity -> (ctx, args, child_complexity) { args[:value] + child_complexity }
|
211
|
+
resolve -> (obj, args, ctx) { args[:value] }
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
GraphQL::Schema.new(query: query_type)
|
216
|
+
}
|
217
|
+
let(:query_string) {%|
|
218
|
+
{
|
219
|
+
a: complexity(value: 3) { value }
|
220
|
+
b: complexity(value: 6) {
|
221
|
+
value
|
222
|
+
complexity(value: 1) {
|
223
|
+
value
|
224
|
+
}
|
225
|
+
}
|
226
|
+
}
|
227
|
+
|}
|
228
|
+
|
229
|
+
it "sums the complexity" do
|
230
|
+
reduce_result
|
231
|
+
# 10 from `complexity`, `0.3` from `value`
|
232
|
+
assert_equal complexities, [query, 10.3]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|