graphql-client 0.18.0 → 0.22.0

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