graphql 1.12.19 → 1.12.20

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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 92aba0a1935a0022b97ea1630cde64884a727ce714fae6a2bca543666c73bde0
4
- data.tar.gz: 5db896ad77a52ec4a025f526bab4f3c4b3d18206e009c2d8c3e14dd434f12ae6
3
+ metadata.gz: b8312601f8e51973aaa9418c4c4691b6eab97140351c91d47f6c2c4f4deb88fb
4
+ data.tar.gz: 4cb698ef0a6739ceca4026b4dfaa594faf5658ac10a6005b051b70ec34af7f41
5
5
  SHA512:
6
- metadata.gz: 2985936af9bba4d0feec8263dd4a6c60c09110e39aba1a97359e4d02900732f02900e30d798074932bb6fe2a5ed0e00441be859bb3035ab0c819ccec3c9a0f66
7
- data.tar.gz: e8e961e540b54a5c54d6d78c444eef422bd55264ee8ffc9c3a231f2614c570ee75de5c2888f3b13974304c48f60072e04bb0d0444ea6e1a13f4a37652dbd2f0b
6
+ metadata.gz: e9122ecfa0c4c28d219af2c828adb1b5e7e0ae29b10df55f543cbfa8973d8d9706ba5258c3de4f620b6cba5f16d6e6e031ebb6605193153ce5be821eaeb1adaa
7
+ data.tar.gz: 4db8f3435262d72f1d0d1995407f83c76a55dc0b5f28f7b5d8c6c4a378b2da00e4119e371585bdfac847ae5661c6e91e3bfdfcde82cd232198456905dee1f1a0
@@ -32,20 +32,28 @@ module Graphql
32
32
 
33
33
  # Return a string UUID for `object`
34
34
  def self.id_from_object(object, type_definition, query_ctx)
35
- # Here's a simple implementation which:
36
- # - joins the type name & object.id
37
- # - encodes it with base64:
38
- # GraphQL::Schema::UniqueWithinType.encode(type_definition.name, object.id)
35
+ # For example, use Rails' GlobalID library (https://github.com/rails/globalid):
36
+ object_id = object.to_global_id.to_s
37
+ # Remove this redundant prefix to make IDs shorter:
38
+ object_id = object_id.sub("gid://\#{GlobalID.app}/", "")
39
+ encoded_id = Base64.urlsafe_encode64(object_id)
40
+ # Remove the "=" padding
41
+ encoded_id = encoded_id.sub(/=+/, "")
42
+ # Add a type hint
43
+ type_hint = type_definition.graphql_name.first
44
+ "\#{type_hint}_\#{encoded_id}"
39
45
  end
40
46
 
41
47
  # Given a string UUID, find the object
42
- def self.object_from_id(id, query_ctx)
43
- # For example, to decode the UUIDs generated above:
44
- # type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id)
45
- #
46
- # Then, based on `type_name` and `id`
47
- # find an object in your application
48
- # ...
48
+ def self.object_from_id(encoded_id_with_hint, query_ctx)
49
+ # For example, use Rails' GlobalID library (https://github.com/rails/globalid):
50
+ # Split off the type hint
51
+ _type_hint, encoded_id = encoded_id_with_hint.split("_", 2)
52
+ # Decode the ID
53
+ id = Base64.urlsafe_decode64(encoded_id)
54
+ # Rebuild it for Rails then find the object:
55
+ full_global_id = "gid://\#{GlobalID.app}/\#{id}"
56
+ GlobalID::Locator.locate(full_global_id)
49
57
  end
50
58
  RUBY
51
59
  inject_into_file schema_file_path, schema_code, before: /^end\n/m, force: false
@@ -4,11 +4,23 @@ class <%= schema_name %> < GraphQL::Schema
4
4
  <% if options[:batch] %>
5
5
  # GraphQL::Batch setup:
6
6
  use GraphQL::Batch
7
+ <% else %>
8
+ # For batch-loading (see https://graphql-ruby.org/dataloader/overview.html)
9
+ use GraphQL::Dataloader
7
10
  <% end %>
11
+ # GraphQL-Ruby calls this when something goes wrong while running a query:
12
+ def self.type_error(err)
13
+ # if err.is_a?(GraphQL::InvalidNullError)
14
+ # # report to your bug tracker here
15
+ # return nil
16
+ # end
17
+ super
18
+ end
19
+
8
20
  # Union and Interface Resolution
9
21
  def self.resolve_type(abstract_type, obj, ctx)
10
22
  # TODO: Implement this function
11
- # to return the correct object type for `obj`
23
+ # to return the correct GraphQL object type for `obj`
12
24
  raise(GraphQL::RequiredImplementationMissingError)
13
25
  end
14
26
  end
@@ -6,7 +6,11 @@ module GraphQL
6
6
  # Called by {Dataloader} to prepare the {Source}'s internal state
7
7
  # @api private
8
8
  def setup(dataloader)
9
+ # These keys have been requested but haven't been fetched yet
9
10
  @pending_keys = []
11
+ # These keys have been passed to `fetch` but haven't been finished yet
12
+ @fetching_keys = []
13
+ # { key => result }
10
14
  @results = {}
11
15
  @dataloader = dataloader
12
16
  end
@@ -64,29 +68,46 @@ module GraphQL
64
68
  # Then run the batch and update the cache.
65
69
  # @return [void]
66
70
  def sync
71
+ pending_keys = @pending_keys.dup
67
72
  @dataloader.yield
73
+ iterations = 0
74
+ while pending_keys.any? { |k| !@results.key?(k) }
75
+ iterations += 1
76
+ if iterations > 1000
77
+ raise "#{self.class}#sync tried 1000 times to load pending keys (#{pending_keys}), but they still weren't loaded. There is likely a circular dependency."
78
+ end
79
+ @dataloader.yield
80
+ end
81
+ nil
68
82
  end
69
83
 
70
84
  # @return [Boolean] True if this source has any pending requests for data.
71
85
  def pending?
72
- @pending_keys.any?
86
+ !@pending_keys.empty?
73
87
  end
74
88
 
75
89
  # Called by {GraphQL::Dataloader} to resolve and pending requests to this source.
76
90
  # @api private
77
91
  # @return [void]
78
92
  def run_pending_keys
93
+ if !@fetching_keys.empty?
94
+ @pending_keys -= @fetching_keys
95
+ end
79
96
  return if @pending_keys.empty?
80
97
  fetch_keys = @pending_keys.uniq
98
+ @fetching_keys.concat(fetch_keys)
81
99
  @pending_keys = []
82
100
  results = fetch(fetch_keys)
83
101
  fetch_keys.each_with_index do |key, idx|
84
102
  @results[key] = results[idx]
85
103
  end
104
+ nil
86
105
  rescue StandardError => error
87
106
  fetch_keys.each { |key| @results[key] = error }
88
107
  ensure
89
- nil
108
+ if fetch_keys
109
+ @fetching_keys -= fetch_keys
110
+ end
90
111
  end
91
112
 
92
113
  # These arguments are given to `dataloader.with(source_class, ...)`. The object
@@ -116,6 +137,13 @@ module GraphQL
116
137
  # @return [Object] The result from {#fetch} for `key`.
117
138
  # @api private
118
139
  def result_for(key)
140
+ if !@results.key?(key)
141
+ raise <<-ERR
142
+ Invariant: fetching result for a key on #{self.class} that hasn't been loaded yet (#{key.inspect}, loaded: #{@results.keys})
143
+
144
+ This key should have been loaded already. This is a bug in GraphQL::Dataloader, please report it on GitHub: https://github.com/rmosolgo/graphql-ruby/issues/new.
145
+ ERR
146
+ end
119
147
  result = @results[key]
120
148
 
121
149
  raise result if result.class <= StandardError
@@ -3,11 +3,7 @@
3
3
  module GraphQL
4
4
  module Deprecation
5
5
  def self.warn(message)
6
- if defined?(ActiveSupport::Deprecation)
7
- ActiveSupport::Deprecation.warn(message)
8
- else
9
- Kernel.warn(message)
10
- end
6
+ Kernel.warn(message)
11
7
  end
12
8
  end
13
9
  end
@@ -70,23 +70,42 @@ module GraphQL
70
70
  wrappers = context ? context.namespace(:connections)[:all_wrappers] : all_wrappers
71
71
  impl = wrapper_for(items, wrappers: wrappers)
72
72
 
73
- if impl.nil?
74
- raise ImplementationMissingError, "Couldn't find a connection wrapper for #{items.class} during #{field.path} (#{items.inspect})"
73
+ if impl
74
+ impl.new(
75
+ items,
76
+ context: context,
77
+ parent: parent,
78
+ field: field,
79
+ max_page_size: field.has_max_page_size? ? field.max_page_size : context.schema.default_max_page_size,
80
+ first: arguments[:first],
81
+ after: arguments[:after],
82
+ last: arguments[:last],
83
+ before: arguments[:before],
84
+ arguments: arguments,
85
+ edge_class: edge_class_for_field(field),
86
+ )
87
+ else
88
+ begin
89
+ connection_class = GraphQL::Relay::BaseConnection.connection_for_nodes(items)
90
+ if parent.is_a?(GraphQL::Schema::Object)
91
+ parent = parent.object
92
+ end
93
+ connection_class.new(
94
+ items,
95
+ arguments,
96
+ field: field,
97
+ max_page_size: field.max_page_size,
98
+ parent: parent,
99
+ context: context,
100
+ )
101
+ rescue RuntimeError => err
102
+ if err.message.include?("No connection implementation to wrap")
103
+ raise ImplementationMissingError, "Couldn't find a connection wrapper for #{items.class} during #{field.path} (#{items.inspect})"
104
+ else
105
+ raise err
106
+ end
107
+ end
75
108
  end
76
-
77
- impl.new(
78
- items,
79
- context: context,
80
- parent: parent,
81
- field: field,
82
- max_page_size: field.has_max_page_size? ? field.max_page_size : context.schema.default_max_page_size,
83
- first: arguments[:first],
84
- after: arguments[:after],
85
- last: arguments[:last],
86
- before: arguments[:before],
87
- arguments: arguments,
88
- edge_class: edge_class_for_field(field),
89
- )
90
109
  end
91
110
 
92
111
  # use an override if there is one
@@ -32,8 +32,10 @@ module GraphQL
32
32
 
33
33
  private
34
34
 
35
+ attr_reader :nodes
36
+
35
37
  def locations
36
- @nodes.map do |node|
38
+ nodes.map do |node|
37
39
  h = {"line" => node.line, "column" => node.col}
38
40
  h["filename"] = node.filename if node.filename
39
41
  h
@@ -10,6 +10,7 @@ module GraphQL
10
10
  #
11
11
  # Original Algorithm: https://github.com/graphql/graphql-js/blob/master/src/validation/rules/OverlappingFieldsCanBeMerged.js
12
12
  NO_ARGS = {}.freeze
13
+
13
14
  Field = Struct.new(:node, :definition, :owner_type, :parents)
14
15
  FragmentSpread = Struct.new(:name, :parents)
15
16
 
@@ -17,20 +18,43 @@ module GraphQL
17
18
  super
18
19
  @visited_fragments = {}
19
20
  @compared_fragments = {}
21
+ @conflict_count = 0
20
22
  end
21
23
 
22
24
  def on_operation_definition(node, _parent)
23
- conflicts_within_selection_set(node, type_definition)
25
+ setting_errors { conflicts_within_selection_set(node, type_definition) }
24
26
  super
25
27
  end
26
28
 
27
29
  def on_field(node, _parent)
28
- conflicts_within_selection_set(node, type_definition)
30
+ setting_errors { conflicts_within_selection_set(node, type_definition) }
29
31
  super
30
32
  end
31
33
 
32
34
  private
33
35
 
36
+ def field_conflicts
37
+ @field_conflicts ||= Hash.new do |errors, field|
38
+ errors[field] = GraphQL::StaticValidation::FieldsWillMergeError.new(kind: :field, field_name: field)
39
+ end
40
+ end
41
+
42
+ def arg_conflicts
43
+ @arg_conflicts ||= Hash.new do |errors, field|
44
+ errors[field] = GraphQL::StaticValidation::FieldsWillMergeError.new(kind: :argument, field_name: field)
45
+ end
46
+ end
47
+
48
+ def setting_errors
49
+ @field_conflicts = nil
50
+ @arg_conflicts = nil
51
+
52
+ yield
53
+
54
+ field_conflicts.each_value { |error| add_error(error) }
55
+ arg_conflicts.each_value { |error| add_error(error) }
56
+ end
57
+
34
58
  def conflicts_within_selection_set(node, parent_type)
35
59
  return if parent_type.nil?
36
60
 
@@ -183,6 +207,8 @@ module GraphQL
183
207
  end
184
208
 
185
209
  def find_conflict(response_key, field1, field2, mutually_exclusive: false)
210
+ return if @conflict_count >= context.max_errors
211
+
186
212
  node1 = field1.node
187
213
  node2 = field2.node
188
214
 
@@ -191,28 +217,21 @@ module GraphQL
191
217
 
192
218
  if !are_mutually_exclusive
193
219
  if node1.name != node2.name
194
- errored_nodes = [node1.name, node2.name].sort.join(" or ")
195
- msg = "Field '#{response_key}' has a field conflict: #{errored_nodes}?"
196
- add_error(GraphQL::StaticValidation::FieldsWillMergeError.new(
197
- msg,
198
- nodes: [node1, node2],
199
- path: [],
200
- field_name: response_key,
201
- conflicts: errored_nodes
202
- ))
220
+ conflict = field_conflicts[response_key]
221
+
222
+ conflict.add_conflict(node1, node1.name)
223
+ conflict.add_conflict(node2, node2.name)
224
+
225
+ @conflict_count += 1
203
226
  end
204
227
 
205
228
  if !same_arguments?(node1, node2)
206
- args = [serialize_field_args(node1), serialize_field_args(node2)]
207
- conflicts = args.map { |arg| GraphQL::Language.serialize(arg) }.join(" or ")
208
- msg = "Field '#{response_key}' has an argument conflict: #{conflicts}?"
209
- add_error(GraphQL::StaticValidation::FieldsWillMergeError.new(
210
- msg,
211
- nodes: [node1, node2],
212
- path: [],
213
- field_name: response_key,
214
- conflicts: conflicts
215
- ))
229
+ conflict = arg_conflicts[response_key]
230
+
231
+ conflict.add_conflict(node1, GraphQL::Language.serialize(serialize_field_args(node1)))
232
+ conflict.add_conflict(node2, GraphQL::Language.serialize(serialize_field_args(node2)))
233
+
234
+ @conflict_count += 1
216
235
  end
217
236
  end
218
237
 
@@ -3,12 +3,33 @@ module GraphQL
3
3
  module StaticValidation
4
4
  class FieldsWillMergeError < StaticValidation::Error
5
5
  attr_reader :field_name
6
- attr_reader :conflicts
6
+ attr_reader :kind
7
+
8
+ def initialize(kind:, field_name:)
9
+ super(nil)
7
10
 
8
- def initialize(message, path: nil, nodes: [], field_name:, conflicts:)
9
- super(message, path: path, nodes: nodes)
10
11
  @field_name = field_name
11
- @conflicts = conflicts
12
+ @kind = kind
13
+ @conflicts = []
14
+ end
15
+
16
+ def message
17
+ "Field '#{field_name}' has #{kind == :argument ? 'an' : 'a'} #{kind} conflict: #{conflicts}?"
18
+ end
19
+
20
+ def path
21
+ []
22
+ end
23
+
24
+ def conflicts
25
+ @conflicts.join(' or ')
26
+ end
27
+
28
+ def add_conflict(node, conflict_str)
29
+ return if nodes.include?(node)
30
+
31
+ @nodes << node
32
+ @conflicts << conflict_str
12
33
  end
13
34
 
14
35
  # A hash representation of this Message
@@ -15,7 +15,8 @@ module GraphQL
15
15
  extend Forwardable
16
16
 
17
17
  attr_reader :query, :errors, :visitor,
18
- :on_dependency_resolve_handlers
18
+ :on_dependency_resolve_handlers,
19
+ :max_errors
19
20
 
20
21
  def_delegators :@query, :schema, :document, :fragments, :operations, :warden
21
22
 
@@ -53,6 +53,36 @@ module GraphQL
53
53
 
54
54
  class << self
55
55
  private
56
+
57
+ # This method does not support cyclic references in the Hash,
58
+ # nor does it support Hashes whose keys are not sortable
59
+ # with respect to their peers ( cases where a <=> b might throw an error )
60
+ def deep_sort_hash_keys(hash_to_sort)
61
+ raise ArgumentError.new("Argument must be a Hash") unless hash_to_sort.is_a?(Hash)
62
+ hash_to_sort.keys.sort.map do |k|
63
+ if hash_to_sort[k].is_a?(Hash)
64
+ [k, deep_sort_hash_keys(hash_to_sort[k])]
65
+ elsif hash_to_sort[k].is_a?(Array)
66
+ [k, deep_sort_array_hashes(hash_to_sort[k])]
67
+ else
68
+ [k, hash_to_sort[k]]
69
+ end
70
+ end.to_h
71
+ end
72
+
73
+ def deep_sort_array_hashes(array_to_inspect)
74
+ raise ArgumentError.new("Argument must be an Array") unless array_to_inspect.is_a?(Array)
75
+ array_to_inspect.map do |v|
76
+ if v.is_a?(Hash)
77
+ deep_sort_hash_keys(v)
78
+ elsif v.is_a?(Array)
79
+ deep_sort_array_hashes(v)
80
+ else
81
+ v
82
+ end
83
+ end
84
+ end
85
+
56
86
  def stringify_args(arg_owner, args)
57
87
  arg_owner = arg_owner.respond_to?(:unwrap) ? arg_owner.unwrap : arg_owner # remove list and non-null wrappers
58
88
  case args
@@ -65,12 +95,26 @@ module GraphQL
65
95
 
66
96
  if arg_defn
67
97
  normalized_arg_name = camelized_arg_name
68
- next_args[normalized_arg_name] = stringify_args(arg_defn.type, v)
69
98
  else
70
99
  normalized_arg_name = arg_name
71
100
  arg_defn = get_arg_definition(arg_owner, normalized_arg_name)
72
101
  end
73
- next_args[normalized_arg_name] = stringify_args(arg_defn.type, v)
102
+ arg_base_type = arg_defn.type.unwrap
103
+ # In the case where the value being emitted is seen as a "JSON"
104
+ # type, treat the value as one atomic unit of serialization
105
+ is_json_definition = arg_base_type && arg_base_type <= GraphQL::Types::JSON
106
+ if is_json_definition
107
+ sorted_value = if v.is_a?(Hash)
108
+ deep_sort_hash_keys(v)
109
+ elsif v.is_a?(Array)
110
+ deep_sort_array_hashes(v)
111
+ else
112
+ v
113
+ end
114
+ next_args[normalized_arg_name] = sorted_value.respond_to?(:to_json) ? sorted_value.to_json : sorted_value
115
+ else
116
+ next_args[normalized_arg_name] = stringify_args(arg_base_type, v)
117
+ end
74
118
  end
75
119
  # Make sure they're deeply sorted
76
120
  next_args.sort.to_h
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.12.19"
3
+ VERSION = "1.12.20"
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.12.19
4
+ version: 1.12.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-05 00:00:00.000000000 Z
11
+ date: 2021-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips