graphql 1.4.4 → 1.4.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -26,11 +26,21 @@ rule
26
26
  }
27
27
  )
28
28
  }
29
- | selection_set {
29
+ | LCURLY selection_list RCURLY {
30
30
  return make_node(
31
31
  :OperationDefinition, {
32
32
  operation_type: "query",
33
- selections: val[0],
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
  }
@@ -14,8 +14,12 @@ module GraphQL
14
14
  extend Forwardable
15
15
 
16
16
  class OperationNameMissingError < GraphQL::ExecutionError
17
- def initialize(names)
18
- msg = "You must provide an operation name from: #{names.join(", ")}"
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(@operations.keys)
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 operations.length == 0 || !operations.key?(operation_name)
248
+ elsif !operations.key?(operation_name)
245
249
  nil
246
250
  else
247
- operations[operation_name]
251
+ operations.fetch(operation_name)
248
252
  end
249
253
  end
250
254
 
@@ -28,6 +28,7 @@ module GraphQL
28
28
  GraphQL::StaticValidation::VariableUsagesAreAllowed,
29
29
  GraphQL::StaticValidation::MutationRootExists,
30
30
  GraphQL::StaticValidation::SubscriptionRootExists,
31
+ GraphQL::StaticValidation::OperationNamesAreValid,
31
32
  ]
32
33
  end
33
34
  end
@@ -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
- if resolved_type.kind.scalar? && ast_field.selections.any?
22
- error = message("Selections can't be made on scalars (field '#{ast_field.name}' returns #{resolved_type.name} but has selections [#{ast_field.selections.map(&:name).join(", ")}])", ast_field, context: context)
23
- elsif resolved_type.kind.object? && ast_field.selections.none?
24
- error = message("Objects must have selections (field '#{ast_field.name}' returns #{resolved_type.name} but has no selections)", ast_field, context: context)
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
- error = nil
31
+ nil
27
32
  end
28
33
 
29
- if !error.nil?
30
- context.errors << error
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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.4.4"
3
+ VERSION = "1.4.5"
4
4
  end
@@ -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" => "You must provide an operation name from: getCheese1, getCheese2"}
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
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-02-17 00:00:00.000000000 Z
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
@@ -1,5 +0,0 @@
1
- Types::BirdType = GraphQL::InterfaceType.define do
2
- name "Bird"
3
- field :wingspan, !types.Int
4
- field :foliage, types[Types::ColorType]
5
- end