graphql 1.12.5 → 1.12.10
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.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/generators/graphql/install_generator.rb +1 -1
- data/lib/generators/graphql/templates/graphql_controller.erb +2 -2
- data/lib/graphql.rb +13 -11
- data/lib/graphql/dataloader.rb +36 -5
- data/lib/graphql/execution/errors.rb +109 -11
- data/lib/graphql/execution/interpreter/runtime.rb +32 -36
- data/lib/graphql/introspection.rb +1 -1
- data/lib/graphql/introspection/directive_type.rb +7 -3
- data/lib/graphql/language.rb +1 -0
- data/lib/graphql/language/cache.rb +37 -0
- data/lib/graphql/language/parser.rb +15 -5
- data/lib/graphql/language/parser.y +15 -5
- data/lib/graphql/pagination/active_record_relation_connection.rb +7 -0
- data/lib/graphql/pagination/connection.rb +6 -1
- data/lib/graphql/pagination/connections.rb +2 -1
- data/lib/graphql/pagination/relation_connection.rb +12 -1
- data/lib/graphql/query.rb +1 -3
- data/lib/graphql/query/null_context.rb +7 -1
- data/lib/graphql/query/validation_pipeline.rb +1 -1
- data/lib/graphql/railtie.rb +9 -1
- data/lib/graphql/rake_task.rb +3 -0
- data/lib/graphql/schema.rb +23 -37
- data/lib/graphql/schema/argument.rb +3 -1
- data/lib/graphql/schema/field/connection_extension.rb +1 -0
- data/lib/graphql/schema/input_object.rb +2 -2
- data/lib/graphql/schema/loader.rb +8 -0
- data/lib/graphql/schema/member/base_dsl_methods.rb +3 -15
- data/lib/graphql/schema/object.rb +19 -5
- data/lib/graphql/schema/resolver.rb +24 -22
- data/lib/graphql/schema/scalar.rb +3 -1
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +3 -1
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +6 -2
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +17 -8
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
- data/lib/graphql/static_validation/validator.rb +5 -0
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +4 -3
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +0 -3
- data/lib/graphql/subscriptions/serialize.rb +11 -1
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +2 -1
- data/lib/graphql/types/relay/base_connection.rb +4 -0
- data/lib/graphql/types/relay/connection_behaviors.rb +38 -5
- data/lib/graphql/types/relay/edge_behaviors.rb +12 -1
- data/lib/graphql/version.rb +1 -1
- metadata +7 -6
@@ -169,6 +169,12 @@ module GraphQL
|
|
169
169
|
def build_fields(type_defn, fields, type_resolver)
|
170
170
|
loader = self
|
171
171
|
fields.each do |field_hash|
|
172
|
+
unwrapped_field_hash = field_hash
|
173
|
+
while (of_type = unwrapped_field_hash["ofType"])
|
174
|
+
unwrapped_field_hash = of_type
|
175
|
+
end
|
176
|
+
type_name = unwrapped_field_hash["name"]
|
177
|
+
|
172
178
|
type_defn.field(
|
173
179
|
field_hash["name"],
|
174
180
|
type: type_resolver.call(field_hash["type"]),
|
@@ -176,6 +182,8 @@ module GraphQL
|
|
176
182
|
deprecation_reason: field_hash["deprecationReason"],
|
177
183
|
null: true,
|
178
184
|
camelize: false,
|
185
|
+
connection_extension: nil,
|
186
|
+
connection: type_name.end_with?("Connection"),
|
179
187
|
) do
|
180
188
|
if field_hash["args"].any?
|
181
189
|
loader.build_arguments(self, field_hash["args"], type_resolver)
|
@@ -113,27 +113,15 @@ module GraphQL
|
|
113
113
|
end
|
114
114
|
|
115
115
|
def visible?(context)
|
116
|
-
|
117
|
-
@mutation.visible?(context)
|
118
|
-
else
|
119
|
-
true
|
120
|
-
end
|
116
|
+
true
|
121
117
|
end
|
122
118
|
|
123
119
|
def accessible?(context)
|
124
|
-
|
125
|
-
@mutation.accessible?(context)
|
126
|
-
else
|
127
|
-
true
|
128
|
-
end
|
120
|
+
true
|
129
121
|
end
|
130
122
|
|
131
123
|
def authorized?(object, context)
|
132
|
-
|
133
|
-
@mutation.authorized?(object, context)
|
134
|
-
else
|
135
|
-
true
|
136
|
-
end
|
124
|
+
true
|
137
125
|
end
|
138
126
|
end
|
139
127
|
end
|
@@ -48,12 +48,26 @@ module GraphQL
|
|
48
48
|
# @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy]
|
49
49
|
# @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false`
|
50
50
|
def authorized_new(object, context)
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
51
|
+
trace_payload = { context: context, type: self, object: object, path: context[:current_path] }
|
52
|
+
|
53
|
+
maybe_lazy_auth_val = context.query.trace("authorized", trace_payload) do
|
54
|
+
context.query.with_error_handling do
|
55
|
+
begin
|
56
|
+
authorized?(object, context)
|
57
|
+
rescue GraphQL::UnauthorizedError => err
|
58
|
+
context.schema.unauthorized_object(err)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
auth_val = if context.schema.lazy?(maybe_lazy_auth_val)
|
64
|
+
GraphQL::Execution::Lazy.new do
|
65
|
+
context.query.trace("authorized_lazy", trace_payload) do
|
66
|
+
context.schema.sync_lazy(maybe_lazy_auth_val)
|
67
|
+
end
|
56
68
|
end
|
69
|
+
else
|
70
|
+
maybe_lazy_auth_val
|
57
71
|
end
|
58
72
|
|
59
73
|
context.schema.after_lazy(auth_val) do |is_authorized|
|
@@ -333,30 +333,32 @@ module GraphQL
|
|
333
333
|
arg_defn = super(*args, from_resolver: true, **kwargs)
|
334
334
|
own_arguments_loads_as_type[arg_defn.keyword] = loads if loads
|
335
335
|
|
336
|
-
if
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
336
|
+
if !method_defined?(:"load_#{arg_defn.keyword}")
|
337
|
+
if loads && arg_defn.type.list?
|
338
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
339
|
+
def load_#{arg_defn.keyword}(values)
|
340
|
+
argument = @arguments_by_keyword[:#{arg_defn.keyword}]
|
341
|
+
lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
|
342
|
+
context.schema.after_lazy(values) do |values2|
|
343
|
+
GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, lookup_as_type, value, context) })
|
344
|
+
end
|
343
345
|
end
|
346
|
+
RUBY
|
347
|
+
elsif loads
|
348
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
349
|
+
def load_#{arg_defn.keyword}(value)
|
350
|
+
argument = @arguments_by_keyword[:#{arg_defn.keyword}]
|
351
|
+
lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
|
352
|
+
load_application_object(argument, lookup_as_type, value, context)
|
353
|
+
end
|
354
|
+
RUBY
|
355
|
+
else
|
356
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
357
|
+
def load_#{arg_defn.keyword}(value)
|
358
|
+
value
|
359
|
+
end
|
360
|
+
RUBY
|
344
361
|
end
|
345
|
-
RUBY
|
346
|
-
elsif loads
|
347
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
348
|
-
def load_#{arg_defn.keyword}(value)
|
349
|
-
argument = @arguments_by_keyword[:#{arg_defn.keyword}]
|
350
|
-
lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
|
351
|
-
load_application_object(argument, lookup_as_type, value, context)
|
352
|
-
end
|
353
|
-
RUBY
|
354
|
-
else
|
355
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
356
|
-
def load_#{arg_defn.keyword}(value)
|
357
|
-
value
|
358
|
-
end
|
359
|
-
RUBY
|
360
362
|
end
|
361
363
|
|
362
364
|
arg_defn
|
@@ -44,7 +44,9 @@ module GraphQL
|
|
44
44
|
def validate_non_null_input(value, ctx)
|
45
45
|
result = Query::InputValidationResult.new
|
46
46
|
coerced_result = begin
|
47
|
-
|
47
|
+
ctx.query.with_error_handling do
|
48
|
+
coerce_input(value, ctx)
|
49
|
+
end
|
48
50
|
rescue GraphQL::CoercionError => err
|
49
51
|
err
|
50
52
|
end
|
@@ -41,7 +41,9 @@ module GraphQL
|
|
41
41
|
error_options = {
|
42
42
|
nodes: parent,
|
43
43
|
type: kind_of_node,
|
44
|
-
|
44
|
+
argument_name: node.name,
|
45
|
+
argument: arg_defn,
|
46
|
+
value: node.value
|
45
47
|
}
|
46
48
|
if coerce_extensions
|
47
49
|
error_options[:coerce_extensions] = coerce_extensions
|
@@ -4,13 +4,17 @@ module GraphQL
|
|
4
4
|
class ArgumentLiteralsAreCompatibleError < StaticValidation::Error
|
5
5
|
attr_reader :type_name
|
6
6
|
attr_reader :argument_name
|
7
|
+
attr_reader :argument
|
8
|
+
attr_reader :value
|
7
9
|
|
8
|
-
def initialize(message, path: nil, nodes: [], type:,
|
10
|
+
def initialize(message, path: nil, nodes: [], type:, argument_name: nil, extensions: nil, coerce_extensions: nil, argument: nil, value: nil)
|
9
11
|
super(message, path: path, nodes: nodes)
|
10
12
|
@type_name = type
|
11
|
-
@argument_name =
|
13
|
+
@argument_name = argument_name
|
12
14
|
@extensions = extensions
|
13
15
|
@coerce_extensions = coerce_extensions
|
16
|
+
@argument = argument
|
17
|
+
@value = value
|
14
18
|
end
|
15
19
|
|
16
20
|
# A hash representation of this Message
|
@@ -5,12 +5,14 @@ module GraphQL
|
|
5
5
|
attr_reader :name
|
6
6
|
attr_reader :type_name
|
7
7
|
attr_reader :argument_name
|
8
|
+
attr_reader :parent
|
8
9
|
|
9
|
-
def initialize(message, path: nil, nodes: [], name:, type:,
|
10
|
+
def initialize(message, path: nil, nodes: [], name:, type:, argument_name:, parent:)
|
10
11
|
super(message, path: path, nodes: nodes)
|
11
12
|
@name = name
|
12
13
|
@type_name = type
|
13
|
-
@argument_name =
|
14
|
+
@argument_name = argument_name
|
15
|
+
@parent = parent
|
14
16
|
end
|
15
17
|
|
16
18
|
# A hash representation of this Message
|
@@ -373,17 +373,26 @@ module GraphQL
|
|
373
373
|
# In this context, `parents` represends the "self scope" of the field,
|
374
374
|
# what types may be found at this point in the query.
|
375
375
|
def mutually_exclusive?(parents1, parents2)
|
376
|
-
parents1.
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
376
|
+
if parents1.empty? || parents2.empty?
|
377
|
+
false
|
378
|
+
elsif parents1.length == parents2.length
|
379
|
+
parents1.length.times.any? do |i|
|
380
|
+
type1 = parents1[i - 1]
|
381
|
+
type2 = parents2[i - 1]
|
382
|
+
if type1 == type2
|
383
|
+
# If the types we're comparing are the same type,
|
384
|
+
# then they aren't mutually exclusive
|
385
|
+
false
|
386
|
+
else
|
387
|
+
# Check if these two scopes have _any_ types in common.
|
388
|
+
possible_right_types = context.query.possible_types(type1)
|
389
|
+
possible_left_types = context.query.possible_types(type2)
|
390
|
+
(possible_right_types & possible_left_types).empty?
|
382
391
|
end
|
383
392
|
end
|
393
|
+
else
|
394
|
+
true
|
384
395
|
end
|
385
|
-
|
386
|
-
false
|
387
396
|
end
|
388
397
|
end
|
389
398
|
end
|
@@ -23,7 +23,7 @@ module GraphQL
|
|
23
23
|
defn = if arg_defn && arg_defn.type.unwrap.kind.input_object?
|
24
24
|
arg_defn.type.unwrap
|
25
25
|
else
|
26
|
-
context.field_definition
|
26
|
+
context.directive_definition || context.field_definition
|
27
27
|
end
|
28
28
|
|
29
29
|
parent_type = context.warden.get_argument(defn, parent_name(parent, defn))
|
@@ -29,8 +29,8 @@ module GraphQL
|
|
29
29
|
context.directive_definition.arguments
|
30
30
|
when GraphQL::Language::Nodes::InputObject
|
31
31
|
arg_type = context.argument_definition.type.unwrap
|
32
|
-
if arg_type.
|
33
|
-
arguments = arg_type.
|
32
|
+
if arg_type.kind.input_object?
|
33
|
+
arguments = arg_type.arguments
|
34
34
|
else
|
35
35
|
# This is some kind of error
|
36
36
|
nil
|
@@ -34,12 +34,12 @@ module GraphQL
|
|
34
34
|
# channel: self,
|
35
35
|
# }
|
36
36
|
#
|
37
|
-
# result = MySchema.execute(
|
37
|
+
# result = MySchema.execute(
|
38
38
|
# query: query,
|
39
39
|
# context: context,
|
40
40
|
# variables: variables,
|
41
41
|
# operation_name: operation_name
|
42
|
-
#
|
42
|
+
# )
|
43
43
|
#
|
44
44
|
# payload = {
|
45
45
|
# result: result.to_h,
|
@@ -146,14 +146,15 @@ module GraphQL
|
|
146
146
|
def setup_stream(channel, initial_event)
|
147
147
|
topic = initial_event.topic
|
148
148
|
channel.stream_from(stream_event_name(initial_event), coder: @action_cable_coder) do |message|
|
149
|
-
object = @serializer.load(message)
|
150
149
|
events_by_fingerprint = @events[topic]
|
150
|
+
object = nil
|
151
151
|
events_by_fingerprint.each do |_fingerprint, events|
|
152
152
|
if events.any? && events.first == initial_event
|
153
153
|
# The fingerprint has told us that this response should be shared by all subscribers,
|
154
154
|
# so just run it once, then deliver the result to every subscriber
|
155
155
|
first_event = events.first
|
156
156
|
first_subscription_id = first_event.context.fetch(:subscription_id)
|
157
|
+
object ||= @serializer.load(message)
|
157
158
|
result = execute_update(first_subscription_id, first_event, object)
|
158
159
|
# Having calculated the result _once_, send the same payload to all subscribers
|
159
160
|
events.each do |event|
|
@@ -35,9 +35,6 @@ module GraphQL
|
|
35
35
|
pt = @query.possible_types(current_type)
|
36
36
|
pt.each do |object_type|
|
37
37
|
ot_field = @query.get_field(object_type, current_field.graphql_name)
|
38
|
-
if !ot_field
|
39
|
-
binding.pry
|
40
|
-
end
|
41
38
|
# Inherited fields would be exactly the same object;
|
42
39
|
# only check fields that are overrides of the inherited one
|
43
40
|
if ot_field && ot_field != current_field
|
@@ -55,7 +55,14 @@ module GraphQL
|
|
55
55
|
# @return [Object] An object that load Global::Identification recursive
|
56
56
|
def load_value(value)
|
57
57
|
if value.is_a?(Array)
|
58
|
-
value.
|
58
|
+
is_gids = (v1 = value[0]).is_a?(Hash) && v1.size == 1 && v1[GLOBALID_KEY]
|
59
|
+
if is_gids
|
60
|
+
# Assume it's an array of global IDs
|
61
|
+
ids = value.map { |v| v[GLOBALID_KEY] }
|
62
|
+
GlobalID::Locator.locate_many(ids)
|
63
|
+
else
|
64
|
+
value.map { |item| load_value(item) }
|
65
|
+
end
|
59
66
|
elsif value.is_a?(Hash)
|
60
67
|
if value.size == 1
|
61
68
|
case value.keys.first # there's only 1 key
|
@@ -70,6 +77,9 @@ module GraphQL
|
|
70
77
|
when OPEN_STRUCT_KEY
|
71
78
|
ostruct_values = load_value(value[OPEN_STRUCT_KEY])
|
72
79
|
OpenStruct.new(ostruct_values)
|
80
|
+
else
|
81
|
+
key = value.keys.first
|
82
|
+
{ key => load_value(value[key]) }
|
73
83
|
end
|
74
84
|
else
|
75
85
|
loaded_h = {}
|
@@ -3,8 +3,9 @@
|
|
3
3
|
module GraphQL
|
4
4
|
module Tracing
|
5
5
|
# This implementation forwards events to ActiveSupport::Notifications
|
6
|
-
# with a `graphql
|
6
|
+
# with a `graphql` suffix.
|
7
7
|
#
|
8
|
+
# @see KEYS for event names
|
8
9
|
module ActiveSupportNotificationsTracing
|
9
10
|
# A cache of frequently-used keys to avoid needless string allocations
|
10
11
|
KEYS = {
|
@@ -24,6 +24,10 @@ module GraphQL
|
|
24
24
|
# end
|
25
25
|
# class Types::PostConnection < Types::BaseConnection
|
26
26
|
# edge_type(Types::PostEdge)
|
27
|
+
# edges_nullable(true)
|
28
|
+
# edge_nullable(true)
|
29
|
+
# node_nullable(true)
|
30
|
+
# has_nodes_field(true)
|
27
31
|
# end
|
28
32
|
#
|
29
33
|
# @see Relay::BaseEdge for edge types
|
@@ -11,7 +11,10 @@ module GraphQL
|
|
11
11
|
child_class.extend(ClassMethods)
|
12
12
|
child_class.extend(Relay::DefaultRelay)
|
13
13
|
child_class.default_relay(true)
|
14
|
+
child_class.has_nodes_field(true)
|
14
15
|
child_class.node_nullable(true)
|
16
|
+
child_class.edges_nullable(true)
|
17
|
+
child_class.edge_nullable(true)
|
15
18
|
add_page_info_field(child_class)
|
16
19
|
end
|
17
20
|
|
@@ -32,7 +35,7 @@ module GraphQL
|
|
32
35
|
# It's called when you subclass this base connection, trying to use the
|
33
36
|
# class name to set defaults. You can call it again in the class definition
|
34
37
|
# to override the default (or provide a value, if the default lookup failed).
|
35
|
-
def edge_type(edge_type_class, edge_class: GraphQL::Relay::Edge, node_type: edge_type_class.node_type, nodes_field:
|
38
|
+
def edge_type(edge_type_class, edge_class: GraphQL::Relay::Edge, node_type: edge_type_class.node_type, nodes_field: self.has_nodes_field, node_nullable: self.node_nullable, edges_nullable: self.edges_nullable, edge_nullable: self.edge_nullable)
|
36
39
|
# Set this connection's graphql name
|
37
40
|
node_type_name = node_type.graphql_name
|
38
41
|
|
@@ -40,8 +43,8 @@ module GraphQL
|
|
40
43
|
@edge_type = edge_type_class
|
41
44
|
@edge_class = edge_class
|
42
45
|
|
43
|
-
field :edges, [edge_type_class, null:
|
44
|
-
null:
|
46
|
+
field :edges, [edge_type_class, null: edge_nullable],
|
47
|
+
null: edges_nullable,
|
45
48
|
description: "A list of edges.",
|
46
49
|
legacy_edge_class: edge_class, # This is used by the old runtime only, for EdgesInstrumentation
|
47
50
|
connection: false
|
@@ -77,9 +80,39 @@ module GraphQL
|
|
77
80
|
# Use `node_nullable(false)` in your base class to make non-null `node` and `nodes` fields.
|
78
81
|
def node_nullable(new_value = nil)
|
79
82
|
if new_value.nil?
|
80
|
-
@node_nullable
|
83
|
+
defined?(@node_nullable) ? @node_nullable : superclass.node_nullable
|
81
84
|
else
|
82
|
-
@node_nullable
|
85
|
+
@node_nullable = new_value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Set the default `edges_nullable` for this class and its child classes. (Defaults to `true`.)
|
90
|
+
# Use `edges_nullable(false)` in your base class to make non-null `edges` fields.
|
91
|
+
def edges_nullable(new_value = nil)
|
92
|
+
if new_value.nil?
|
93
|
+
defined?(@edges_nullable) ? @edges_nullable : superclass.edges_nullable
|
94
|
+
else
|
95
|
+
@edges_nullable = new_value
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Set the default `edge_nullable` for this class and its child classes. (Defaults to `true`.)
|
100
|
+
# Use `edge_nullable(false)` in your base class to make non-null `edge` fields.
|
101
|
+
def edge_nullable(new_value = nil)
|
102
|
+
if new_value.nil?
|
103
|
+
defined?(@edge_nullable) ? @edge_nullable : superclass.edge_nullable
|
104
|
+
else
|
105
|
+
@edge_nullable = new_value
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Set the default `nodes_field` for this class and its child classes. (Defaults to `true`.)
|
110
|
+
# Use `nodes_field(false)` in your base class to prevent adding of a nodes field.
|
111
|
+
def has_nodes_field(new_value = nil)
|
112
|
+
if new_value.nil?
|
113
|
+
defined?(@nodes_field) ? @nodes_field : superclass.has_nodes_field
|
114
|
+
else
|
115
|
+
@nodes_field = new_value
|
83
116
|
end
|
84
117
|
end
|
85
118
|
|