graphql 1.11.4 → 1.11.5

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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/templates/union.erb +1 -1
  3. data/lib/graphql.rb +16 -0
  4. data/lib/graphql/argument.rb +3 -3
  5. data/lib/graphql/backtrace/tracer.rb +2 -1
  6. data/lib/graphql/execution/interpreter.rb +10 -0
  7. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  8. data/lib/graphql/execution/interpreter/runtime.rb +32 -18
  9. data/lib/graphql/introspection.rb +96 -0
  10. data/lib/graphql/introspection/field_type.rb +7 -3
  11. data/lib/graphql/introspection/input_value_type.rb +6 -0
  12. data/lib/graphql/introspection/introspection_query.rb +6 -92
  13. data/lib/graphql/introspection/type_type.rb +7 -3
  14. data/lib/graphql/language/block_string.rb +24 -5
  15. data/lib/graphql/language/lexer.rb +7 -3
  16. data/lib/graphql/language/lexer.rl +7 -3
  17. data/lib/graphql/language/nodes.rb +1 -1
  18. data/lib/graphql/language/parser.rb +107 -103
  19. data/lib/graphql/language/parser.y +4 -0
  20. data/lib/graphql/language/sanitized_printer.rb +59 -26
  21. data/lib/graphql/name_validator.rb +6 -7
  22. data/lib/graphql/pagination/connections.rb +2 -0
  23. data/lib/graphql/query.rb +2 -2
  24. data/lib/graphql/query/context.rb +10 -2
  25. data/lib/graphql/schema.rb +30 -16
  26. data/lib/graphql/schema/argument.rb +56 -5
  27. data/lib/graphql/schema/build_from_definition.rb +60 -35
  28. data/lib/graphql/schema/directive/deprecated.rb +1 -1
  29. data/lib/graphql/schema/field.rb +10 -4
  30. data/lib/graphql/schema/input_object.rb +5 -3
  31. data/lib/graphql/schema/interface.rb +1 -1
  32. data/lib/graphql/schema/late_bound_type.rb +2 -2
  33. data/lib/graphql/schema/loader.rb +1 -0
  34. data/lib/graphql/schema/member/build_type.rb +14 -4
  35. data/lib/graphql/schema/member/has_arguments.rb +3 -1
  36. data/lib/graphql/schema/member/type_system_helpers.rb +2 -2
  37. data/lib/graphql/schema/relay_classic_mutation.rb +3 -1
  38. data/lib/graphql/schema/timeout.rb +29 -15
  39. data/lib/graphql/schema/validation.rb +8 -0
  40. data/lib/graphql/static_validation/validator.rb +7 -4
  41. data/lib/graphql/subscriptions.rb +1 -3
  42. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +21 -7
  43. data/lib/graphql/version.rb +1 -1
  44. metadata +2 -2
@@ -4,7 +4,7 @@ module GraphQL
4
4
  class Directive < GraphQL::Schema::Member
5
5
  class Deprecated < GraphQL::Schema::Directive
6
6
  description "Marks an element of a GraphQL schema as no longer supported."
7
- locations(GraphQL::Schema::Directive::FIELD_DEFINITION, GraphQL::Schema::Directive::ENUM_VALUE)
7
+ locations(GraphQL::Schema::Directive::FIELD_DEFINITION, GraphQL::Schema::Directive::ENUM_VALUE, GraphQL::Schema::Directive::ARGUMENT_DEFINITION, GraphQL::Schema::Directive::INPUT_FIELD_DEFINITION)
8
8
 
9
9
  reason_description = "Explains why this element was deprecated, usually also including a "\
10
10
  "suggestion for how to access supported similar data. Formatted "\
@@ -203,7 +203,7 @@ module GraphQL
203
203
  # @param broadcastable [Boolean] Whether or not this field can be distributed in subscription broadcasts
204
204
  # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
205
205
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
206
- def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: [], extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, &definition_block)
206
+ def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, &definition_block)
207
207
  if name.nil?
208
208
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
209
209
  end
@@ -250,7 +250,7 @@ module GraphQL
250
250
  method_name = method || hash_key || name_s
251
251
  resolver_method ||= name_s.to_sym
252
252
 
253
- @method_str = method_name.to_s
253
+ @method_str = -method_name.to_s
254
254
  @method_sym = method_name.to_sym
255
255
  @resolver_method = resolver_method
256
256
  @complexity = complexity
@@ -274,7 +274,7 @@ module GraphQL
274
274
  if arg.is_a?(Hash)
275
275
  argument(name: name, **arg)
276
276
  else
277
- own_arguments[name] = arg
277
+ add_argument(arg)
278
278
  end
279
279
  end
280
280
 
@@ -282,7 +282,7 @@ module GraphQL
282
282
  @subscription_scope = subscription_scope
283
283
 
284
284
  # Do this last so we have as much context as possible when initializing them:
285
- @extensions = []
285
+ @extensions = EMPTY_ARRAY
286
286
  if extensions.any?
287
287
  self.extensions(extensions)
288
288
  end
@@ -343,6 +343,9 @@ module GraphQL
343
343
  # Read the value
344
344
  @extensions
345
345
  else
346
+ if @extensions.frozen?
347
+ @extensions = @extensions.dup
348
+ end
346
349
  new_extensions.each do |extension|
347
350
  if extension.is_a?(Hash)
348
351
  extension = extension.to_a[0]
@@ -380,6 +383,9 @@ module GraphQL
380
383
  # Read the value
381
384
  @extras
382
385
  else
386
+ if @extras.frozen?
387
+ @extras = @extras.dup
388
+ end
383
389
  # Append to the set of extras on this field
384
390
  @extras.concat(new_extras)
385
391
  end
@@ -126,9 +126,11 @@ module GraphQL
126
126
  argument_defn = super(*args, **kwargs, &block)
127
127
  # Add a method access
128
128
  method_name = argument_defn.keyword
129
- define_method(method_name) do
130
- self[method_name]
131
- end
129
+ class_eval <<-RUBY, __FILE__, __LINE__
130
+ def #{method_name}
131
+ self[#{method_name.inspect}]
132
+ end
133
+ RUBY
132
134
  end
133
135
 
134
136
  def to_graphql
@@ -30,7 +30,7 @@ module GraphQL
30
30
 
31
31
  # The interface is accessible if any of its possible types are accessible
32
32
  def accessible?(context)
33
- context.schema.possible_types(self).each do |type|
33
+ context.schema.possible_types(self, context).each do |type|
34
34
  if context.schema.accessible?(type, context)
35
35
  return true
36
36
  end
@@ -16,11 +16,11 @@ module GraphQL
16
16
  end
17
17
 
18
18
  def to_non_null_type
19
- GraphQL::NonNullType.new(of_type: self)
19
+ @to_non_null_type ||= GraphQL::NonNullType.new(of_type: self)
20
20
  end
21
21
 
22
22
  def to_list_type
23
- GraphQL::ListType.new(of_type: self)
23
+ @to_list_type ||= GraphQL::ListType.new(of_type: self)
24
24
  end
25
25
 
26
26
  def inspect
@@ -189,6 +189,7 @@ module GraphQL
189
189
  kwargs = {
190
190
  type: type_resolver.call(arg["type"]),
191
191
  description: arg["description"],
192
+ deprecation_reason: arg["deprecationReason"],
192
193
  required: false,
193
194
  method_access: false,
194
195
  camelize: false,
@@ -4,6 +4,10 @@ module GraphQL
4
4
  class Member
5
5
  # @api private
6
6
  module BuildType
7
+ if !String.method_defined?(:match?)
8
+ using GraphQL::StringMatchBackport
9
+ end
10
+
7
11
  LIST_TYPE_ERROR = "Use an array of [T] or [T, null: true] for list types; other arrays are not supported"
8
12
 
9
13
  module_function
@@ -162,10 +166,16 @@ module GraphQL
162
166
  end
163
167
 
164
168
  def underscore(string)
165
- string
166
- .gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2') # URLDecoder -> URL_Decoder
167
- .gsub(/([a-z\d])([A-Z])/,'\1_\2') # someThing -> some_Thing
168
- .downcase
169
+ if string.match?(/\A[a-z_]+\Z/)
170
+ return string
171
+ end
172
+ string2 = string.dup
173
+
174
+ string2.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') # URLDecoder -> URL_Decoder
175
+ string2.gsub!(/([a-z\d])([A-Z])/,'\1_\2') # someThing -> some_Thing
176
+ string2.downcase!
177
+
178
+ string2
169
179
  end
170
180
  end
171
181
  end
@@ -43,6 +43,7 @@ module GraphQL
43
43
  # @param arg_defn [GraphQL::Schema::Argument]
44
44
  # @return [GraphQL::Schema::Argument]
45
45
  def add_argument(arg_defn)
46
+ @own_arguments ||= {}
46
47
  own_arguments[arg_defn.name] = arg_defn
47
48
  arg_defn
48
49
  end
@@ -229,8 +230,9 @@ module GraphQL
229
230
  end
230
231
  end
231
232
 
233
+ NO_ARGUMENTS = {}.freeze
232
234
  def own_arguments
233
- @own_arguments ||= {}
235
+ @own_arguments || NO_ARGUMENTS
234
236
  end
235
237
  end
236
238
  end
@@ -6,12 +6,12 @@ module GraphQL
6
6
  module TypeSystemHelpers
7
7
  # @return [Schema::NonNull] Make a non-null-type representation of this type
8
8
  def to_non_null_type
9
- GraphQL::Schema::NonNull.new(self)
9
+ @to_non_null_type ||= GraphQL::Schema::NonNull.new(self)
10
10
  end
11
11
 
12
12
  # @return [Schema::List] Make a list-type representation of this type
13
13
  def to_list_type
14
- GraphQL::Schema::List.new(self)
14
+ @to_list_type ||= GraphQL::Schema::List.new(self)
15
15
  end
16
16
 
17
17
  # @return [Boolean] true if this is a non-nullable type. A nullable list of non-nullables is considered nullable.
@@ -122,7 +122,9 @@ module GraphQL
122
122
  graphql_name("#{mutation_name}Input")
123
123
  description("Autogenerated input type of #{mutation_name}")
124
124
  mutation(mutation_class)
125
- own_arguments.merge!(mutation_args)
125
+ mutation_args.each do |_name, arg|
126
+ add_argument(arg)
127
+ end
126
128
  argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
127
129
  end
128
130
  end
@@ -7,7 +7,7 @@ module GraphQL
7
7
  # to the `errors` key. Any already-resolved fields will be in the `data` key, so
8
8
  # you'll get a partial response.
9
9
  #
10
- # You can subclass `GraphQL::Schema::Timeout` and override the `handle_timeout` method
10
+ # You can subclass `GraphQL::Schema::Timeout` and override `max_seconds` and/or `handle_timeout`
11
11
  # to provide custom logic when a timeout error occurs.
12
12
  #
13
13
  # Note that this will stop a query _in between_ field resolutions, but
@@ -33,8 +33,6 @@ module GraphQL
33
33
  # end
34
34
  #
35
35
  class Timeout
36
- attr_reader :max_seconds
37
-
38
36
  def self.use(schema, **options)
39
37
  tracer = new(**options)
40
38
  schema.tracer(tracer)
@@ -48,32 +46,39 @@ module GraphQL
48
46
  def trace(key, data)
49
47
  case key
50
48
  when 'execute_multiplex'
51
- timeout_state = {
52
- timeout_at: Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) + max_seconds * 1000,
53
- timed_out: false
54
- }
55
-
56
49
  data.fetch(:multiplex).queries.each do |query|
50
+ timeout_duration_s = max_seconds(query)
51
+ timeout_state = if timeout_duration_s == false
52
+ # if the method returns `false`, don't apply a timeout
53
+ false
54
+ else
55
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
56
+ timeout_at = now + (max_seconds(query) * 1000)
57
+ {
58
+ timeout_at: timeout_at,
59
+ timed_out: false
60
+ }
61
+ end
57
62
  query.context.namespace(self.class)[:state] = timeout_state
58
63
  end
59
64
 
60
65
  yield
61
66
  when 'execute_field', 'execute_field_lazy'
62
- query = data[:context] ? data.fetch(:context).query : data.fetch(:query)
63
- timeout_state = query.context.namespace(self.class).fetch(:state)
64
- if Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at)
67
+ query_context = data[:context] || data[:query].context
68
+ timeout_state = query_context.namespace(self.class).fetch(:state)
69
+ # If the `:state` is `false`, then `max_seconds(query)` opted out of timeout for this query.
70
+ if timeout_state != false && Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at)
65
71
  error = if data[:context]
66
- context = data.fetch(:context)
67
- GraphQL::Schema::Timeout::TimeoutError.new(context.parent_type, context.field)
72
+ GraphQL::Schema::Timeout::TimeoutError.new(query_context.parent_type, query_context.field)
68
73
  else
69
74
  field = data.fetch(:field)
70
75
  GraphQL::Schema::Timeout::TimeoutError.new(field.owner, field)
71
76
  end
72
77
 
73
78
  # Only invoke the timeout callback for the first timeout
74
- unless timeout_state[:timed_out]
79
+ if !timeout_state[:timed_out]
75
80
  timeout_state[:timed_out] = true
76
- handle_timeout(error, query)
81
+ handle_timeout(error, query_context.query)
77
82
  end
78
83
 
79
84
  error
@@ -85,6 +90,15 @@ module GraphQL
85
90
  end
86
91
  end
87
92
 
93
+ # Called at the start of each query.
94
+ # The default implementation returns the `max_seconds:` value from installing this plugin.
95
+ #
96
+ # @param query [GraphQL::Query] The query that's about to run
97
+ # @return [Integer, false] The number of seconds after which to interrupt query execution and call {#handle_error}, or `false` to bypass the timeout.
98
+ def max_seconds(query)
99
+ @max_seconds
100
+ end
101
+
88
102
  # Invoked when a query times out.
89
103
  # @param error [GraphQL::Schema::Timeout::TimeoutError]
90
104
  # @param query [GraphQL::Error]
@@ -133,6 +133,12 @@ module GraphQL
133
133
  end
134
134
  }
135
135
 
136
+ DEPRECATED_ARGUMENTS_ARE_OPTIONAL = ->(argument) {
137
+ if argument.deprecation_reason && argument.type.non_null?
138
+ "must be optional because it's deprecated"
139
+ end
140
+ }
141
+
136
142
  TYPE_IS_VALID_INPUT_TYPE = ->(type) {
137
143
  outer_type = type.type
138
144
  inner_type = outer_type.respond_to?(:unwrap) ? outer_type.unwrap : nil
@@ -265,8 +271,10 @@ module GraphQL
265
271
  Rules::NAME_IS_STRING,
266
272
  Rules::RESERVED_NAME,
267
273
  Rules::DESCRIPTION_IS_STRING_OR_NIL,
274
+ Rules.assert_property(:deprecation_reason, String, NilClass),
268
275
  Rules::TYPE_IS_VALID_INPUT_TYPE,
269
276
  Rules::DEFAULT_VALUE_IS_VALID_FOR_TYPE,
277
+ Rules::DEPRECATED_ARGUMENTS_ARE_OPTIONAL,
270
278
  ],
271
279
  GraphQL::BaseType => [
272
280
  Rules::NAME_IS_STRING,
@@ -32,10 +32,13 @@ module GraphQL
32
32
 
33
33
  context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class)
34
34
 
35
- # Attach legacy-style rules
36
- rules_to_use.each do |rule_class_or_module|
37
- if rule_class_or_module.method_defined?(:validate)
38
- rule_class_or_module.new.validate(context)
35
+ # Attach legacy-style rules.
36
+ # Only loop through rules if it has legacy-style rules
37
+ unless (legacy_rules = rules_to_use - GraphQL::StaticValidation::ALL_RULES).empty?
38
+ legacy_rules.each do |rule_class_or_module|
39
+ if rule_class_or_module.method_defined?(:validate)
40
+ rule_class_or_module.new.validate(context)
41
+ end
39
42
  end
40
43
  end
41
44
 
@@ -4,9 +4,7 @@ require "graphql/subscriptions/broadcast_analyzer"
4
4
  require "graphql/subscriptions/event"
5
5
  require "graphql/subscriptions/instrumentation"
6
6
  require "graphql/subscriptions/serialize"
7
- if defined?(ActionCable)
8
- require "graphql/subscriptions/action_cable_subscriptions"
9
- end
7
+ require "graphql/subscriptions/action_cable_subscriptions"
10
8
  require "graphql/subscriptions/subscription_root"
11
9
  require "graphql/subscriptions/default_subscription_resolve_extension"
12
10
 
@@ -4,7 +4,7 @@ module GraphQL
4
4
  # A subscriptions implementation that sends data
5
5
  # as ActionCable broadcastings.
6
6
  #
7
- # Experimental, some things to keep in mind:
7
+ # Some things to keep in mind:
8
8
  #
9
9
  # - No queueing system; ActiveJob should be added
10
10
  # - Take care to reload context when re-delivering the subscription. (see {Query#subscription_update?})
@@ -86,28 +86,32 @@ module GraphQL
86
86
  EVENT_PREFIX = "graphql-event:"
87
87
 
88
88
  # @param serializer [<#dump(obj), #load(string)] Used for serializing messages before handing them to `.broadcast(msg)`
89
- def initialize(serializer: Serialize, **rest)
89
+ # @param namespace [string] Used to namespace events and subscriptions (default: '')
90
+ def initialize(serializer: Serialize, namespace: '', action_cable: ActionCable, action_cable_coder: ActiveSupport::JSON, **rest)
90
91
  # A per-process map of subscriptions to deliver.
91
92
  # This is provided by Rails, so let's use it
92
93
  @subscriptions = Concurrent::Map.new
93
94
  @events = Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new { |h2, k2| h2[k2] = Concurrent::Array.new } }
95
+ @action_cable = action_cable
96
+ @action_cable_coder = action_cable_coder
94
97
  @serializer = serializer
98
+ @transmit_ns = namespace
95
99
  super
96
100
  end
97
101
 
98
102
  # An event was triggered; Push the data over ActionCable.
99
103
  # Subscribers will re-evaluate locally.
100
104
  def execute_all(event, object)
101
- stream = EVENT_PREFIX + event.topic
105
+ stream = stream_event_name(event)
102
106
  message = @serializer.dump(object)
103
- ActionCable.server.broadcast(stream, message)
107
+ @action_cable.server.broadcast(stream, message)
104
108
  end
105
109
 
106
110
  # This subscription was re-evaluated.
107
111
  # Send it to the specific stream where this client was waiting.
108
112
  def deliver(subscription_id, result)
109
113
  payload = { result: result.to_h, more: true }
110
- ActionCable.server.broadcast(SUBSCRIPTION_PREFIX + subscription_id, payload)
114
+ @action_cable.server.broadcast(stream_subscription_name(subscription_id), payload)
111
115
  end
112
116
 
113
117
  # A query was run where these events were subscribed to.
@@ -117,7 +121,7 @@ module GraphQL
117
121
  def write_subscription(query, events)
118
122
  channel = query.context.fetch(:channel)
119
123
  subscription_id = query.context[:subscription_id] ||= build_id
120
- stream = query.context[:action_cable_stream] ||= SUBSCRIPTION_PREFIX + subscription_id
124
+ stream = stream_subscription_name(subscription_id)
121
125
  channel.stream_from(stream)
122
126
  @subscriptions[subscription_id] = query
123
127
  events.each do |event|
@@ -141,7 +145,7 @@ module GraphQL
141
145
  #
142
146
  def setup_stream(channel, initial_event)
143
147
  topic = initial_event.topic
144
- channel.stream_from(EVENT_PREFIX + topic, coder: ActiveSupport::JSON) do |message|
148
+ channel.stream_from(stream_event_name(initial_event), coder: @action_cable_coder) do |message|
145
149
  object = @serializer.load(message)
146
150
  events_by_fingerprint = @events[topic]
147
151
  events_by_fingerprint.each do |_fingerprint, events|
@@ -197,6 +201,16 @@ module GraphQL
197
201
  end
198
202
  end
199
203
  end
204
+
205
+ private
206
+
207
+ def stream_subscription_name(subscription_id)
208
+ [SUBSCRIPTION_PREFIX, @transmit_ns, subscription_id].join
209
+ end
210
+
211
+ def stream_event_name(event)
212
+ [EVENT_PREFIX, @transmit_ns, event.topic].join
213
+ end
200
214
  end
201
215
  end
202
216
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.11.4"
3
+ VERSION = "1.11.5"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.11.4
4
+ version: 1.11.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-25 00:00:00.000000000 Z
11
+ date: 2020-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips