graphql 0.15.3 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|