graphql-client 0.16.0 → 0.19.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 +21 -14
- data/lib/graphql/client/definition.rb +70 -43
- data/lib/graphql/client/definition_variables.rb +18 -11
- data/lib/graphql/client/document_types.rb +34 -20
- data/lib/graphql/client/http.rb +1 -1
- data/lib/graphql/client/schema/interface_type.rb +1 -1
- data/lib/graphql/client/schema/object_type.rb +151 -92
- data/lib/graphql/client/schema/union_type.rb +1 -1
- data/lib/graphql/client/type_stack.rb +144 -0
- data/lib/graphql/client/view_module.rb +1 -1
- data/lib/graphql/client.rb +85 -61
- data/lib/rubocop/cop/graphql/overfetch.rb +38 -22
- metadata +19 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3d469dea9c19c2b7b70f57554d66dab3374c496ef09bc013d9464509faac1243
|
4
|
+
data.tar.gz: 7fdc9c16119e205cc1e200ee4fc5d882776bbd94c1294dc075963428d7d9f7e4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51ad31a0fd1faf1275037177e7b22f29f597ab9c03868831422de07f563a1c2911a32372832f45053c2430648a267067a721b66df5697fbc1e8735fefddf78b1
|
7
|
+
data.tar.gz: 6162d16ba9cefdd82356c89fd799744bcb0777716c0a379c403f519c4aa5fb1c1d2a0a15955852f31d76326fcb6ceebb24fc861c85d211078e5b492bbbc95e0f
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# graphql-client [](https://badge.fury.io/rb/graphql-client) [](https://badge.fury.io/rb/graphql-client) [](https://github.com/github/graphql-client/actions?query=workflow)
|
2
2
|
|
3
3
|
GraphQL Client is a Ruby library for declaring, composing and executing GraphQL queries.
|
4
4
|
|
@@ -12,6 +12,8 @@ module GraphQL
|
|
12
12
|
|
13
13
|
# Enforcements collocated object access best practices.
|
14
14
|
module CollocatedEnforcement
|
15
|
+
extend self
|
16
|
+
|
15
17
|
# Public: Ignore collocated caller enforcement for the scope of the block.
|
16
18
|
def allow_noncollocated_callers
|
17
19
|
Thread.current[:query_result_caller_location_ignore] = true
|
@@ -20,6 +22,23 @@ module GraphQL
|
|
20
22
|
Thread.current[:query_result_caller_location_ignore] = nil
|
21
23
|
end
|
22
24
|
|
25
|
+
def verify_collocated_path(location, path, method = "method")
|
26
|
+
return yield if Thread.current[:query_result_caller_location_ignore]
|
27
|
+
|
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")
|
30
|
+
error.set_backtrace(caller(2))
|
31
|
+
raise error
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
Thread.current[:query_result_caller_location_ignore] = true
|
36
|
+
yield
|
37
|
+
ensure
|
38
|
+
Thread.current[:query_result_caller_location_ignore] = nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
23
42
|
# Internal: Decorate method with collocated caller enforcement.
|
24
43
|
#
|
25
44
|
# mod - Target Module/Class
|
@@ -31,21 +50,9 @@ module GraphQL
|
|
31
50
|
mod.prepend(Module.new do
|
32
51
|
methods.each do |method|
|
33
52
|
define_method(method) do |*args, &block|
|
34
|
-
|
35
|
-
|
36
|
-
locations = caller_locations(1, 1)
|
37
|
-
|
38
|
-
if (locations.first.path != path) && !(caller_locations.any? { |cl| WHITELISTED_GEM_NAMES.any? { |g| cl.path.include?("gems/#{g}") } })
|
39
|
-
error = NonCollocatedCallerError.new("#{method} was called outside of '#{path}' https://git.io/v1syX")
|
40
|
-
error.set_backtrace(caller(1))
|
41
|
-
raise error
|
42
|
-
end
|
43
|
-
|
44
|
-
begin
|
45
|
-
Thread.current[:query_result_caller_location_ignore] = true
|
53
|
+
location = caller_locations(1, 1)[0]
|
54
|
+
CollocatedEnforcement.verify_collocated_path(location, path, method) do
|
46
55
|
super(*args, &block)
|
47
|
-
ensure
|
48
|
-
Thread.current[:query_result_caller_location_ignore] = nil
|
49
56
|
end
|
50
57
|
end
|
51
58
|
end
|
@@ -115,9 +115,24 @@ module GraphQL
|
|
115
115
|
else
|
116
116
|
cast_object(obj)
|
117
117
|
end
|
118
|
+
when GraphQL::Client::Schema::ObjectType::WithDefinition
|
119
|
+
case obj
|
120
|
+
when schema_class.klass
|
121
|
+
if obj._definer == schema_class
|
122
|
+
obj
|
123
|
+
else
|
124
|
+
cast_object(obj)
|
125
|
+
end
|
126
|
+
when nil
|
127
|
+
nil
|
128
|
+
when Hash
|
129
|
+
schema_class.new(obj, errors)
|
130
|
+
else
|
131
|
+
cast_object(obj)
|
132
|
+
end
|
118
133
|
when GraphQL::Client::Schema::ObjectType
|
119
134
|
case obj
|
120
|
-
when
|
135
|
+
when nil, schema_class
|
121
136
|
obj
|
122
137
|
when Hash
|
123
138
|
schema_class.new(obj, errors)
|
@@ -132,41 +147,58 @@ module GraphQL
|
|
132
147
|
# Internal: Nodes AST indexes.
|
133
148
|
def indexes
|
134
149
|
@indexes ||= begin
|
135
|
-
visitor =
|
136
|
-
definitions = index_node_definitions(visitor)
|
137
|
-
spreads = index_spreads(visitor)
|
150
|
+
visitor = DefinitionVisitor.new(document)
|
138
151
|
visitor.visit
|
139
|
-
{ definitions: definitions, spreads: spreads }
|
152
|
+
{ definitions: visitor.definitions, spreads: visitor.spreads }
|
140
153
|
end
|
141
154
|
end
|
142
155
|
|
143
|
-
|
156
|
+
class DefinitionVisitor < GraphQL::Language::Visitor
|
157
|
+
attr_reader :spreads, :definitions
|
144
158
|
|
145
|
-
def
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
schema_class.cast(obj.to_h, obj.errors)
|
151
|
-
else
|
152
|
-
raise TypeError, "unexpected #{obj.class}"
|
153
|
-
end
|
159
|
+
def initialize(doc)
|
160
|
+
super
|
161
|
+
@spreads = {}
|
162
|
+
@definitions = {}
|
163
|
+
@current_definition = nil
|
154
164
|
end
|
155
165
|
|
156
|
-
|
166
|
+
def on_field(node, parent)
|
167
|
+
@definitions[node] = @current_definition
|
168
|
+
@spreads[node] = get_spreads(node)
|
169
|
+
super
|
170
|
+
end
|
157
171
|
|
158
|
-
def
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
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
|
164
180
|
|
165
|
-
|
166
|
-
|
167
|
-
|
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
|
168
189
|
|
169
|
-
|
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
|
170
202
|
end
|
171
203
|
|
172
204
|
def flatten_spreads(node)
|
@@ -183,25 +215,20 @@ module GraphQL
|
|
183
215
|
end
|
184
216
|
spreads
|
185
217
|
end
|
218
|
+
end
|
219
|
+
|
220
|
+
private
|
186
221
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
visitor[GraphQL::Language::Nodes::OperationDefinition].leave << leave_definition
|
196
|
-
|
197
|
-
definitions = {}
|
198
|
-
on_node = ->(node, _parent) { definitions[node] = current_definition }
|
199
|
-
visitor[GraphQL::Language::Nodes::Field] << on_node
|
200
|
-
visitor[GraphQL::Language::Nodes::FragmentDefinition] << on_node
|
201
|
-
visitor[GraphQL::Language::Nodes::InlineFragment] << on_node
|
202
|
-
visitor[GraphQL::Language::Nodes::OperationDefinition] << on_node
|
203
|
-
definitions
|
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}"
|
204
230
|
end
|
231
|
+
end
|
205
232
|
end
|
206
233
|
end
|
207
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
|
data/lib/graphql/client/http.rb
CHANGED
@@ -21,7 +21,7 @@ module GraphQL
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def define_class(definition, ast_nodes)
|
24
|
-
possible_type_names = definition.client.
|
24
|
+
possible_type_names = definition.client.possible_types(type).map(&:graphql_name)
|
25
25
|
possible_types = possible_type_names.map { |concrete_type_name|
|
26
26
|
schema_module.get_class(concrete_type_name).define_class(definition, ast_nodes)
|
27
27
|
}
|
@@ -16,6 +16,53 @@ module GraphQL
|
|
16
16
|
|
17
17
|
define_singleton_method(:type) { type }
|
18
18
|
define_singleton_method(:fields) { fields }
|
19
|
+
|
20
|
+
const_set(:READERS, {})
|
21
|
+
const_set(:PREDICATES, {})
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class WithDefinition
|
26
|
+
include BaseType
|
27
|
+
include ObjectType
|
28
|
+
|
29
|
+
EMPTY_SET = Set.new.freeze
|
30
|
+
|
31
|
+
attr_reader :klass, :defined_fields, :definition
|
32
|
+
|
33
|
+
def type
|
34
|
+
@klass.type
|
35
|
+
end
|
36
|
+
|
37
|
+
def fields
|
38
|
+
@klass.fields
|
39
|
+
end
|
40
|
+
|
41
|
+
def spreads
|
42
|
+
if defined?(@spreads)
|
43
|
+
@spreads
|
44
|
+
else
|
45
|
+
EMPTY_SET
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(klass, defined_fields, definition, spreads)
|
50
|
+
@klass = klass
|
51
|
+
@defined_fields = defined_fields.map do |k, v|
|
52
|
+
[-k.to_s, v]
|
53
|
+
end.to_h
|
54
|
+
@definition = definition
|
55
|
+
@spreads = spreads unless spreads.empty?
|
56
|
+
|
57
|
+
@defined_fields.keys.each do |attr|
|
58
|
+
name = ActiveSupport::Inflector.underscore(attr)
|
59
|
+
@klass::READERS[:"#{name}"] ||= attr
|
60
|
+
@klass::PREDICATES[:"#{name}?"] ||= attr
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def new(data = {}, errors = Errors.new)
|
65
|
+
@klass.new(data, errors, self)
|
19
66
|
end
|
20
67
|
end
|
21
68
|
|
@@ -46,56 +93,9 @@ module GraphQL
|
|
46
93
|
field_classes[result_name.to_sym] = schema_module.define_class(definition, field_ast_nodes, field_return_type)
|
47
94
|
end
|
48
95
|
|
49
|
-
|
50
|
-
klass.define_fields(field_classes)
|
51
|
-
klass.instance_variable_set(:@source_definition, definition)
|
52
|
-
klass.instance_variable_set(:@_spreads, definition.indexes[:spreads][ast_nodes.first])
|
53
|
-
|
54
|
-
if definition.client.enforce_collocated_callers
|
55
|
-
keys = field_classes.keys.map { |key| ActiveSupport::Inflector.underscore(key) }
|
56
|
-
Client.enforce_collocated_callers(klass, keys, definition.source_location[0])
|
57
|
-
end
|
96
|
+
spreads = definition.indexes[:spreads][ast_nodes.first]
|
58
97
|
|
59
|
-
|
60
|
-
end
|
61
|
-
|
62
|
-
PREDICATE_CACHE = Hash.new { |h, name|
|
63
|
-
h[name] = -> { @data[name] ? true : false }
|
64
|
-
}
|
65
|
-
|
66
|
-
METHOD_CACHE = Hash.new { |h, key|
|
67
|
-
h[key] = -> {
|
68
|
-
name = key.to_s
|
69
|
-
type = self.class::FIELDS[key]
|
70
|
-
@casted_data.fetch(name) do
|
71
|
-
@casted_data[name] = type.cast(@data[name], @errors.filter_by_path(name))
|
72
|
-
end
|
73
|
-
}
|
74
|
-
}
|
75
|
-
|
76
|
-
MODULE_CACHE = Hash.new do |h, fields|
|
77
|
-
h[fields] = Module.new do
|
78
|
-
fields.each do |name|
|
79
|
-
GraphQL::Client::Schema::ObjectType.define_cached_field(name, self)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
FIELDS_CACHE = Hash.new { |h, k| h[k] = k }
|
85
|
-
|
86
|
-
def define_fields(fields)
|
87
|
-
const_set :FIELDS, FIELDS_CACHE[fields]
|
88
|
-
mod = MODULE_CACHE[fields.keys.sort]
|
89
|
-
include mod
|
90
|
-
end
|
91
|
-
|
92
|
-
def self.define_cached_field(name, ctx)
|
93
|
-
key = name
|
94
|
-
name = -name.to_s
|
95
|
-
method_name = ActiveSupport::Inflector.underscore(name)
|
96
|
-
|
97
|
-
ctx.send(:define_method, method_name, &METHOD_CACHE[key])
|
98
|
-
ctx.send(:define_method, "#{method_name}?", &PREDICATE_CACHE[name])
|
98
|
+
WithDefinition.new(self, field_classes, definition, spreads)
|
99
99
|
end
|
100
100
|
|
101
101
|
def define_field(name, type)
|
@@ -132,14 +132,13 @@ module GraphQL
|
|
132
132
|
case selected_ast_node
|
133
133
|
when GraphQL::Language::Nodes::InlineFragment
|
134
134
|
continue_selection = if selected_ast_node.type.nil?
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
end
|
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
|
143
142
|
|
144
143
|
if continue_selection
|
145
144
|
selected_ast_node.selections.each do |next_selected_ast_node|
|
@@ -150,10 +149,8 @@ module GraphQL
|
|
150
149
|
fragment_definition = definition.document.definitions.find do |defn|
|
151
150
|
defn.is_a?(GraphQL::Language::Nodes::FragmentDefinition) && defn.name == selected_ast_node.name
|
152
151
|
end
|
153
|
-
|
154
|
-
schema = definition.client.schema
|
155
152
|
type_condition = definition.client.get_type(fragment_definition.type.name)
|
156
|
-
applicable_types =
|
153
|
+
applicable_types = definition.client.possible_types(type_condition)
|
157
154
|
# continue if this object type is one of the types matching the fragment condition
|
158
155
|
continue_selection = applicable_types.include?(type)
|
159
156
|
|
@@ -177,17 +174,16 @@ module GraphQL
|
|
177
174
|
end
|
178
175
|
|
179
176
|
class ObjectClass
|
180
|
-
|
181
|
-
attr_reader :source_definition
|
182
|
-
attr_reader :_spreads
|
183
|
-
end
|
184
|
-
|
185
|
-
extend ClassMethods
|
186
|
-
|
187
|
-
def initialize(data = {}, errors = Errors.new)
|
177
|
+
def initialize(data = {}, errors = Errors.new, definer = nil)
|
188
178
|
@data = data
|
189
179
|
@casted_data = {}
|
190
180
|
@errors = errors
|
181
|
+
|
182
|
+
# If we are not provided a definition, we can use this empty default
|
183
|
+
definer ||= ObjectType::WithDefinition.new(self.class, {}, nil, [])
|
184
|
+
|
185
|
+
@definer = definer
|
186
|
+
@enforce_collocated_callers = source_definition && source_definition.client.enforce_collocated_callers
|
191
187
|
end
|
192
188
|
|
193
189
|
# Public: Returns the raw response data
|
@@ -197,42 +193,85 @@ module GraphQL
|
|
197
193
|
@data
|
198
194
|
end
|
199
195
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
attr_reader :errors
|
196
|
+
def _definer
|
197
|
+
@definer
|
198
|
+
end
|
204
199
|
|
205
|
-
def
|
206
|
-
|
207
|
-
|
208
|
-
type = self.class.type
|
200
|
+
def _spreads
|
201
|
+
@definer.spreads
|
202
|
+
end
|
209
203
|
|
210
|
-
|
211
|
-
|
212
|
-
|
204
|
+
def source_definition
|
205
|
+
@definer.definition
|
206
|
+
end
|
213
207
|
|
214
|
-
|
215
|
-
|
216
|
-
|
208
|
+
def respond_to_missing?(name, priv)
|
209
|
+
if (attr = self.class::READERS[name]) || (attr = self.class::PREDICATES[name])
|
210
|
+
@definer.defined_fields.key?(attr) || super
|
211
|
+
else
|
212
|
+
super
|
217
213
|
end
|
214
|
+
end
|
218
215
|
|
219
|
-
|
220
|
-
|
216
|
+
# Public: Return errors associated with data.
|
217
|
+
#
|
218
|
+
# It's possible to define "errors" as a field. Ideally this shouldn't
|
219
|
+
# happen, but if it does we should prefer the field rather than the
|
220
|
+
# builtin error type.
|
221
|
+
#
|
222
|
+
# Returns Errors collection.
|
223
|
+
def errors
|
224
|
+
if type = @definer.defined_fields["errors"]
|
225
|
+
read_attribute("errors", type)
|
226
|
+
else
|
227
|
+
@errors
|
221
228
|
end
|
229
|
+
end
|
222
230
|
|
223
|
-
|
224
|
-
|
225
|
-
|
231
|
+
def method_missing(name, *args)
|
232
|
+
if (attr = self.class::READERS[name]) && (type = @definer.defined_fields[attr])
|
233
|
+
if @enforce_collocated_callers
|
234
|
+
verify_collocated_path do
|
235
|
+
read_attribute(attr, type)
|
236
|
+
end
|
237
|
+
else
|
238
|
+
read_attribute(attr, type)
|
239
|
+
end
|
240
|
+
elsif (attr = self.class::PREDICATES[name]) && @definer.defined_fields[attr]
|
241
|
+
has_attribute?(attr)
|
226
242
|
else
|
227
|
-
|
228
|
-
|
229
|
-
|
243
|
+
begin
|
244
|
+
super
|
245
|
+
rescue NoMethodError => e
|
246
|
+
type = self.class.type
|
230
247
|
|
231
|
-
|
248
|
+
if ActiveSupport::Inflector.underscore(e.name.to_s) != e.name.to_s
|
249
|
+
raise e
|
250
|
+
end
|
251
|
+
|
252
|
+
all_fields = type.respond_to?(:all_fields) ? type.all_fields : type.fields.values
|
253
|
+
field = all_fields.find do |f|
|
254
|
+
f.name == e.name.to_s || ActiveSupport::Inflector.underscore(f.name) == e.name.to_s
|
255
|
+
end
|
256
|
+
|
257
|
+
unless field
|
258
|
+
raise UnimplementedFieldError, "undefined field `#{e.name}' on #{type.graphql_name} type. https://git.io/v1y3m"
|
259
|
+
end
|
260
|
+
|
261
|
+
if @data.key?(field.name)
|
262
|
+
raise ImplicitlyFetchedFieldError, "implicitly fetched field `#{field.name}' on #{type} type. https://git.io/v1yGL"
|
263
|
+
else
|
264
|
+
raise UnfetchedFieldError, "unfetched field `#{field.name}' on #{type} type. https://git.io/v1y3U"
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
232
268
|
end
|
233
269
|
|
234
270
|
def inspect
|
235
|
-
parent = self.class
|
271
|
+
parent = self.class
|
272
|
+
until parent.superclass == ObjectClass
|
273
|
+
parent = parent.superclass
|
274
|
+
end
|
236
275
|
|
237
276
|
ivars = @data.map { |key, value|
|
238
277
|
if value.is_a?(Hash) || value.is_a?(Array)
|
@@ -247,6 +286,26 @@ module GraphQL
|
|
247
286
|
buf << ">"
|
248
287
|
buf
|
249
288
|
end
|
289
|
+
|
290
|
+
private
|
291
|
+
|
292
|
+
def verify_collocated_path
|
293
|
+
location = caller_locations(2, 1)[0]
|
294
|
+
|
295
|
+
CollocatedEnforcement.verify_collocated_path(location, source_definition.source_location[0]) do
|
296
|
+
yield
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def read_attribute(attr, type)
|
301
|
+
@casted_data.fetch(attr) do
|
302
|
+
@casted_data[attr] = type.cast(@data[attr], @errors.filter_by_path(attr))
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def has_attribute?(attr)
|
307
|
+
!!@data[attr]
|
308
|
+
end
|
250
309
|
end
|
251
310
|
end
|
252
311
|
end
|
@@ -21,7 +21,7 @@ module GraphQL
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def define_class(definition, ast_nodes)
|
24
|
-
possible_type_names = definition.client.
|
24
|
+
possible_type_names = definition.client.possible_types(type).map(&:graphql_name)
|
25
25
|
possible_types = possible_type_names.map { |concrete_type_name|
|
26
26
|
schema_module.get_class(concrete_type_name).define_class(definition, ast_nodes)
|
27
27
|
}
|
@@ -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"
|
@@ -49,12 +50,12 @@ module GraphQL
|
|
49
50
|
when GraphQL::Schema, Class
|
50
51
|
schema
|
51
52
|
when Hash
|
52
|
-
GraphQL::Schema
|
53
|
+
GraphQL::Schema.from_introspection(schema)
|
53
54
|
when String
|
54
55
|
if schema.end_with?(".json") && File.exist?(schema)
|
55
56
|
load_schema(File.read(schema))
|
56
57
|
elsif schema =~ /\A\s*{/
|
57
|
-
load_schema(JSON.parse(schema))
|
58
|
+
load_schema(JSON.parse(schema, freeze: true))
|
58
59
|
end
|
59
60
|
else
|
60
61
|
if schema.respond_to?(:execute)
|
@@ -97,10 +98,31 @@ module GraphQL
|
|
97
98
|
@document_tracking_enabled = false
|
98
99
|
@allow_dynamic_queries = false
|
99
100
|
@enforce_collocated_callers = enforce_collocated_callers
|
100
|
-
|
101
|
+
if schema.is_a?(Class)
|
102
|
+
@possible_types = schema.possible_types
|
103
|
+
end
|
101
104
|
@types = Schema.generate(@schema)
|
102
105
|
end
|
103
106
|
|
107
|
+
# A cache of the schema's merged possible types
|
108
|
+
# @param type_condition [Class, String] a type definition or type name
|
109
|
+
def possible_types(type_condition = nil)
|
110
|
+
if type_condition
|
111
|
+
if defined?(@possible_types)
|
112
|
+
if type_condition.respond_to?(:graphql_name)
|
113
|
+
type_condition = type_condition.graphql_name
|
114
|
+
end
|
115
|
+
@possible_types[type_condition]
|
116
|
+
else
|
117
|
+
@schema.possible_types(type_condition)
|
118
|
+
end
|
119
|
+
elsif defined?(@possible_types)
|
120
|
+
@possible_types
|
121
|
+
else
|
122
|
+
@schema.possible_types(type_condition)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
104
126
|
def parse(str, filename = nil, lineno = nil)
|
105
127
|
if filename.nil? && lineno.nil?
|
106
128
|
location = caller_locations(1, 1).first
|
@@ -135,11 +157,7 @@ module GraphQL
|
|
135
157
|
# which corresponds to the spread.
|
136
158
|
# We depend on ActiveSupport to either find the already-loaded
|
137
159
|
# constant, or to load the constant by name
|
138
|
-
|
139
|
-
fragment = ActiveSupport::Inflector.constantize(const_name)
|
140
|
-
rescue NameError
|
141
|
-
fragment = nil
|
142
|
-
end
|
160
|
+
fragment = ActiveSupport::Inflector.safe_constantize(const_name)
|
143
161
|
|
144
162
|
case fragment
|
145
163
|
when FragmentDefinition
|
@@ -173,12 +191,8 @@ module GraphQL
|
|
173
191
|
|
174
192
|
doc.definitions.each do |node|
|
175
193
|
if node.name.nil?
|
176
|
-
|
177
|
-
|
178
|
-
doc = doc.replace_child(node, node_with_name)
|
179
|
-
else
|
180
|
-
node.name = "__anonymous__"
|
181
|
-
end
|
194
|
+
node_with_name = node.merge(name: "__anonymous__")
|
195
|
+
doc = doc.replace_child(node, node_with_name)
|
182
196
|
end
|
183
197
|
end
|
184
198
|
|
@@ -200,37 +214,13 @@ module GraphQL
|
|
200
214
|
raise error
|
201
215
|
end
|
202
216
|
|
203
|
-
definitions =
|
204
|
-
doc.definitions.each do |node|
|
205
|
-
sliced_document = Language::DefinitionSlice.slice(document_dependencies, node.name)
|
206
|
-
definition = Definition.for(
|
207
|
-
client: self,
|
208
|
-
ast_node: node,
|
209
|
-
document: sliced_document,
|
210
|
-
source_document: doc,
|
211
|
-
source_location: source_location
|
212
|
-
)
|
213
|
-
definitions[node.name] = definition
|
214
|
-
end
|
217
|
+
definitions = sliced_definitions(document_dependencies, doc, source_location: source_location)
|
215
218
|
|
216
|
-
|
217
|
-
|
218
|
-
visitor.visit
|
219
|
-
else
|
220
|
-
name_hook = RenameNodeHook.new(definitions)
|
221
|
-
visitor = Language::Visitor.new(document_dependencies)
|
222
|
-
visitor[Language::Nodes::FragmentDefinition].leave << name_hook.method(:rename_node)
|
223
|
-
visitor[Language::Nodes::OperationDefinition].leave << name_hook.method(:rename_node)
|
224
|
-
visitor[Language::Nodes::FragmentSpread].leave << name_hook.method(:rename_node)
|
225
|
-
visitor.visit
|
226
|
-
end
|
219
|
+
visitor = RenameNodeVisitor.new(document_dependencies, definitions: definitions)
|
220
|
+
visitor.visit
|
227
221
|
|
228
222
|
if document_tracking_enabled
|
229
|
-
|
230
|
-
@document = @document.merge(definitions: @document.definitions + doc.definitions)
|
231
|
-
else
|
232
|
-
@document.definitions.concat(doc.definitions)
|
233
|
-
end
|
223
|
+
@document = @document.merge(definitions: @document.definitions + doc.definitions)
|
234
224
|
end
|
235
225
|
|
236
226
|
if definitions["__anonymous__"]
|
@@ -276,27 +266,9 @@ module GraphQL
|
|
276
266
|
end
|
277
267
|
end
|
278
268
|
|
279
|
-
class RenameNodeHook
|
280
|
-
def initialize(definitions)
|
281
|
-
@definitions = definitions
|
282
|
-
end
|
283
|
-
|
284
|
-
def rename_node(node, _parent)
|
285
|
-
definition = @definitions[node.name]
|
286
|
-
if definition
|
287
|
-
node.extend(LazyName)
|
288
|
-
node._definition = definition
|
289
|
-
end
|
290
|
-
end
|
291
|
-
end
|
292
|
-
|
293
269
|
# Public: A wrapper to use the more-efficient `.get_type` when it's available from GraphQL-Ruby (1.10+)
|
294
270
|
def get_type(type_name)
|
295
|
-
|
296
|
-
@schema.get_type(type_name)
|
297
|
-
else
|
298
|
-
@schema.types[type_name]
|
299
|
-
end
|
271
|
+
@schema.get_type(type_name)
|
300
272
|
end
|
301
273
|
|
302
274
|
# Public: Create operation definition from a fragment definition.
|
@@ -424,6 +396,58 @@ module GraphQL
|
|
424
396
|
|
425
397
|
private
|
426
398
|
|
399
|
+
def sliced_definitions(document_dependencies, doc, source_location:)
|
400
|
+
dependencies = document_dependencies.definitions.map do |node|
|
401
|
+
[node.name, find_definition_dependencies(node)]
|
402
|
+
end.to_h
|
403
|
+
|
404
|
+
doc.definitions.map do |node|
|
405
|
+
deps = Set.new
|
406
|
+
definitions = document_dependencies.definitions.map { |x| [x.name, x] }.to_h
|
407
|
+
|
408
|
+
queue = [node.name]
|
409
|
+
while name = queue.shift
|
410
|
+
next if deps.include?(name)
|
411
|
+
deps.add(name)
|
412
|
+
queue.concat dependencies[name]
|
413
|
+
end
|
414
|
+
|
415
|
+
definitions = document_dependencies.definitions.select { |x| deps.include?(x.name) }
|
416
|
+
sliced_document = Language::Nodes::Document.new(definitions: definitions)
|
417
|
+
definition = Definition.for(
|
418
|
+
client: self,
|
419
|
+
ast_node: node,
|
420
|
+
document: sliced_document,
|
421
|
+
source_document: doc,
|
422
|
+
source_location: source_location
|
423
|
+
)
|
424
|
+
|
425
|
+
[node.name, definition]
|
426
|
+
end.to_h
|
427
|
+
end
|
428
|
+
|
429
|
+
class GatherNamesVisitor < GraphQL::Language::Visitor
|
430
|
+
def initialize(node)
|
431
|
+
@names = []
|
432
|
+
super
|
433
|
+
end
|
434
|
+
|
435
|
+
attr_reader :names
|
436
|
+
|
437
|
+
def on_fragment_spread(node, parent)
|
438
|
+
@names << node.name
|
439
|
+
super
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
def find_definition_dependencies(node)
|
444
|
+
visitor = GatherNamesVisitor.new(node)
|
445
|
+
visitor.visit
|
446
|
+
names = visitor.names
|
447
|
+
names.uniq!
|
448
|
+
names
|
449
|
+
end
|
450
|
+
|
427
451
|
def deep_freeze_json_object(obj)
|
428
452
|
case obj
|
429
453
|
when String
|
@@ -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.19.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -28,16 +28,16 @@ dependencies:
|
|
28
28
|
name: graphql
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '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: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: actionpack
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -100,42 +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
|
-
- - "
|
115
|
+
- - ">="
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: '0
|
117
|
+
version: '0'
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
|
-
- - "
|
122
|
+
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version: '0
|
124
|
+
version: '0'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: rubocop
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
129
|
- - "~>"
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version:
|
131
|
+
version: 1.57.0
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
136
|
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version:
|
138
|
+
version: 1.57.0
|
139
139
|
description: A Ruby library for declaring, composing and executing GraphQL queries
|
140
140
|
email: engineering@github.com
|
141
141
|
executables: []
|
@@ -176,6 +176,7 @@ files:
|
|
176
176
|
- lib/graphql/client/schema/scalar_type.rb
|
177
177
|
- lib/graphql/client/schema/skip_directive.rb
|
178
178
|
- lib/graphql/client/schema/union_type.rb
|
179
|
+
- lib/graphql/client/type_stack.rb
|
179
180
|
- lib/graphql/client/view_module.rb
|
180
181
|
- lib/rubocop/cop/graphql/heredoc.rb
|
181
182
|
- lib/rubocop/cop/graphql/overfetch.rb
|
@@ -183,7 +184,7 @@ homepage: https://github.com/github/graphql-client
|
|
183
184
|
licenses:
|
184
185
|
- MIT
|
185
186
|
metadata: {}
|
186
|
-
post_install_message:
|
187
|
+
post_install_message:
|
187
188
|
rdoc_options: []
|
188
189
|
require_paths:
|
189
190
|
- lib
|
@@ -198,8 +199,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
198
199
|
- !ruby/object:Gem::Version
|
199
200
|
version: '0'
|
200
201
|
requirements: []
|
201
|
-
rubygems_version: 3.
|
202
|
-
signing_key:
|
202
|
+
rubygems_version: 3.1.6
|
203
|
+
signing_key:
|
203
204
|
specification_version: 4
|
204
205
|
summary: GraphQL Client
|
205
206
|
test_files: []
|