graphql 1.9.16 → 1.9.21

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +8 -0
  3. data/lib/graphql/argument.rb +2 -2
  4. data/lib/graphql/define/assign_object_field.rb +2 -2
  5. data/lib/graphql/define/defined_object_proxy.rb +3 -0
  6. data/lib/graphql/define/instance_definable.rb +14 -3
  7. data/lib/graphql/execution/errors.rb +15 -14
  8. data/lib/graphql/execution/execute.rb +1 -1
  9. data/lib/graphql/execution/interpreter/runtime.rb +39 -17
  10. data/lib/graphql/execution/multiplex.rb +3 -3
  11. data/lib/graphql/introspection/entry_points.rb +2 -1
  12. data/lib/graphql/introspection/schema_type.rb +2 -1
  13. data/lib/graphql/language/document_from_schema_definition.rb +9 -3
  14. data/lib/graphql/language/nodes.rb +2 -2
  15. data/lib/graphql/query.rb +7 -1
  16. data/lib/graphql/query/context.rb +31 -9
  17. data/lib/graphql/query/null_context.rb +4 -0
  18. data/lib/graphql/query/variables.rb +3 -1
  19. data/lib/graphql/relay/node.rb +2 -2
  20. data/lib/graphql/schema.rb +58 -7
  21. data/lib/graphql/schema/argument.rb +4 -0
  22. data/lib/graphql/schema/base_64_bp.rb +3 -2
  23. data/lib/graphql/schema/build_from_definition.rb +26 -9
  24. data/lib/graphql/schema/directive.rb +7 -1
  25. data/lib/graphql/schema/introspection_system.rb +4 -1
  26. data/lib/graphql/schema/loader.rb +9 -3
  27. data/lib/graphql/schema/member/has_fields.rb +1 -4
  28. data/lib/graphql/schema/mutation.rb +1 -1
  29. data/lib/graphql/schema/object.rb +6 -4
  30. data/lib/graphql/schema/possible_types.rb +3 -3
  31. data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
  32. data/lib/graphql/schema/resolver.rb +1 -1
  33. data/lib/graphql/schema/subscription.rb +5 -5
  34. data/lib/graphql/schema/type_membership.rb +34 -0
  35. data/lib/graphql/schema/union.rb +26 -6
  36. data/lib/graphql/schema/warden.rb +77 -3
  37. data/lib/graphql/subscriptions.rb +2 -2
  38. data/lib/graphql/subscriptions/subscription_root.rb +10 -2
  39. data/lib/graphql/union_type.rb +58 -23
  40. data/lib/graphql/version.rb +1 -1
  41. metadata +6 -5
@@ -35,10 +35,12 @@ module GraphQL
35
35
  # @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy]
36
36
  # @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false`
37
37
  def authorized_new(object, context)
38
- auth_val = begin
39
- authorized?(object, context)
40
- rescue GraphQL::UnauthorizedError => err
41
- context.schema.unauthorized_object(err)
38
+ auth_val = context.query.with_error_handling do
39
+ begin
40
+ authorized?(object, context)
41
+ rescue GraphQL::UnauthorizedError => err
42
+ context.schema.unauthorized_object(err)
43
+ end
42
44
  end
43
45
 
44
46
  context.schema.after_lazy(auth_val) do |is_authorized|
@@ -20,12 +20,12 @@ module GraphQL
20
20
  end
21
21
  end
22
22
 
23
- def possible_types(type_defn)
23
+ def possible_types(type_defn, ctx)
24
24
  case type_defn
25
25
  when Module
26
- possible_types(type_defn.graphql_definition)
26
+ possible_types(type_defn.graphql_definition, ctx)
27
27
  when GraphQL::UnionType
28
- type_defn.possible_types
28
+ type_defn.possible_types(ctx)
29
29
  when GraphQL::InterfaceType
30
30
  @interface_implementers[type_defn]
31
31
  when GraphQL::BaseType
@@ -61,7 +61,7 @@ module GraphQL
61
61
  end
62
62
 
63
63
  return_value = if input_kwargs.any?
64
- super(input_kwargs)
64
+ super(**input_kwargs)
65
65
  else
66
66
  super()
67
67
  end
@@ -76,7 +76,7 @@ module GraphQL
76
76
  context.schema.after_lazy(load_arguments_val) do |loaded_args|
77
77
  # Then call `authorized?`, which may raise or may return a lazy object
78
78
  authorized_val = if loaded_args.any?
79
- authorized?(loaded_args)
79
+ authorized?(**loaded_args)
80
80
  else
81
81
  authorized?
82
82
  end
@@ -39,12 +39,12 @@ module GraphQL
39
39
  def resolve(**args)
40
40
  # Dispatch based on `@mode`, which will raise a `NoMethodError` if we ever
41
41
  # have an unexpected `@mode`
42
- public_send("resolve_#{@mode}", args)
42
+ public_send("resolve_#{@mode}", **args)
43
43
  end
44
44
 
45
45
  # Wrap the user-defined `#subscribe` hook
46
- def resolve_subscribe(args)
47
- ret_val = args.any? ? subscribe(args) : subscribe
46
+ def resolve_subscribe(**args)
47
+ ret_val = args.any? ? subscribe(**args) : subscribe
48
48
  if ret_val == :no_response
49
49
  context.skip
50
50
  else
@@ -62,8 +62,8 @@ module GraphQL
62
62
  end
63
63
 
64
64
  # Wrap the user-provided `#update` hook
65
- def resolve_update(args)
66
- ret_val = args.any? ? update(args) : update
65
+ def resolve_update(**args)
66
+ ret_val = args.any? ? update(**args) : update
67
67
  if ret_val == :no_update
68
68
  raise NoUpdateError
69
69
  else
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ # This class joins an object type to an abstract type (interface or union) of which
6
+ # it is a member.
7
+ #
8
+ # TODO: Not yet implemented for interfaces.
9
+ class TypeMembership
10
+ # @return [Class<GraphQL::Schema::Object>]
11
+ attr_reader :object_type
12
+
13
+ # @return [Class<GraphQL::Schema::Union>, Module<GraphQL::Schema::Interface>]
14
+ attr_reader :abstract_type
15
+
16
+ # Called when an object is hooked up to an abstract type, such as {Schema::Union.possible_types}
17
+ # or {Schema::Object.implements} (for interfaces).
18
+ #
19
+ # @param abstract_type [Class<GraphQL::Schema::Union>, Module<GraphQL::Schema::Interface>]
20
+ # @param object_type [Class<GraphQL::Schema::Object>]
21
+ # @param options [Hash] Any options passed to `.possible_types` or `.implements`
22
+ def initialize(abstract_type, object_type, **options)
23
+ @abstract_type = abstract_type
24
+ @object_type = object_type
25
+ @options = options
26
+ end
27
+
28
+ # @return [Boolean] if false, {#object_type} will be treated as _not_ a member of {#abstract_type}
29
+ def visible?(_ctx)
30
+ true
31
+ end
32
+ end
33
+ end
34
+ end
@@ -5,13 +5,19 @@ module GraphQL
5
5
  extend GraphQL::Schema::Member::AcceptsDefinition
6
6
 
7
7
  class << self
8
- def possible_types(*types)
8
+ def possible_types(*types, context: GraphQL::Query::NullContext, **options)
9
9
  if types.any?
10
- @possible_types = types
10
+ types.each do |t|
11
+ type_memberships << type_membership_class.new(self, t, **options)
12
+ end
11
13
  else
12
- all_possible_types = @possible_types || []
13
- all_possible_types += super if defined?(super)
14
- all_possible_types.uniq
14
+ visible_types = []
15
+ type_memberships.each do |type_membership|
16
+ if type_membership.visible?(context)
17
+ visible_types << type_membership.object_type
18
+ end
19
+ end
20
+ visible_types
15
21
  end
16
22
  end
17
23
 
@@ -19,7 +25,7 @@ module GraphQL
19
25
  type_defn = GraphQL::UnionType.new
20
26
  type_defn.name = graphql_name
21
27
  type_defn.description = description
22
- type_defn.possible_types = possible_types
28
+ type_defn.type_memberships = type_memberships
23
29
  if respond_to?(:resolve_type)
24
30
  type_defn.resolve_type = method(:resolve_type)
25
31
  end
@@ -27,9 +33,23 @@ module GraphQL
27
33
  type_defn
28
34
  end
29
35
 
36
+ def type_membership_class(membership_class = nil)
37
+ if membership_class
38
+ @type_membership_class = membership_class
39
+ else
40
+ @type_membership_class || find_inherited_value(:type_membership_class, GraphQL::Schema::TypeMembership)
41
+ end
42
+ end
43
+
30
44
  def kind
31
45
  GraphQL::TypeKinds::UNION
32
46
  end
47
+
48
+ private
49
+
50
+ def type_memberships
51
+ @type_memberships ||= []
52
+ end
33
53
  end
34
54
  end
35
55
  end
@@ -1,4 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
2
5
  module GraphQL
3
6
  class Schema
4
7
  # Restrict access to a {GraphQL::Schema} with a user-defined filter.
@@ -40,7 +43,8 @@ module GraphQL
40
43
  # @param deep_check [Boolean]
41
44
  def initialize(filter, context:, schema:)
42
45
  @schema = schema
43
- @visibility_cache = read_through { |m| filter.call(m, context) }
46
+ @context = context
47
+ @visibility_cache = read_through { |m| filter.call(m, @context) }
44
48
  end
45
49
 
46
50
  # @return [Array<GraphQL::BaseType>] Visible types in the schema
@@ -62,6 +66,17 @@ module GraphQL
62
66
  @visible_types[type_name]
63
67
  end
64
68
 
69
+ # @return [Array<GraphQL::BaseType>] Visible and reachable types in the schema
70
+ def reachable_types
71
+ @reachable_types ||= reachable_type_set.to_a
72
+ end
73
+
74
+ # @return Boolean True if the type is visible and reachable in the schema
75
+ def reachable_type?(type_name)
76
+ type = get_type(type_name)
77
+ type && reachable_type_set.include?(type)
78
+ end
79
+
65
80
  # @return [GraphQL::Field, nil] The field named `field_name` on `parent_type`, if it exists
66
81
  def get_field(parent_type, field_name)
67
82
 
@@ -81,7 +96,7 @@ module GraphQL
81
96
 
82
97
  # @return [Array<GraphQL::BaseType>] The types which may be member of `type_defn`
83
98
  def possible_types(type_defn)
84
- @visible_possible_types ||= read_through { |type_defn| @schema.possible_types(type_defn).select { |t| visible_type?(t) } }
99
+ @visible_possible_types ||= read_through { |type_defn| @schema.possible_types(type_defn, @context).select { |t| visible_type?(t) } }
85
100
  @visible_possible_types[type_defn]
86
101
  end
87
102
 
@@ -170,7 +185,7 @@ module GraphQL
170
185
  end
171
186
 
172
187
  def visible_possible_types?(type_defn)
173
- @schema.possible_types(type_defn).any? { |t| visible_type?(t) }
188
+ @schema.possible_types(type_defn, @context).any? { |t| visible_type?(t) }
174
189
  end
175
190
 
176
191
  def visible?(member)
@@ -180,6 +195,65 @@ module GraphQL
180
195
  def read_through
181
196
  Hash.new { |h, k| h[k] = yield(k) }
182
197
  end
198
+
199
+ def reachable_type_set
200
+ return @reachable_type_set if defined?(@reachable_type_set)
201
+
202
+ @reachable_type_set = Set.new
203
+
204
+ unvisited_types = []
205
+ ['query', 'mutation', 'subscription'].each do |op_name|
206
+ root_type = root_type_for_operation(op_name)
207
+ unvisited_types << root_type if root_type
208
+ end
209
+ unvisited_types.concat(@schema.introspection_system.object_types)
210
+ @schema.orphan_types.each do |orphan_type|
211
+ unvisited_types << orphan_type.graphql_definition if get_type(orphan_type.graphql_name)
212
+ end
213
+
214
+ until unvisited_types.empty?
215
+ type = unvisited_types.pop
216
+ if @reachable_type_set.add?(type)
217
+ if type.kind.input_object?
218
+ # recurse into visible arguments
219
+ arguments(type).each do |argument|
220
+ argument_type = argument.type.unwrap
221
+ unvisited_types << argument_type
222
+ end
223
+ elsif type.kind.union?
224
+ # recurse into visible possible types
225
+ possible_types(type).each do |possible_type|
226
+ unvisited_types << possible_type
227
+ end
228
+ elsif type.kind.fields?
229
+ if type.kind.interface?
230
+ # recurse into visible possible types
231
+ possible_types(type).each do |possible_type|
232
+ unvisited_types << possible_type
233
+ end
234
+ elsif type.kind.object?
235
+ # recurse into visible implemented interfaces
236
+ interfaces(type).each do |interface|
237
+ unvisited_types << interface
238
+ end
239
+ end
240
+
241
+ # recurse into visible fields
242
+ fields(type).each do |field|
243
+ field_type = field.type.unwrap
244
+ unvisited_types << field_type
245
+ # recurse into visible arguments
246
+ arguments(field).each do |argument|
247
+ argument_type = argument.type.unwrap
248
+ unvisited_types << argument_type
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
254
+
255
+ @reachable_type_set
256
+ end
183
257
  end
184
258
  end
185
259
  end
@@ -20,7 +20,7 @@ module GraphQL
20
20
  def self.use(defn, options = {})
21
21
  schema = defn.target
22
22
  options[:schema] = schema
23
- schema.subscriptions = self.new(options)
23
+ schema.subscriptions = self.new(**options)
24
24
  instrumentation = Subscriptions::Instrumentation.new(schema: schema)
25
25
  defn.instrument(:field, instrumentation)
26
26
  defn.instrument(:query, instrumentation)
@@ -90,7 +90,7 @@ module GraphQL
90
90
  operation_name = query_data.fetch(:operation_name)
91
91
  # Re-evaluate the saved query
92
92
  result = @schema.execute(
93
- {
93
+ **{
94
94
  query: query_string,
95
95
  context: context,
96
96
  subscription_topic: event.topic,
@@ -40,7 +40,7 @@ module GraphQL
40
40
  # for the backend to register:
41
41
  events << Subscriptions::Event.new(
42
42
  name: field.name,
43
- arguments: arguments,
43
+ arguments: arguments_without_field_extras(arguments: arguments),
44
44
  context: context,
45
45
  field: field,
46
46
  )
@@ -48,7 +48,7 @@ module GraphQL
48
48
  value
49
49
  elsif context.query.subscription_topic == Subscriptions::Event.serialize(
50
50
  field.name,
51
- arguments,
51
+ arguments_without_field_extras(arguments: arguments),
52
52
  field,
53
53
  scope: (field.subscription_scope ? context[field.subscription_scope] : nil),
54
54
  )
@@ -60,6 +60,14 @@ module GraphQL
60
60
  context.skip
61
61
  end
62
62
  end
63
+
64
+ private
65
+
66
+ def arguments_without_field_extras(arguments:)
67
+ arguments.dup.tap do |event_args|
68
+ field.extras.each { |k| event_args.delete(k) }
69
+ end
70
+ end
63
71
  end
64
72
  end
65
73
  end
@@ -24,22 +24,34 @@ module GraphQL
24
24
  # }
25
25
  #
26
26
  class UnionType < GraphQL::BaseType
27
- accepts_definitions :possible_types, :resolve_type
28
- ensure_defined :possible_types, :resolve_type, :resolve_type_proc
27
+ # Rubocop was unhappy about the syntax when this was a proc literal
28
+ class AcceptPossibleTypesDefinition
29
+ def self.call(target, possible_types, options = {})
30
+ target.add_possible_types(possible_types, **options)
31
+ end
32
+ end
33
+
34
+ accepts_definitions :resolve_type, :type_membership_class,
35
+ possible_types: AcceptPossibleTypesDefinition
36
+ ensure_defined :possible_types, :resolve_type, :resolve_type_proc, :type_membership_class
29
37
 
30
38
  attr_accessor :resolve_type_proc
39
+ attr_reader :type_memberships
40
+ attr_accessor :type_membership_class
31
41
 
32
42
  def initialize
33
43
  super
34
- @dirty_possible_types = []
35
- @clean_possible_types = nil
44
+ @type_membership_class = GraphQL::Schema::TypeMembership
45
+ @type_memberships = []
46
+ @cached_possible_types = nil
36
47
  @resolve_type_proc = nil
37
48
  end
38
49
 
39
50
  def initialize_copy(other)
40
51
  super
41
- @clean_possible_types = nil
42
- @dirty_possible_types = other.dirty_possible_types.dup
52
+ @type_membership_class = other.type_membership_class
53
+ @type_memberships = other.type_memberships.dup
54
+ @cached_possible_types = nil
43
55
  end
44
56
 
45
57
  def kind
@@ -47,33 +59,44 @@ module GraphQL
47
59
  end
48
60
 
49
61
  # @return [Boolean] True if `child_type_defn` is a member of this {UnionType}
50
- def include?(child_type_defn)
51
- possible_types.include?(child_type_defn)
52
- end
53
-
54
- def possible_types=(new_possible_types)
55
- @clean_possible_types = nil
56
- @dirty_possible_types = new_possible_types
62
+ def include?(child_type_defn, ctx = GraphQL::Query::NullContext)
63
+ possible_types(ctx).include?(child_type_defn)
57
64
  end
58
65
 
59
66
  # @return [Array<GraphQL::ObjectType>] Types which may be found in this union
60
- def possible_types
61
- @clean_possible_types ||= begin
62
- if @dirty_possible_types.respond_to?(:map)
63
- @dirty_possible_types.map { |type| GraphQL::BaseType.resolve_related_type(type) }
64
- else
65
- @dirty_possible_types
66
- end
67
+ def possible_types(ctx = GraphQL::Query::NullContext)
68
+ if ctx == GraphQL::Query::NullContext
69
+ # Only cache the default case; if we cached for every `ctx`, it would be a memory leak
70
+ # (The warden should cache calls to this method, so it's called only once per query,
71
+ # unless user code calls it directly.)
72
+ @cached_possible_types ||= possible_types_for_context(ctx)
73
+ else
74
+ possible_types_for_context(ctx)
67
75
  end
68
76
  end
69
77
 
78
+ def possible_types=(types)
79
+ # This is a re-assignment, so clear the previous values
80
+ @type_memberships = []
81
+ @cached_possible_types = nil
82
+ add_possible_types(types, **{})
83
+ end
84
+
85
+ def add_possible_types(types, **options)
86
+ @type_memberships ||= []
87
+ Array(types).each { |t|
88
+ @type_memberships << self.type_membership_class.new(self, t, **options)
89
+ }
90
+ nil
91
+ end
92
+
70
93
  # Get a possible type of this {UnionType} by type name
71
94
  # @param type_name [String]
72
95
  # @param ctx [GraphQL::Query::Context] The context for the current query
73
96
  # @return [GraphQL::ObjectType, nil] The type named `type_name` if it exists and is a member of this {UnionType}, (else `nil`)
74
97
  def get_possible_type(type_name, ctx)
75
98
  type = ctx.query.get_type(type_name)
76
- type if type && ctx.query.schema.possible_types(self).include?(type)
99
+ type if type && ctx.query.schema.possible_types(self, ctx).include?(type)
77
100
  end
78
101
 
79
102
  # Check if a type is a possible type of this {UnionType}
@@ -93,8 +116,20 @@ module GraphQL
93
116
  @resolve_type_proc = new_resolve_type_proc
94
117
  end
95
118
 
96
- protected
119
+ def type_memberships=(type_memberships)
120
+ @type_memberships = type_memberships
121
+ end
122
+
123
+ private
97
124
 
98
- attr_reader :dirty_possible_types
125
+ def possible_types_for_context(ctx)
126
+ visible_types = []
127
+ @type_memberships.each do |type_membership|
128
+ if type_membership.visible?(ctx)
129
+ visible_types << BaseType.resolve_related_type(type_membership.object_type)
130
+ end
131
+ end
132
+ visible_types
133
+ end
99
134
  end
100
135
  end