graphql 1.12.20 → 1.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +3 -1
  3. data/lib/generators/graphql/install_generator.rb +9 -2
  4. data/lib/generators/graphql/mutation_generator.rb +1 -1
  5. data/lib/generators/graphql/object_generator.rb +2 -1
  6. data/lib/generators/graphql/templates/schema.erb +2 -2
  7. data/lib/generators/graphql/type_generator.rb +0 -1
  8. data/lib/graphql/analysis/ast/field_usage.rb +2 -2
  9. data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
  10. data/lib/graphql/analysis/ast/visitor.rb +4 -4
  11. data/lib/graphql/backtrace/table.rb +1 -1
  12. data/lib/graphql/base_type.rb +4 -2
  13. data/lib/graphql/boolean_type.rb +1 -1
  14. data/lib/graphql/dataloader.rb +55 -22
  15. data/lib/graphql/directive/deprecated_directive.rb +1 -1
  16. data/lib/graphql/directive/include_directive.rb +1 -1
  17. data/lib/graphql/directive/skip_directive.rb +1 -1
  18. data/lib/graphql/directive.rb +0 -4
  19. data/lib/graphql/enum_type.rb +5 -1
  20. data/lib/graphql/execution/errors.rb +1 -0
  21. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  22. data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -2
  23. data/lib/graphql/execution/interpreter/runtime.rb +30 -18
  24. data/lib/graphql/execution/lookahead.rb +2 -2
  25. data/lib/graphql/execution/multiplex.rb +4 -1
  26. data/lib/graphql/float_type.rb +1 -1
  27. data/lib/graphql/id_type.rb +1 -1
  28. data/lib/graphql/int_type.rb +1 -1
  29. data/lib/graphql/introspection/directive_type.rb +1 -1
  30. data/lib/graphql/introspection/entry_points.rb +2 -2
  31. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  32. data/lib/graphql/introspection/field_type.rb +2 -2
  33. data/lib/graphql/introspection/input_value_type.rb +4 -4
  34. data/lib/graphql/introspection/schema_type.rb +2 -2
  35. data/lib/graphql/introspection/type_type.rb +10 -10
  36. data/lib/graphql/language/block_string.rb +2 -6
  37. data/lib/graphql/language/document_from_schema_definition.rb +4 -2
  38. data/lib/graphql/language/lexer.rb +0 -3
  39. data/lib/graphql/language/lexer.rl +0 -4
  40. data/lib/graphql/language/nodes.rb +12 -2
  41. data/lib/graphql/language/parser.rb +442 -434
  42. data/lib/graphql/language/parser.y +5 -4
  43. data/lib/graphql/language/printer.rb +6 -1
  44. data/lib/graphql/language/sanitized_printer.rb +5 -5
  45. data/lib/graphql/language/token.rb +0 -4
  46. data/lib/graphql/name_validator.rb +0 -4
  47. data/lib/graphql/query/arguments.rb +1 -1
  48. data/lib/graphql/query/arguments_cache.rb +1 -1
  49. data/lib/graphql/query/context.rb +15 -2
  50. data/lib/graphql/query/literal_input.rb +1 -1
  51. data/lib/graphql/query/null_context.rb +12 -7
  52. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  53. data/lib/graphql/query/variables.rb +5 -1
  54. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  55. data/lib/graphql/relay/global_id_resolve.rb +1 -1
  56. data/lib/graphql/relay/page_info.rb +1 -1
  57. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  58. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  59. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  60. data/lib/graphql/rubocop.rb +4 -0
  61. data/lib/graphql/schema/addition.rb +37 -28
  62. data/lib/graphql/schema/argument.rb +8 -6
  63. data/lib/graphql/schema/build_from_definition.rb +5 -5
  64. data/lib/graphql/schema/directive/feature.rb +1 -1
  65. data/lib/graphql/schema/directive/flagged.rb +2 -2
  66. data/lib/graphql/schema/directive/include.rb +1 -1
  67. data/lib/graphql/schema/directive/skip.rb +1 -1
  68. data/lib/graphql/schema/directive/transform.rb +1 -1
  69. data/lib/graphql/schema/directive.rb +7 -3
  70. data/lib/graphql/schema/enum.rb +60 -10
  71. data/lib/graphql/schema/enum_value.rb +6 -0
  72. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  73. data/lib/graphql/schema/field.rb +130 -39
  74. data/lib/graphql/schema/field_extension.rb +52 -2
  75. data/lib/graphql/schema/find_inherited_value.rb +1 -0
  76. data/lib/graphql/schema/finder.rb +5 -5
  77. data/lib/graphql/schema/input_object.rb +8 -5
  78. data/lib/graphql/schema/interface.rb +11 -20
  79. data/lib/graphql/schema/introspection_system.rb +1 -1
  80. data/lib/graphql/schema/list.rb +3 -1
  81. data/lib/graphql/schema/member/accepts_definition.rb +15 -3
  82. data/lib/graphql/schema/member/build_type.rb +0 -4
  83. data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
  84. data/lib/graphql/schema/member/has_arguments.rb +55 -13
  85. data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
  86. data/lib/graphql/schema/member/has_fields.rb +76 -18
  87. data/lib/graphql/schema/member/has_interfaces.rb +90 -0
  88. data/lib/graphql/schema/member.rb +1 -0
  89. data/lib/graphql/schema/non_null.rb +3 -1
  90. data/lib/graphql/schema/object.rb +10 -75
  91. data/lib/graphql/schema/printer.rb +1 -1
  92. data/lib/graphql/schema/relay_classic_mutation.rb +29 -3
  93. data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
  94. data/lib/graphql/schema/resolver.rb +19 -5
  95. data/lib/graphql/schema/scalar.rb +2 -0
  96. data/lib/graphql/schema/subscription.rb +11 -1
  97. data/lib/graphql/schema/traversal.rb +1 -1
  98. data/lib/graphql/schema/type_expression.rb +1 -1
  99. data/lib/graphql/schema/type_membership.rb +18 -4
  100. data/lib/graphql/schema/union.rb +8 -1
  101. data/lib/graphql/schema/validator/format_validator.rb +3 -5
  102. data/lib/graphql/schema/validator/numericality_validator.rb +1 -0
  103. data/lib/graphql/schema/validator.rb +4 -7
  104. data/lib/graphql/schema/warden.rb +116 -52
  105. data/lib/graphql/schema.rb +106 -22
  106. data/lib/graphql/static_validation/base_visitor.rb +5 -5
  107. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  108. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  109. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  110. data/lib/graphql/static_validation/rules/fields_will_merge.rb +15 -8
  111. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -1
  112. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
  113. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +7 -7
  114. data/lib/graphql/string_type.rb +1 -1
  115. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -4
  116. data/lib/graphql/subscriptions/event.rb +20 -12
  117. data/lib/graphql/subscriptions.rb +17 -19
  118. data/lib/graphql/types/relay/connection_behaviors.rb +26 -9
  119. data/lib/graphql/types/relay/default_relay.rb +5 -1
  120. data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
  121. data/lib/graphql/types/relay/has_node_field.rb +1 -1
  122. data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
  123. data/lib/graphql/version.rb +1 -1
  124. data/lib/graphql.rb +10 -32
  125. metadata +10 -5
@@ -56,7 +56,7 @@ module GraphQL
56
56
  def self.print_introspection_schema
57
57
  query_root = Class.new(GraphQL::Schema::Object) do
58
58
  graphql_name "Root"
59
- field :throwaway_field, String, null: true
59
+ field :throwaway_field, String
60
60
  end
61
61
  schema = Class.new(GraphQL::Schema) { query(query_root) }
62
62
 
@@ -22,7 +22,7 @@ module GraphQL
22
22
  #
23
23
  class RelayClassicMutation < GraphQL::Schema::Mutation
24
24
  # The payload should always include this field
25
- field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.", null: true)
25
+ field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.")
26
26
  # Relay classic default:
27
27
  null(true)
28
28
 
@@ -81,6 +81,31 @@ module GraphQL
81
81
  end
82
82
 
83
83
  class << self
84
+
85
+ # Also apply this argument to the input type:
86
+ def argument(*args, **kwargs, &block)
87
+ it = input_type # make sure any inherited arguments are already added to it
88
+ arg = super
89
+
90
+ # This definition might be overriding something inherited;
91
+ # if it is, remove the inherited definition so it's not confused at runtime as having multiple definitions
92
+ prev_args = it.own_arguments[arg.graphql_name]
93
+ case prev_args
94
+ when GraphQL::Schema::Argument
95
+ if prev_args.owner != self
96
+ it.own_arguments.delete(arg.graphql_name)
97
+ end
98
+ when Array
99
+ prev_args.reject! { |a| a.owner != self }
100
+ if prev_args.empty?
101
+ it.own_arguments.delete(arg.graphql_name)
102
+ end
103
+ end
104
+
105
+ it.add_argument(arg)
106
+ arg
107
+ end
108
+
84
109
  # The base class for generated input object types
85
110
  # @param new_class [Class] The base class to use for generating input object definitions
86
111
  # @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject})
@@ -115,14 +140,15 @@ module GraphQL
115
140
  # To customize how input objects are generated, override this method
116
141
  # @return [Class] a subclass of {.input_object_class}
117
142
  def generate_input_type
118
- mutation_args = arguments
143
+ mutation_args = all_argument_definitions
119
144
  mutation_name = graphql_name
120
145
  mutation_class = self
121
146
  Class.new(input_object_class) do
122
147
  graphql_name("#{mutation_name}Input")
123
148
  description("Autogenerated input type of #{mutation_name}")
124
149
  mutation(mutation_class)
125
- mutation_args.each do |_name, arg|
150
+ # these might be inherited:
151
+ mutation_args.each do |arg|
126
152
  add_argument(arg)
127
153
  end
128
154
  argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
@@ -38,6 +38,9 @@ module GraphQL
38
38
  # @return [Class]
39
39
  def object_class(new_class = nil)
40
40
  if new_class
41
+ if defined?(@payload_type)
42
+ raise "Can't configure `object_class(...)` after the payload type has already been initialized. Move this configuration higher up the class definition."
43
+ end
41
44
  @object_class = new_class
42
45
  else
43
46
  @object_class || find_inherited_value(:object_class, GraphQL::Schema::Object)
@@ -46,6 +49,28 @@ module GraphQL
46
49
 
47
50
  NO_INTERFACES = [].freeze
48
51
 
52
+ def field(*args, **kwargs, &block)
53
+ pt = payload_type # make sure it's initialized with any inherited fields
54
+ field_defn = super
55
+
56
+ # Remove any inherited fields to avoid false conflicts at runtime
57
+ prev_fields = pt.own_fields[field_defn.graphql_name]
58
+ case prev_fields
59
+ when GraphQL::Schema::Field
60
+ if prev_fields.owner != self
61
+ pt.own_fields.delete(field_defn.graphql_name)
62
+ end
63
+ when Array
64
+ prev_fields.reject! { |f| f.owner != self }
65
+ if prev_fields.empty?
66
+ pt.own_fields.delete(field_defn.graphql_name)
67
+ end
68
+ end
69
+
70
+ pt.add_field(field_defn, method_conflict_warning: false)
71
+ field_defn
72
+ end
73
+
49
74
  private
50
75
 
51
76
  # Build a subclass of {.object_class} based on `self`.
@@ -53,11 +78,11 @@ module GraphQL
53
78
  # Override this hook to customize return type generation.
54
79
  def generate_payload_type
55
80
  resolver_name = graphql_name
56
- resolver_fields = fields
81
+ resolver_fields = all_field_definitions
57
82
  Class.new(object_class) do
58
83
  graphql_name("#{resolver_name}Payload")
59
84
  description("Autogenerated return type of #{resolver_name}")
60
- resolver_fields.each do |name, f|
85
+ resolver_fields.each do |f|
61
86
  # Reattach the already-defined field here
62
87
  # (The field's `.owner` will still point to the mutation, not the object type, I think)
63
88
  # Don't re-warn about a method conflict. Since this type is generated, it should be fixed in the resolver instead.
@@ -37,7 +37,7 @@ module GraphQL
37
37
  @field = field
38
38
  # Since this hash is constantly rebuilt, cache it for this call
39
39
  @arguments_by_keyword = {}
40
- self.class.arguments.each do |name, arg|
40
+ self.class.arguments(context).each do |name, arg|
41
41
  @arguments_by_keyword[arg.keyword] = arg
42
42
  end
43
43
  @prepared_arguments = nil
@@ -145,7 +145,7 @@ module GraphQL
145
145
  # @raise [GraphQL::UnauthorizedError] To signal an authorization failure
146
146
  # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.)
147
147
  def authorized?(**inputs)
148
- self.class.arguments.each_value do |argument|
148
+ self.class.arguments(context).each_value do |argument|
149
149
  arg_keyword = argument.keyword
150
150
  if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
151
151
  arg_auth, err = argument.authorized?(self, arg_value, context)
@@ -199,8 +199,8 @@ module GraphQL
199
199
  end
200
200
  end
201
201
 
202
- def get_argument(name)
203
- self.class.get_argument(name)
202
+ def get_argument(name, context = GraphQL::Query::NullContext)
203
+ self.class.get_argument(name, context)
204
204
  end
205
205
 
206
206
  class << self
@@ -305,13 +305,27 @@ module GraphQL
305
305
  end
306
306
 
307
307
  def field_options
308
+
309
+ all_args = {}
310
+ all_argument_definitions.each do |arg|
311
+ if (prev_entry = all_args[arg.graphql_name])
312
+ if prev_entry.is_a?(Array)
313
+ prev_entry << arg
314
+ else
315
+ all_args[arg.graphql_name] = [prev_entry, arg]
316
+ end
317
+ else
318
+ all_args[arg.graphql_name] = arg
319
+ end
320
+ end
321
+
308
322
  field_opts = {
309
323
  type: type_expr,
310
324
  description: description,
311
325
  extras: extras,
312
326
  resolver_method: :resolve_with_support,
313
327
  resolver_class: self,
314
- arguments: arguments,
328
+ arguments: all_args,
315
329
  null: null,
316
330
  complexity: complexity,
317
331
  broadcastable: broadcastable?,
@@ -14,6 +14,8 @@ module GraphQL
14
14
  val
15
15
  end
16
16
 
17
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
18
+
17
19
  def to_graphql
18
20
  type_defn = GraphQL::ScalarType.new
19
21
  type_defn.name = graphql_name
@@ -103,10 +103,12 @@ module GraphQL
103
103
  # Call this method to provide a new subscription_scope; OR
104
104
  # call it without an argument to get the subscription_scope
105
105
  # @param new_scope [Symbol]
106
+ # @param optional [Boolean] If true, then don't require `scope:` to be provided to updates to this subscription.
106
107
  # @return [Symbol]
107
- def self.subscription_scope(new_scope = READING_SCOPE)
108
+ def self.subscription_scope(new_scope = READING_SCOPE, optional: false)
108
109
  if new_scope != READING_SCOPE
109
110
  @subscription_scope = new_scope
111
+ @subscription_scope_optional = optional
110
112
  elsif defined?(@subscription_scope)
111
113
  @subscription_scope
112
114
  else
@@ -114,6 +116,14 @@ module GraphQL
114
116
  end
115
117
  end
116
118
 
119
+ def self.subscription_scope_optional?
120
+ if defined?(@subscription_scope_optional)
121
+ @subscription_scope_optional
122
+ else
123
+ find_inherited_value(:subscription_scope_optional, false)
124
+ end
125
+ end
126
+
117
127
  # This is called during initial subscription to get a "name" for this subscription.
118
128
  # Later, when `.trigger` is called, this will be called again to build another "name".
119
129
  # Any subscribers with matching topic will begin the update flow.
@@ -173,7 +173,7 @@ Some late-bound types couldn't be resolved:
173
173
  end
174
174
  when Class
175
175
  if member.respond_to?(:graphql_definition)
176
- graphql_member = member.graphql_definition
176
+ graphql_member = member.graphql_definition(silence_deprecation_warning: true)
177
177
  visit(schema, graphql_member, context_description)
178
178
  else
179
179
  raise GraphQL::Schema::InvalidTypeError.new("Unexpected traversal member: #{member} (#{member.class.name})")
@@ -11,7 +11,7 @@ module GraphQL
11
11
  def self.build_type(type_owner, ast_node)
12
12
  case ast_node
13
13
  when GraphQL::Language::Nodes::TypeName
14
- type_owner.get_type(ast_node.name)
14
+ type_owner.get_type(ast_node.name) # rubocop:disable Development/ContextIsPassedCop -- this is a `context` or `warden`, it's already query-aware
15
15
  when GraphQL::Language::Nodes::NonNullType
16
16
  ast_inner_type = ast_node.of_type
17
17
  inner_type = build_type(type_owner, ast_inner_type)
@@ -4,8 +4,6 @@ module GraphQL
4
4
  class Schema
5
5
  # This class joins an object type to an abstract type (interface or union) of which
6
6
  # it is a member.
7
- #
8
- # TODO: Not yet implemented for interfaces.
9
7
  class TypeMembership
10
8
  # @return [Class<GraphQL::Schema::Object>]
11
9
  attr_accessor :object_type
@@ -26,9 +24,25 @@ module GraphQL
26
24
  end
27
25
 
28
26
  # @return [Boolean] if false, {#object_type} will be treated as _not_ a member of {#abstract_type}
29
- def visible?(_ctx)
30
- true
27
+ def visible?(ctx)
28
+ warden = Warden.from_context(ctx)
29
+ (@object_type.respond_to?(:visible?) ? warden.visible_type?(@object_type, ctx) : true) &&
30
+ (@abstract_type.respond_to?(:visible?) ? warden.visible_type?(@abstract_type, ctx) : true)
31
31
  end
32
+
33
+ def graphql_name
34
+ "#{@object_type.graphql_name}.#{@abstract_type.kind.interface? ? "implements" : "belongsTo" }.#{@abstract_type.graphql_name}"
35
+ end
36
+
37
+ def path
38
+ graphql_name
39
+ end
40
+
41
+ def inspect
42
+ "#<#{self.class} #{@object_type.inspect} => #{@abstract_type.inspect}>"
43
+ end
44
+
45
+ alias :type_class :itself
32
46
  end
33
47
  end
34
48
  end
@@ -19,8 +19,9 @@ module GraphQL
19
19
  end
20
20
  else
21
21
  visible_types = []
22
+ warden = Warden.from_context(context)
22
23
  type_memberships.each do |type_membership|
23
- if type_membership.visible?(context)
24
+ if warden.visible_type_membership?(type_membership, context)
24
25
  visible_types << type_membership.object_type
25
26
  end
26
27
  end
@@ -28,6 +29,12 @@ module GraphQL
28
29
  end
29
30
  end
30
31
 
32
+ def all_possible_types
33
+ type_memberships.map(&:object_type)
34
+ end
35
+
36
+ prepend GraphQL::Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
37
+
31
38
  def to_graphql
32
39
  type_defn = GraphQL::UnionType.new
33
40
  type_defn.name = graphql_name
@@ -18,10 +18,6 @@ module GraphQL
18
18
  # # It's pretty hard to come up with a legitimate use case for `without:`
19
19
  #
20
20
  class FormatValidator < Validator
21
- if !String.method_defined?(:match?)
22
- using GraphQL::StringMatchBackport
23
- end
24
-
25
21
  # @param with [RegExp, nil]
26
22
  # @param without [Regexp, nil]
27
23
  # @param message [String]
@@ -38,7 +34,9 @@ module GraphQL
38
34
  end
39
35
 
40
36
  def validate(_object, _context, value)
41
- if value.nil? ||
37
+ if permitted_empty_value?(value)
38
+ # Do nothing
39
+ elsif value.nil? ||
42
40
  (@with_pattern && !value.match?(@with_pattern)) ||
43
41
  (@without_pattern && value.match?(@without_pattern))
44
42
  @message
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module GraphQL
2
3
  class Schema
3
4
  class Validator
@@ -48,16 +48,13 @@ module GraphQL
48
48
  EMPTY_ARRAY
49
49
  else
50
50
  validates_hash = validates_hash.dup
51
- allow_null = validates_hash.delete(:allow_null)
52
- allow_blank = validates_hash.delete(:allow_blank)
53
51
 
54
- # This could be {...}.compact on Ruby 2.4+
55
52
  default_options = {}
56
- if !allow_null.nil?
57
- default_options[:allow_null] = allow_null
53
+ if validates_hash[:allow_null]
54
+ default_options[:allow_null] = validates_hash.delete(:allow_null)
58
55
  end
59
- if !allow_blank.nil?
60
- default_options[:allow_blank] = allow_blank
56
+ if validates_hash[:allow_blank]
57
+ default_options[:allow_blank] = validates_hash.delete(:allow_blank)
61
58
  end
62
59
 
63
60
  # allow_nil or allow_blank are the _only_ validations:
@@ -37,6 +37,50 @@ module GraphQL
37
37
  #
38
38
  # @api private
39
39
  class Warden
40
+ def self.from_context(context)
41
+ (context.respond_to?(:warden) && context.warden) || PassThruWarden
42
+ end
43
+
44
+ # @param visibility_method [Symbol] a Warden method to call for this entry
45
+ # @param entry [Object, Array<Object>] One or more definitions for a given name in a GraphQL Schema
46
+ # @param context [GraphQL::Query::Context]
47
+ # @param warden [Warden]
48
+ # @return [Object] `entry` or one of `entry`'s items if exactly one of them is visible for this context
49
+ # @return [nil] If neither `entry` nor any of `entry`'s items are visible for this context
50
+ def self.visible_entry?(visibility_method, entry, context, warden = Warden.from_context(context))
51
+ if entry.is_a?(Array)
52
+ visible_item = nil
53
+ entry.each do |item|
54
+ if warden.public_send(visibility_method, item, context)
55
+ if visible_item.nil?
56
+ visible_item = item
57
+ else
58
+ raise Schema::DuplicateNamesError, "Found two visible definitions for `#{item.path}`: #{visible_item.inspect}, #{item.inspect}"
59
+ end
60
+ end
61
+ end
62
+ visible_item
63
+ elsif warden.public_send(visibility_method, entry, context)
64
+ entry
65
+ else
66
+ nil
67
+ end
68
+ end
69
+
70
+ # This is used when a caller provides a Hash for context.
71
+ # We want to call the schema's hooks, but we don't have a full-blown warden.
72
+ # The `context` arguments to these methods exist purely to simplify the code that
73
+ # calls methods on this object, so it will have everything it needs.
74
+ class PassThruWarden
75
+ class << self
76
+ def visible_field?(field, ctx); field.visible?(ctx); end
77
+ def visible_argument?(arg, ctx); arg.visible?(ctx); end
78
+ def visible_type?(type, ctx); type.visible?(ctx); end
79
+ def visible_enum_value?(ev, ctx); ev.visible?(ctx); end
80
+ def visible_type_membership?(tm, ctx); tm.visible?(ctx); end
81
+ end
82
+ end
83
+
40
84
  # @param filter [<#call(member)>] Objects are hidden when `.call(member, ctx)` returns true
41
85
  # @param context [GraphQL::Query::Context]
42
86
  # @param schema [GraphQL::Schema]
@@ -54,8 +98,8 @@ module GraphQL
54
98
  def types
55
99
  @types ||= begin
56
100
  vis_types = {}
57
- @schema.types.each do |n, t|
58
- if visible_type?(t)
101
+ @schema.types(@context).each do |n, t|
102
+ if visible_and_reachable_type?(t)
59
103
  vis_types[n] = t
60
104
  end
61
105
  end
@@ -66,8 +110,8 @@ module GraphQL
66
110
  # @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)
67
111
  def get_type(type_name)
68
112
  @visible_types ||= read_through do |name|
69
- type_defn = @schema.get_type(name)
70
- if type_defn && visible_type?(type_defn)
113
+ type_defn = @schema.get_type(name, @context)
114
+ if type_defn && visible_and_reachable_type?(type_defn)
71
115
  type_defn
72
116
  else
73
117
  nil
@@ -84,7 +128,7 @@ module GraphQL
84
128
 
85
129
  # @return Boolean True if the type is visible and reachable in the schema
86
130
  def reachable_type?(type_name)
87
- type = get_type(type_name)
131
+ type = get_type(type_name) # rubocop:disable Development/ContextIsPassedCop -- `self` is query-aware
88
132
  type && reachable_type_set.include?(type)
89
133
  end
90
134
 
@@ -92,8 +136,8 @@ module GraphQL
92
136
  def get_field(parent_type, field_name)
93
137
  @visible_parent_fields ||= read_through do |type|
94
138
  read_through do |f_name|
95
- field_defn = @schema.get_field(type, f_name)
96
- if field_defn && visible_field?(type, field_defn)
139
+ field_defn = @schema.get_field(type, f_name, @context)
140
+ if field_defn && visible_field?(field_defn, nil, type)
97
141
  field_defn
98
142
  else
99
143
  nil
@@ -106,15 +150,15 @@ module GraphQL
106
150
 
107
151
  # @return [GraphQL::Argument, nil] The argument named `argument_name` on `parent_type`, if it exists and is visible
108
152
  def get_argument(parent_type, argument_name)
109
- argument = parent_type.get_argument(argument_name)
110
- return argument if argument && visible_argument?(argument)
153
+ argument = parent_type.get_argument(argument_name, @context)
154
+ return argument if argument && visible_argument?(argument, @context)
111
155
  end
112
156
 
113
157
  # @return [Array<GraphQL::BaseType>] The types which may be member of `type_defn`
114
158
  def possible_types(type_defn)
115
159
  @visible_possible_types ||= read_through { |type_defn|
116
160
  pt = @schema.possible_types(type_defn, @context)
117
- pt.select { |t| visible_type?(t) }
161
+ pt.select { |t| visible_and_reachable_type?(t) }
118
162
  }
119
163
  @visible_possible_types[type_defn]
120
164
  end
@@ -122,26 +166,31 @@ module GraphQL
122
166
  # @param type_defn [GraphQL::ObjectType, GraphQL::InterfaceType]
123
167
  # @return [Array<GraphQL::Field>] Fields on `type_defn`
124
168
  def fields(type_defn)
125
- @visible_fields ||= read_through { |t| @schema.get_fields(t).each_value.select { |f| visible_field?(t, f) } }
169
+ @visible_fields ||= read_through { |t| @schema.get_fields(t, @context).values }
126
170
  @visible_fields[type_defn]
127
171
  end
128
172
 
129
173
  # @param argument_owner [GraphQL::Field, GraphQL::InputObjectType]
130
174
  # @return [Array<GraphQL::Argument>] Visible arguments on `argument_owner`
131
175
  def arguments(argument_owner)
132
- @visible_arguments ||= read_through { |o| o.arguments.each_value.select { |a| visible_argument?(a) } }
176
+ @visible_arguments ||= read_through { |o| o.arguments(@context).each_value.select { |a| visible_argument?(a) } }
133
177
  @visible_arguments[argument_owner]
134
178
  end
135
179
 
136
180
  # @return [Array<GraphQL::EnumType::EnumValue>] Visible members of `enum_defn`
137
181
  def enum_values(enum_defn)
138
- @visible_enum_values ||= read_through { |e| e.values.each_value.select { |enum_value_defn| visible?(enum_value_defn) } }
139
- @visible_enum_values[enum_defn]
182
+ @visible_enum_arrays ||= read_through { |e| e.enum_values(@context) }
183
+ @visible_enum_arrays[enum_defn]
184
+ end
185
+
186
+ def visible_enum_value?(enum_value, _ctx = nil)
187
+ @visible_enum_values ||= read_through { |ev| visible?(ev) }
188
+ @visible_enum_values[enum_value]
140
189
  end
141
190
 
142
191
  # @return [Array<GraphQL::InterfaceType>] Visible interfaces implemented by `obj_type`
143
192
  def interfaces(obj_type)
144
- @visible_interfaces ||= read_through { |t| t.interfaces(@context).select { |i| visible?(i) } }
193
+ @visible_interfaces ||= read_through { |t| t.interfaces(@context).select { |i| visible_type?(i) } }
145
194
  @visible_interfaces[obj_type]
146
195
  end
147
196
 
@@ -158,25 +207,52 @@ module GraphQL
158
207
  end
159
208
  end
160
209
 
161
- private
162
-
163
- def union_memberships(obj_type)
164
- @unions ||= read_through { |obj_type| @schema.union_memberships(obj_type).select { |u| visible?(u) } }
165
- @unions[obj_type]
166
- end
167
-
168
- def visible_argument?(arg_defn)
169
- visible?(arg_defn) && visible_type?(arg_defn.type.unwrap)
170
- end
171
-
172
- def visible_field?(owner_type, field_defn)
210
+ # @param owner [Class, Module] If provided, confirm that field has the given owner.
211
+ def visible_field?(field_defn, _ctx = nil, owner = field_defn.owner)
173
212
  # This field is visible in its own right
174
213
  visible?(field_defn) &&
175
214
  # This field's return type is visible
176
- visible_type?(field_defn.type.unwrap) &&
215
+ visible_and_reachable_type?(field_defn.type.unwrap) &&
177
216
  # This field is either defined on this object type,
178
217
  # or the interface it's inherited from is also visible
179
- ((field_defn.respond_to?(:owner) && field_defn.owner == owner_type) || field_on_visible_interface?(field_defn, owner_type))
218
+ ((field_defn.respond_to?(:owner) && field_defn.owner == owner) || field_on_visible_interface?(field_defn, owner))
219
+ end
220
+
221
+ def visible_argument?(arg_defn, _ctx = nil)
222
+ visible?(arg_defn) && visible_and_reachable_type?(arg_defn.type.unwrap)
223
+ end
224
+
225
+ def visible_type?(type_defn, _ctx = nil)
226
+ @type_visibility ||= read_through { |type_defn| visible?(type_defn) }
227
+ @type_visibility[type_defn]
228
+ end
229
+
230
+ def visible_type_membership?(type_membership, _ctx = nil)
231
+ visible?(type_membership)
232
+ end
233
+
234
+ private
235
+
236
+ def visible_and_reachable_type?(type_defn)
237
+ @visible_and_reachable_type ||= read_through do |type_defn|
238
+ next false unless visible_type?(type_defn)
239
+ next true if root_type?(type_defn) || type_defn.introspection?
240
+
241
+ if type_defn.kind.union?
242
+ visible_possible_types?(type_defn) && (referenced?(type_defn) || orphan_type?(type_defn))
243
+ elsif type_defn.kind.interface?
244
+ visible_possible_types?(type_defn)
245
+ else
246
+ referenced?(type_defn) || visible_abstract_type?(type_defn)
247
+ end
248
+ end
249
+
250
+ @visible_and_reachable_type[type_defn]
251
+ end
252
+
253
+ def union_memberships(obj_type)
254
+ @unions ||= read_through { |obj_type| @schema.union_memberships(obj_type).select { |u| visible?(u) } }
255
+ @unions[obj_type]
180
256
  end
181
257
 
182
258
  # We need this to tell whether a field was inherited by an interface
@@ -195,10 +271,10 @@ module GraphQL
195
271
  any_interface_has_visible_field = false
196
272
  ints = unfiltered_interfaces(type_defn)
197
273
  ints.each do |interface_type|
198
- if (iface_field_defn = interface_type.get_field(field_defn.graphql_name))
274
+ if (iface_field_defn = interface_type.get_field(field_defn.graphql_name, @context))
199
275
  any_interface_has_field = true
200
276
 
201
- if interfaces(type_defn).include?(interface_type) && visible_field?(interface_type, iface_field_defn)
277
+ if interfaces(type_defn).include?(interface_type) && visible_field?(iface_field_defn, nil, interface_type)
202
278
  any_interface_has_visible_field = true
203
279
  end
204
280
  end
@@ -215,23 +291,6 @@ module GraphQL
215
291
  end
216
292
  end
217
293
 
218
- def visible_type?(type_defn)
219
- @type_visibility ||= read_through do |type_defn|
220
- next false unless visible?(type_defn)
221
- next true if root_type?(type_defn) || type_defn.introspection?
222
-
223
- if type_defn.kind.union?
224
- visible_possible_types?(type_defn) && (referenced?(type_defn) || orphan_type?(type_defn))
225
- elsif type_defn.kind.interface?
226
- visible_possible_types?(type_defn)
227
- else
228
- referenced?(type_defn) || visible_abstract_type?(type_defn)
229
- end
230
- end
231
-
232
- @type_visibility[type_defn]
233
- end
234
-
235
294
  def root_type?(type_defn)
236
295
  @query == type_defn ||
237
296
  @mutation == type_defn ||
@@ -259,7 +318,7 @@ module GraphQL
259
318
  end
260
319
 
261
320
  def visible_possible_types?(type_defn)
262
- possible_types(type_defn).any? { |t| visible_type?(t) }
321
+ possible_types(type_defn).any? { |t| visible_and_reachable_type?(t) }
263
322
  end
264
323
 
265
324
  def visible?(member)
@@ -274,6 +333,7 @@ module GraphQL
274
333
  return @reachable_type_set if defined?(@reachable_type_set)
275
334
 
276
335
  @reachable_type_set = Set.new
336
+ rt_hash = {}
277
337
 
278
338
  unvisited_types = []
279
339
  ['query', 'mutation', 'subscription'].each do |op_name|
@@ -283,16 +343,16 @@ module GraphQL
283
343
  unvisited_types.concat(@schema.introspection_system.types.values)
284
344
 
285
345
  directives.each do |dir_class|
286
- dir_class.arguments.values.each do |arg_defn|
346
+ arguments(dir_class).each do |arg_defn|
287
347
  arg_t = arg_defn.type.unwrap
288
- if get_type(arg_t.graphql_name)
348
+ if get_type(arg_t.graphql_name) # rubocop:disable Development/ContextIsPassedCop -- `self` is query-aware
289
349
  unvisited_types << arg_t
290
350
  end
291
351
  end
292
352
  end
293
353
 
294
354
  @schema.orphan_types.each do |orphan_type|
295
- if get_type(orphan_type.graphql_name)
355
+ if get_type(orphan_type.graphql_name) == orphan_type # rubocop:disable Development/ContextIsPassedCop -- `self` is query-aware
296
356
  unvisited_types << orphan_type
297
357
  end
298
358
  end
@@ -300,6 +360,10 @@ module GraphQL
300
360
  until unvisited_types.empty?
301
361
  type = unvisited_types.pop
302
362
  if @reachable_type_set.add?(type)
363
+ type_by_name = rt_hash[type.graphql_name] ||= type
364
+ if type_by_name != type
365
+ raise DuplicateNamesError, "Found two visible type definitions for `#{type.graphql_name}`: #{type.inspect}, #{type_by_name.inspect}"
366
+ end
303
367
  if type.kind.input_object?
304
368
  # recurse into visible arguments
305
369
  arguments(type).each do |argument|