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 +4 -4
- data/README.md +1 -1
- data/lib/graphql/client/collocated_enforcement.rb +1 -1
- data/lib/graphql/client/definition.rb +54 -42
- data/lib/graphql/client/definition_variables.rb +18 -11
- data/lib/graphql/client/document_types.rb +34 -20
- data/lib/graphql/client/errors.rb +1 -1
- data/lib/graphql/client/log_subscriber.rb +14 -2
- data/lib/graphql/client/response.rb +1 -0
- data/lib/graphql/client/schema/enum_type.rb +17 -2
- data/lib/graphql/client/schema/object_type.rb +11 -11
- data/lib/graphql/client/schema.rb +3 -2
- data/lib/graphql/client/type_stack.rb +144 -0
- data/lib/graphql/client.rb +22 -8
- data/lib/rubocop/cop/graphql/overfetch.rb +38 -22
- metadata +17 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4064d832c0bf6433b6b6629c50d5c20182164bb6e870661d670fd6ba3ab0ecdc
|
4
|
+
data.tar.gz: 21f4aea0033c36f86d14ae4fe52294ae0193cc1485154cf3b15519b9ac0b8d8f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4507464c1a14fcac7aecaacf07d9b8297f288b717d7c59e12368426b427d5bdeacf7c19fd3e037df8d26482ad719b8edfc2dd26665d8d59518a10720ab1968c0
|
7
|
+
data.tar.gz: f1c34616ea07b5bb4926f7489225859b3b9bff50485a90d74a55f9ce4e4aca74eeb5420cb793522470de9ac3ccbf85628ac0d3c3b0e01955024aebaf5a420d37
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# graphql-client [](https://badge.fury.io/rb/graphql-client) [](https://github.com/github/graphql-client/actions?query=workflow)
|
1
|
+
# graphql-client [](https://badge.fury.io/rb/graphql-client) [](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://
|
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 =
|
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
|
-
|
156
|
+
class DefinitionVisitor < GraphQL::Language::Visitor
|
157
|
+
attr_reader :spreads, :definitions
|
159
158
|
|
160
|
-
def
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
-
|
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
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
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
|
-
|
181
|
-
|
182
|
-
|
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
|
-
|
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
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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 =
|
28
|
-
|
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
|
-
|
33
|
-
if definition =
|
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 =
|
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
|
@@ -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
|
-
|
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
|
-
|
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
|
@@ -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
|
-
|
82
|
-
|
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
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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://
|
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://
|
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://
|
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
|
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
|
data/lib/graphql/client.rb
CHANGED
@@ -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://
|
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
|
-
|
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.
|
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
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
57
|
-
|
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
|
-
|
60
|
-
names << "#{name}?"
|
68
|
+
private
|
61
69
|
|
62
|
-
|
63
|
-
|
70
|
+
def field_aliases(name)
|
71
|
+
names = Set.new
|
64
72
|
|
65
|
-
|
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.
|
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:
|
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:
|
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:
|
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:
|
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:
|
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
|
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
|
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:
|
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:
|
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.
|
203
|
+
rubygems_version: 3.5.3
|
208
204
|
signing_key:
|
209
205
|
specification_version: 4
|
210
206
|
summary: GraphQL Client
|