graphql 1.8.0 → 1.8.1

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.
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