graphql 1.11.4 → 1.11.5

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