graphql-client 0.15.0 → 0.18.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 +25 -8
- data/lib/graphql/client/definition_variables.rb +6 -6
- data/lib/graphql/client/document_types.rb +5 -2
- data/lib/graphql/client/http.rb +1 -1
- data/lib/graphql/client/query_typename.rb +2 -2
- data/lib/graphql/client/schema/enum_type.rb +2 -2
- data/lib/graphql/client/schema/interface_type.rb +3 -3
- data/lib/graphql/client/schema/object_type.rb +148 -88
- data/lib/graphql/client/schema/possible_types.rb +1 -1
- data/lib/graphql/client/schema/scalar_type.rb +2 -2
- data/lib/graphql/client/schema/union_type.rb +3 -3
- data/lib/graphql/client/schema.rb +18 -17
- data/lib/graphql/client/view_module.rb +1 -1
- data/lib/graphql/client.rb +98 -44
- metadata +13 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82b89599e02036868b2249fbd3ac2aeb89b6043b19040bdaeaf66f31b5f1b28e
|
4
|
+
data.tar.gz: 2e168f61452bd5041aa01dce3a09c99959b629afeb2880ca629d1034f22fb0d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f14f21ff57e8dc29dbef6e5fb2ee6d2fb552075986ef57c8f89a3d7ce5b2faa8e2965b20a5b8f7735394b3ee13cdc8e55923bda0dd02fa8625f7e2b824dab7b
|
7
|
+
data.tar.gz: ee04576697c258bdc3959366d0cccef720e24eb2358ecbb28c020d912c5b241ac7ba195a8240656c6f825109c23a321ef2449892b4d893e29802c03b6580f557
|
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
|
@@ -45,7 +45,7 @@ module GraphQL
|
|
45
45
|
raise "Unexpected operation_type: #{ast_node.operation_type}"
|
46
46
|
end
|
47
47
|
when GraphQL::Language::Nodes::FragmentDefinition
|
48
|
-
@client.
|
48
|
+
@client.get_type(ast_node.type.name)
|
49
49
|
else
|
50
50
|
raise "Unexpected ast_node: #{ast_node}"
|
51
51
|
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)
|
@@ -144,8 +159,8 @@ module GraphQL
|
|
144
159
|
|
145
160
|
def cast_object(obj)
|
146
161
|
if obj.class.is_a?(GraphQL::Client::Schema::ObjectType)
|
147
|
-
unless obj.
|
148
|
-
raise TypeError, "#{definition_node.name} is not included in #{obj.
|
162
|
+
unless obj._spreads.include?(definition_node.name)
|
163
|
+
raise TypeError, "#{definition_node.name} is not included in #{obj.source_definition.name}"
|
149
164
|
end
|
150
165
|
schema_class.cast(obj.to_h, obj.errors)
|
151
166
|
else
|
@@ -170,16 +185,18 @@ module GraphQL
|
|
170
185
|
end
|
171
186
|
|
172
187
|
def flatten_spreads(node)
|
173
|
-
|
188
|
+
spreads = []
|
189
|
+
node.selections.each do |selection|
|
174
190
|
case selection
|
175
191
|
when Language::Nodes::FragmentSpread
|
176
|
-
selection
|
192
|
+
spreads << selection
|
177
193
|
when Language::Nodes::InlineFragment
|
178
|
-
flatten_spreads(selection)
|
194
|
+
spreads.concat(flatten_spreads(selection))
|
179
195
|
else
|
180
|
-
|
196
|
+
# Do nothing, not a spread
|
181
197
|
end
|
182
198
|
end
|
199
|
+
spreads
|
183
200
|
end
|
184
201
|
|
185
202
|
def index_node_definitions(visitor)
|
@@ -14,7 +14,7 @@ module GraphQL
|
|
14
14
|
#
|
15
15
|
# Returns a Hash[Symbol] to GraphQL::Type objects.
|
16
16
|
def self.variables(schema, document, definition_name = nil)
|
17
|
-
unless schema.is_a?(GraphQL::Schema)
|
17
|
+
unless schema.is_a?(GraphQL::Schema) || (schema.is_a?(Class) && schema < GraphQL::Schema)
|
18
18
|
raise TypeError, "expected schema to be a GraphQL::Schema, but was #{schema.class}"
|
19
19
|
end
|
20
20
|
|
@@ -35,7 +35,7 @@ module GraphQL
|
|
35
35
|
|
36
36
|
if existing_type && existing_type.unwrap != definition.type.unwrap
|
37
37
|
raise GraphQL::Client::ValidationError, "$#{node.name} was already declared as #{existing_type.unwrap}, but was #{definition.type.unwrap}"
|
38
|
-
elsif !existing_type.
|
38
|
+
elsif !(existing_type && existing_type.kind.non_null?)
|
39
39
|
variables[node.name.to_sym] = definition.type
|
40
40
|
end
|
41
41
|
end
|
@@ -66,13 +66,13 @@ module GraphQL
|
|
66
66
|
#
|
67
67
|
# Returns GraphQL::Language::Nodes::Type.
|
68
68
|
def self.variable_node(type)
|
69
|
-
case type
|
70
|
-
when
|
69
|
+
case type.kind.name
|
70
|
+
when "NON_NULL"
|
71
71
|
GraphQL::Language::Nodes::NonNullType.new(of_type: variable_node(type.of_type))
|
72
|
-
when
|
72
|
+
when "LIST"
|
73
73
|
GraphQL::Language::Nodes::ListType.new(of_type: variable_node(type.of_type))
|
74
74
|
else
|
75
|
-
GraphQL::Language::Nodes::TypeName.new(name: type.
|
75
|
+
GraphQL::Language::Nodes::TypeName.new(name: type.graphql_name)
|
76
76
|
end
|
77
77
|
end
|
78
78
|
end
|
@@ -12,7 +12,7 @@ module GraphQL
|
|
12
12
|
#
|
13
13
|
# Returns a Hash[Language::Nodes::Node] to GraphQL::Type objects.
|
14
14
|
def self.analyze_types(schema, document)
|
15
|
-
unless schema.is_a?(GraphQL::Schema)
|
15
|
+
unless schema.is_a?(GraphQL::Schema) || (schema.is_a?(Class) && schema < GraphQL::Schema)
|
16
16
|
raise TypeError, "expected schema to be a GraphQL::Schema, but was #{schema.class}"
|
17
17
|
end
|
18
18
|
|
@@ -40,7 +40,10 @@ module GraphQL
|
|
40
40
|
visitor.visit
|
41
41
|
|
42
42
|
fields
|
43
|
-
rescue StandardError
|
43
|
+
rescue StandardError => err
|
44
|
+
if err.is_a?(TypeError)
|
45
|
+
raise
|
46
|
+
end
|
44
47
|
# FIXME: TypeStack my crash on invalid documents
|
45
48
|
fields
|
46
49
|
end
|
data/lib/graphql/client/http.rb
CHANGED
@@ -28,8 +28,8 @@ module GraphQL
|
|
28
28
|
type = @types[node]
|
29
29
|
type = type && type.unwrap
|
30
30
|
|
31
|
-
if (node.selections.any? && (type.nil? || type.
|
32
|
-
(node.selections.none? && type.
|
31
|
+
if (node.selections.any? && (type.nil? || type.kind.interface? || type.kind.union?)) ||
|
32
|
+
(node.selections.none? && (type && type.kind.object?))
|
33
33
|
names = QueryTypename.node_flatten_selections(node.selections).map { |s| s.respond_to?(:name) ? s.name : nil }
|
34
34
|
names = Set.new(names.compact)
|
35
35
|
|
@@ -41,8 +41,8 @@ module GraphQL
|
|
41
41
|
#
|
42
42
|
# type - GraphQL::EnumType instance
|
43
43
|
def initialize(type)
|
44
|
-
unless type.
|
45
|
-
raise "expected type to be
|
44
|
+
unless type.kind.enum?
|
45
|
+
raise "expected type to be an Enum, but was #{type.class}"
|
46
46
|
end
|
47
47
|
|
48
48
|
@type = type
|
@@ -9,8 +9,8 @@ module GraphQL
|
|
9
9
|
include BaseType
|
10
10
|
|
11
11
|
def initialize(type)
|
12
|
-
unless type.
|
13
|
-
raise "expected type to be
|
12
|
+
unless type.kind.interface?
|
13
|
+
raise "expected type to be an Interface, but was #{type.class}"
|
14
14
|
end
|
15
15
|
|
16
16
|
@type = type
|
@@ -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
|
|
@@ -41,61 +88,14 @@ module GraphQL
|
|
41
88
|
field_nodes.each do |result_name, field_ast_nodes|
|
42
89
|
# `result_name` might be an alias, so make sure to get the proper name
|
43
90
|
field_name = field_ast_nodes.first.name
|
44
|
-
field_definition = definition.client.schema.get_field(type.
|
91
|
+
field_definition = definition.client.schema.get_field(type.graphql_name, field_name)
|
45
92
|
field_return_type = field_definition.type
|
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
|
58
|
-
|
59
|
-
klass
|
60
|
-
end
|
61
|
-
|
62
|
-
PREDICATE_CACHE = Hash.new { |h, name|
|
63
|
-
h[name] = -> { @data[name] ? true : false }
|
64
|
-
}
|
96
|
+
spreads = definition.indexes[:spreads][ast_nodes.first]
|
65
97
|
|
66
|
-
|
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)
|
@@ -134,9 +134,8 @@ module GraphQL
|
|
134
134
|
continue_selection = if selected_ast_node.type.nil?
|
135
135
|
true
|
136
136
|
else
|
137
|
-
|
138
|
-
|
139
|
-
applicable_types = schema.possible_types(type_condition)
|
137
|
+
type_condition = definition.client.get_type(selected_ast_node.type.name)
|
138
|
+
applicable_types = definition.client.possible_types(type_condition)
|
140
139
|
# continue if this object type is one of the types matching the fragment condition
|
141
140
|
applicable_types.include?(type)
|
142
141
|
end
|
@@ -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
|
-
|
155
|
-
type_condition = schema.types[fragment_definition.type.name]
|
156
|
-
applicable_types = schema.possible_types(type_condition)
|
152
|
+
type_condition = definition.client.get_type(fragment_definition.type.name)
|
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,41 +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
|
-
|
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
|
216
213
|
end
|
214
|
+
end
|
217
215
|
|
218
|
-
|
219
|
-
|
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
|
220
228
|
end
|
229
|
+
end
|
221
230
|
|
222
|
-
|
223
|
-
|
224
|
-
|
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)
|
225
242
|
else
|
226
|
-
|
227
|
-
|
228
|
-
|
243
|
+
begin
|
244
|
+
super
|
245
|
+
rescue NoMethodError => e
|
246
|
+
type = self.class.type
|
229
247
|
|
230
|
-
|
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
|
231
268
|
end
|
232
269
|
|
233
270
|
def inspect
|
234
|
-
parent = self.class
|
271
|
+
parent = self.class
|
272
|
+
until parent.superclass == ObjectClass
|
273
|
+
parent = parent.superclass
|
274
|
+
end
|
235
275
|
|
236
276
|
ivars = @data.map { |key, value|
|
237
277
|
if value.is_a?(Hash) || value.is_a?(Array)
|
@@ -246,6 +286,26 @@ module GraphQL
|
|
246
286
|
buf << ">"
|
247
287
|
buf
|
248
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
|
249
309
|
end
|
250
310
|
end
|
251
311
|
end
|
@@ -12,8 +12,8 @@ module GraphQL
|
|
12
12
|
#
|
13
13
|
# type - GraphQL::BaseType instance
|
14
14
|
def initialize(type)
|
15
|
-
unless type.
|
16
|
-
raise "expected type to be a
|
15
|
+
unless type.kind.scalar?
|
16
|
+
raise "expected type to be a Scalar, but was #{type.class}"
|
17
17
|
end
|
18
18
|
|
19
19
|
@type = type
|
@@ -9,8 +9,8 @@ module GraphQL
|
|
9
9
|
include BaseType
|
10
10
|
|
11
11
|
def initialize(type)
|
12
|
-
unless type.
|
13
|
-
raise "expected type to be a
|
12
|
+
unless type.kind.union?
|
13
|
+
raise "expected type to be a Union, but was #{type.class}"
|
14
14
|
end
|
15
15
|
|
16
16
|
@type = type
|
@@ -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,13 +16,13 @@ module GraphQL
|
|
16
16
|
module Schema
|
17
17
|
module ClassMethods
|
18
18
|
def define_class(definition, ast_nodes, type)
|
19
|
-
type_class = case type
|
20
|
-
when
|
19
|
+
type_class = case type.kind.name
|
20
|
+
when "NON_NULL"
|
21
21
|
define_class(definition, ast_nodes, type.of_type).to_non_null_type
|
22
|
-
when
|
22
|
+
when "LIST"
|
23
23
|
define_class(definition, ast_nodes, type.of_type).to_list_type
|
24
24
|
else
|
25
|
-
get_class(type.
|
25
|
+
get_class(type.graphql_name).define_class(definition, ast_nodes)
|
26
26
|
end
|
27
27
|
|
28
28
|
ast_nodes.each do |ast_node|
|
@@ -62,7 +62,7 @@ module GraphQL
|
|
62
62
|
private
|
63
63
|
|
64
64
|
def normalize_type_name(type_name)
|
65
|
-
|
65
|
+
/\A[A-Z]/.match?(type_name) ? type_name : type_name.camelize
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
@@ -85,18 +85,18 @@ module GraphQL
|
|
85
85
|
def self.class_for(schema, type, cache)
|
86
86
|
return cache[type] if cache[type]
|
87
87
|
|
88
|
-
case type
|
89
|
-
when
|
88
|
+
case type.kind.name
|
89
|
+
when "INPUT_OBJECT"
|
90
90
|
nil
|
91
|
-
when
|
91
|
+
when "SCALAR"
|
92
92
|
cache[type] = ScalarType.new(type)
|
93
|
-
when
|
93
|
+
when "ENUM"
|
94
94
|
cache[type] = EnumType.new(type)
|
95
|
-
when
|
95
|
+
when "LIST"
|
96
96
|
cache[type] = class_for(schema, type.of_type, cache).to_list_type
|
97
|
-
when
|
97
|
+
when "NON_NULL"
|
98
98
|
cache[type] = class_for(schema, type.of_type, cache).to_non_null_type
|
99
|
-
when
|
99
|
+
when "UNION"
|
100
100
|
klass = cache[type] = UnionType.new(type)
|
101
101
|
|
102
102
|
type.possible_types.each do |possible_type|
|
@@ -105,22 +105,23 @@ module GraphQL
|
|
105
105
|
end
|
106
106
|
|
107
107
|
klass
|
108
|
-
when
|
108
|
+
when "INTERFACE"
|
109
109
|
cache[type] = InterfaceType.new(type)
|
110
|
-
when
|
110
|
+
when "OBJECT"
|
111
111
|
klass = cache[type] = ObjectType.new(type)
|
112
112
|
|
113
113
|
type.interfaces.each do |interface|
|
114
114
|
klass.send :include, class_for(schema, interface, cache)
|
115
115
|
end
|
116
|
-
|
117
|
-
type.all_fields.
|
116
|
+
# Legacy objects have `.all_fields`
|
117
|
+
all_fields = type.respond_to?(:all_fields) ? type.all_fields : type.fields.values
|
118
|
+
all_fields.each do |field|
|
118
119
|
klass.fields[field.name.to_sym] = class_for(schema, field.type, cache)
|
119
120
|
end
|
120
121
|
|
121
122
|
klass
|
122
123
|
else
|
123
|
-
raise TypeError, "unexpected #{type.class}"
|
124
|
+
raise TypeError, "unexpected #{type.class} (#{type.inspect})"
|
124
125
|
end
|
125
126
|
end
|
126
127
|
end
|
data/lib/graphql/client.rb
CHANGED
@@ -46,15 +46,15 @@ module GraphQL
|
|
46
46
|
|
47
47
|
def self.load_schema(schema)
|
48
48
|
case schema
|
49
|
-
when GraphQL::Schema
|
49
|
+
when GraphQL::Schema, Class
|
50
50
|
schema
|
51
51
|
when Hash
|
52
|
-
GraphQL::Schema
|
52
|
+
GraphQL::Schema.from_introspection(schema)
|
53
53
|
when String
|
54
54
|
if schema.end_with?(".json") && File.exist?(schema)
|
55
55
|
load_schema(File.read(schema))
|
56
56
|
elsif schema =~ /\A\s*{/
|
57
|
-
load_schema(JSON.parse(schema))
|
57
|
+
load_schema(JSON.parse(schema, freeze: true))
|
58
58
|
end
|
59
59
|
else
|
60
60
|
if schema.respond_to?(:execute)
|
@@ -97,10 +97,31 @@ module GraphQL
|
|
97
97
|
@document_tracking_enabled = false
|
98
98
|
@allow_dynamic_queries = false
|
99
99
|
@enforce_collocated_callers = enforce_collocated_callers
|
100
|
-
|
100
|
+
if schema.is_a?(Class)
|
101
|
+
@possible_types = schema.possible_types
|
102
|
+
end
|
101
103
|
@types = Schema.generate(@schema)
|
102
104
|
end
|
103
105
|
|
106
|
+
# A cache of the schema's merged possible types
|
107
|
+
# @param type_condition [Class, String] a type definition or type name
|
108
|
+
def possible_types(type_condition = nil)
|
109
|
+
if type_condition
|
110
|
+
if defined?(@possible_types)
|
111
|
+
if type_condition.respond_to?(:graphql_name)
|
112
|
+
type_condition = type_condition.graphql_name
|
113
|
+
end
|
114
|
+
@possible_types[type_condition]
|
115
|
+
else
|
116
|
+
@schema.possible_types(type_condition)
|
117
|
+
end
|
118
|
+
elsif defined?(@possible_types)
|
119
|
+
@possible_types
|
120
|
+
else
|
121
|
+
@schema.possible_types(type_condition)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
104
125
|
def parse(str, filename = nil, lineno = nil)
|
105
126
|
if filename.nil? && lineno.nil?
|
106
127
|
location = caller_locations(1, 1).first
|
@@ -135,11 +156,7 @@ module GraphQL
|
|
135
156
|
# which corresponds to the spread.
|
136
157
|
# We depend on ActiveSupport to either find the already-loaded
|
137
158
|
# constant, or to load the constant by name
|
138
|
-
|
139
|
-
fragment = ActiveSupport::Inflector.constantize(const_name)
|
140
|
-
rescue NameError
|
141
|
-
fragment = nil
|
142
|
-
end
|
159
|
+
fragment = ActiveSupport::Inflector.safe_constantize(const_name)
|
143
160
|
|
144
161
|
case fragment
|
145
162
|
when FragmentDefinition
|
@@ -173,12 +190,8 @@ module GraphQL
|
|
173
190
|
|
174
191
|
doc.definitions.each do |node|
|
175
192
|
if node.name.nil?
|
176
|
-
|
177
|
-
|
178
|
-
doc = doc.replace_child(node, node_with_name)
|
179
|
-
else
|
180
|
-
node.name = "__anonymous__"
|
181
|
-
end
|
193
|
+
node_with_name = node.merge(name: "__anonymous__")
|
194
|
+
doc = doc.replace_child(node, node_with_name)
|
182
195
|
end
|
183
196
|
end
|
184
197
|
|
@@ -200,32 +213,13 @@ module GraphQL
|
|
200
213
|
raise error
|
201
214
|
end
|
202
215
|
|
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
|
216
|
+
definitions = sliced_definitions(document_dependencies, doc, source_location: source_location)
|
215
217
|
|
216
|
-
|
217
|
-
visitor = Language::Visitor.new(document_dependencies)
|
218
|
-
visitor[Language::Nodes::FragmentDefinition].leave << name_hook.method(:rename_node)
|
219
|
-
visitor[Language::Nodes::OperationDefinition].leave << name_hook.method(:rename_node)
|
220
|
-
visitor[Language::Nodes::FragmentSpread].leave << name_hook.method(:rename_node)
|
218
|
+
visitor = RenameNodeVisitor.new(document_dependencies, definitions: definitions)
|
221
219
|
visitor.visit
|
222
220
|
|
223
221
|
if document_tracking_enabled
|
224
|
-
|
225
|
-
@document = @document.merge(definitions: @document.definitions + doc.definitions)
|
226
|
-
else
|
227
|
-
@document.definitions.concat(doc.definitions)
|
228
|
-
end
|
222
|
+
@document = @document.merge(definitions: @document.definitions + doc.definitions)
|
229
223
|
end
|
230
224
|
|
231
225
|
if definitions["__anonymous__"]
|
@@ -239,12 +233,30 @@ module GraphQL
|
|
239
233
|
end
|
240
234
|
end
|
241
235
|
|
242
|
-
class
|
243
|
-
def initialize(definitions)
|
236
|
+
class RenameNodeVisitor < GraphQL::Language::Visitor
|
237
|
+
def initialize(document, definitions:)
|
238
|
+
super(document)
|
244
239
|
@definitions = definitions
|
245
240
|
end
|
246
241
|
|
247
|
-
def
|
242
|
+
def on_fragment_definition(node, _parent)
|
243
|
+
rename_node(node)
|
244
|
+
super
|
245
|
+
end
|
246
|
+
|
247
|
+
def on_operation_definition(node, _parent)
|
248
|
+
rename_node(node)
|
249
|
+
super
|
250
|
+
end
|
251
|
+
|
252
|
+
def on_fragment_spread(node, _parent)
|
253
|
+
rename_node(node)
|
254
|
+
super
|
255
|
+
end
|
256
|
+
|
257
|
+
private
|
258
|
+
|
259
|
+
def rename_node(node)
|
248
260
|
definition = @definitions[node.name]
|
249
261
|
if definition
|
250
262
|
node.extend(LazyName)
|
@@ -253,6 +265,10 @@ module GraphQL
|
|
253
265
|
end
|
254
266
|
end
|
255
267
|
|
268
|
+
# Public: A wrapper to use the more-efficient `.get_type` when it's available from GraphQL-Ruby (1.10+)
|
269
|
+
def get_type(type_name)
|
270
|
+
@schema.get_type(type_name)
|
271
|
+
end
|
256
272
|
|
257
273
|
# Public: Create operation definition from a fragment definition.
|
258
274
|
#
|
@@ -288,15 +304,15 @@ module GraphQL
|
|
288
304
|
variables = GraphQL::Client::DefinitionVariables.operation_variables(self.schema, fragment.document, fragment.definition_name)
|
289
305
|
type_name = fragment.definition_node.type.name
|
290
306
|
|
291
|
-
if schema.query && type_name == schema.query.
|
307
|
+
if schema.query && type_name == schema.query.graphql_name
|
292
308
|
operation_type = "query"
|
293
|
-
elsif schema.mutation && type_name == schema.mutation.
|
309
|
+
elsif schema.mutation && type_name == schema.mutation.graphql_name
|
294
310
|
operation_type = "mutation"
|
295
|
-
elsif schema.subscription && type_name == schema.subscription.
|
311
|
+
elsif schema.subscription && type_name == schema.subscription.graphql_name
|
296
312
|
operation_type = "subscription"
|
297
313
|
else
|
298
314
|
types = [schema.query, schema.mutation, schema.subscription].compact
|
299
|
-
raise Error, "Fragment must be defined on #{types.map(&:
|
315
|
+
raise Error, "Fragment must be defined on #{types.map(&:graphql_name).join(", ")}"
|
300
316
|
end
|
301
317
|
|
302
318
|
doc_ast = GraphQL::Language::Nodes::Document.new(definitions: [
|
@@ -379,6 +395,44 @@ module GraphQL
|
|
379
395
|
|
380
396
|
private
|
381
397
|
|
398
|
+
def sliced_definitions(document_dependencies, doc, source_location:)
|
399
|
+
dependencies = document_dependencies.definitions.map do |node|
|
400
|
+
[node.name, find_definition_dependencies(node)]
|
401
|
+
end.to_h
|
402
|
+
|
403
|
+
doc.definitions.map do |node|
|
404
|
+
deps = Set.new
|
405
|
+
definitions = document_dependencies.definitions.map { |x| [x.name, x] }.to_h
|
406
|
+
|
407
|
+
queue = [node.name]
|
408
|
+
while name = queue.shift
|
409
|
+
next if deps.include?(name)
|
410
|
+
deps.add(name)
|
411
|
+
queue.concat dependencies[name]
|
412
|
+
end
|
413
|
+
|
414
|
+
definitions = document_dependencies.definitions.select { |x| deps.include?(x.name) }
|
415
|
+
sliced_document = Language::Nodes::Document.new(definitions: definitions)
|
416
|
+
definition = Definition.for(
|
417
|
+
client: self,
|
418
|
+
ast_node: node,
|
419
|
+
document: sliced_document,
|
420
|
+
source_document: doc,
|
421
|
+
source_location: source_location
|
422
|
+
)
|
423
|
+
|
424
|
+
[node.name, definition]
|
425
|
+
end.to_h
|
426
|
+
end
|
427
|
+
|
428
|
+
def find_definition_dependencies(node)
|
429
|
+
names = []
|
430
|
+
visitor = Language::Visitor.new(node)
|
431
|
+
visitor[Language::Nodes::FragmentSpread] << -> (node, parent) { names << node.name }
|
432
|
+
visitor.visit
|
433
|
+
names.uniq
|
434
|
+
end
|
435
|
+
|
382
436
|
def deep_freeze_json_object(obj)
|
383
437
|
case obj
|
384
438
|
when String
|
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.18.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: 2022-05-02 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
|
@@ -115,6 +115,9 @@ dependencies:
|
|
115
115
|
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
117
|
version: '0.10'
|
118
|
+
- - "<="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: 0.16.0
|
118
121
|
type: :development
|
119
122
|
prerelease: false
|
120
123
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -122,6 +125,9 @@ dependencies:
|
|
122
125
|
- - "~>"
|
123
126
|
- !ruby/object:Gem::Version
|
124
127
|
version: '0.10'
|
128
|
+
- - "<="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: 0.16.0
|
125
131
|
- !ruby/object:Gem::Dependency
|
126
132
|
name: rubocop
|
127
133
|
requirement: !ruby/object:Gem::Requirement
|
@@ -198,7 +204,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
198
204
|
- !ruby/object:Gem::Version
|
199
205
|
version: '0'
|
200
206
|
requirements: []
|
201
|
-
rubygems_version: 3.
|
207
|
+
rubygems_version: 3.2.9
|
202
208
|
signing_key:
|
203
209
|
specification_version: 4
|
204
210
|
summary: GraphQL Client
|