graphql 1.8.0 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/compatibility/schema_parser_specification.rb +28 -0
  3. data/lib/graphql/enum_type.rb +3 -3
  4. data/lib/graphql/execution_error.rb +13 -1
  5. data/lib/graphql/introspection.rb +0 -3
  6. data/lib/graphql/introspection/dynamic_fields.rb +1 -1
  7. data/lib/graphql/introspection/entry_points.rb +1 -1
  8. data/lib/graphql/introspection/type_type.rb +1 -2
  9. data/lib/graphql/language/parser.rb +2 -2
  10. data/lib/graphql/language/parser.y +2 -2
  11. data/lib/graphql/railtie.rb +6 -6
  12. data/lib/graphql/schema/resolver.rb +0 -1
  13. data/lib/graphql/schema/warden.rb +17 -10
  14. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
  15. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +2 -2
  16. data/lib/graphql/upgrader/member.rb +6 -6
  17. data/lib/graphql/version.rb +1 -1
  18. data/spec/fixtures/upgrader/blame_range.transformed.rb +2 -2
  19. data/spec/fixtures/upgrader/delete_project.transformed.rb +4 -4
  20. data/spec/fixtures/upgrader/increment_count.transformed.rb +1 -1
  21. data/spec/fixtures/upgrader/photo.transformed.rb +1 -1
  22. data/spec/fixtures/upgrader/starrable.transformed.rb +5 -5
  23. data/spec/fixtures/upgrader/subscribable.transformed.rb +7 -7
  24. data/spec/fixtures/upgrader/type_x.transformed.rb +6 -6
  25. data/spec/graphql/execution/multiplex_spec.rb +1 -1
  26. data/spec/graphql/execution_error_spec.rb +20 -1
  27. data/spec/graphql/introspection/schema_type_spec.rb +1 -0
  28. data/spec/graphql/relay/mongo_relation_connection_spec.rb +12 -1
  29. data/spec/graphql/schema/warden_spec.rb +129 -0
  30. data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +17 -9
  31. data/spec/graphql/upgrader/member_spec.rb +5 -5
  32. data/spec/spec_helper.rb +20 -1
  33. data/spec/support/dummy/schema.rb +7 -0
  34. metadata +2 -21
  35. data/lib/graphql/introspection/schema_field.rb +0 -16
  36. data/lib/graphql/introspection/type_by_name_field.rb +0 -22
  37. data/lib/graphql/introspection/typename_field.rb +0 -12
  38. data/spec/graphql/introspection/type_by_name_field_spec.rb +0 -38
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f5283bf51e22ac60f8d9368d9cc19b8c6d038d8b
4
- data.tar.gz: 147ec429661a6fbc1aaef685ba94c8a81d87910e
3
+ metadata.gz: 6d3ae1b02473b2855990db63798b1c5d2fea46de
4
+ data.tar.gz: cfdc979146afea3eb44a969b66ac3a0763956db7
5
5
  SHA512:
6
- metadata.gz: 06a2a8730fc1c13dc0a0d76a35b19e89dd40ad842e41618abb1b05a7f75b667eede56c106728e3e68bd531e36e29b28050b991319599b7952ac3285fb3f9be3c
7
- data.tar.gz: 8231c7696681477decf92b0665f3e4249b308153338c8d7cf5102b7e95133b9ac7aa6156f3dddc026c4178ea9fcb25d7f181f752298d5cf38185141af653a471
6
+ metadata.gz: 762efe82d940d001685c0cf242e1b6fc0c7ee9a5b77de66c4917268b6d2ae0340346daf707d74d41c9b1dd77b71c2d2d1f887e74e2eae2ab9c7869a787a5a74b
7
+ data.tar.gz: 4bcd06d7c866bb65154310cb8d11965902372860747aaaa99ebf44a73ebac3f6b1743e459e47730ba44e7fab57588031352bd1ed63925a42ec89550d32bf7906
@@ -78,6 +78,34 @@ module GraphQL
78
78
  assert_equal [], type.values[2].directives
79
79
  end
80
80
 
81
+ def test_it_parses_union_types
82
+ document = parse(
83
+ "union BagOfThings = \n" \
84
+ "A |\n" \
85
+ "B |\n" \
86
+ "C"
87
+ )
88
+
89
+ union = document.definitions.first
90
+
91
+ assert_equal GraphQL::Language::Nodes::UnionTypeDefinition, union.class
92
+ assert_equal 'BagOfThings', union.name
93
+ assert_equal 3, union.types.length
94
+ assert_equal [1, 1], union.position
95
+
96
+ assert_equal GraphQL::Language::Nodes::TypeName, union.types[0].class
97
+ assert_equal 'A', union.types[0].name
98
+ assert_equal [2, 1], union.types[0].position
99
+
100
+ assert_equal GraphQL::Language::Nodes::TypeName, union.types[1].class
101
+ assert_equal 'B', union.types[1].name
102
+ assert_equal [3, 1], union.types[1].position
103
+
104
+ assert_equal GraphQL::Language::Nodes::TypeName, union.types[2].class
105
+ assert_equal 'C', union.types[2].name
106
+ assert_equal [4, 1], union.types[2].position
107
+ end
108
+
81
109
  def test_it_parses_input_types
82
110
  document = parse('
83
111
  input EmptyMutationInput {
@@ -12,8 +12,8 @@ module GraphQL
12
12
  #
13
13
  # @example An enum of programming languages
14
14
  # LanguageEnum = GraphQL::EnumType.define do
15
- # name "Languages"
16
- # description "Programming languages for Web projects"
15
+ # name "Language"
16
+ # description "Programming language for Web projects"
17
17
  # value("PYTHON", "A dynamic, function-oriented language")
18
18
  # value("RUBY", "A very dynamic language aimed at programmer happiness")
19
19
  # value("JAVASCRIPT", "Accidental lingua franca of the web")
@@ -57,7 +57,7 @@ module GraphQL
57
57
  # }
58
58
  #
59
59
  # @example Enum whose values are different in ActiveRecord-land
60
- # class Language < ActiveRecord::BaseType
60
+ # class Language < ActiveRecord::Base
61
61
  # enum language: {
62
62
  # rb: 0
63
63
  # }
@@ -12,11 +12,19 @@ module GraphQL
12
12
  attr_accessor :path
13
13
 
14
14
  # @return [Hash] Optional data for error objects
15
+ # @deprecated Use `extensions` instead of `options`. The GraphQL spec
16
+ # recommends that any custom entries in an error be under the
17
+ # `extensions` key.
15
18
  attr_accessor :options
16
19
 
17
- def initialize(message, ast_node: nil, options: nil)
20
+ # @return [Hash] Optional custom data for error objects which will be added
21
+ # under the `extensions` key.
22
+ attr_accessor :extensions
23
+
24
+ def initialize(message, ast_node: nil, options: nil, extensions: nil)
18
25
  @ast_node = ast_node
19
26
  @options = options
27
+ @extensions = extensions
20
28
  super(message)
21
29
  end
22
30
 
@@ -39,6 +47,10 @@ module GraphQL
39
47
  if options
40
48
  hash.merge!(options)
41
49
  end
50
+ if extensions
51
+ hash["extensions"] ||= {}
52
+ hash["extensions"].merge!(extensions)
53
+ end
42
54
  hash
43
55
  end
44
56
  end
@@ -5,7 +5,6 @@ module GraphQL
5
5
  end
6
6
 
7
7
  require "graphql/introspection/base_object"
8
- require "graphql/introspection/typename_field"
9
8
  require "graphql/introspection/input_value_type"
10
9
  require "graphql/introspection/enum_value_type"
11
10
  require "graphql/introspection/type_kind_enum"
@@ -14,8 +13,6 @@ require "graphql/introspection/field_type"
14
13
  require "graphql/introspection/directive_location_enum"
15
14
  require "graphql/introspection/directive_type"
16
15
  require "graphql/introspection/schema_type"
17
- require "graphql/introspection/schema_field"
18
- require "graphql/introspection/type_by_name_field"
19
16
  require "graphql/introspection/introspection_query"
20
17
  require "graphql/introspection/dynamic_fields"
21
18
  require "graphql/introspection/entry_points"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  module Introspection
4
- class DynamicFields < GraphQL::Schema::Object
4
+ class DynamicFields < Introspection::BaseObject
5
5
  field :__typename, String, "The name of this type", null: false, extras: [:irep_node]
6
6
  def __typename(irep_node:)
7
7
  irep_node.owner_type.name
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  module Introspection
4
- class EntryPoints < GraphQL::Schema::Object
4
+ class EntryPoints < Introspection::BaseObject
5
5
  field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false
6
6
  field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system", null: true do
7
7
  argument :name, String, required: true
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  module Introspection
4
- class TypeType < GraphQL::Schema::Object
4
+ class TypeType < Introspection::BaseObject
5
5
  graphql_name "__Type"
6
6
  description "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in "\
7
7
  "GraphQL as represented by the `__TypeKind` enum.\n\n"\
@@ -24,7 +24,6 @@ module GraphQL
24
24
  end
25
25
  field :input_fields, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: true
26
26
  field :of_type, GraphQL::Schema::LateBoundType.new("__Type"), null: true
27
- introspection true
28
27
 
29
28
  def kind
30
29
  @object.kind.name
@@ -1651,14 +1651,14 @@ module_eval(<<'.,.,', 'parser.y', 341)
1651
1651
 
1652
1652
  module_eval(<<'.,.,', 'parser.y', 345)
1653
1653
  def _reduce_141(val, _values, result)
1654
- return [make_node(:TypeName, name: val[0])]
1654
+ return [make_node(:TypeName, name: val[0], position_source: val[0])]
1655
1655
  result
1656
1656
  end
1657
1657
  .,.,
1658
1658
 
1659
1659
  module_eval(<<'.,.,', 'parser.y', 346)
1660
1660
  def _reduce_142(val, _values, result)
1661
- val[0] << make_node(:TypeName, name: val[2])
1661
+ val[0] << make_node(:TypeName, name: val[2], position_source: val[2])
1662
1662
  result
1663
1663
  end
1664
1664
  .,.,
@@ -343,8 +343,8 @@ rule
343
343
  }
344
344
 
345
345
  union_members:
346
- name { return [make_node(:TypeName, name: val[0])]}
347
- | union_members PIPE name { val[0] << make_node(:TypeName, name: val[2]) }
346
+ name { return [make_node(:TypeName, name: val[0], position_source: val[0])]}
347
+ | union_members PIPE name { val[0] << make_node(:TypeName, name: val[2], position_source: val[2]) }
348
348
 
349
349
  union_type_definition:
350
350
  UNION name directives_list_opt EQUALS union_members {
@@ -40,7 +40,7 @@ module GraphQL
40
40
  unless File.exists?(destination_file)
41
41
  FileUtils.mkdir_p(File.dirname(destination_file))
42
42
  File.open(destination_file, 'w') do |f|
43
- f.write "class Types::BaseScalar < GraphQL::Schema::Scalar\nend"
43
+ f.puts "class Types::BaseScalar < GraphQL::Schema::Scalar\nend"
44
44
  end
45
45
  end
46
46
 
@@ -48,7 +48,7 @@ module GraphQL
48
48
  unless File.exists?(destination_file)
49
49
  FileUtils.mkdir_p(File.dirname(destination_file))
50
50
  File.open(destination_file, 'w') do |f|
51
- f.write "class Types::BaseInputObject < GraphQL::Schema::InputObject\nend"
51
+ f.puts "class Types::BaseInputObject < GraphQL::Schema::InputObject\nend"
52
52
  end
53
53
  end
54
54
 
@@ -56,7 +56,7 @@ module GraphQL
56
56
  unless File.exists?(destination_file)
57
57
  FileUtils.mkdir_p(File.dirname(destination_file))
58
58
  File.open(destination_file, 'w') do |f|
59
- f.write "class Types::BaseEnum < GraphQL::Schema::Enum\nend"
59
+ f.puts "class Types::BaseEnum < GraphQL::Schema::Enum\nend"
60
60
  end
61
61
  end
62
62
 
@@ -64,7 +64,7 @@ module GraphQL
64
64
  unless File.exists?(destination_file)
65
65
  FileUtils.mkdir_p(File.dirname(destination_file))
66
66
  File.open(destination_file, 'w') do |f|
67
- f.write "class Types::BaseUnion < GraphQL::Schema::Union\nend"
67
+ f.puts "class Types::BaseUnion < GraphQL::Schema::Union\nend"
68
68
  end
69
69
  end
70
70
 
@@ -72,14 +72,14 @@ module GraphQL
72
72
  unless File.exists?(destination_file)
73
73
  FileUtils.mkdir_p(File.dirname(destination_file))
74
74
  File.open(destination_file, 'w') do |f|
75
- f.write "module Types::BaseInterface\n include GraphQL::Schema::Interface\nend"
75
+ f.puts "module Types::BaseInterface\n include GraphQL::Schema::Interface\nend"
76
76
  end
77
77
  end
78
78
 
79
79
  destination_file = File.join(base_dir, "types", "base_object.rb")
80
80
  unless File.exists?(destination_file)
81
81
  File.open(destination_file, 'w') do |f|
82
- f.write "class Types::BaseObject < GraphQL::Schema::Object\nend"
82
+ f.puts "class Types::BaseObject < GraphQL::Schema::Object\nend"
83
83
  end
84
84
  end
85
85
  end
@@ -112,7 +112,6 @@ module GraphQL
112
112
  end
113
113
 
114
114
  # A non-normalized type configuration, without `null` applied
115
- # @api private
116
115
  def type_expr
117
116
  @type_expr || (superclass.respond_to?(:type_expr) ? superclass.type_expr : nil)
118
117
  end
@@ -136,13 +136,17 @@ module GraphQL
136
136
  end
137
137
 
138
138
  def visible_type?(type_defn)
139
- visible?(type_defn) && (
140
- root_type?(type_defn) ||
141
- type_defn.introspection? ||
142
- referenced?(type_defn) ||
143
- visible_abstract_type?(type_defn) ||
144
- possible_types?(type_defn)
145
- )
139
+ return false unless visible?(type_defn)
140
+ return true if root_type?(type_defn)
141
+ return true if type_defn.introspection?
142
+
143
+ if type_defn.kind.union?
144
+ visible_possible_types?(type_defn) && (referenced?(type_defn) || orphan_type?(type_defn))
145
+ elsif type_defn.kind.interface?
146
+ visible_possible_types?(type_defn)
147
+ else
148
+ referenced?(type_defn) || visible_abstract_type?(type_defn)
149
+ end
146
150
  end
147
151
 
148
152
  def root_type?(type_defn)
@@ -154,6 +158,10 @@ module GraphQL
154
158
  members.any? { |m| visible?(m) }
155
159
  end
156
160
 
161
+ def orphan_type?(type_defn)
162
+ @schema.orphan_types.include?(type_defn)
163
+ end
164
+
157
165
  def visible_abstract_type?(type_defn)
158
166
  type_defn.kind.object? && (
159
167
  interfaces(type_defn).any? ||
@@ -161,9 +169,8 @@ module GraphQL
161
169
  )
162
170
  end
163
171
 
164
- def possible_types?(type_defn)
165
- (type_defn.kind.union? || type_defn.kind.interface?) &&
166
- @schema.possible_types(type_defn).any? { |t| visible_type?(t) }
172
+ def visible_possible_types?(type_defn)
173
+ @schema.possible_types(type_defn).any? { |t| visible_type?(t) }
167
174
  end
168
175
 
169
176
  def visible?(member)
@@ -19,7 +19,7 @@ module GraphQL
19
19
  field = context.warden.get_field(parent_type, ast_field.name)
20
20
 
21
21
  if field.nil?
22
- if parent_type.kind.union?
22
+ if parent_type.kind.union?
23
23
  context.errors << message("Selections can't be made directly on unions (see selections on #{parent_type.name})", parent, context: context)
24
24
  else
25
25
  context.errors << message("Field '#{ast_field.name}' doesn't exist on type '#{parent_type.name}'", ast_field, context: context)
@@ -29,8 +29,8 @@ module GraphQL
29
29
  else
30
30
  "Selections can't be made on scalars (%{node_name} returns #{resolved_type.name} but has selections [#{ast_node.selections.map(&:name).join(", ")}])"
31
31
  end
32
- elsif resolved_type.kind.object? && ast_node.selections.none?
33
- "Objects must have selections (%{node_name} returns #{resolved_type.name} but has no selections)"
32
+ elsif resolved_type.kind.fields? && ast_node.selections.none?
33
+ "Field must have selections (%{node_name} returns #{resolved_type.name} but has no selections. Did you mean '#{ast_node.name} { ... }'?)"
34
34
  else
35
35
  nil
36
36
  end
@@ -356,10 +356,10 @@ module GraphQL
356
356
  # This is not good, it will hit false positives
357
357
  # Should use AST to make this substitution
358
358
  if obj_arg_name != "_"
359
- proc_body.gsub!(/([^\w:.]|^)#{obj_arg_name}([^\w:]|$)/, '\1@object\2')
359
+ proc_body.gsub!(/([^\w:.]|^)#{obj_arg_name}([^\w:]|$)/, '\1object\2')
360
360
  end
361
361
  if ctx_arg_name != "_"
362
- proc_body.gsub!(/([^\w:.]|^)#{ctx_arg_name}([^\w:]|$)/, '\1@context\2')
362
+ proc_body.gsub!(/([^\w:.]|^)#{ctx_arg_name}([^\w:]|$)/, '\1context\2')
363
363
  end
364
364
 
365
365
  method_defn = "def #{@proc_name}(**#{args_arg_name})\n#{method_defn_indent} #{proc_body}\n#{method_defn_indent}end\n"
@@ -491,8 +491,8 @@ module GraphQL
491
491
  # - Get the three argument names (obj, arg, ctx)
492
492
  # - Get the proc body
493
493
  # - Find and replace:
494
- # - The ctx argument becomes `@context`
495
- # - The obj argument becomes `@object`
494
+ # - The ctx argument becomes `context`
495
+ # - The obj argument becomes `object`
496
496
  # - Args is trickier:
497
497
  # - If it's not used, remove it
498
498
  # - If it's used, abandon ship and make it `**args`
@@ -513,10 +513,10 @@ module GraphQL
513
513
  # This is not good, it will hit false positives
514
514
  # Should use AST to make this substitution
515
515
  if obj_arg_name != "_"
516
- proc_body.gsub!(/([^\w:.]|^)#{obj_arg_name}([^\w:]|$)/, '\1@object\2')
516
+ proc_body.gsub!(/([^\w:.]|^)#{obj_arg_name}([^\w:]|$)/, '\1object\2')
517
517
  end
518
518
  if ctx_arg_name != "_"
519
- proc_body.gsub!(/([^\w:.]|^)#{ctx_arg_name}([^\w:]|$)/, '\1@context\2')
519
+ proc_body.gsub!(/([^\w:.]|^)#{ctx_arg_name}([^\w:]|$)/, '\1context\2')
520
520
  end
521
521
 
522
522
  method_def_indent = " " * (processor.resolve_indent - 2)
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.8.0"
3
+ VERSION = "1.8.1"
4
4
  end
@@ -13,13 +13,13 @@ module Platform
13
13
  field :starting_line, Integer, description: "The starting line for the range", null: false
14
14
 
15
15
  def starting_line
16
- @object.lines.first[:lineno]
16
+ object.lines.first[:lineno]
17
17
  end
18
18
 
19
19
  field :ending_line, Integer, description: "The ending line for the range", null: false
20
20
 
21
21
  def ending_line
22
- @object.lines.first[:lineno] + (@object.lines.length - 1)
22
+ object.lines.first[:lineno] + (object.lines.length - 1)
23
23
  end
24
24
 
25
25
  field :commit, Objects::Commit, description: "Identifies the line author", null: false
@@ -12,13 +12,13 @@ module Platform
12
12
 
13
13
  def resolve(**inputs)
14
14
  project = Platform::Helpers::NodeIdentification.typed_object_from_id(
15
- [Objects::Project], inputs[:project_id], @context
15
+ [Objects::Project], inputs[:project_id], context
16
16
  )
17
17
 
18
- @context[:permission].can_modify?("DeleteProject", project).sync
19
- @context[:abilities].authorize_content(:project, :destroy, owner: project.owner)
18
+ context[:permission].can_modify?("DeleteProject", project).sync
19
+ context[:abilities].authorize_content(:project, :destroy, owner: project.owner)
20
20
 
21
- project.enqueue_delete(actor: @context[:viewer])
21
+ project.enqueue_delete(actor: context[:viewer])
22
22
 
23
23
  { owner: project.owner }
24
24
  end
@@ -20,7 +20,7 @@ module Platform
20
20
  { abcDef: 1 }
21
21
  some_method do { xyzAbc: 1 } end
22
22
 
23
- thing = Platform::Helpers::NodeIdentification.typed_object_from_id(Objects::Thing, inputs[:thing_id], @context)
23
+ thing = Platform::Helpers::NodeIdentification.typed_object_from_id(Objects::Thing, inputs[:thing_id], context)
24
24
  raise Errors::Validation.new("Thing not found.") unless thing
25
25
 
26
26
  ThingActivity.track(thing.id, Time.now.change(min: 0, sec: 0))
@@ -5,7 +5,7 @@ module Platform
5
5
  field :caption, String, null: true
6
6
 
7
7
  def caption
8
- @object.caption
8
+ object.caption
9
9
  end
10
10
  end
11
11
  end
@@ -13,9 +13,9 @@ module Platform
13
13
  end
14
14
 
15
15
  def viewer_has_starred(**arguments)
16
- if @context[:viewer]
16
+ if context[:viewer]
17
17
  ->(test_inner_proc) do
18
- @context[:viewer].starred?(@object)
18
+ context[:viewer].starred?(object)
19
19
  end
20
20
  else
21
21
  false
@@ -27,11 +27,11 @@ module Platform
27
27
  end
28
28
 
29
29
  def stargazers(**arguments)
30
- scope = case @object
30
+ scope = case object
31
31
  when Repository
32
- @object.stars
32
+ object.stars
33
33
  when Gist
34
- GistStar.where(gist_id: @object.id)
34
+ GistStar.where(gist_id: object.id)
35
35
  end
36
36
 
37
37
  table = scope.table_name
@@ -11,17 +11,17 @@ module Platform
11
11
  field :viewer_subscription, Enums::SubscriptionState, description: "Identifies if the viewer is watching, not watching, or ignoring the subscribable entity.", null: false
12
12
 
13
13
  def viewer_subscription
14
- if @context[:viewer].nil?
14
+ if context[:viewer].nil?
15
15
  return "unsubscribed"
16
16
  end
17
17
 
18
- subscription_status_response = @object.async_subscription_status(@context[:viewer]).sync
18
+ subscription_status_response = object.async_subscription_status(context[:viewer]).sync
19
19
 
20
20
  if subscription_status_response.failed?
21
21
  error = Platform::Errors::ServiceUnavailable.new("Subscriptions are currently unavailable. Please try again later.")
22
- error.ast_node = @context.irep_node.ast_node
23
- error.path = @context.path
24
- @context.errors << error
22
+ error.ast_node = context.irep_node.ast_node
23
+ error.path = context.path
24
+ context.errors << error
25
25
  return "unavailable"
26
26
  end
27
27
 
@@ -38,9 +38,9 @@ module Platform
38
38
  field :viewer_can_subscribe, Boolean, description: "Check if the viewer is able to change their subscription status for the repository.", null: false
39
39
 
40
40
  def viewer_can_subscribe
41
- return false if @context[:viewer].nil?
41
+ return false if context[:viewer].nil?
42
42
 
43
- @object.async_subscription_status(@context[:viewer]).then(&:success?)
43
+ object.async_subscription_status(context[:viewer]).then(&:success?)
44
44
  end
45
45
 
46
46
  field :issues, function: Platform::Functions::Issues.new, description: "A list of issues associated with the milestone.", connection: true
@@ -23,12 +23,12 @@ module Platform
23
23
  def f4(**arguments)
24
24
  Class1.new(
25
25
  a: Class2.new(
26
- b: @object.b_1,
27
- c: @object.c_1
26
+ b: object.b_1,
27
+ c: object.c_1
28
28
  ),
29
29
  d: Class3.new(
30
- b: @object.b_2,
31
- c: @object.c_3,
30
+ b: object.b_2,
31
+ c: object.c_3,
32
32
  )
33
33
  )
34
34
  end
@@ -46,9 +46,9 @@ module Platform
46
46
  field :f10, String, null: true
47
47
 
48
48
  def f10
49
- @object.something do |_|
49
+ object.something do |_|
50
50
  xyz_obj.obj
51
- @object.f10
51
+ object.f10
52
52
  end
53
53
  end
54
54
  end
@@ -92,7 +92,7 @@ describe GraphQL::Execution::Multiplex do
92
92
  },
93
93
  {
94
94
  "errors" => [{
95
- "message"=>"Objects must have selections (field 'nullableNestedSum' returns LazySum but has no selections)",
95
+ "message"=>"Field must have selections (field 'nullableNestedSum' returns LazySum but has no selections. Did you mean 'nullableNestedSum { ... }'?)",
96
96
  "locations"=>[{"line"=>1, "column"=>4}],
97
97
  "fields"=>["query", "validationError"]
98
98
  }]
@@ -271,7 +271,26 @@ describe GraphQL::ExecutionError do
271
271
  assert_equal(expected_result, result)
272
272
  end
273
273
  end
274
-
274
+
275
+ describe "extensions in ExecutionError" do
276
+ let(:query_string) {%|
277
+ {
278
+ executionErrorWithExtensions
279
+ }
280
+ |}
281
+ it "the error is inserted into the errors key with custom data set in `extensions`" do
282
+ expected_result = {
283
+ "data"=>{"executionErrorWithExtensions"=>nil},
284
+ "errors"=>
285
+ [{"message"=>"Permission Denied!",
286
+ "locations"=>[{"line"=>3, "column"=>7}],
287
+ "path"=>["executionErrorWithExtensions"],
288
+ "extensions"=>{"code"=>"permission_denied"}}]
289
+ }
290
+ assert_equal(expected_result, result)
291
+ end
292
+ end
293
+
275
294
  describe "more than one ExecutionError" do
276
295
  let(:query_string) { %|{ multipleErrorsOnNonNullableField} |}
277
296
  it "the errors are inserted into the errors key and the data is nil even for a NonNullable field " do
@@ -30,6 +30,7 @@ describe GraphQL::Introspection::SchemaType do
30
30
  {"name"=>"deepNonNull"},
31
31
  {"name"=>"error"},
32
32
  {"name"=>"executionError"},
33
+ {"name"=>"executionErrorWithExtensions"},
33
34
  {"name"=>"executionErrorWithOptions"},
34
35
  {"name"=>"favoriteEdible"},
35
36
  {"name"=>"fromSource"},
@@ -1,7 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
  require 'spec_helper'
3
3
 
4
- describe GraphQL::Relay::RelationConnection do
4
+ if MONGO_DETECTED
5
+ require "support/star_trek/data"
6
+ require "support/star_trek/schema"
7
+ end
8
+
9
+ describe GraphQL::Relay::MongoRelationConnection do
10
+ before do
11
+ if !MONGO_DETECTED
12
+ skip("Mongo not detected")
13
+ end
14
+ end
15
+
5
16
  def get_names(result)
6
17
  ships = result["data"]["federation"]["bases"]["edges"]
7
18
  ships.map { |e| e["node"]["name"] }
@@ -358,6 +358,135 @@ describe GraphQL::Schema::Warden do
358
358
  assert_equal false, possible_type_names(res["data"]["LanguageMember"]).include?("Phoneme")
359
359
  end
360
360
 
361
+ it "hides interfaces if all possible types are hidden" do
362
+ sdl = %|
363
+ type Query {
364
+ a: String
365
+ repository: Repository
366
+ }
367
+
368
+ type Repository implements Node {
369
+ id: ID!
370
+ }
371
+
372
+ interface Node {
373
+ id: ID!
374
+ }
375
+ |
376
+
377
+ schema = GraphQL::Schema.from_definition(sdl)
378
+
379
+ query_string = %|
380
+ {
381
+ Node: __type(name: "Node") { name }
382
+ }
383
+ |
384
+
385
+ res = schema.execute(query_string)
386
+ assert res["data"]["Node"]
387
+
388
+ res = schema.execute(query_string, except: ->(m, _) { m.name == "Repository" })
389
+ assert_nil res["data"]["Node"]
390
+ end
391
+
392
+ it "hides unions if all possible types are hidden or its references are hidden" do
393
+ sdl = "
394
+ type Query {
395
+ bag: BagOfThings
396
+ }
397
+
398
+ type A {
399
+ id: ID!
400
+ }
401
+
402
+ type B {
403
+ id: ID!
404
+ }
405
+
406
+ type C {
407
+ id: ID!
408
+ }
409
+
410
+ union BagOfThings = A | B | C
411
+ "
412
+
413
+ schema = GraphQL::Schema.from_definition(sdl)
414
+ schema.orphan_types = []
415
+
416
+ query_string = %|
417
+ {
418
+ BagOfThings: __type(name: "BagOfThings") { name }
419
+ Query: __type(name: "Query") { fields { name } }
420
+ }
421
+ |
422
+
423
+ res = schema.execute(query_string)
424
+ assert res["data"]["BagOfThings"]
425
+ assert_equal ["bag"], res["data"]["Query"]["fields"].map { |f| f["name"] }
426
+
427
+ # Hide the union when all its possible types are gone. This will cause the field to be hidden too.
428
+ res = schema.execute(query_string, except: ->(m, _) { ["A", "B", "C"].include?(m.name) })
429
+ assert_nil res["data"]["BagOfThings"]
430
+ assert_equal [], res["data"]["Query"]["fields"]
431
+
432
+ res = schema.execute(query_string, except: ->(m, _) { m.name == "bag" })
433
+ assert_nil res["data"]["BagOfThings"]
434
+ assert_equal [], res["data"]["Query"]["fields"]
435
+
436
+ # Unreferenced but still visible because orphan type
437
+ schema.orphan_types = [schema.find("BagOfThings")]
438
+ res = schema.execute(query_string, except: ->(m, _) { m.name == "bag" })
439
+ assert res["data"]["BagOfThings"]
440
+ end
441
+
442
+ it "hides interfaces if all possible types are hidden or its references are hidden" do
443
+ sdl = "
444
+ type Query {
445
+ node: Node
446
+ }
447
+
448
+ type A implements Node {
449
+ id: ID!
450
+ }
451
+
452
+ type B implements Node {
453
+ id: ID!
454
+ }
455
+
456
+ type C implements Node {
457
+ id: ID!
458
+ }
459
+
460
+ interface Node {
461
+ id: ID!
462
+ }
463
+ "
464
+
465
+ schema = GraphQL::Schema.from_definition(sdl)
466
+
467
+ query_string = %|
468
+ {
469
+ Node: __type(name: "Node") { name }
470
+ Query: __type(name: "Query") { fields { name } }
471
+ }
472
+ |
473
+
474
+ res = schema.execute(query_string)
475
+ assert res["data"]["Node"]
476
+ assert_equal ["node"], res["data"]["Query"]["fields"].map { |f| f["name"] }
477
+
478
+ # When the possible types are all hidden, hide the interface and fields pointing to it
479
+ res = schema.execute(query_string, except: ->(m, _) { ["A", "B", "C"].include?(m.name) })
480
+ assert_nil res["data"]["Node"]
481
+ assert_equal [], res["data"]["Query"]["fields"]
482
+
483
+ # Even when it's not the return value of a field,
484
+ # still show the interface since it allows code reuse
485
+ res = schema.execute(query_string, except: ->(m, _) { m.name == "node" })
486
+ assert_equal "Node", res["data"]["Node"]["name"]
487
+ assert_equal [], res["data"]["Query"]["fields"]
488
+ end
489
+
361
490
  it "can't be a fragment condition" do
362
491
  query_string = %|
363
492
  {
@@ -6,32 +6,40 @@ describe GraphQL::StaticValidation::FieldsHaveAppropriateSelections do
6
6
  let(:query_string) {"
7
7
  query getCheese {
8
8
  okCheese: cheese(id: 1) { fatContent, similarCheese(source: YAK) { source } }
9
- missingFieldsCheese: cheese(id: 1)
9
+ missingFieldsObject: cheese(id: 1)
10
+ missingFieldsInterface: cheese(id: 1) { selfAsEdible }
10
11
  illegalSelectionCheese: cheese(id: 1) { id { something, ... someFields } }
11
12
  incorrectFragmentSpread: cheese(id: 1) { flavor { ... on String { __typename } } }
12
13
  }
13
14
  "}
14
15
 
15
16
  it "adds errors for selections on scalars" do
16
- assert_equal(3, errors.length)
17
+ assert_equal(4, errors.length)
17
18
 
18
19
  illegal_selection_error = {
19
20
  "message"=>"Selections can't be made on scalars (field 'id' returns Int but has selections [something, someFields])",
20
- "locations"=>[{"line"=>5, "column"=>47}],
21
+ "locations"=>[{"line"=>6, "column"=>47}],
21
22
  "fields"=>["query getCheese", "illegalSelectionCheese", "id"],
22
23
  }
23
24
  assert_includes(errors, illegal_selection_error, "finds illegal selections on scalars")
24
25
 
25
- selection_required_error = {
26
- "message"=>"Objects must have selections (field 'cheese' returns Cheese but has no selections)",
26
+ objects_selection_required_error = {
27
+ "message"=>"Field must have selections (field 'cheese' returns Cheese but has no selections. Did you mean 'cheese { ... }'?)",
27
28
  "locations"=>[{"line"=>4, "column"=>7}],
28
- "fields"=>["query getCheese", "missingFieldsCheese"],
29
+ "fields"=>["query getCheese", "missingFieldsObject"],
30
+ }
31
+ assert_includes(errors, objects_selection_required_error, "finds objects without selections")
32
+
33
+ interfaces_selection_required_error = {
34
+ "message"=>"Field must have selections (field 'selfAsEdible' returns Edible but has no selections. Did you mean 'selfAsEdible { ... }'?)",
35
+ "locations"=>[{"line"=>5, "column"=>47}],
36
+ "fields"=>["query getCheese", "missingFieldsInterface", "selfAsEdible"],
29
37
  }
30
- assert_includes(errors, selection_required_error, "finds objects without selections")
38
+ assert_includes(errors, interfaces_selection_required_error, "finds interfaces without selections")
31
39
 
32
40
  incorrect_fragment_error = {
33
41
  "message"=>"Selections can't be made on scalars (field 'flavor' returns String but has inline fragments [String])",
34
- "locations"=>[{"line"=>6, "column"=>48}],
42
+ "locations"=>[{"line"=>7, "column"=>48}],
35
43
  "fields"=>["query getCheese", "incorrectFragmentSpread", "flavor"],
36
44
  }
37
45
  assert_includes(errors, incorrect_fragment_error, "finds scalar fields with selections")
@@ -43,7 +51,7 @@ describe GraphQL::StaticValidation::FieldsHaveAppropriateSelections do
43
51
  assert_equal(1, errors.length)
44
52
 
45
53
  selections_required_error = {
46
- "message"=> "Objects must have selections (anonymous query returns Query but has no selections)",
54
+ "message"=> "Field must have selections (anonymous query returns Query but has no selections. Did you mean ' { ... }'?)",
47
55
  "locations"=>[{"line"=>1, "column"=>1}],
48
56
  "fields"=>["query"]
49
57
  }
@@ -195,7 +195,7 @@ RUBY
195
195
  end
196
196
 
197
197
  describe "resolve proc to method" do
198
- it "converts @object and @context" do
198
+ it "converts object and context" do
199
199
  old = %{
200
200
  field :firstName, !types.String do
201
201
  resolve ->(obj, arg, ctx) {
@@ -211,11 +211,11 @@ RUBY
211
211
  field :first_name, String, null: false
212
212
 
213
213
  def first_name
214
- @context.something
214
+ context.something
215
215
  other_ctx # test combined identifiers
216
216
 
217
- @object[@context] + @object
218
- @object.given_name
217
+ object[context] + object
218
+ object.given_name
219
219
  end
220
220
  }
221
221
  assert_equal new, upgrade(old)
@@ -233,7 +233,7 @@ RUBY
233
233
  field :first_name, String, null: false
234
234
 
235
235
  def first_name
236
- @object.given_name
236
+ object.given_name
237
237
  end
238
238
  }
239
239
  assert_equal new, upgrade(old)
@@ -27,6 +27,22 @@ require "pry"
27
27
  require "minitest/autorun"
28
28
  require "minitest/focus"
29
29
  require "minitest/reporters"
30
+
31
+ MONGO_DETECTED = begin
32
+ require "mongo"
33
+ Mongo::Client.new('mongodb://127.0.0.1:27017/graphql_ruby_test',
34
+ connect_timeout: 1,
35
+ socket_timeout: 1,
36
+ server_selection_timeout: 1,
37
+ logger: Logger.new(nil)
38
+ )
39
+ .database
40
+ .collections
41
+ rescue StandardError, LoadError => err # rubocop:disable Lint/UselessAssignment
42
+ # puts err.message, err.backtrace
43
+ false
44
+ end
45
+
30
46
  Minitest::Reporters.use! Minitest::Reporters::DefaultReporter.new(color: true)
31
47
 
32
48
  Minitest::Spec.make_my_diffs_pretty!
@@ -62,9 +78,12 @@ NO_OP_RESOLVE_TYPE = ->(type, obj, ctx) {
62
78
 
63
79
  # Load support files
64
80
  Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each do |f|
81
+ # These require mongodb in order to run,
82
+ # so only load them in the specific tests that require them.
83
+ next if f.include?("star_trek")
84
+
65
85
  unless rails_should_be_installed?
66
86
  next if f.end_with?('star_wars/data.rb')
67
- next if f.end_with?('star_trek/data.rb')
68
87
  next if f.end_with?('base_generator_test.rb')
69
88
  end
70
89
  require f
@@ -381,6 +381,13 @@ module Dummy
381
381
  }
382
382
  end
383
383
 
384
+ field :executionErrorWithExtensions do
385
+ type GraphQL::INT_TYPE
386
+ resolve ->(t, a, c) {
387
+ GraphQL::ExecutionError.new("Permission Denied!", extensions: { "code" => "permission_denied" })
388
+ }
389
+ end
390
+
384
391
  # To test possibly-null fields
385
392
  field :maybeNull, MaybeNullType do
386
393
  resolve ->(t, a, c) { OpenStruct.new(cheese: nil) }
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.8.0
4
+ version: 1.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-17 00:00:00.000000000 Z
11
+ date: 2018-06-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -304,20 +304,6 @@ dependencies:
304
304
  - - ">="
305
305
  - !ruby/object:Gem::Version
306
306
  version: '0'
307
- - !ruby/object:Gem::Dependency
308
- name: algoliasearch-jekyll
309
- requirement: !ruby/object:Gem::Requirement
310
- requirements:
311
- - - ">="
312
- - !ruby/object:Gem::Version
313
- version: '0'
314
- type: :development
315
- prerelease: false
316
- version_requirements: !ruby/object:Gem::Requirement
317
- requirements:
318
- - - ">="
319
- - !ruby/object:Gem::Version
320
- version: '0'
321
307
  description: A plain-Ruby implementation of GraphQL.
322
308
  email:
323
309
  - rdmosolgo@gmail.com
@@ -433,12 +419,9 @@ files:
433
419
  - lib/graphql/introspection/field_type.rb
434
420
  - lib/graphql/introspection/input_value_type.rb
435
421
  - lib/graphql/introspection/introspection_query.rb
436
- - lib/graphql/introspection/schema_field.rb
437
422
  - lib/graphql/introspection/schema_type.rb
438
- - lib/graphql/introspection/type_by_name_field.rb
439
423
  - lib/graphql/introspection/type_kind_enum.rb
440
424
  - lib/graphql/introspection/type_type.rb
441
- - lib/graphql/introspection/typename_field.rb
442
425
  - lib/graphql/invalid_name_error.rb
443
426
  - lib/graphql/invalid_null_error.rb
444
427
  - lib/graphql/language.rb
@@ -1005,7 +988,6 @@ files:
1005
988
  - spec/graphql/introspection/input_value_type_spec.rb
1006
989
  - spec/graphql/introspection/introspection_query_spec.rb
1007
990
  - spec/graphql/introspection/schema_type_spec.rb
1008
- - spec/graphql/introspection/type_by_name_field_spec.rb
1009
991
  - spec/graphql/introspection/type_type_spec.rb
1010
992
  - spec/graphql/language/block_string_spec.rb
1011
993
  - spec/graphql/language/definition_slice_spec.rb
@@ -1553,7 +1535,6 @@ test_files:
1553
1535
  - spec/graphql/introspection/input_value_type_spec.rb
1554
1536
  - spec/graphql/introspection/introspection_query_spec.rb
1555
1537
  - spec/graphql/introspection/schema_type_spec.rb
1556
- - spec/graphql/introspection/type_by_name_field_spec.rb
1557
1538
  - spec/graphql/introspection/type_type_spec.rb
1558
1539
  - spec/graphql/language/block_string_spec.rb
1559
1540
  - spec/graphql/language/definition_slice_spec.rb
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Introspection
4
- SchemaField = GraphQL::Field.define do
5
- name("__schema")
6
- description("This GraphQL schema")
7
- type(GraphQL::Schema::LateBoundType.new("__Schema").to_non_null_type)
8
- resolve ->(o, a, ctx) {
9
- # Apply wrapping manually since this field isn't wrapped by instrumentation
10
- schema = ctx.query.schema
11
- schema_type = schema.introspection_system.schema_type
12
- schema_type.metadata[:type_class].new(schema, ctx.query.context)
13
- }
14
- end
15
- end
16
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Introspection
4
- TypeByNameField = GraphQL::Field.define do
5
- name("__type")
6
- description("A type in the GraphQL system")
7
- introspection true
8
- type(GraphQL::Schema::LateBoundType.new("__Type"))
9
- argument :name, !types.String
10
- resolve ->(o, args, ctx) {
11
- type = ctx.warden.get_type(args["name"])
12
- if type
13
- # Apply wrapping manually since this field isn't wrapped by instrumentation
14
- type_type = ctx.schema.introspection_system.type_type
15
- type_type.metadata[:type_class].new(type, ctx)
16
- else
17
- nil
18
- end
19
- }
20
- end
21
- end
22
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Introspection
4
- TypenameField = GraphQL::Field.define do
5
- name "__typename"
6
- description "The name of this type"
7
- type -> { !GraphQL::STRING_TYPE }
8
- introspection true
9
- resolve ->(obj, a, ctx) { ctx.irep_node.owner_type }
10
- end
11
- end
12
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
- require "spec_helper"
3
-
4
- describe GraphQL::Introspection::TypeByNameField do
5
- describe "after instrumentation" do
6
- # Just make sure it returns a new object, not the original field
7
- class DupInstrumenter
8
- def self.instrument(t, f)
9
- f.redefine {
10
- resolve ->(o, a, c) { :no_op }
11
- }
12
- end
13
- end
14
-
15
- class ArgAnalyzer
16
- def call(_, _, node)
17
- if node.ast_node.is_a?(GraphQL::Language::Nodes::Field)
18
- node.arguments
19
- end
20
- end
21
- end
22
-
23
- let(:instrumented_schema) {
24
- # This was probably assigned earlier in the test suite, but to simulate an application, clear it.
25
- GraphQL::Introspection::TypeByNameField.arguments_class = nil
26
-
27
- Dummy::Schema.redefine {
28
- instrument(:field, DupInstrumenter)
29
- query_analyzer(ArgAnalyzer.new)
30
- }
31
- }
32
-
33
- it "still works with __type" do
34
- res = instrumented_schema.execute("{ __type(name: \"X\") { name } }")
35
- assert_equal({"data"=>{"__type"=>nil}}, res)
36
- end
37
- end
38
- end