graphql 1.4.4 → 1.4.5
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/execution/execute.rb +7 -0
- data/lib/graphql/language/parser.rb +666 -654
- data/lib/graphql/language/parser.y +12 -2
- data/lib/graphql/query.rb +10 -6
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +27 -10
- data/lib/graphql/static_validation/rules/operation_names_are_valid.rb +28 -0
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/execution/execute_spec.rb +74 -0
- data/spec/graphql/query/executor_spec.rb +13 -1
- data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +14 -0
- data/spec/graphql/static_validation/rules/operation_names_are_valid_spec.rb +79 -0
- metadata +5 -4
- data/spec/tmp/app/graphql/types/bird_type.rb +0 -5
@@ -26,11 +26,21 @@ rule
|
|
26
26
|
}
|
27
27
|
)
|
28
28
|
}
|
29
|
-
|
|
29
|
+
| LCURLY selection_list RCURLY {
|
30
30
|
return make_node(
|
31
31
|
:OperationDefinition, {
|
32
32
|
operation_type: "query",
|
33
|
-
selections: val[
|
33
|
+
selections: val[1],
|
34
|
+
position_source: val[0],
|
35
|
+
}
|
36
|
+
)
|
37
|
+
}
|
38
|
+
| LCURLY RCURLY {
|
39
|
+
return make_node(
|
40
|
+
:OperationDefinition, {
|
41
|
+
operation_type: "query",
|
42
|
+
selections: [],
|
43
|
+
position_source: val[0],
|
34
44
|
}
|
35
45
|
)
|
36
46
|
}
|
data/lib/graphql/query.rb
CHANGED
@@ -14,8 +14,12 @@ module GraphQL
|
|
14
14
|
extend Forwardable
|
15
15
|
|
16
16
|
class OperationNameMissingError < GraphQL::ExecutionError
|
17
|
-
def initialize(
|
18
|
-
msg =
|
17
|
+
def initialize(name)
|
18
|
+
msg = if name.nil?
|
19
|
+
%|An operation name is required|
|
20
|
+
else
|
21
|
+
%|No operation named "#{name}"|
|
22
|
+
end
|
19
23
|
super(msg)
|
20
24
|
end
|
21
25
|
end
|
@@ -84,7 +88,7 @@ module GraphQL
|
|
84
88
|
if @operations.any?
|
85
89
|
@selected_operation = find_operation(@operations, @operation_name)
|
86
90
|
if @selected_operation.nil?
|
87
|
-
@validation_errors << GraphQL::Query::OperationNameMissingError.new(@
|
91
|
+
@validation_errors << GraphQL::Query::OperationNameMissingError.new(@operation_name)
|
88
92
|
else
|
89
93
|
@ast_variables = @selected_operation.variables
|
90
94
|
@mutation = @selected_operation.operation_type == "mutation"
|
@@ -239,12 +243,12 @@ module GraphQL
|
|
239
243
|
end
|
240
244
|
|
241
245
|
def find_operation(operations, operation_name)
|
242
|
-
if operations.length == 1
|
246
|
+
if operation_name.nil? && operations.length == 1
|
243
247
|
operations.values.first
|
244
|
-
elsif
|
248
|
+
elsif !operations.key?(operation_name)
|
245
249
|
nil
|
246
250
|
else
|
247
|
-
operations
|
251
|
+
operations.fetch(operation_name)
|
248
252
|
end
|
249
253
|
end
|
250
254
|
|
@@ -9,25 +9,42 @@ module GraphQL
|
|
9
9
|
def validate(context)
|
10
10
|
context.visitor[GraphQL::Language::Nodes::Field] << ->(node, parent) {
|
11
11
|
field_defn = context.field_definition
|
12
|
-
validate_field_selections(node, field_defn, context)
|
12
|
+
validate_field_selections(node, field_defn.type.unwrap, context)
|
13
|
+
}
|
14
|
+
|
15
|
+
context.visitor[GraphQL::Language::Nodes::OperationDefinition] << ->(node, parent) {
|
16
|
+
validate_field_selections(node, context.type_definition, context)
|
13
17
|
}
|
14
18
|
end
|
15
19
|
|
16
20
|
private
|
17
21
|
|
18
|
-
def validate_field_selections(ast_field, field_defn, context)
|
19
|
-
resolved_type = field_defn.type.unwrap
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
23
|
+
def validate_field_selections(ast_node, resolved_type, context)
|
24
|
+
msg = if resolved_type.nil?
|
25
|
+
nil
|
26
|
+
elsif resolved_type.kind.scalar? && ast_node.selections.any?
|
27
|
+
"Selections can't be made on scalars (%{node_name} returns #{resolved_type.name} but has selections [#{ast_node.selections.map(&:name).join(", ")}])"
|
28
|
+
elsif resolved_type.kind.object? && ast_node.selections.none?
|
29
|
+
"Objects must have selections (%{node_name} returns #{resolved_type.name} but has no selections)"
|
25
30
|
else
|
26
|
-
|
31
|
+
nil
|
27
32
|
end
|
28
33
|
|
29
|
-
if
|
30
|
-
|
34
|
+
if msg
|
35
|
+
node_name = case ast_node
|
36
|
+
when GraphQL::Language::Nodes::Field
|
37
|
+
"field '#{ast_node.name}'"
|
38
|
+
when GraphQL::Language::Nodes::OperationDefinition
|
39
|
+
if ast_node.name.nil?
|
40
|
+
"anonymous query"
|
41
|
+
else
|
42
|
+
"#{ast_node.operation_type} '#{ast_node.name}'"
|
43
|
+
end
|
44
|
+
else
|
45
|
+
raise("Unexpected node #{ast_node}")
|
46
|
+
end
|
47
|
+
context.errors << message(msg % { node_name: node_name }, ast_node, context: context)
|
31
48
|
GraphQL::Language::Visitor::SKIP
|
32
49
|
end
|
33
50
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
module StaticValidation
|
4
|
+
class OperationNamesAreValid
|
5
|
+
include GraphQL::StaticValidation::Message::MessageHelper
|
6
|
+
|
7
|
+
def validate(context)
|
8
|
+
op_names = Hash.new { |h, k| h[k] = [] }
|
9
|
+
|
10
|
+
context.visitor[GraphQL::Language::Nodes::OperationDefinition].enter << -> (node, _parent) {
|
11
|
+
op_names[node.name] << node
|
12
|
+
}
|
13
|
+
|
14
|
+
context.visitor[GraphQL::Language::Nodes::Document].leave << -> (node, _parent) {
|
15
|
+
op_count = op_names.values.inject(0) { |m, v| m += v.size }
|
16
|
+
|
17
|
+
op_names.each do |name, nodes|
|
18
|
+
if name.nil? && op_count > 1
|
19
|
+
context.errors << message(%|Operation name is required when multiple operations are present|, nodes, context: context)
|
20
|
+
elsif nodes.length > 1
|
21
|
+
context.errors << message(%|Operation name "#{name}" must be unique|, nodes, context: context)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/graphql/version.rb
CHANGED
@@ -3,3 +3,77 @@ require "spec_helper"
|
|
3
3
|
|
4
4
|
ExecuteSuite = GraphQL::Compatibility::ExecutionSpecification.build_suite(GraphQL::Execution::Execute)
|
5
5
|
LazyExecuteSuite = GraphQL::Compatibility::LazyExecutionSpecification.build_suite(GraphQL::Execution::Execute)
|
6
|
+
|
7
|
+
describe GraphQL::Execution::Execute do
|
8
|
+
describe "null propagation on mutation root" do
|
9
|
+
module MutationNullTestRoot
|
10
|
+
INTS = []
|
11
|
+
def self.push(args, ctx)
|
12
|
+
if args[:int] == 13
|
13
|
+
nil
|
14
|
+
else
|
15
|
+
INTS << args[:int]
|
16
|
+
args[:int]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def ints
|
21
|
+
INTS
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:root) { MutationNullTestRoot }
|
26
|
+
|
27
|
+
let(:query_str) do
|
28
|
+
<<-GRAPHQL
|
29
|
+
mutation {
|
30
|
+
one: push(int: 1)
|
31
|
+
thirteen: push(int: 13)
|
32
|
+
two: push(int: 2)
|
33
|
+
}
|
34
|
+
GRAPHQL
|
35
|
+
end
|
36
|
+
|
37
|
+
before do
|
38
|
+
MutationNullTestRoot::INTS.clear
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "when root fields are non-nullable" do
|
42
|
+
let(:schema) { GraphQL::Schema.from_definition <<-GRAPHQL
|
43
|
+
type Mutation {
|
44
|
+
push(int: Int!): Int!
|
45
|
+
}
|
46
|
+
|
47
|
+
type Query {
|
48
|
+
ints: [Int!]
|
49
|
+
}
|
50
|
+
GRAPHQL
|
51
|
+
}
|
52
|
+
|
53
|
+
it "propagates null to the root mutation and halts mutation execution" do
|
54
|
+
res = schema.execute(query_str, root_value: root)
|
55
|
+
assert_equal [1], MutationNullTestRoot::INTS
|
56
|
+
assert_equal(nil, res["data"])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'mutation fields are nullable' do
|
61
|
+
let(:schema) { GraphQL::Schema.from_definition <<-GRAPHQL
|
62
|
+
type Mutation {
|
63
|
+
push(int: Int!): Int
|
64
|
+
}
|
65
|
+
|
66
|
+
type Query {
|
67
|
+
ints: [Int!]
|
68
|
+
}
|
69
|
+
GRAPHQL
|
70
|
+
}
|
71
|
+
|
72
|
+
it 'does not halt execution and returns data for the successful mutations' do
|
73
|
+
res = schema.execute(query_str, root_value: root)
|
74
|
+
assert_equal [1, 2], MutationNullTestRoot::INTS
|
75
|
+
assert_equal({"one"=>1, "thirteen"=>nil, "two"=>2}, res["data"])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -38,7 +38,19 @@ describe GraphQL::Query::Executor do
|
|
38
38
|
it "returns an error" do
|
39
39
|
expected = {
|
40
40
|
"errors" => [
|
41
|
-
{"message" => "
|
41
|
+
{"message" => "An operation name is required"}
|
42
|
+
]
|
43
|
+
}
|
44
|
+
assert_equal(expected, result)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "when the named operation is not present" do
|
49
|
+
let(:operation_name) { "nonsenseOperation" }
|
50
|
+
it "returns an error" do
|
51
|
+
expected = {
|
52
|
+
"errors" => [
|
53
|
+
{"message" => 'No operation named "nonsenseOperation"'}
|
42
54
|
]
|
43
55
|
}
|
44
56
|
assert_equal(expected, result)
|
@@ -28,4 +28,18 @@ describe GraphQL::StaticValidation::FieldsHaveAppropriateSelections do
|
|
28
28
|
}
|
29
29
|
assert_includes(errors, selection_required_error, "finds objects without selections")
|
30
30
|
end
|
31
|
+
|
32
|
+
describe "anonymous operations" do
|
33
|
+
let(:query_string) { "{ }" }
|
34
|
+
it "requires selections" do
|
35
|
+
assert_equal(1, errors.length)
|
36
|
+
|
37
|
+
selections_required_error = {
|
38
|
+
"message"=> "Objects must have selections (anonymous query returns Query but has no selections)",
|
39
|
+
"locations"=>[{"line"=>1, "column"=>1}],
|
40
|
+
"fields"=>["query"]
|
41
|
+
}
|
42
|
+
assert_includes(errors, selections_required_error)
|
43
|
+
end
|
44
|
+
end
|
31
45
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe GraphQL::StaticValidation::OperationNamesAreValid do
|
5
|
+
include StaticValidationHelpers
|
6
|
+
|
7
|
+
describe "when there are multiple operations" do
|
8
|
+
let(:query_string) { <<-GRAPHQL
|
9
|
+
query getCheese {
|
10
|
+
cheese(id: 1) { flavor }
|
11
|
+
}
|
12
|
+
|
13
|
+
{
|
14
|
+
cheese(id: 2) { flavor }
|
15
|
+
}
|
16
|
+
|
17
|
+
{
|
18
|
+
cheese(id: 3) { flavor }
|
19
|
+
}
|
20
|
+
GRAPHQL
|
21
|
+
}
|
22
|
+
|
23
|
+
it "must have operation names" do
|
24
|
+
assert_equal 1, errors.length
|
25
|
+
requires_name_error = {
|
26
|
+
"message"=>"Operation name is required when multiple operations are present",
|
27
|
+
"locations"=>[{"line"=>5, "column"=>5}, {"line"=>9, "column"=>5}],
|
28
|
+
"fields"=>[],
|
29
|
+
}
|
30
|
+
assert_includes(errors, requires_name_error)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "when there are only unnamed operations" do
|
35
|
+
let(:query_string) { <<-GRAPHQL
|
36
|
+
{
|
37
|
+
cheese(id: 2) { flavor }
|
38
|
+
}
|
39
|
+
|
40
|
+
{
|
41
|
+
cheese(id: 3) { flavor }
|
42
|
+
}
|
43
|
+
GRAPHQL
|
44
|
+
}
|
45
|
+
|
46
|
+
it "requires names" do
|
47
|
+
assert_equal 1, errors.length
|
48
|
+
requires_name_error = {
|
49
|
+
"message"=>"Operation name is required when multiple operations are present",
|
50
|
+
"locations"=>[{"line"=>1, "column"=>5}, {"line"=>5, "column"=>5}],
|
51
|
+
"fields"=>[],
|
52
|
+
}
|
53
|
+
assert_includes(errors, requires_name_error)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "when multiple operations have names" do
|
58
|
+
let(:query_string) { <<-GRAPHQL
|
59
|
+
query getCheese {
|
60
|
+
cheese(id: 1) { flavor }
|
61
|
+
}
|
62
|
+
|
63
|
+
query getCheese {
|
64
|
+
cheese(id: 2) { flavor }
|
65
|
+
}
|
66
|
+
GRAPHQL
|
67
|
+
}
|
68
|
+
|
69
|
+
it "must be unique" do
|
70
|
+
assert_equal 1, errors.length
|
71
|
+
name_uniqueness_error = {
|
72
|
+
"message"=>'Operation name "getCheese" must be unique',
|
73
|
+
"locations"=>[{"line"=>1, "column"=>5}, {"line"=>5, "column"=>5}],
|
74
|
+
"fields"=>[],
|
75
|
+
}
|
76
|
+
assert_includes(errors, name_uniqueness_error)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.4.
|
4
|
+
version: 1.4.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-03-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: codeclimate-test-reporter
|
@@ -433,6 +433,7 @@ files:
|
|
433
433
|
- lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb
|
434
434
|
- lib/graphql/static_validation/rules/fragments_are_used.rb
|
435
435
|
- lib/graphql/static_validation/rules/mutation_root_exists.rb
|
436
|
+
- lib/graphql/static_validation/rules/operation_names_are_valid.rb
|
436
437
|
- lib/graphql/static_validation/rules/required_arguments_are_present.rb
|
437
438
|
- lib/graphql/static_validation/rules/subscription_root_exists.rb
|
438
439
|
- lib/graphql/static_validation/rules/unique_directives_per_location.rb
|
@@ -535,6 +536,7 @@ files:
|
|
535
536
|
- spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb
|
536
537
|
- spec/graphql/static_validation/rules/fragments_are_used_spec.rb
|
537
538
|
- spec/graphql/static_validation/rules/mutation_root_exists_spec.rb
|
539
|
+
- spec/graphql/static_validation/rules/operation_names_are_valid_spec.rb
|
538
540
|
- spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb
|
539
541
|
- spec/graphql/static_validation/rules/subscription_root_exists_spec.rb
|
540
542
|
- spec/graphql/static_validation/rules/unique_directives_per_location_spec.rb
|
@@ -553,7 +555,6 @@ files:
|
|
553
555
|
- spec/support/star_wars/data.rb
|
554
556
|
- spec/support/star_wars/schema.rb
|
555
557
|
- spec/support/static_validation_helpers.rb
|
556
|
-
- spec/tmp/app/graphql/types/bird_type.rb
|
557
558
|
homepage: http://github.com/rmosolgo/graphql-ruby
|
558
559
|
licenses:
|
559
560
|
- MIT
|
@@ -665,6 +666,7 @@ test_files:
|
|
665
666
|
- spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb
|
666
667
|
- spec/graphql/static_validation/rules/fragments_are_used_spec.rb
|
667
668
|
- spec/graphql/static_validation/rules/mutation_root_exists_spec.rb
|
669
|
+
- spec/graphql/static_validation/rules/operation_names_are_valid_spec.rb
|
668
670
|
- spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb
|
669
671
|
- spec/graphql/static_validation/rules/subscription_root_exists_spec.rb
|
670
672
|
- spec/graphql/static_validation/rules/unique_directives_per_location_spec.rb
|
@@ -683,4 +685,3 @@ test_files:
|
|
683
685
|
- spec/support/star_wars/data.rb
|
684
686
|
- spec/support/star_wars/schema.rb
|
685
687
|
- spec/support/static_validation_helpers.rb
|
686
|
-
- spec/tmp/app/graphql/types/bird_type.rb
|