graphql-client 0.18.0 → 0.26.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: aab80b9e2051c30118b8db5fc9dbd813926d8a180bf49371bf23e6a09aaaf4eb
4
+ data.tar.gz: 20de5ca5f098763481ffa6bfa79308b1db5339376878f98c7d3ecb8d4442d5cd
5
5
  SHA512:
6
- metadata.gz: 3f14f21ff57e8dc29dbef6e5fb2ee6d2fb552075986ef57c8f89a3d7ce5b2faa8e2965b20a5b8f7735394b3ee13cdc8e55923bda0dd02fa8625f7e2b824dab7b
7
- data.tar.gz: ee04576697c258bdc3959366d0cccef720e24eb2358ecbb28c020d912c5b241ac7ba195a8240656c6f825109c23a321ef2449892b4d893e29802c03b6580f557
6
+ metadata.gz: 81eb7e3021e3cdf8201e86971ed7d63f58d60e132d22b7c4ee35fd6b0a42fa730f4a9c9dd6638027f583d98638d270ef1d7994f0b5976ad4200a8c4164f58864
7
+ data.tar.gz: 7a752365bd27b213506bf6958c8accf9941f85bc8a7c73518950b431c1b7563584d29c0569f3a8a8a4c221e704c9ea3ff05333f005af004e60454b21f1acaaf9
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
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require "action_view"
3
+ require "logger"
3
4
 
4
5
  module GraphQL
5
6
  class Client
@@ -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]
@@ -14,7 +14,7 @@ module GraphQL
14
14
  # Assumes GraphQL endpoint follows the express-graphql endpoint conventions.
15
15
  # https://github.com/graphql/express-graphql#http-usage
16
16
  #
17
- # Production applications should consider implementing there own network
17
+ # Production applications should consider implementing their own network
18
18
  # adapter. This class exists for trivial stock usage and allows for minimal
19
19
  # request header configuration.
20
20
  class HTTP
@@ -45,6 +45,11 @@ module GraphQL
45
45
  {}
46
46
  end
47
47
 
48
+ # Public: full reponse from last request
49
+ #
50
+ # Returns Hash.
51
+ attr_reader :last_response
52
+
48
53
  # Public: Make an HTTP request for GraphQL query.
49
54
  #
50
55
  # Implements Client's "execute" adapter interface.
@@ -71,6 +76,7 @@ module GraphQL
71
76
  request.body = JSON.generate(body)
72
77
 
73
78
  response = connection.request(request)
79
+ @last_response = response.to_hash
74
80
  case response
75
81
  when Net::HTTPOK, Net::HTTPBadRequest
76
82
  JSON.parse(response.body)
@@ -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
  #
@@ -30,12 +31,18 @@ module GraphQL
30
31
  # Public: Hash of server specific extension metadata.
31
32
  attr_reader :extensions
32
33
 
34
+ # Public: Complete response hash returned from server.
35
+ #
36
+ # Returns Hash
37
+ attr_reader :full_response
38
+
33
39
  # Internal: Initialize base class.
34
- def initialize(hash, data: nil, errors: Errors.new, extensions: {})
40
+ def initialize(hash, data: nil, errors: Errors.new, extensions: {}, full_response: nil)
35
41
  @original_hash = hash
36
42
  @data = data
37
43
  @errors = errors
38
44
  @extensions = extensions
45
+ @full_response = full_response
39
46
  end
40
47
  end
41
48
  end
@@ -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: [])
@@ -99,8 +100,13 @@ module GraphQL
99
100
  @enforce_collocated_callers = enforce_collocated_callers
100
101
  if schema.is_a?(Class)
101
102
  @possible_types = schema.possible_types
103
+ key, _types = @possible_types.first
104
+ # GraphQL-Ruby 2.3.5+ has classes here instead of strings
105
+ if key.is_a?(Module)
106
+ @possible_types = @possible_types.transform_keys(&:graphql_name)
107
+ end
102
108
  end
103
- @types = Schema.generate(@schema)
109
+ @types = Schema.generate(@schema, raise_on_unknown_enum_value: raise_on_unknown_enum_value)
104
110
  end
105
111
 
106
112
  # A cache of the schema's merged possible types
@@ -337,7 +343,7 @@ module GraphQL
337
343
  end
338
344
 
339
345
  if allow_dynamic_queries == false && definition.name.nil?
340
- raise DynamicQueryError, "expected definition to be assigned to a static constant https://git.io/vXXSE"
346
+ 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
347
  end
342
348
 
343
349
  variables = deep_stringify_keys(variables)
@@ -374,12 +380,12 @@ module GraphQL
374
380
  error_payload = payload.merge(message: error["message"], error: error)
375
381
  ActiveSupport::Notifications.instrument("error.graphql", error_payload)
376
382
  end
377
-
378
383
  Response.new(
379
384
  result,
380
385
  data: definition.new(data, Errors.new(errors, ["data"])),
381
386
  errors: Errors.new(errors),
382
- extensions: extensions
387
+ extensions: extensions,
388
+ full_response: execute.respond_to?("last_response") ? execute.last_response : nil
383
389
  )
384
390
  end
385
391
 
@@ -402,7 +408,6 @@ module GraphQL
402
408
 
403
409
  doc.definitions.map do |node|
404
410
  deps = Set.new
405
- definitions = document_dependencies.definitions.map { |x| [x.name, x] }.to_h
406
411
 
407
412
  queue = [node.name]
408
413
  while name = queue.shift
@@ -425,12 +430,26 @@ module GraphQL
425
430
  end.to_h
426
431
  end
427
432
 
433
+ class GatherNamesVisitor < GraphQL::Language::Visitor
434
+ def initialize(node)
435
+ @names = []
436
+ super
437
+ end
438
+
439
+ attr_reader :names
440
+
441
+ def on_fragment_spread(node, parent)
442
+ @names << node.name
443
+ super
444
+ end
445
+ end
446
+
428
447
  def find_definition_dependencies(node)
429
- names = []
430
- visitor = Language::Visitor.new(node)
431
- visitor[Language::Nodes::FragmentSpread] << -> (node, parent) { names << node.name }
448
+ visitor = GatherNamesVisitor.new(node)
432
449
  visitor.visit
433
- names.uniq
450
+ names = visitor.names
451
+ names.uniq!
452
+ names
434
453
  end
435
454
 
436
455
  def deep_freeze_json_object(obj)
@@ -5,7 +5,7 @@ module RuboCop
5
5
  module Cop
6
6
  module GraphQL
7
7
  # Public: Cop for enforcing non-interpolated GRAPHQL heredocs.
8
- class Heredoc < Cop
8
+ class Heredoc < Base
9
9
  def on_dstr(node)
10
10
  check_str(node)
11
11
  end
@@ -19,11 +19,11 @@ module RuboCop
19
19
  return unless node.location.expression.source =~ /^<<(-|~)?GRAPHQL/
20
20
 
21
21
  node.each_child_node(:begin) do |begin_node|
22
- add_offense(begin_node, location: :expression, message: "Do not interpolate variables into GraphQL queries, " \
22
+ add_offense(begin_node, message: "Do not interpolate variables into GraphQL queries, " \
23
23
  "used variables instead.")
24
24
  end
25
25
 
26
- add_offense(node, location: :expression, message: "GraphQL heredocs should be quoted. <<-'GRAPHQL'")
26
+ add_offense(node, message: "GraphQL heredocs should be quoted. <<-'GRAPHQL'")
27
27
  end
28
28
 
29
29
  def autocorrect(node)
@@ -8,7 +8,7 @@ module RuboCop
8
8
  module Cop
9
9
  module GraphQL
10
10
  # Public: Rubocop for catching overfetched fields in ERB templates.
11
- class Overfetch < Cop
11
+ class Overfetch < Base
12
12
  if defined?(RangeHelp)
13
13
  # rubocop 0.53 moved the #source_range method into this module
14
14
  include RangeHelp
@@ -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(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,13 @@
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.26.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2022-05-02 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activesupport
@@ -30,14 +29,14 @@ dependencies:
30
29
  requirements:
31
30
  - - ">="
32
31
  - !ruby/object:Gem::Version
33
- version: '0'
32
+ version: 1.13.0
34
33
  type: :runtime
35
34
  prerelease: false
36
35
  version_requirements: !ruby/object:Gem::Requirement
37
36
  requirements:
38
37
  - - ">="
39
38
  - !ruby/object:Gem::Version
40
- version: '0'
39
+ version: 1.13.0
41
40
  - !ruby/object:Gem::Dependency
42
41
  name: actionpack
43
42
  requirement: !ruby/object:Gem::Requirement
@@ -100,48 +99,42 @@ dependencies:
100
99
  requirements:
101
100
  - - "~>"
102
101
  - !ruby/object:Gem::Version
103
- version: '11.2'
102
+ version: 13.2.1
104
103
  type: :development
105
104
  prerelease: false
106
105
  version_requirements: !ruby/object:Gem::Requirement
107
106
  requirements:
108
107
  - - "~>"
109
108
  - !ruby/object:Gem::Version
110
- version: '11.2'
109
+ version: 13.2.1
111
110
  - !ruby/object:Gem::Dependency
112
111
  name: rubocop-github
113
112
  requirement: !ruby/object:Gem::Requirement
114
113
  requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '0.10'
118
- - - "<="
114
+ - - ">="
119
115
  - !ruby/object:Gem::Version
120
- version: 0.16.0
116
+ version: '0'
121
117
  type: :development
122
118
  prerelease: false
123
119
  version_requirements: !ruby/object:Gem::Requirement
124
120
  requirements:
125
- - - "~>"
126
- - !ruby/object:Gem::Version
127
- version: '0.10'
128
- - - "<="
121
+ - - ">="
129
122
  - !ruby/object:Gem::Version
130
- version: 0.16.0
123
+ version: '0'
131
124
  - !ruby/object:Gem::Dependency
132
125
  name: rubocop
133
126
  requirement: !ruby/object:Gem::Requirement
134
127
  requirements:
135
128
  - - "~>"
136
129
  - !ruby/object:Gem::Version
137
- version: '0.55'
130
+ version: 1.75.8
138
131
  type: :development
139
132
  prerelease: false
140
133
  version_requirements: !ruby/object:Gem::Requirement
141
134
  requirements:
142
135
  - - "~>"
143
136
  - !ruby/object:Gem::Version
144
- version: '0.55'
137
+ version: 1.75.8
145
138
  description: A Ruby library for declaring, composing and executing GraphQL queries
146
139
  email: engineering@github.com
147
140
  executables: []
@@ -182,14 +175,15 @@ files:
182
175
  - lib/graphql/client/schema/scalar_type.rb
183
176
  - lib/graphql/client/schema/skip_directive.rb
184
177
  - lib/graphql/client/schema/union_type.rb
178
+ - lib/graphql/client/type_stack.rb
185
179
  - lib/graphql/client/view_module.rb
186
180
  - lib/rubocop/cop/graphql/heredoc.rb
187
181
  - lib/rubocop/cop/graphql/overfetch.rb
188
- homepage: https://github.com/github/graphql-client
182
+ homepage: https://github.com/github-community-projects/graphql-client
189
183
  licenses:
190
184
  - MIT
191
- metadata: {}
192
- post_install_message:
185
+ metadata:
186
+ rubygems_mfa_required: 'true'
193
187
  rdoc_options: []
194
188
  require_paths:
195
189
  - lib
@@ -204,8 +198,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
204
198
  - !ruby/object:Gem::Version
205
199
  version: '0'
206
200
  requirements: []
207
- rubygems_version: 3.2.9
208
- signing_key:
201
+ rubygems_version: 3.6.7
209
202
  specification_version: 4
210
203
  summary: GraphQL Client
211
204
  test_files: []