graphql 1.9.16 → 1.9.21

Sign up to get free protection for your applications and to get access to all the features.
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