graphql-client 0.18.0 → 0.22.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82b89599e02036868b2249fbd3ac2aeb89b6043b19040bdaeaf66f31b5f1b28e
4
- data.tar.gz: 2e168f61452bd5041aa01dce3a09c99959b629afeb2880ca629d1034f22fb0d9
3
+ metadata.gz: 4064d832c0bf6433b6b6629c50d5c20182164bb6e870661d670fd6ba3ab0ecdc
4
+ data.tar.gz: 21f4aea0033c36f86d14ae4fe52294ae0193cc1485154cf3b15519b9ac0b8d8f
5
5
  SHA512:
6
- metadata.gz: 3f14f21ff57e8dc29dbef6e5fb2ee6d2fb552075986ef57c8f89a3d7ce5b2faa8e2965b20a5b8f7735394b3ee13cdc8e55923bda0dd02fa8625f7e2b824dab7b
7
- data.tar.gz: ee04576697c258bdc3959366d0cccef720e24eb2358ecbb28c020d912c5b241ac7ba195a8240656c6f825109c23a321ef2449892b4d893e29802c03b6580f557
6
+ metadata.gz: 4507464c1a14fcac7aecaacf07d9b8297f288b717d7c59e12368426b427d5bdeacf7c19fd3e037df8d26482ad719b8edfc2dd26665d8d59518a10720ab1968c0
7
+ data.tar.gz: f1c34616ea07b5bb4926f7489225859b3b9bff50485a90d74a55f9ce4e4aca74eeb5420cb793522470de9ac3ccbf85628ac0d3c3b0e01955024aebaf5a420d37
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # graphql-client [![Gem Version](https://badge.fury.io/rb/graphql-client.svg)](https://badge.fury.io/rb/graphql-client) [![CI](https://github.com/github/graphql-client/workflows/CI/badge.svg)](https://github.com/github/graphql-client/actions?query=workflow)
1
+ # graphql-client [![Gem Version](https://badge.fury.io/rb/graphql-client.svg)](https://badge.fury.io/rb/graphql-client) [![CI](https://github.com/github-community-projects/graphql-client/workflows/CI/badge.svg)](https://github.com/github-community-projects/graphql-client/actions?query=workflow)
2
2
 
3
3
  GraphQL Client is a Ruby library for declaring, composing and executing GraphQL queries.
4
4
 
@@ -26,7 +26,7 @@ module GraphQL
26
26
  return yield if Thread.current[:query_result_caller_location_ignore]
27
27
 
28
28
  if (location.path != path) && !(WHITELISTED_GEM_NAMES.any? { |g| location.path.include?("gems/#{g}") })
29
- error = NonCollocatedCallerError.new("#{method} was called outside of '#{path}' https://git.io/v1syX")
29
+ error = NonCollocatedCallerError.new("#{method} was called outside of '#{path}' https://github.com/github-community-projects/graphql-client/blob/master/guides/collocated-call-sites.md")
30
30
  error.set_backtrace(caller(2))
31
31
  raise error
32
32
  end
@@ -147,41 +147,58 @@ module GraphQL
147
147
  # Internal: Nodes AST indexes.
148
148
  def indexes
149
149
  @indexes ||= begin
150
- visitor = GraphQL::Language::Visitor.new(document)
151
- definitions = index_node_definitions(visitor)
152
- spreads = index_spreads(visitor)
150
+ visitor = DefinitionVisitor.new(document)
153
151
  visitor.visit
154
- { definitions: definitions, spreads: spreads }
152
+ { definitions: visitor.definitions, spreads: visitor.spreads }
155
153
  end
156
154
  end
157
155
 
158
- private
156
+ class DefinitionVisitor < GraphQL::Language::Visitor
157
+ attr_reader :spreads, :definitions
159
158
 
160
- def cast_object(obj)
161
- if obj.class.is_a?(GraphQL::Client::Schema::ObjectType)
162
- unless obj._spreads.include?(definition_node.name)
163
- raise TypeError, "#{definition_node.name} is not included in #{obj.source_definition.name}"
164
- end
165
- schema_class.cast(obj.to_h, obj.errors)
166
- else
167
- raise TypeError, "unexpected #{obj.class}"
168
- end
159
+ def initialize(doc)
160
+ super
161
+ @spreads = {}
162
+ @definitions = {}
163
+ @current_definition = nil
169
164
  end
170
165
 
171
- EMPTY_SET = Set.new.freeze
166
+ def on_field(node, parent)
167
+ @definitions[node] = @current_definition
168
+ @spreads[node] = get_spreads(node)
169
+ super
170
+ end
172
171
 
173
- def index_spreads(visitor)
174
- spreads = {}
175
- on_node = ->(node, _parent) do
176
- node_spreads = flatten_spreads(node).map(&:name)
177
- spreads[node] = node_spreads.empty? ? EMPTY_SET : Set.new(node_spreads).freeze
178
- end
172
+ def on_fragment_definition(node, parent)
173
+ @current_definition = node
174
+ @definitions[node] = @current_definition
175
+ @spreads[node] = get_spreads(node)
176
+ super
177
+ ensure
178
+ @current_definition = nil
179
+ end
179
180
 
180
- visitor[GraphQL::Language::Nodes::Field] << on_node
181
- visitor[GraphQL::Language::Nodes::FragmentDefinition] << on_node
182
- visitor[GraphQL::Language::Nodes::OperationDefinition] << on_node
181
+ def on_operation_definition(node, parent)
182
+ @current_definition = node
183
+ @definitions[node] = @current_definition
184
+ @spreads[node] = get_spreads(node)
185
+ super
186
+ ensure
187
+ @current_definition = nil
188
+ end
183
189
 
184
- spreads
190
+ def on_inline_fragment(node, parent)
191
+ @definitions[node] = @current_definition
192
+ super
193
+ end
194
+
195
+ private
196
+
197
+ EMPTY_SET = Set.new.freeze
198
+
199
+ def get_spreads(node)
200
+ node_spreads = flatten_spreads(node).map(&:name)
201
+ node_spreads.empty? ? EMPTY_SET : Set.new(node_spreads).freeze
185
202
  end
186
203
 
187
204
  def flatten_spreads(node)
@@ -198,25 +215,20 @@ module GraphQL
198
215
  end
199
216
  spreads
200
217
  end
218
+ end
201
219
 
202
- def index_node_definitions(visitor)
203
- current_definition = nil
204
- enter_definition = ->(node, _parent) { current_definition = node }
205
- leave_definition = ->(node, _parent) { current_definition = nil }
206
-
207
- visitor[GraphQL::Language::Nodes::FragmentDefinition].enter << enter_definition
208
- visitor[GraphQL::Language::Nodes::FragmentDefinition].leave << leave_definition
209
- visitor[GraphQL::Language::Nodes::OperationDefinition].enter << enter_definition
210
- visitor[GraphQL::Language::Nodes::OperationDefinition].leave << leave_definition
211
-
212
- definitions = {}
213
- on_node = ->(node, _parent) { definitions[node] = current_definition }
214
- visitor[GraphQL::Language::Nodes::Field] << on_node
215
- visitor[GraphQL::Language::Nodes::FragmentDefinition] << on_node
216
- visitor[GraphQL::Language::Nodes::InlineFragment] << on_node
217
- visitor[GraphQL::Language::Nodes::OperationDefinition] << on_node
218
- definitions
220
+ private
221
+
222
+ def cast_object(obj)
223
+ if obj.class.is_a?(GraphQL::Client::Schema::ObjectType)
224
+ unless obj._spreads.include?(definition_node.name)
225
+ raise TypeError, "#{definition_node.name} is not included in #{obj.source_definition.name}"
226
+ end
227
+ schema_class.cast(obj.to_h, obj.errors)
228
+ else
229
+ raise TypeError, "unexpected #{obj.class}"
219
230
  end
231
+ end
220
232
  end
221
233
  end
222
234
  end
@@ -24,26 +24,33 @@ module GraphQL
24
24
 
25
25
  sliced_document = GraphQL::Language::DefinitionSlice.slice(document, definition_name)
26
26
 
27
- visitor = GraphQL::Language::Visitor.new(sliced_document)
28
- type_stack = GraphQL::StaticValidation::TypeStack.new(schema, visitor)
27
+ visitor = VariablesVisitor.new(sliced_document, schema: schema)
28
+ visitor.visit
29
+ visitor.variables
30
+ end
31
+
32
+ class VariablesVisitor < GraphQL::Language::Visitor
33
+ prepend GraphQL::Client::TypeStack
34
+
35
+ def initialize(*_args, **_kwargs)
36
+ super
37
+ @variables = {}
38
+ end
29
39
 
30
- variables = {}
40
+ attr_reader :variables
31
41
 
32
- visitor[GraphQL::Language::Nodes::VariableIdentifier] << ->(node, parent) do
33
- if definition = type_stack.argument_definitions.last
34
- existing_type = variables[node.name.to_sym]
42
+ def on_variable_identifier(node, parent)
43
+ if definition = @argument_definitions.last
44
+ existing_type = @variables[node.name.to_sym]
35
45
 
36
46
  if existing_type && existing_type.unwrap != definition.type.unwrap
37
47
  raise GraphQL::Client::ValidationError, "$#{node.name} was already declared as #{existing_type.unwrap}, but was #{definition.type.unwrap}"
38
48
  elsif !(existing_type && existing_type.kind.non_null?)
39
- variables[node.name.to_sym] = definition.type
49
+ @variables[node.name.to_sym] = definition.type
40
50
  end
41
51
  end
52
+ super
42
53
  end
43
-
44
- visitor.visit
45
-
46
- variables
47
54
  end
48
55
 
49
56
  # Internal: Detect all variables used in a given operation or fragment
@@ -1,10 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql"
3
+ require "graphql/client/type_stack"
3
4
 
4
5
  module GraphQL
5
6
  class Client
6
7
  # Internal: Use schema to detect definition and field types.
7
8
  module DocumentTypes
9
+ class AnalyzeTypesVisitor < GraphQL::Language::Visitor
10
+ prepend GraphQL::Client::TypeStack
11
+ attr_reader :fields
12
+
13
+ def initialize(*a, **kw)
14
+ @fields = {}
15
+ super
16
+ end
17
+
18
+ def on_operation_definition(node, _parent)
19
+ @fields[node] = @object_types.last
20
+ super
21
+ end
22
+
23
+ def on_fragment_definition(node, _parent)
24
+ @fields[node] = @object_types.last
25
+ super
26
+ end
27
+
28
+ def on_inline_fragment(node, _parent)
29
+ @fields[node] = @object_types.last
30
+ super
31
+ end
32
+
33
+ def on_field(node, _parent)
34
+ @fields[node] = @field_definitions.last.type
35
+ super
36
+ end
37
+ end
38
+
8
39
  # Internal: Detect all types used in a given document
9
40
  #
10
41
  # schema - A GraphQL::Schema
@@ -20,32 +51,15 @@ module GraphQL
20
51
  raise TypeError, "expected schema to be a GraphQL::Language::Nodes::Document, but was #{document.class}"
21
52
  end
22
53
 
23
- visitor = GraphQL::Language::Visitor.new(document)
24
- type_stack = GraphQL::StaticValidation::TypeStack.new(schema, visitor)
25
-
26
- fields = {}
27
-
28
- visitor[GraphQL::Language::Nodes::OperationDefinition] << ->(node, _parent) do
29
- fields[node] = type_stack.object_types.last
30
- end
31
- visitor[GraphQL::Language::Nodes::FragmentDefinition] << ->(node, _parent) do
32
- fields[node] = type_stack.object_types.last
33
- end
34
- visitor[GraphQL::Language::Nodes::InlineFragment] << ->(node, _parent) do
35
- fields[node] = type_stack.object_types.last
36
- end
37
- visitor[GraphQL::Language::Nodes::Field] << ->(node, _parent) do
38
- fields[node] = type_stack.field_definitions.last.type
39
- end
54
+ visitor = AnalyzeTypesVisitor.new(document, schema: schema)
40
55
  visitor.visit
41
-
42
- fields
56
+ visitor.fields
43
57
  rescue StandardError => err
44
58
  if err.is_a?(TypeError)
45
59
  raise
46
60
  end
47
61
  # FIXME: TypeStack my crash on invalid documents
48
- fields
62
+ visitor.fields
49
63
  end
50
64
  end
51
65
  end
@@ -21,7 +21,7 @@ module GraphQL
21
21
  errors.each do |error|
22
22
  path = ["data"]
23
23
  current = data
24
- error.fetch("path", []).each do |key|
24
+ error["path"].to_a.each do |key|
25
25
  break unless current
26
26
  path << key
27
27
  current = current[key]
@@ -16,11 +16,18 @@ module GraphQL
16
16
  # GraphQL::Client::LogSubscriber.attach_to :graphql
17
17
  #
18
18
  class LogSubscriber < ActiveSupport::LogSubscriber
19
+ SHOULD_USE_KWARGS = private_instance_methods.include?(:mode_from)
20
+
19
21
  def query(event)
20
22
  logger.info do
21
23
  name = event.payload[:operation_name].gsub("__", "::")
22
24
  type = event.payload[:operation_type].upcase
23
- color("#{name} #{type} (#{event.duration.round(1)}ms)", nil, true)
25
+
26
+ if SHOULD_USE_KWARGS
27
+ color("#{name} #{type} (#{event.duration.round(1)}ms)", nil, bold: true)
28
+ else
29
+ color("#{name} #{type} (#{event.duration.round(1)}ms)", nil, true)
30
+ end
24
31
  end
25
32
 
26
33
  logger.debug do
@@ -32,7 +39,12 @@ module GraphQL
32
39
  logger.error do
33
40
  name = event.payload[:operation_name].gsub("__", "::")
34
41
  message = event.payload[:message]
35
- color("#{name} ERROR: #{message}", nil, true)
42
+
43
+ if SHOULD_USE_KWARGS
44
+ color("#{name} ERROR: #{message}", nil, bold: true)
45
+ else
46
+ color("#{name} ERROR: #{message}", nil, true)
47
+ end
36
48
  end
37
49
  end
38
50
  end
@@ -12,6 +12,7 @@ module GraphQL
12
12
  # Returns Hash.
13
13
  attr_reader :original_hash
14
14
  alias_method :to_h, :original_hash
15
+ alias_method :to_hash, :original_hash
15
16
 
16
17
  # Public: Wrapped ObjectType of data returned from the server.
17
18
  #
@@ -16,6 +16,10 @@ module GraphQL
16
16
  @enum = enum
17
17
  end
18
18
 
19
+ def unknown_enum_value?
20
+ false
21
+ end
22
+
19
23
  def respond_to_missing?(method_name, include_private = false)
20
24
  if method_name[-1] == "?" && @enum.include?(method_name[0..-2])
21
25
  true
@@ -37,6 +41,12 @@ module GraphQL
37
41
  end
38
42
  end
39
43
 
44
+ class UnexpectedEnumValue < String
45
+ def unknown_enum_value?
46
+ true
47
+ end
48
+ end
49
+
40
50
  # Internal: Construct enum wrapper from another GraphQL::EnumType.
41
51
  #
42
52
  # type - GraphQL::EnumType instance
@@ -78,8 +88,13 @@ module GraphQL
78
88
  def cast(value, _errors = nil)
79
89
  case value
80
90
  when String
81
- raise Error, "unexpected enum value #{value}" unless @values.key?(value)
82
- @values[value]
91
+ if @values.key?(value)
92
+ @values[value]
93
+ elsif schema_module.raise_on_unknown_enum_value
94
+ raise Error, "unexpected enum value #{value}"
95
+ else
96
+ UnexpectedEnumValue.new(value).freeze
97
+ end
83
98
  when NilClass
84
99
  value
85
100
  else
@@ -3,7 +3,6 @@ require "active_support/inflector"
3
3
  require "graphql/client/error"
4
4
  require "graphql/client/errors"
5
5
  require "graphql/client/schema/base_type"
6
- require "graphql/client/schema/possible_types"
7
6
 
8
7
  module GraphQL
9
8
  class Client
@@ -132,13 +131,13 @@ module GraphQL
132
131
  case selected_ast_node
133
132
  when GraphQL::Language::Nodes::InlineFragment
134
133
  continue_selection = if selected_ast_node.type.nil?
135
- true
136
- else
137
- type_condition = definition.client.get_type(selected_ast_node.type.name)
138
- applicable_types = definition.client.possible_types(type_condition)
139
- # continue if this object type is one of the types matching the fragment condition
140
- applicable_types.include?(type)
141
- end
134
+ true
135
+ else
136
+ type_condition = definition.client.get_type(selected_ast_node.type.name)
137
+ applicable_types = definition.client.possible_types(type_condition)
138
+ # continue if this object type is one of the types matching the fragment condition
139
+ applicable_types.include?(type)
140
+ end
142
141
 
143
142
  if continue_selection
144
143
  selected_ast_node.selections.each do |next_selected_ast_node|
@@ -192,6 +191,7 @@ module GraphQL
192
191
  def to_h
193
192
  @data
194
193
  end
194
+ alias :to_hash :to_h
195
195
 
196
196
  def _definer
197
197
  @definer
@@ -255,13 +255,13 @@ module GraphQL
255
255
  end
256
256
 
257
257
  unless field
258
- raise UnimplementedFieldError, "undefined field `#{e.name}' on #{type.graphql_name} type. https://git.io/v1y3m"
258
+ raise UnimplementedFieldError, "undefined field `#{e.name}' on #{type.graphql_name} type. https://github.com/github-community-projects/graphql-client/blob/master/guides/unimplemented-field-error.md"
259
259
  end
260
260
 
261
261
  if @data.key?(field.name)
262
- raise ImplicitlyFetchedFieldError, "implicitly fetched field `#{field.name}' on #{type} type. https://git.io/v1yGL"
262
+ raise ImplicitlyFetchedFieldError, "implicitly fetched field `#{field.name}' on #{type} type. https://github.com/github-community-projects/graphql-client/blob/master/guides/implicitly-fetched-field-error.md"
263
263
  else
264
- raise UnfetchedFieldError, "unfetched field `#{field.name}' on #{type} type. https://git.io/v1y3U"
264
+ raise UnfetchedFieldError, "unfetched field `#{field.name}' on #{type} type. https://github.com/github-community-projects/graphql-client/blob/master/guides/unfetched-field-error.md"
265
265
  end
266
266
  end
267
267
  end
@@ -43,7 +43,7 @@ module GraphQL
43
43
  def set_class(type_name, klass)
44
44
  class_name = normalize_type_name(type_name)
45
45
 
46
- if constants.include?(class_name.to_sym)
46
+ if const_defined?(class_name, false)
47
47
  raise ArgumentError,
48
48
  "Can't define #{class_name} to represent type #{type_name} " \
49
49
  "because it's already defined"
@@ -66,9 +66,10 @@ module GraphQL
66
66
  end
67
67
  end
68
68
 
69
- def self.generate(schema)
69
+ def self.generate(schema, raise_on_unknown_enum_value: true)
70
70
  mod = Module.new
71
71
  mod.extend ClassMethods
72
+ mod.define_singleton_method(:raise_on_unknown_enum_value) { raise_on_unknown_enum_value }
72
73
 
73
74
  cache = {}
74
75
  schema.types.each do |name, type|
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Client
4
+ module TypeStack
5
+ # @return [GraphQL::Schema] the schema whose types are present in this document
6
+ attr_reader :schema
7
+
8
+ # When it enters an object (starting with query or mutation root), it's pushed on this stack.
9
+ # When it exits, it's popped off.
10
+ # @return [Array<GraphQL::ObjectType, GraphQL::Union, GraphQL::Interface>]
11
+ attr_reader :object_types
12
+
13
+ # When it enters a field, it's pushed on this stack (useful for nested fields, args).
14
+ # When it exits, it's popped off.
15
+ # @return [Array<GraphQL::Field>] fields which have been entered
16
+ attr_reader :field_definitions
17
+
18
+ # Directives are pushed on, then popped off while traversing the tree
19
+ # @return [Array<GraphQL::Node::Directive>] directives which have been entered
20
+ attr_reader :directive_definitions
21
+
22
+ # @return [Array<GraphQL::Node::Argument>] arguments which have been entered
23
+ attr_reader :argument_definitions
24
+
25
+ # @return [Array<String>] fields which have been entered (by their AST name)
26
+ attr_reader :path
27
+
28
+ # @param schema [GraphQL::Schema] the schema whose types to use when climbing this document
29
+ # @param visitor [GraphQL::Language::Visitor] a visitor to follow & watch the types
30
+ def initialize(document, schema:, **rest)
31
+ @schema = schema
32
+ @object_types = []
33
+ @field_definitions = []
34
+ @directive_definitions = []
35
+ @argument_definitions = []
36
+ @path = []
37
+ super(document, **rest)
38
+ end
39
+
40
+ def on_directive(node, parent)
41
+ directive_defn = @schema.directives[node.name]
42
+ @directive_definitions.push(directive_defn)
43
+ super(node, parent)
44
+ ensure
45
+ @directive_definitions.pop
46
+ end
47
+
48
+ def on_field(node, parent)
49
+ parent_type = @object_types.last
50
+ parent_type = parent_type.unwrap
51
+
52
+ field_definition = @schema.get_field(parent_type, node.name)
53
+ @field_definitions.push(field_definition)
54
+ if !field_definition.nil?
55
+ next_object_type = field_definition.type
56
+ @object_types.push(next_object_type)
57
+ else
58
+ @object_types.push(nil)
59
+ end
60
+ @path.push(node.alias || node.name)
61
+ super(node, parent)
62
+ ensure
63
+ @field_definitions.pop
64
+ @object_types.pop
65
+ @path.pop
66
+ end
67
+
68
+ def on_argument(node, parent)
69
+ if @argument_definitions.last
70
+ arg_type = @argument_definitions.last.type.unwrap
71
+ if arg_type.kind.input_object?
72
+ argument_defn = arg_type.arguments[node.name]
73
+ else
74
+ argument_defn = nil
75
+ end
76
+ elsif @directive_definitions.last
77
+ argument_defn = @directive_definitions.last.arguments[node.name]
78
+ elsif @field_definitions.last
79
+ argument_defn = @field_definitions.last.arguments[node.name]
80
+ else
81
+ argument_defn = nil
82
+ end
83
+ @argument_definitions.push(argument_defn)
84
+ @path.push(node.name)
85
+ super(node, parent)
86
+ ensure
87
+ @argument_definitions.pop
88
+ @path.pop
89
+ end
90
+
91
+ def on_operation_definition(node, parent)
92
+ # eg, QueryType, MutationType
93
+ object_type = @schema.root_type_for_operation(node.operation_type)
94
+ @object_types.push(object_type)
95
+ @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
96
+ super(node, parent)
97
+ ensure
98
+ @object_types.pop
99
+ @path.pop
100
+ end
101
+
102
+ def on_inline_fragment(node, parent)
103
+ object_type = if node.type
104
+ @schema.get_type(node.type.name)
105
+ else
106
+ @object_types.last
107
+ end
108
+ if !object_type.nil?
109
+ object_type = object_type.unwrap
110
+ end
111
+ @object_types.push(object_type)
112
+ @path.push("...#{node.type ? " on #{node.type.to_query_string}" : ""}")
113
+ super(node, parent)
114
+ ensure
115
+ @object_types.pop
116
+ @path.pop
117
+ end
118
+
119
+ def on_fragment_definition(node, parent)
120
+ object_type = if node.type
121
+ @schema.get_type(node.type.name)
122
+ else
123
+ @object_types.last
124
+ end
125
+ if !object_type.nil?
126
+ object_type = object_type.unwrap
127
+ end
128
+ @object_types.push(object_type)
129
+ @path.push("fragment #{node.name}")
130
+ super(node, parent)
131
+ ensure
132
+ @object_types.pop
133
+ @path.pop
134
+ end
135
+
136
+ def on_fragment_spread(node, parent)
137
+ @path.push("... #{node.name}")
138
+ super(node, parent)
139
+ ensure
140
+ @path.pop
141
+ end
142
+ end
143
+ end
144
+ end
@@ -2,6 +2,7 @@
2
2
  require "active_support/inflector"
3
3
  require "active_support/notifications"
4
4
  require "graphql"
5
+ require "graphql/client/type_stack"
5
6
  require "graphql/client/collocated_enforcement"
6
7
  require "graphql/client/definition_variables"
7
8
  require "graphql/client/definition"
@@ -90,7 +91,7 @@ module GraphQL
90
91
  result
91
92
  end
92
93
 
93
- def initialize(schema:, execute: nil, enforce_collocated_callers: false)
94
+ def initialize(schema:, execute: nil, enforce_collocated_callers: false, raise_on_unknown_enum_value: true)
94
95
  @schema = self.class.load_schema(schema)
95
96
  @execute = execute
96
97
  @document = GraphQL::Language::Nodes::Document.new(definitions: [])
@@ -100,7 +101,7 @@ module GraphQL
100
101
  if schema.is_a?(Class)
101
102
  @possible_types = schema.possible_types
102
103
  end
103
- @types = Schema.generate(@schema)
104
+ @types = Schema.generate(@schema, raise_on_unknown_enum_value: raise_on_unknown_enum_value)
104
105
  end
105
106
 
106
107
  # A cache of the schema's merged possible types
@@ -337,7 +338,7 @@ module GraphQL
337
338
  end
338
339
 
339
340
  if allow_dynamic_queries == false && definition.name.nil?
340
- raise DynamicQueryError, "expected definition to be assigned to a static constant https://git.io/vXXSE"
341
+ raise DynamicQueryError, "expected definition to be assigned to a static constant https://github.com/github-community-projects/graphql-client/blob/master/guides/dynamic-query-error.md"
341
342
  end
342
343
 
343
344
  variables = deep_stringify_keys(variables)
@@ -402,7 +403,6 @@ module GraphQL
402
403
 
403
404
  doc.definitions.map do |node|
404
405
  deps = Set.new
405
- definitions = document_dependencies.definitions.map { |x| [x.name, x] }.to_h
406
406
 
407
407
  queue = [node.name]
408
408
  while name = queue.shift
@@ -425,12 +425,26 @@ module GraphQL
425
425
  end.to_h
426
426
  end
427
427
 
428
+ class GatherNamesVisitor < GraphQL::Language::Visitor
429
+ def initialize(node)
430
+ @names = []
431
+ super
432
+ end
433
+
434
+ attr_reader :names
435
+
436
+ def on_fragment_spread(node, parent)
437
+ @names << node.name
438
+ super
439
+ end
440
+ end
441
+
428
442
  def find_definition_dependencies(node)
429
- names = []
430
- visitor = Language::Visitor.new(node)
431
- visitor[Language::Nodes::FragmentSpread] << -> (node, parent) { names << node.name }
443
+ visitor = GatherNamesVisitor.new(node)
432
444
  visitor.visit
433
- names.uniq
445
+ names = visitor.names
446
+ names.uniq!
447
+ names
434
448
  end
435
449
 
436
450
  def deep_freeze_json_object(obj)
@@ -21,19 +21,12 @@ module RuboCop
21
21
  query, = ::GraphQL::Client::ViewModule.extract_graphql_section(erb)
22
22
  return unless query
23
23
 
24
- aliases = {}
25
- fields = {}
26
- ranges = {}
27
-
28
24
  # TODO: Use GraphQL client parser
29
25
  document = ::GraphQL.parse(query.gsub(/::/, "__"))
30
-
31
- visitor = ::GraphQL::Language::Visitor.new(document)
32
- visitor[::GraphQL::Language::Nodes::Field] << ->(node, _parent) do
33
- name = node.alias || node.name
34
- fields[name] ||= 0
35
- field_aliases(name).each { |n| (aliases[n] ||= []) << name }
36
- ranges[name] ||= source_range(processed_source.buffer, node.line, 0)
26
+ visitor = OverfetchVisitor.new(document) do |line_num|
27
+ # `source_range` is private to this object,
28
+ # so yield back out to it to get this info:
29
+ source_range(processed_source.buffer, line_num, 0)
37
30
  end
38
31
  visitor.visit
39
32
 
@@ -41,30 +34,53 @@ module RuboCop
41
34
  method_names = method_names_for(*node)
42
35
 
43
36
  method_names.each do |method_name|
44
- aliases.fetch(method_name, []).each do |field_name|
45
- fields[field_name] += 1
37
+ visitor.aliases.fetch(method_name, []).each do |field_name|
38
+ visitor.fields[field_name] += 1
46
39
  end
47
40
  end
48
41
  end
49
42
 
50
- fields.each do |field, count|
43
+ visitor.fields.each do |field, count|
51
44
  next if count > 0
52
- add_offense(nil, location: ranges[field], message: "GraphQL field '#{field}' query but was not used in template.")
45
+ add_offense(nil, location: visitor.ranges[field], message: "GraphQL field '#{field}' query but was not used in template.")
53
46
  end
54
47
  end
55
48
 
56
- def field_aliases(name)
57
- names = Set.new
49
+ class OverfetchVisitor < ::GraphQL::Language::Visitor
50
+ def initialize(doc, &range_for_line)
51
+ super(doc)
52
+ @range_for_line = range_for_line
53
+ @fields = {}
54
+ @aliases = {}
55
+ @ranges = {}
56
+ end
57
+
58
+ attr_reader :fields, :aliases, :ranges
59
+
60
+ def on_field(node, parent)
61
+ name = node.alias || node.name
62
+ fields[name] ||= 0
63
+ field_aliases(name).each { |n| (aliases[n] ||= []) << name }
64
+ ranges[name] ||= @range_for_line.call(node.line)
65
+ super
66
+ end
58
67
 
59
- names << name
60
- names << "#{name}?"
68
+ private
61
69
 
62
- names << underscore_name = ActiveSupport::Inflector.underscore(name)
63
- names << "#{underscore_name}?"
70
+ def field_aliases(name)
71
+ names = Set.new
64
72
 
65
- names
73
+ names << name
74
+ names << "#{name}?"
75
+
76
+ names << underscore_name = ActiveSupport::Inflector.underscore(name)
77
+ names << "#{underscore_name}?"
78
+
79
+ names
80
+ end
66
81
  end
67
82
 
83
+
68
84
  def method_names_for(*node)
69
85
  receiver, method_name, *_args = node
70
86
  method_names = []
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.0
4
+ version: 0.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-02 00:00:00.000000000 Z
11
+ date: 2024-04-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 1.13.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 1.13.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: actionpack
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -100,48 +100,42 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '11.2'
103
+ version: 13.1.0
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '11.2'
110
+ version: 13.1.0
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rubocop-github
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '0.10'
118
- - - "<="
115
+ - - ">="
119
116
  - !ruby/object:Gem::Version
120
- version: 0.16.0
117
+ version: '0'
121
118
  type: :development
122
119
  prerelease: false
123
120
  version_requirements: !ruby/object:Gem::Requirement
124
121
  requirements:
125
- - - "~>"
126
- - !ruby/object:Gem::Version
127
- version: '0.10'
128
- - - "<="
122
+ - - ">="
129
123
  - !ruby/object:Gem::Version
130
- version: 0.16.0
124
+ version: '0'
131
125
  - !ruby/object:Gem::Dependency
132
126
  name: rubocop
133
127
  requirement: !ruby/object:Gem::Requirement
134
128
  requirements:
135
129
  - - "~>"
136
130
  - !ruby/object:Gem::Version
137
- version: '0.55'
131
+ version: 1.57.0
138
132
  type: :development
139
133
  prerelease: false
140
134
  version_requirements: !ruby/object:Gem::Requirement
141
135
  requirements:
142
136
  - - "~>"
143
137
  - !ruby/object:Gem::Version
144
- version: '0.55'
138
+ version: 1.57.0
145
139
  description: A Ruby library for declaring, composing and executing GraphQL queries
146
140
  email: engineering@github.com
147
141
  executables: []
@@ -182,13 +176,15 @@ files:
182
176
  - lib/graphql/client/schema/scalar_type.rb
183
177
  - lib/graphql/client/schema/skip_directive.rb
184
178
  - lib/graphql/client/schema/union_type.rb
179
+ - lib/graphql/client/type_stack.rb
185
180
  - lib/graphql/client/view_module.rb
186
181
  - lib/rubocop/cop/graphql/heredoc.rb
187
182
  - lib/rubocop/cop/graphql/overfetch.rb
188
- homepage: https://github.com/github/graphql-client
183
+ homepage: https://github.com/github-community-projects/graphql-client
189
184
  licenses:
190
185
  - MIT
191
- metadata: {}
186
+ metadata:
187
+ rubygems_mfa_required: 'true'
192
188
  post_install_message:
193
189
  rdoc_options: []
194
190
  require_paths:
@@ -204,7 +200,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
204
200
  - !ruby/object:Gem::Version
205
201
  version: '0'
206
202
  requirements: []
207
- rubygems_version: 3.2.9
203
+ rubygems_version: 3.5.3
208
204
  signing_key:
209
205
  specification_version: 4
210
206
  summary: GraphQL Client