graphql 2.0.27 → 2.1.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  3. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  4. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  5. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  6. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  7. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  8. data/lib/generators/graphql/templates/base_field.erb +2 -0
  9. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  10. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  11. data/lib/generators/graphql/templates/base_object.erb +2 -0
  12. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  13. data/lib/generators/graphql/templates/base_union.erb +2 -0
  14. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  15. data/lib/generators/graphql/templates/loader.erb +2 -0
  16. data/lib/generators/graphql/templates/mutation.erb +2 -0
  17. data/lib/generators/graphql/templates/node_type.erb +2 -0
  18. data/lib/generators/graphql/templates/query_type.erb +2 -0
  19. data/lib/generators/graphql/templates/schema.erb +2 -0
  20. data/lib/graphql/analysis/ast/analyzer.rb +7 -0
  21. data/lib/graphql/analysis/ast/visitor.rb +2 -2
  22. data/lib/graphql/analysis/ast.rb +15 -11
  23. data/lib/graphql/dataloader/source.rb +7 -0
  24. data/lib/graphql/dataloader.rb +9 -0
  25. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +170 -0
  26. data/lib/graphql/execution/interpreter/runtime.rb +95 -254
  27. data/lib/graphql/execution/interpreter.rb +0 -6
  28. data/lib/graphql/execution/lookahead.rb +1 -1
  29. data/lib/graphql/introspection/dynamic_fields.rb +1 -1
  30. data/lib/graphql/introspection/entry_points.rb +2 -2
  31. data/lib/graphql/language/block_string.rb +28 -16
  32. data/lib/graphql/language/definition_slice.rb +1 -1
  33. data/lib/graphql/language/document_from_schema_definition.rb +36 -35
  34. data/lib/graphql/language/nodes.rb +2 -2
  35. data/lib/graphql/language/printer.rb +294 -145
  36. data/lib/graphql/language/sanitized_printer.rb +20 -22
  37. data/lib/graphql/language/static_visitor.rb +167 -0
  38. data/lib/graphql/language/visitor.rb +20 -81
  39. data/lib/graphql/language.rb +1 -0
  40. data/lib/graphql/pagination/connection.rb +23 -1
  41. data/lib/graphql/query/context/scoped_context.rb +101 -0
  42. data/lib/graphql/query/context.rb +32 -98
  43. data/lib/graphql/query.rb +2 -19
  44. data/lib/graphql/rake_task.rb +3 -12
  45. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  46. data/lib/graphql/schema/field/connection_extension.rb +1 -15
  47. data/lib/graphql/schema/field/scope_extension.rb +7 -1
  48. data/lib/graphql/schema/field.rb +6 -3
  49. data/lib/graphql/schema/has_single_input_argument.rb +156 -0
  50. data/lib/graphql/schema/introspection_system.rb +2 -0
  51. data/lib/graphql/schema/member/base_dsl_methods.rb +2 -1
  52. data/lib/graphql/schema/member/has_arguments.rb +14 -2
  53. data/lib/graphql/schema/member/has_fields.rb +4 -1
  54. data/lib/graphql/schema/member/has_interfaces.rb +21 -7
  55. data/lib/graphql/schema/member/scoped.rb +19 -0
  56. data/lib/graphql/schema/object.rb +8 -0
  57. data/lib/graphql/schema/printer.rb +8 -7
  58. data/lib/graphql/schema/relay_classic_mutation.rb +6 -128
  59. data/lib/graphql/schema/resolver.rb +4 -0
  60. data/lib/graphql/schema/scalar.rb +3 -3
  61. data/lib/graphql/schema/subscription.rb +11 -4
  62. data/lib/graphql/schema/warden.rb +23 -37
  63. data/lib/graphql/schema.rb +23 -22
  64. data/lib/graphql/static_validation/all_rules.rb +1 -1
  65. data/lib/graphql/static_validation/base_visitor.rb +1 -1
  66. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  67. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  68. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  69. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  70. data/lib/graphql/static_validation/validation_context.rb +5 -5
  71. data/lib/graphql/static_validation.rb +0 -1
  72. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
  73. data/lib/graphql/subscriptions.rb +11 -6
  74. data/lib/graphql/tracing/appoptics_trace.rb +2 -2
  75. data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
  76. data/lib/graphql/types/relay/connection_behaviors.rb +19 -2
  77. data/lib/graphql/types/relay/edge_behaviors.rb +7 -0
  78. data/lib/graphql/version.rb +1 -1
  79. data/lib/graphql.rb +1 -2
  80. metadata +7 -4
  81. data/lib/graphql/filter.rb +0 -59
  82. data/lib/graphql/static_validation/type_stack.rb +0 -216
@@ -36,15 +36,11 @@ module GraphQL
36
36
 
37
37
  # @param schema [GraphQL::Schema]
38
38
  # @param context [Hash]
39
- # @param only [<#call(member, ctx)>]
40
- # @param except [<#call(member, ctx)>]
41
39
  # @param introspection [Boolean] Should include the introspection types in the string?
42
- def initialize(schema, context: nil, only: nil, except: nil, introspection: false)
40
+ def initialize(schema, context: nil, introspection: false)
43
41
  @document_from_schema = GraphQL::Language::DocumentFromSchemaDefinition.new(
44
42
  schema,
45
43
  context: context,
46
- only: only,
47
- except: except,
48
44
  include_introspection_types: introspection,
49
45
  )
50
46
 
@@ -61,7 +57,12 @@ module GraphQL
61
57
  false
62
58
  end
63
59
  end
64
- schema = Class.new(GraphQL::Schema) { query(query_root) }
60
+ schema = Class.new(GraphQL::Schema) {
61
+ query(query_root)
62
+ def self.visible?(member, _ctx)
63
+ member.graphql_name != "Root"
64
+ end
65
+ }
65
66
 
66
67
  introspection_schema_ast = GraphQL::Language::DocumentFromSchemaDefinition.new(
67
68
  schema,
@@ -94,7 +95,7 @@ module GraphQL
94
95
 
95
96
  class IntrospectionPrinter < GraphQL::Language::Printer
96
97
  def print_schema_definition(schema)
97
- "schema {\n query: Root\n}"
98
+ print_string("schema {\n query: Root\n}")
98
99
  end
99
100
  end
100
101
  end
@@ -21,6 +21,10 @@ module GraphQL
21
21
  # @see {GraphQL::Schema::Mutation} for an example, it's basically the same.
22
22
  #
23
23
  class RelayClassicMutation < GraphQL::Schema::Mutation
24
+ include GraphQL::Schema::HasSingleInputArgument
25
+
26
+ argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
27
+
24
28
  # The payload should always include this field
25
29
  field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.")
26
30
  # Relay classic default:
@@ -31,34 +35,14 @@ module GraphQL
31
35
  def resolve_with_support(**inputs)
32
36
  input = inputs[:input].to_kwargs
33
37
 
34
- new_extras = field ? field.extras : []
35
- all_extras = self.class.extras + new_extras
36
-
37
- # Transfer these from the top-level hash to the
38
- # shortcutted `input:` object
39
- all_extras.each do |ext|
40
- # It's possible that the `extra` was not passed along by this point,
41
- # don't re-add it if it wasn't given here.
42
- if inputs.key?(ext)
43
- input[ext] = inputs[ext]
44
- end
45
- end
46
-
47
38
  if input
48
39
  # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are.
49
40
  input_kwargs = input.to_h
50
41
  client_mutation_id = input_kwargs.delete(:client_mutation_id)
51
- else
52
- # Relay Classic Mutations with no `argument`s
53
- # don't require `input:`
54
- input_kwargs = {}
42
+ inputs[:input] = input_kwargs
55
43
  end
56
44
 
57
- return_value = if input_kwargs.any?
58
- super(**input_kwargs)
59
- else
60
- super()
61
- end
45
+ return_value = super(**inputs)
62
46
 
63
47
  context.query.after_lazy(return_value) do |return_hash|
64
48
  # It might be an error
@@ -68,112 +52,6 @@ module GraphQL
68
52
  return_hash
69
53
  end
70
54
  end
71
-
72
- class << self
73
- def dummy
74
- @dummy ||= begin
75
- d = Class.new(GraphQL::Schema::Resolver)
76
- d.argument_class(self.argument_class)
77
- # TODO make this lazier?
78
- d.argument(:input, input_type, description: "Parameters for #{self.graphql_name}")
79
- d
80
- end
81
- end
82
-
83
- def field_arguments(context = GraphQL::Query::NullContext)
84
- dummy.arguments(context)
85
- end
86
-
87
- def get_field_argument(name, context = GraphQL::Query::NullContext)
88
- dummy.get_argument(name, context)
89
- end
90
-
91
- def own_field_arguments
92
- dummy.own_arguments
93
- end
94
-
95
- def all_field_argument_definitions
96
- dummy.all_argument_definitions
97
- end
98
-
99
- # Also apply this argument to the input type:
100
- def argument(*args, own_argument: false, **kwargs, &block)
101
- it = input_type # make sure any inherited arguments are already added to it
102
- arg = super(*args, **kwargs, &block)
103
-
104
- # This definition might be overriding something inherited;
105
- # if it is, remove the inherited definition so it's not confused at runtime as having multiple definitions
106
- prev_args = it.own_arguments[arg.graphql_name]
107
- case prev_args
108
- when GraphQL::Schema::Argument
109
- if prev_args.owner != self
110
- it.own_arguments.delete(arg.graphql_name)
111
- end
112
- when Array
113
- prev_args.reject! { |a| a.owner != self }
114
- if prev_args.empty?
115
- it.own_arguments.delete(arg.graphql_name)
116
- end
117
- end
118
-
119
- it.add_argument(arg)
120
- arg
121
- end
122
-
123
- # The base class for generated input object types
124
- # @param new_class [Class] The base class to use for generating input object definitions
125
- # @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject})
126
- def input_object_class(new_class = nil)
127
- if new_class
128
- @input_object_class = new_class
129
- end
130
- @input_object_class || (superclass.respond_to?(:input_object_class) ? superclass.input_object_class : GraphQL::Schema::InputObject)
131
- end
132
-
133
- # @param new_input_type [Class, nil] If provided, it configures this mutation to accept `new_input_type` instead of generating an input type
134
- # @return [Class] The generated {Schema::InputObject} class for this mutation's `input`
135
- def input_type(new_input_type = nil)
136
- if new_input_type
137
- @input_type = new_input_type
138
- end
139
- @input_type ||= generate_input_type
140
- end
141
-
142
- private
143
-
144
- # Generate the input type for the `input:` argument
145
- # To customize how input objects are generated, override this method
146
- # @return [Class] a subclass of {.input_object_class}
147
- def generate_input_type
148
- mutation_args = all_argument_definitions
149
- mutation_class = self
150
- Class.new(input_object_class) do
151
- class << self
152
- def default_graphql_name
153
- "#{self.mutation.graphql_name}Input"
154
- end
155
-
156
- def description(new_desc = nil)
157
- super || "Autogenerated input type of #{self.mutation.graphql_name}"
158
- end
159
- end
160
- mutation(mutation_class)
161
- # these might be inherited:
162
- mutation_args.each do |arg|
163
- add_argument(arg)
164
- end
165
- argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
166
- end
167
- end
168
- end
169
-
170
- private
171
-
172
- def authorize_arguments(args, values)
173
- # remove the `input` wrapper to match values
174
- input_args = args["input"].type.unwrap.arguments(context)
175
- super(input_args, values)
176
- end
177
55
  end
178
56
  end
179
57
  end
@@ -214,6 +214,10 @@ module GraphQL
214
214
  arguments(context)
215
215
  end
216
216
 
217
+ def any_field_arguments?
218
+ any_arguments?
219
+ end
220
+
217
221
  def get_field_argument(name, context = GraphQL::Query::NullContext)
218
222
  get_argument(name, context)
219
223
  end
@@ -19,9 +19,9 @@ module GraphQL
19
19
 
20
20
  def specified_by_url(new_url = nil)
21
21
  if new_url
22
- @specified_by_url = new_url
23
- elsif defined?(@specified_by_url)
24
- @specified_by_url
22
+ directive(GraphQL::Schema::Directive::SpecifiedBy, url: new_url)
23
+ elsif (directive = directives.find { |dir| dir.graphql_name == "specifiedBy" })
24
+ directive.arguments[:url] # rubocop:disable Development/ContextIsPassedCop
25
25
  elsif superclass.respond_to?(:specified_by_url)
26
26
  superclass.specified_by_url
27
27
  else
@@ -28,14 +28,19 @@ module GraphQL
28
28
  def resolve_with_support(**args)
29
29
  result = nil
30
30
  unsubscribed = true
31
- catch :graphql_subscription_unsubscribed do
31
+ unsubscribed_result = catch :graphql_subscription_unsubscribed do
32
32
  result = super
33
33
  unsubscribed = false
34
34
  end
35
35
 
36
36
 
37
37
  if unsubscribed
38
- context.skip
38
+ if unsubscribed_result
39
+ context.namespace(:subscriptions)[:final_update] = true
40
+ unsubscribed_result
41
+ else
42
+ context.skip
43
+ end
39
44
  else
40
45
  result
41
46
  end
@@ -94,9 +99,11 @@ module GraphQL
94
99
  end
95
100
 
96
101
  # Call this to halt execution and remove this subscription from the system
97
- def unsubscribe
102
+ # @param update_value [Object] if present, deliver this update before unsubscribing
103
+ # @return [void]
104
+ def unsubscribe(update_value = nil)
98
105
  context.namespace(:subscriptions)[:unsubscribed] = true
99
- throw :graphql_subscription_unsubscribed
106
+ throw :graphql_subscription_unsubscribed, update_value
100
107
  end
101
108
 
102
109
  READING_SCOPE = ::Object.new
@@ -4,37 +4,12 @@ require 'set'
4
4
 
5
5
  module GraphQL
6
6
  class Schema
7
- # Restrict access to a {GraphQL::Schema} with a user-defined filter.
7
+ # Restrict access to a {GraphQL::Schema} with a user-defined `visible?` implementations.
8
8
  #
9
9
  # When validating and executing a query, all access to schema members
10
10
  # should go through a warden. If you access the schema directly,
11
11
  # you may show a client something that it shouldn't be allowed to see.
12
12
  #
13
- # @example Hiding private fields
14
- # private_members = -> (member, ctx) { member.metadata[:private] }
15
- # result = Schema.execute(query_string, except: private_members)
16
- #
17
- # @example Custom filter implementation
18
- # # It must respond to `#call(member)`.
19
- # class MissingRequiredFlags
20
- # def initialize(user)
21
- # @user = user
22
- # end
23
- #
24
- # # Return `false` if any required flags are missing
25
- # def call(member, ctx)
26
- # member.metadata[:required_flags].any? do |flag|
27
- # !@user.has_flag?(flag)
28
- # end
29
- # end
30
- # end
31
- #
32
- # # Then, use the custom filter in query:
33
- # missing_required_flags = MissingRequiredFlags.new(current_user)
34
- #
35
- # # This query can only access members which match the user's flags
36
- # result = Schema.execute(query_string, except: missing_required_flags)
37
- #
38
13
  # @api private
39
14
  class Warden
40
15
  def self.from_context(context)
@@ -114,22 +89,16 @@ module GraphQL
114
89
  def interfaces(obj_type); obj_type.interfaces; end
115
90
  end
116
91
 
117
- # @param filter [<#call(member)>] Objects are hidden when `.call(member, ctx)` returns true
118
92
  # @param context [GraphQL::Query::Context]
119
93
  # @param schema [GraphQL::Schema]
120
- def initialize(filter = nil, context:, schema:)
94
+ def initialize(context:, schema:)
121
95
  @schema = schema
122
96
  # Cache these to avoid repeated hits to the inheritance chain when one isn't present
123
97
  @query = @schema.query
124
98
  @mutation = @schema.mutation
125
99
  @subscription = @schema.subscription
126
100
  @context = context
127
- @visibility_cache = if filter
128
- read_through { |m| filter.call(m, context) }
129
- else
130
- read_through { |m| schema.visible?(m, context) }
131
- end
132
-
101
+ @visibility_cache = read_through { |m| schema.visible?(m, context) }
133
102
  @visibility_cache.compare_by_identity
134
103
  # Initialize all ivars to improve object shape consistency:
135
104
  @types = @visible_types = @reachable_types = @visible_parent_fields =
@@ -219,7 +188,16 @@ module GraphQL
219
188
  # @param argument_owner [GraphQL::Field, GraphQL::InputObjectType]
220
189
  # @return [Array<GraphQL::Argument>] Visible arguments on `argument_owner`
221
190
  def arguments(argument_owner, ctx = nil)
222
- @visible_arguments ||= read_through { |o| o.arguments(@context).each_value.select { |a| visible_argument?(a, @context) } }
191
+ @visible_arguments ||= read_through { |o|
192
+ args = o.arguments(@context)
193
+ if args.any?
194
+ args = args.values
195
+ args.select! { |a| visible_argument?(a, @context) }
196
+ args
197
+ else
198
+ EmptyObjects::EMPTY_ARRAY
199
+ end
200
+ }
223
201
  @visible_arguments[argument_owner]
224
202
  end
225
203
 
@@ -242,7 +220,13 @@ module GraphQL
242
220
 
243
221
  # @return [Array<GraphQL::InterfaceType>] Visible interfaces implemented by `obj_type`
244
222
  def interfaces(obj_type)
245
- @visible_interfaces ||= read_through { |t| t.interfaces(@context).select { |i| visible_type?(i) } }
223
+ @visible_interfaces ||= read_through { |t|
224
+ ints = t.interfaces(@context)
225
+ if ints.any?
226
+ ints.select! { |i| visible_type?(i) }
227
+ end
228
+ ints
229
+ }
246
230
  @visible_interfaces[obj_type]
247
231
  end
248
232
 
@@ -385,7 +369,9 @@ module GraphQL
385
369
  end
386
370
 
387
371
  def read_through
388
- Hash.new { |h, k| h[k] = yield(k) }
372
+ h = Hash.new { |h, k| h[k] = yield(k) }
373
+ h.compare_by_identity
374
+ h
389
375
  end
390
376
 
391
377
  def reachable_type_set
@@ -37,10 +37,12 @@ require "graphql/schema/directive/skip"
37
37
  require "graphql/schema/directive/feature"
38
38
  require "graphql/schema/directive/flagged"
39
39
  require "graphql/schema/directive/transform"
40
+ require "graphql/schema/directive/specified_by"
40
41
  require "graphql/schema/type_membership"
41
42
 
42
43
  require "graphql/schema/resolver"
43
44
  require "graphql/schema/mutation"
45
+ require "graphql/schema/has_single_input_argument"
44
46
  require "graphql/schema/relay_classic_mutation"
45
47
  require "graphql/schema/subscription"
46
48
 
@@ -222,7 +224,7 @@ module GraphQL
222
224
  # @param include_specified_by_url [Boolean] If true, scalar types' `specifiedByUrl:` will be included in the response
223
225
  # @param include_is_one_of [Boolean] If true, `isOneOf: true|false` will be included with input objects
224
226
  # @return [Hash] GraphQL result
225
- def as_json(only: nil, except: nil, context: {}, include_deprecated_args: true, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false)
227
+ def as_json(context: {}, include_deprecated_args: true, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false)
226
228
  introspection_query = Introspection.query(
227
229
  include_deprecated_args: include_deprecated_args,
228
230
  include_schema_description: include_schema_description,
@@ -231,16 +233,14 @@ module GraphQL
231
233
  include_specified_by_url: include_specified_by_url,
232
234
  )
233
235
 
234
- execute(introspection_query, only: only, except: except, context: context).to_h
236
+ execute(introspection_query, context: context).to_h
235
237
  end
236
238
 
237
239
  # Return the GraphQL IDL for the schema
238
240
  # @param context [Hash]
239
- # @param only [<#call(member, ctx)>]
240
- # @param except [<#call(member, ctx)>]
241
241
  # @return [String]
242
- def to_definition(only: nil, except: nil, context: {})
243
- GraphQL::Schema::Printer.print_schema(self, only: only, except: except, context: context)
242
+ def to_definition(context: {})
243
+ GraphQL::Schema::Printer.print_schema(self, context: context)
244
244
  end
245
245
 
246
246
  # Return the GraphQL::Language::Document IDL AST for the schema
@@ -268,20 +268,6 @@ module GraphQL
268
268
  @find_cache[path] ||= @finder.find(path)
269
269
  end
270
270
 
271
- def default_filter
272
- GraphQL::Filter.new(except: default_mask)
273
- end
274
-
275
- def default_mask(new_mask = nil)
276
- if new_mask
277
- line = caller(2, 10).find { |l| !l.include?("lib/graphql") }
278
- GraphQL::Deprecation.warn("GraphQL::Filter and Schema.mask are deprecated and will be removed in v2.1.0. Implement `visible?` on your schema members instead (https://graphql-ruby.org/authorization/visibility.html).\n #{line}")
279
- @own_default_mask = new_mask
280
- else
281
- @own_default_mask || find_inherited_value(:default_mask, Schema::NullMask)
282
- end
283
- end
284
-
285
271
  def static_validator
286
272
  GraphQL::StaticValidation::Validator.new(schema: self)
287
273
  end
@@ -776,7 +762,16 @@ module GraphQL
776
762
  own_orphan_types.concat(new_orphan_types.flatten)
777
763
  end
778
764
 
779
- find_inherited_value(:orphan_types, EMPTY_ARRAY) + own_orphan_types
765
+ inherited_ot = find_inherited_value(:orphan_types, nil)
766
+ if inherited_ot
767
+ if own_orphan_types.any?
768
+ inherited_ot + own_orphan_types
769
+ else
770
+ inherited_ot
771
+ end
772
+ else
773
+ own_orphan_types
774
+ end
780
775
  end
781
776
 
782
777
  def default_execution_strategy
@@ -978,7 +973,12 @@ module GraphQL
978
973
  new_directives.flatten.each { |d| directive(d) }
979
974
  end
980
975
 
981
- find_inherited_value(:directives, default_directives).merge(own_directives)
976
+ inherited_dirs = find_inherited_value(:directives, default_directives)
977
+ if own_directives.any?
978
+ inherited_dirs.merge(own_directives)
979
+ else
980
+ inherited_dirs
981
+ end
982
982
  end
983
983
 
984
984
  # Attach a single directive to this schema
@@ -994,6 +994,7 @@ module GraphQL
994
994
  "skip" => GraphQL::Schema::Directive::Skip,
995
995
  "deprecated" => GraphQL::Schema::Directive::Deprecated,
996
996
  "oneOf" => GraphQL::Schema::Directive::OneOf,
997
+ "specifiedBy" => GraphQL::Schema::Directive::SpecifiedBy,
997
998
  }.freeze
998
999
  end
999
1000
 
@@ -3,7 +3,7 @@ module GraphQL
3
3
  module StaticValidation
4
4
  # Default rules for {GraphQL::StaticValidation::Validator}
5
5
  #
6
- # Order is important here. Some validators return {GraphQL::Language::Visitor::SKIP}
6
+ # Order is important here. Some validators skip later hooks.
7
7
  # which stops the visit on that node. That way it doesn't try to find fields on types that
8
8
  # don't exist, etc.
9
9
  ALL_RULES = [
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  module StaticValidation
4
- class BaseVisitor < GraphQL::Language::Visitor
4
+ class BaseVisitor < GraphQL::Language::StaticVisitor
5
5
  def initialize(document, context)
6
6
  @path = []
7
7
  @object_types = []
@@ -111,7 +111,7 @@ module GraphQL
111
111
  # that required fields are missing
112
112
  required_field_names = @warden.arguments(type)
113
113
  .select { |argument| argument.type.kind.non_null? && @warden.get_argument(type, argument.name) }
114
- .map(&:name)
114
+ .map!(&:name)
115
115
 
116
116
  present_field_names = ast_node.arguments.map(&:name)
117
117
  missing_required_field_names = required_field_names - present_field_names
@@ -340,7 +340,7 @@ module GraphQL
340
340
  selections.each do |node|
341
341
  case node
342
342
  when GraphQL::Language::Nodes::Field
343
- definition = context.query.get_field(owner_type, node.name)
343
+ definition = context.warden.get_field(owner_type, node.name)
344
344
  fields << Field.new(node, definition, owner_type, parents)
345
345
  when GraphQL::Language::Nodes::InlineFragment
346
346
  fragment_type = node.type ? context.warden.get_type(node.type.name) : owner_type
@@ -21,7 +21,7 @@ module GraphQL
21
21
  present_argument_names = ast_node.arguments.map(&:name)
22
22
  required_argument_names = context.warden.arguments(defn)
23
23
  .select { |a| a.type.kind.non_null? && !a.default_value? && context.warden.get_argument(defn, a.name) }
24
- .map(&:name)
24
+ .map!(&:name)
25
25
 
26
26
  missing_names = required_argument_names - present_argument_names
27
27
  if missing_names.any?
@@ -36,7 +36,7 @@ module GraphQL
36
36
 
37
37
  required_fields = context.warden.arguments(parent_type)
38
38
  .select{|arg| arg.type.kind.non_null?}
39
- .map(&:graphql_name)
39
+ .map!(&:graphql_name)
40
40
 
41
41
  present_fields = ast_node.arguments.map(&:name)
42
42
  missing_fields = required_fields - present_fields
@@ -8,20 +8,20 @@ module GraphQL
8
8
  # It provides access to the schema & fragments which validators may read from.
9
9
  #
10
10
  # It holds a list of errors which each validator may add to.
11
- #
12
- # It also provides limited access to the {TypeStack} instance,
13
- # which tracks state as you climb in and out of different fields.
14
11
  class ValidationContext
15
12
  extend Forwardable
16
13
 
17
14
  attr_reader :query, :errors, :visitor,
18
15
  :on_dependency_resolve_handlers,
19
- :max_errors
16
+ :max_errors, :warden, :schema
17
+
20
18
 
21
- def_delegators :@query, :schema, :document, :fragments, :operations, :warden
19
+ def_delegators :@query, :document, :fragments, :operations
22
20
 
23
21
  def initialize(query, visitor_class, max_errors)
24
22
  @query = query
23
+ @warden = query.warden
24
+ @schema = query.schema
25
25
  @literal_validator = LiteralValidator.new(context: query.context)
26
26
  @errors = []
27
27
  @max_errors = max_errors || Float::INFINITY
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/static_validation/error"
3
3
  require "graphql/static_validation/definition_dependencies"
4
- require "graphql/static_validation/type_stack"
5
4
  require "graphql/static_validation/validator"
6
5
  require "graphql/static_validation/validation_context"
7
6
  require "graphql/static_validation/validation_timeout_error"
@@ -124,7 +124,8 @@ module GraphQL
124
124
  # This subscription was re-evaluated.
125
125
  # Send it to the specific stream where this client was waiting.
126
126
  def deliver(subscription_id, result)
127
- payload = { result: result.to_h, more: true }
127
+ has_more = !result.context.namespace(:subscriptions)[:final_update]
128
+ payload = { result: result.to_h, more: has_more }
128
129
  @action_cable.server.broadcast(stream_subscription_name(subscription_id), payload)
129
130
  end
130
131
 
@@ -125,10 +125,10 @@ module GraphQL
125
125
  variables: variables,
126
126
  root_value: object,
127
127
  }
128
-
128
+
129
129
  # merge event's and query's context together
130
130
  context.merge!(event.context) unless event.context.nil? || context.nil?
131
-
131
+
132
132
  execute_options[:validate] = validate_update?(**execute_options)
133
133
  result = @schema.execute(**execute_options)
134
134
  subscriptions_context = result.context.namespace(:subscriptions)
@@ -136,11 +136,9 @@ module GraphQL
136
136
  result = nil
137
137
  end
138
138
 
139
- unsubscribed = subscriptions_context[:unsubscribed]
140
-
141
- if unsubscribed
139
+ if subscriptions_context[:unsubscribed] && !subscriptions_context[:final_update]
142
140
  # `unsubscribe` was called, clean up on our side
143
- # TODO also send `{more: false}` to client?
141
+ # The transport should also send `{more: false}` to client
144
142
  delete_subscription(subscription_id)
145
143
  result = nil
146
144
  end
@@ -164,7 +162,14 @@ module GraphQL
164
162
  res = execute_update(subscription_id, event, object)
165
163
  if !res.nil?
166
164
  deliver(subscription_id, res)
165
+
166
+ if res.context.namespace(:subscriptions)[:unsubscribed]
167
+ # `unsubscribe` was called, clean up on our side
168
+ # The transport should also send `{more: false}` to client
169
+ delete_subscription(subscription_id)
170
+ end
167
171
  end
172
+
168
173
  end
169
174
 
170
175
  # Event `event` occurred on `object`,
@@ -195,7 +195,7 @@ module GraphQL
195
195
  else
196
196
  [key, data[key]]
197
197
  end
198
- end.flatten(2).each_slice(2).to_h.merge(Spec: 'graphql')
198
+ end.tap { _1.flatten!(2) }.each_slice(2).to_h.merge(Spec: 'graphql')
199
199
  end
200
200
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
201
201
 
@@ -226,7 +226,7 @@ module GraphQL
226
226
  end
227
227
 
228
228
  def graphql_multiplex(data)
229
- names = data.queries.map(&:operations).map(&:keys).flatten.compact
229
+ names = data.queries.map(&:operations).map!(&:keys).tap(&:flatten!).tap(&:compact!)
230
230
  multiplex_transaction_name(names) if names.size > 1
231
231
 
232
232
  [:Operations, names.join(', ')]
@@ -117,7 +117,7 @@ module GraphQL
117
117
  else
118
118
  [key, data[key]]
119
119
  end
120
- end.flatten(2).each_slice(2).to_h.merge(Spec: 'graphql')
120
+ end.tap { _1.flatten!(2) }.each_slice(2).to_h.merge(Spec: 'graphql')
121
121
  end
122
122
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
123
123
 
@@ -148,7 +148,7 @@ module GraphQL
148
148
  end
149
149
 
150
150
  def graphql_multiplex(data)
151
- names = data.queries.map(&:operations).map(&:keys).flatten.compact
151
+ names = data.queries.map(&:operations).map!(&:keys).tap(&:flatten!).tap(&:compact!)
152
152
  multiplex_transaction_name(names) if names.size > 1
153
153
 
154
154
  [:Operations, names.join(', ')]