graphql 1.12.4 → 1.12.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/generators/graphql/install_generator.rb +2 -1
- data/lib/generators/graphql/loader_generator.rb +1 -0
- data/lib/generators/graphql/mutation_generator.rb +1 -0
- data/lib/generators/graphql/relay_generator.rb +1 -0
- data/lib/generators/graphql/templates/graphql_controller.erb +2 -2
- data/lib/generators/graphql/type_generator.rb +1 -0
- data/lib/graphql.rb +13 -11
- data/lib/graphql/backtrace/tracer.rb +2 -2
- data/lib/graphql/dataloader.rb +45 -14
- data/lib/graphql/execution/errors.rb +109 -11
- data/lib/graphql/execution/interpreter.rb +1 -1
- data/lib/graphql/execution/interpreter/runtime.rb +17 -24
- 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 +15 -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/validation_pipeline.rb +1 -1
- data/lib/graphql/railtie.rb +9 -1
- data/lib/graphql/relay/range_add.rb +10 -5
- data/lib/graphql/schema.rb +32 -45
- data/lib/graphql/schema/field/connection_extension.rb +1 -0
- data/lib/graphql/schema/input_object.rb +2 -2
- 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 +28 -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/variable_usages_are_allowed.rb +2 -2
- 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 +3 -0
- 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
- data/readme.md +1 -1
- metadata +3 -2
@@ -42,6 +42,7 @@ module GraphQL
|
|
42
42
|
value.after_value ||= original_arguments[:after]
|
43
43
|
value.last_value ||= original_arguments[:last]
|
44
44
|
value.before_value ||= original_arguments[:before]
|
45
|
+
value.arguments ||= original_arguments
|
45
46
|
value.field ||= field
|
46
47
|
if field.has_max_page_size? && !value.has_max_page_size_override?
|
47
48
|
value.max_page_size = field.max_page_size
|
@@ -226,8 +226,8 @@ module GraphQL
|
|
226
226
|
# It's funny to think of a _result_ of an input object.
|
227
227
|
# This is used for rendering the default value in introspection responses.
|
228
228
|
def coerce_result(value, ctx)
|
229
|
-
# Allow the application to provide values as :
|
230
|
-
value = value.reduce({}) { |memo, (k, v)| memo[k.to_s] = v; memo }
|
229
|
+
# Allow the application to provide values as :snake_symbols, and convert them to the camelStrings
|
230
|
+
value = value.reduce({}) { |memo, (k, v)| memo[Member::BuildType.camelize(k.to_s)] = v; memo }
|
231
231
|
|
232
232
|
result = {}
|
233
233
|
|
@@ -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|
|
@@ -276,8 +276,29 @@ module GraphQL
|
|
276
276
|
end
|
277
277
|
end
|
278
278
|
|
279
|
+
# Get or set the `max_page_size:` which will be configured for fields using this resolver
|
280
|
+
# (`nil` means "unlimited max page size".)
|
281
|
+
# @param max_page_size [Integer, nil] Set a new value
|
282
|
+
# @return [Integer, nil] The `max_page_size` assigned to fields that use this resolver
|
283
|
+
def max_page_size(new_max_page_size = :not_given)
|
284
|
+
if new_max_page_size != :not_given
|
285
|
+
@max_page_size = new_max_page_size
|
286
|
+
elsif defined?(@max_page_size)
|
287
|
+
@max_page_size
|
288
|
+
elsif superclass.respond_to?(:max_page_size)
|
289
|
+
superclass.max_page_size
|
290
|
+
else
|
291
|
+
nil
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# @return [Boolean] `true` if this resolver or a superclass has an assigned `max_page_size`
|
296
|
+
def has_max_page_size?
|
297
|
+
defined?(@max_page_size) || (superclass.respond_to?(:has_max_page_size?) && superclass.has_max_page_size?)
|
298
|
+
end
|
299
|
+
|
279
300
|
def field_options
|
280
|
-
{
|
301
|
+
field_opts = {
|
281
302
|
type: type_expr,
|
282
303
|
description: description,
|
283
304
|
extras: extras,
|
@@ -289,6 +310,12 @@ module GraphQL
|
|
289
310
|
extensions: extensions,
|
290
311
|
broadcastable: broadcastable?,
|
291
312
|
}
|
313
|
+
|
314
|
+
if has_max_page_size?
|
315
|
+
field_opts[:max_page_size] = max_page_size
|
316
|
+
end
|
317
|
+
|
318
|
+
field_opts
|
292
319
|
end
|
293
320
|
|
294
321
|
# A non-normalized type configuration, without `null` applied
|
@@ -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
|
@@ -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
|
@@ -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
|
|
@@ -8,6 +8,7 @@ module GraphQL
|
|
8
8
|
child_class.description("An edge in a connection.")
|
9
9
|
child_class.field(:cursor, String, null: false, description: "A cursor for use in pagination.")
|
10
10
|
child_class.extend(ClassMethods)
|
11
|
+
child_class.node_nullable(true)
|
11
12
|
end
|
12
13
|
|
13
14
|
module ClassMethods
|
@@ -15,7 +16,7 @@ module GraphQL
|
|
15
16
|
#
|
16
17
|
# @param node_type [Class] A `Schema::Object` subclass
|
17
18
|
# @param null [Boolean]
|
18
|
-
def node_type(node_type = nil, null:
|
19
|
+
def node_type(node_type = nil, null: self.node_nullable)
|
19
20
|
if node_type
|
20
21
|
@node_type = node_type
|
21
22
|
# Add a default `node` field
|
@@ -35,6 +36,16 @@ module GraphQL
|
|
35
36
|
def visible?(ctx)
|
36
37
|
node_type.visible?(ctx)
|
37
38
|
end
|
39
|
+
|
40
|
+
# Set the default `node_nullable` for this class and its child classes. (Defaults to `true`.)
|
41
|
+
# Use `node_nullable(false)` in your base class to make non-null `node` field.
|
42
|
+
def node_nullable(new_value = nil)
|
43
|
+
if new_value.nil?
|
44
|
+
defined?(@node_nullable) ? @node_nullable : superclass.node_nullable
|
45
|
+
else
|
46
|
+
@node_nullable = new_value
|
47
|
+
end
|
48
|
+
end
|
38
49
|
end
|
39
50
|
end
|
40
51
|
end
|
data/lib/graphql/version.rb
CHANGED
data/readme.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# graphql <img src="https://cloud.githubusercontent.com/assets/2231765/9094460/cb43861e-3b66-11e5-9fbf-71066ff3ab13.png" height="40" alt="graphql-ruby"/>
|
2
2
|
|
3
|
-
[![
|
3
|
+
[![CI Suite](https://github.com/rmosolgo/graphql-ruby/actions/workflows/ci.yaml/badge.svg)](https://github.com/rmosolgo/graphql-ruby/actions/workflows/ci.yaml)
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/graphql.svg)](https://rubygems.org/gems/graphql)
|
5
5
|
[![Code Climate](https://codeclimate.com/github/rmosolgo/graphql-ruby/badges/gpa.svg)](https://codeclimate.com/github/rmosolgo/graphql-ruby)
|
6
6
|
[![Test Coverage](https://codeclimate.com/github/rmosolgo/graphql-ruby/badges/coverage.svg)](https://codeclimate.com/github/rmosolgo/graphql-ruby)
|