graphql 1.13.0 → 1.13.24
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/lib/generators/graphql/core.rb +3 -8
- data/lib/generators/graphql/enum_generator.rb +4 -10
- data/lib/generators/graphql/field_extractor.rb +31 -0
- data/lib/generators/graphql/input_generator.rb +50 -0
- data/lib/generators/graphql/install/mutation_root_generator.rb +34 -0
- data/lib/generators/graphql/install_generator.rb +10 -3
- data/lib/generators/graphql/interface_generator.rb +7 -7
- data/lib/generators/graphql/mutation_create_generator.rb +22 -0
- data/lib/generators/graphql/mutation_delete_generator.rb +22 -0
- data/lib/generators/graphql/mutation_generator.rb +5 -30
- data/lib/generators/graphql/mutation_update_generator.rb +22 -0
- data/lib/generators/graphql/object_generator.rb +8 -37
- data/lib/generators/graphql/orm_mutations_base.rb +40 -0
- data/lib/generators/graphql/scalar_generator.rb +4 -2
- data/lib/generators/graphql/templates/enum.erb +5 -1
- data/lib/generators/graphql/templates/input.erb +9 -0
- data/lib/generators/graphql/templates/interface.erb +4 -2
- data/lib/generators/graphql/templates/mutation.erb +1 -1
- data/lib/generators/graphql/templates/mutation_create.erb +20 -0
- data/lib/generators/graphql/templates/mutation_delete.erb +20 -0
- data/lib/generators/graphql/templates/mutation_update.erb +21 -0
- data/lib/generators/graphql/templates/object.erb +4 -2
- data/lib/generators/graphql/templates/scalar.erb +3 -1
- data/lib/generators/graphql/templates/union.erb +4 -2
- data/lib/generators/graphql/type_generator.rb +46 -9
- data/lib/generators/graphql/union_generator.rb +5 -5
- data/lib/graphql/analysis/ast/field_usage.rb +6 -2
- data/lib/graphql/analysis/ast/visitor.rb +2 -1
- data/lib/graphql/argument.rb +1 -1
- data/lib/graphql/base_type.rb +5 -3
- data/lib/graphql/boolean_type.rb +1 -1
- data/lib/graphql/dataloader/source.rb +2 -2
- data/lib/graphql/date_encoding_error.rb +16 -0
- data/lib/graphql/define/instance_definable.rb +15 -0
- data/lib/graphql/directive/deprecated_directive.rb +1 -1
- data/lib/graphql/directive/include_directive.rb +1 -1
- data/lib/graphql/directive/skip_directive.rb +1 -1
- data/lib/graphql/directive.rb +1 -1
- data/lib/graphql/enum_type.rb +2 -2
- data/lib/graphql/execution/interpreter/arguments_cache.rb +4 -2
- data/lib/graphql/execution/interpreter/runtime.rb +48 -28
- data/lib/graphql/execution/multiplex.rb +3 -0
- data/lib/graphql/field.rb +1 -1
- data/lib/graphql/float_type.rb +1 -1
- data/lib/graphql/id_type.rb +1 -1
- data/lib/graphql/input_object_type.rb +1 -1
- data/lib/graphql/int_type.rb +1 -1
- data/lib/graphql/interface_type.rb +1 -1
- data/lib/graphql/introspection/directive_location_enum.rb +2 -2
- data/lib/graphql/introspection/directive_type.rb +4 -2
- data/lib/graphql/introspection/field_type.rb +1 -1
- data/lib/graphql/introspection/schema_type.rb +7 -2
- data/lib/graphql/introspection/type_type.rb +14 -8
- data/lib/graphql/introspection.rb +4 -1
- data/lib/graphql/language/block_string.rb +2 -2
- data/lib/graphql/language/document_from_schema_definition.rb +8 -3
- data/lib/graphql/language/lexer.rb +50 -25
- data/lib/graphql/language/lexer.rl +2 -0
- data/lib/graphql/language/nodes.rb +15 -3
- data/lib/graphql/language/parser.rb +829 -816
- data/lib/graphql/language/parser.y +8 -2
- data/lib/graphql/language/printer.rb +4 -0
- data/lib/graphql/object_type.rb +2 -2
- data/lib/graphql/pagination/active_record_relation_connection.rb +43 -6
- data/lib/graphql/pagination/relation_connection.rb +59 -29
- data/lib/graphql/query/context.rb +10 -0
- data/lib/graphql/query/input_validation_result.rb +9 -0
- data/lib/graphql/query/validation_pipeline.rb +2 -3
- data/lib/graphql/query/variable_validation_error.rb +2 -2
- data/lib/graphql/query/variables.rb +30 -3
- data/lib/graphql/query.rb +0 -1
- data/lib/graphql/relay/connection_type.rb +15 -2
- data/lib/graphql/relay/global_id_resolve.rb +1 -2
- data/lib/graphql/relay/mutation.rb +1 -1
- data/lib/graphql/relay/page_info.rb +1 -1
- data/lib/graphql/relay/range_add.rb +4 -0
- data/lib/graphql/rubocop/graphql/default_required_true.rb +4 -4
- data/lib/graphql/scalar_type.rb +1 -1
- data/lib/graphql/schema/argument.rb +29 -16
- data/lib/graphql/schema/build_from_definition.rb +9 -7
- data/lib/graphql/schema/directive.rb +25 -2
- data/lib/graphql/schema/enum.rb +4 -3
- data/lib/graphql/schema/enum_value.rb +3 -1
- data/lib/graphql/schema/field.rb +196 -92
- data/lib/graphql/schema/field_extension.rb +89 -2
- data/lib/graphql/schema/input_object.rb +27 -9
- data/lib/graphql/schema/interface.rb +8 -2
- data/lib/graphql/schema/introspection_system.rb +1 -1
- data/lib/graphql/schema/list.rb +21 -4
- data/lib/graphql/schema/loader.rb +3 -0
- data/lib/graphql/schema/member/accepts_definition.rb +7 -2
- data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
- data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
- data/lib/graphql/schema/member/has_arguments.rb +2 -2
- data/lib/graphql/schema/member/has_fields.rb +1 -1
- data/lib/graphql/schema/member/has_interfaces.rb +11 -1
- data/lib/graphql/schema/member/validates_input.rb +2 -2
- data/lib/graphql/schema/non_null.rb +9 -3
- data/lib/graphql/schema/object.rb +3 -1
- data/lib/graphql/schema/relay_classic_mutation.rb +8 -0
- data/lib/graphql/schema/resolver.rb +19 -13
- data/lib/graphql/schema/scalar.rb +15 -1
- data/lib/graphql/schema/traversal.rb +1 -1
- data/lib/graphql/schema/union.rb +2 -0
- data/lib/graphql/schema/validator/required_validator.rb +29 -15
- data/lib/graphql/schema/validator.rb +4 -7
- data/lib/graphql/schema/warden.rb +11 -2
- data/lib/graphql/schema.rb +34 -10
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/base_visitor.rb +1 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +14 -7
- data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
- data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -1
- data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +6 -0
- data/lib/graphql/static_validation/validation_context.rb +4 -0
- data/lib/graphql/string_type.rb +1 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -0
- data/lib/graphql/subscriptions/serialize.rb +22 -2
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +6 -20
- data/lib/graphql/tracing/data_dog_tracing.rb +24 -15
- data/lib/graphql/tracing/notifications_tracing.rb +59 -0
- data/lib/graphql/tracing/platform_tracing.rb +20 -10
- data/lib/graphql/types/iso_8601_date.rb +13 -5
- data/lib/graphql/types/iso_8601_date_time.rb +8 -1
- data/lib/graphql/types/relay/connection_behaviors.rb +28 -10
- data/lib/graphql/types/relay/default_relay.rb +5 -1
- data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
- data/lib/graphql/types/relay/node_field.rb +2 -3
- data/lib/graphql/types/relay/nodes_field.rb +19 -3
- data/lib/graphql/types/string.rb +1 -1
- data/lib/graphql/union_type.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +14 -1
- metadata +56 -30
- /data/lib/generators/graphql/{templates → install/templates}/base_mutation.erb +0 -0
- /data/lib/generators/graphql/{templates → install/templates}/mutation_type.erb +0 -0
@@ -147,6 +147,7 @@ rule
|
|
147
147
|
name_without_on:
|
148
148
|
IDENTIFIER
|
149
149
|
| FRAGMENT
|
150
|
+
| REPEATABLE
|
150
151
|
| TRUE
|
151
152
|
| FALSE
|
152
153
|
| operation_type
|
@@ -155,6 +156,7 @@ rule
|
|
155
156
|
enum_name: /* any identifier, but not "true", "false" or "null" */
|
156
157
|
IDENTIFIER
|
157
158
|
| FRAGMENT
|
159
|
+
| REPEATABLE
|
158
160
|
| ON
|
159
161
|
| operation_type
|
160
162
|
| schema_keyword
|
@@ -422,10 +424,14 @@ rule
|
|
422
424
|
}
|
423
425
|
|
424
426
|
directive_definition:
|
425
|
-
description_opt DIRECTIVE DIR_SIGN name arguments_definitions_opt ON directive_locations {
|
426
|
-
result = make_node(:DirectiveDefinition, name: val[3], arguments: val[4], locations: val[
|
427
|
+
description_opt DIRECTIVE DIR_SIGN name arguments_definitions_opt directive_repeatable_opt ON directive_locations {
|
428
|
+
result = make_node(:DirectiveDefinition, name: val[3], arguments: val[4], locations: val[7], repeatable: !!val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1])
|
427
429
|
}
|
428
430
|
|
431
|
+
directive_repeatable_opt:
|
432
|
+
/* nothing */
|
433
|
+
| REPEATABLE
|
434
|
+
|
429
435
|
directive_locations:
|
430
436
|
name { result = [make_node(:DirectiveLocation, name: val[0].to_s, position_source: val[0])] }
|
431
437
|
| directive_locations PIPE name { val[0] << make_node(:DirectiveLocation, name: val[2].to_s, position_source: val[2]) }
|
data/lib/graphql/object_type.rb
CHANGED
@@ -4,8 +4,8 @@ module GraphQL
|
|
4
4
|
class ObjectType < GraphQL::BaseType
|
5
5
|
extend Define::InstanceDefinable::DeprecatedDefine
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
deprecated_accepts_definitions :interfaces, :fields, :mutation, :relay_node_type, field: GraphQL::Define::AssignObjectField
|
8
|
+
deprecated_accepts_definitions implements: ->(type, *interfaces, inherit: false) { type.implements(interfaces, inherit: inherit) }
|
9
9
|
|
10
10
|
attr_accessor :fields, :mutation, :relay_node_type
|
11
11
|
ensure_defined(:fields, :mutation, :interfaces, :relay_node_type)
|
@@ -7,13 +7,18 @@ module GraphQL
|
|
7
7
|
class ActiveRecordRelationConnection < Pagination::RelationConnection
|
8
8
|
private
|
9
9
|
|
10
|
-
def relation_larger_than(relation, size)
|
11
|
-
|
12
|
-
|
10
|
+
def relation_larger_than(relation, initial_offset, size)
|
11
|
+
if already_loaded?(relation)
|
12
|
+
(relation.size + initial_offset) > size
|
13
|
+
else
|
14
|
+
set_offset(sliced_nodes, initial_offset + size).exists?
|
15
|
+
end
|
13
16
|
end
|
14
17
|
|
15
18
|
def relation_count(relation)
|
16
|
-
int_or_hash = if
|
19
|
+
int_or_hash = if already_loaded?(relation)
|
20
|
+
relation.size
|
21
|
+
elsif relation.respond_to?(:unscope)
|
17
22
|
relation.unscope(:order).count(:all)
|
18
23
|
else
|
19
24
|
# Rails 3
|
@@ -28,11 +33,19 @@ module GraphQL
|
|
28
33
|
end
|
29
34
|
|
30
35
|
def relation_limit(relation)
|
31
|
-
relation.
|
36
|
+
if relation.is_a?(Array)
|
37
|
+
nil
|
38
|
+
else
|
39
|
+
relation.limit_value
|
40
|
+
end
|
32
41
|
end
|
33
42
|
|
34
43
|
def relation_offset(relation)
|
35
|
-
relation.
|
44
|
+
if relation.is_a?(Array)
|
45
|
+
nil
|
46
|
+
else
|
47
|
+
relation.offset_value
|
48
|
+
end
|
36
49
|
end
|
37
50
|
|
38
51
|
def null_relation(relation)
|
@@ -43,6 +56,30 @@ module GraphQL
|
|
43
56
|
relation.where("1=2")
|
44
57
|
end
|
45
58
|
end
|
59
|
+
|
60
|
+
def set_limit(nodes, limit)
|
61
|
+
if already_loaded?(nodes)
|
62
|
+
nodes.take(limit)
|
63
|
+
else
|
64
|
+
super
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def set_offset(nodes, offset)
|
69
|
+
if already_loaded?(nodes)
|
70
|
+
# If the client sent a bogus cursor beyond the size of the relation,
|
71
|
+
# it might get `nil` from `#[...]`, so return an empty array in that case
|
72
|
+
nodes[offset..-1] || []
|
73
|
+
else
|
74
|
+
super
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def already_loaded?(relation)
|
81
|
+
relation.is_a?(Array) || relation.loaded?
|
82
|
+
end
|
46
83
|
end
|
47
84
|
end
|
48
85
|
end
|
@@ -35,7 +35,7 @@ module GraphQL
|
|
35
35
|
if @nodes && @nodes.count < first
|
36
36
|
false
|
37
37
|
else
|
38
|
-
relation_larger_than(sliced_nodes, first)
|
38
|
+
relation_larger_than(sliced_nodes, @sliced_nodes_offset, first)
|
39
39
|
end
|
40
40
|
else
|
41
41
|
false
|
@@ -47,16 +47,17 @@ module GraphQL
|
|
47
47
|
def cursor_for(item)
|
48
48
|
load_nodes
|
49
49
|
# index in nodes + existing offset + 1 (because it's offset, not index)
|
50
|
-
offset = nodes.index(item) + 1 + (@paged_nodes_offset || 0)
|
50
|
+
offset = nodes.index(item) + 1 + (@paged_nodes_offset || 0) - (relation_offset(items) || 0)
|
51
51
|
encode(offset.to_s)
|
52
52
|
end
|
53
53
|
|
54
54
|
private
|
55
55
|
|
56
56
|
# @param relation [Object] A database query object
|
57
|
+
# @param _initial_offset [Integer] The number of items already excluded from the relation
|
57
58
|
# @param size [Integer] The value against which we check the relation size
|
58
59
|
# @return [Boolean] True if the number of items in this relation is larger than `size`
|
59
|
-
def relation_larger_than(relation, size)
|
60
|
+
def relation_larger_than(relation, _initial_offset, size)
|
60
61
|
relation_count(set_limit(relation, size + 1)) == size + 1
|
61
62
|
end
|
62
63
|
|
@@ -111,30 +112,51 @@ module GraphQL
|
|
111
112
|
end
|
112
113
|
end
|
113
114
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
115
|
+
def calculate_sliced_nodes_parameters
|
116
|
+
if defined?(@sliced_nodes_limit)
|
117
|
+
return
|
118
|
+
else
|
119
|
+
next_offset = relation_offset(items) || 0
|
120
120
|
if after_offset
|
121
|
-
|
122
|
-
paginated_nodes = set_offset(paginated_nodes, previous_offset + after_offset)
|
121
|
+
next_offset += after_offset
|
123
122
|
end
|
124
123
|
|
125
124
|
if before_offset && after_offset
|
126
125
|
if after_offset < before_offset
|
127
126
|
# Get the number of items between the two cursors
|
128
127
|
space_between = before_offset - after_offset - 1
|
129
|
-
|
128
|
+
relation_limit = space_between
|
130
129
|
else
|
131
|
-
# TODO I think this is untested
|
132
130
|
# The cursors overextend one another to an empty set
|
133
|
-
|
131
|
+
@sliced_nodes_null_relation = true
|
134
132
|
end
|
135
133
|
elsif before_offset
|
136
134
|
# Use limit to cut off the tail of the relation
|
137
|
-
|
135
|
+
relation_limit = before_offset - 1
|
136
|
+
end
|
137
|
+
|
138
|
+
@sliced_nodes_limit = relation_limit
|
139
|
+
@sliced_nodes_offset = next_offset
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Apply `before` and `after` to the underlying `items`,
|
144
|
+
# returning a new relation.
|
145
|
+
def sliced_nodes
|
146
|
+
@sliced_nodes ||= begin
|
147
|
+
calculate_sliced_nodes_parameters
|
148
|
+
paginated_nodes = items
|
149
|
+
|
150
|
+
if @sliced_nodes_null_relation
|
151
|
+
paginated_nodes = null_relation(paginated_nodes)
|
152
|
+
else
|
153
|
+
if @sliced_nodes_limit
|
154
|
+
paginated_nodes = set_limit(paginated_nodes, @sliced_nodes_limit)
|
155
|
+
end
|
156
|
+
|
157
|
+
if @sliced_nodes_offset
|
158
|
+
paginated_nodes = set_offset(paginated_nodes, @sliced_nodes_offset)
|
159
|
+
end
|
138
160
|
end
|
139
161
|
|
140
162
|
paginated_nodes
|
@@ -155,32 +177,40 @@ module GraphQL
|
|
155
177
|
# returning a new relation
|
156
178
|
def limited_nodes
|
157
179
|
@limited_nodes ||= begin
|
158
|
-
|
159
|
-
|
180
|
+
calculate_sliced_nodes_parameters
|
181
|
+
if @sliced_nodes_null_relation
|
182
|
+
# it's an empty set
|
183
|
+
return sliced_nodes
|
184
|
+
end
|
185
|
+
relation_limit = @sliced_nodes_limit
|
186
|
+
relation_offset = @sliced_nodes_offset
|
160
187
|
|
161
|
-
if first && (
|
188
|
+
if first && (relation_limit.nil? || relation_limit > first)
|
162
189
|
# `first` would create a stricter limit that the one already applied, so add it
|
163
|
-
|
190
|
+
relation_limit = first
|
164
191
|
end
|
165
192
|
|
166
193
|
if last
|
167
|
-
if
|
168
|
-
if last <=
|
194
|
+
if relation_limit
|
195
|
+
if last <= relation_limit
|
169
196
|
# `last` is a smaller slice than the current limit, so apply it
|
170
|
-
|
171
|
-
|
172
|
-
paginated_nodes = set_limit(paginated_nodes, last)
|
197
|
+
relation_offset += (relation_limit - last)
|
198
|
+
relation_limit = last
|
173
199
|
end
|
174
200
|
else
|
175
201
|
# No limit, so get the last items
|
176
|
-
sliced_nodes_count = relation_count(
|
177
|
-
|
178
|
-
|
179
|
-
paginated_nodes = set_limit(paginated_nodes, last)
|
202
|
+
sliced_nodes_count = relation_count(sliced_nodes)
|
203
|
+
relation_offset += (sliced_nodes_count - [last, sliced_nodes_count].min)
|
204
|
+
relation_limit = last
|
180
205
|
end
|
181
206
|
end
|
182
207
|
|
183
|
-
@paged_nodes_offset = relation_offset
|
208
|
+
@paged_nodes_offset = relation_offset
|
209
|
+
paginated_nodes = items
|
210
|
+
paginated_nodes = set_offset(paginated_nodes, relation_offset)
|
211
|
+
if relation_limit
|
212
|
+
paginated_nodes = set_limit(paginated_nodes, relation_limit)
|
213
|
+
end
|
184
214
|
paginated_nodes
|
185
215
|
end
|
186
216
|
end
|
@@ -156,6 +156,11 @@ module GraphQL
|
|
156
156
|
@scoped_context = {}
|
157
157
|
end
|
158
158
|
|
159
|
+
# @return [Hash] A hash that will be added verbatim to the result hash, as `"extensions" => { ... }`
|
160
|
+
def response_extensions
|
161
|
+
namespace(:__query_result_extensions__)
|
162
|
+
end
|
163
|
+
|
159
164
|
def dataloader
|
160
165
|
@dataloader ||= self[:dataloader] || (query.multiplex ? query.multiplex.dataloader : schema.dataloader_class.new)
|
161
166
|
end
|
@@ -236,6 +241,11 @@ module GraphQL
|
|
236
241
|
@storage[ns]
|
237
242
|
end
|
238
243
|
|
244
|
+
# @return [Boolean] true if this namespace was accessed before
|
245
|
+
def namespace?(ns)
|
246
|
+
@storage.key?(ns)
|
247
|
+
end
|
248
|
+
|
239
249
|
def inspect
|
240
250
|
"#<Query::Context ...>"
|
241
251
|
end
|
@@ -4,6 +4,12 @@ module GraphQL
|
|
4
4
|
class InputValidationResult
|
5
5
|
attr_accessor :problems
|
6
6
|
|
7
|
+
def self.from_problem(explanation, path = nil, extensions: nil, message: nil)
|
8
|
+
result = self.new
|
9
|
+
result.add_problem(explanation, path, extensions: extensions, message: message)
|
10
|
+
result
|
11
|
+
end
|
12
|
+
|
7
13
|
def initialize(valid: true, problems: nil)
|
8
14
|
@valid = valid
|
9
15
|
@problems = problems
|
@@ -38,6 +44,9 @@ module GraphQL
|
|
38
44
|
# It could have been explicitly set on inner_result (if it had no problems)
|
39
45
|
@valid = false
|
40
46
|
end
|
47
|
+
|
48
|
+
VALID = self.new
|
49
|
+
VALID.freeze
|
41
50
|
end
|
42
51
|
end
|
43
52
|
end
|
@@ -16,10 +16,9 @@ module GraphQL
|
|
16
16
|
class ValidationPipeline
|
17
17
|
attr_reader :max_depth, :max_complexity
|
18
18
|
|
19
|
-
def initialize(query:,
|
19
|
+
def initialize(query:, parse_error:, operation_name_error:, max_depth:, max_complexity:)
|
20
20
|
@validation_errors = []
|
21
21
|
@internal_representation = nil
|
22
|
-
@validate = validate
|
23
22
|
@parse_error = parse_error
|
24
23
|
@operation_name_error = operation_name_error
|
25
24
|
@query = query
|
@@ -72,7 +71,7 @@ module GraphQL
|
|
72
71
|
elsif @operation_name_error
|
73
72
|
@validation_errors << @operation_name_error
|
74
73
|
else
|
75
|
-
validation_result = @schema.static_validator.validate(@query, validate: @validate, timeout: @schema.validate_timeout, max_errors: @schema.validate_max_errors)
|
74
|
+
validation_result = @schema.static_validator.validate(@query, validate: @query.validate, timeout: @schema.validate_timeout, max_errors: @schema.validate_max_errors)
|
76
75
|
@validation_errors.concat(validation_result[:errors])
|
77
76
|
@internal_representation = validation_result[:irep]
|
78
77
|
|
@@ -4,11 +4,11 @@ module GraphQL
|
|
4
4
|
class VariableValidationError < GraphQL::ExecutionError
|
5
5
|
attr_accessor :value, :validation_result
|
6
6
|
|
7
|
-
def initialize(variable_ast, type, value, validation_result)
|
7
|
+
def initialize(variable_ast, type, value, validation_result, msg: nil)
|
8
8
|
@value = value
|
9
9
|
@validation_result = validation_result
|
10
10
|
|
11
|
-
msg
|
11
|
+
msg ||= "Variable $#{variable_ast.name} of type #{type.to_type_signature} was provided invalid value"
|
12
12
|
|
13
13
|
if problem_fields.any?
|
14
14
|
msg += " for #{problem_fields.join(", ")}"
|
@@ -17,6 +17,10 @@ module GraphQL
|
|
17
17
|
@provided_variables = GraphQL::Argument.deep_stringify(provided_variables)
|
18
18
|
@errors = []
|
19
19
|
@storage = ast_variables.each_with_object({}) do |ast_variable, memo|
|
20
|
+
if schema.validate_max_errors && schema.validate_max_errors <= @errors.count
|
21
|
+
add_max_errors_reached_message
|
22
|
+
break
|
23
|
+
end
|
20
24
|
# Find the right value for this variable:
|
21
25
|
# - First, use the value provided at runtime
|
22
26
|
# - Then, fall back to the default value from the query string
|
@@ -29,8 +33,9 @@ module GraphQL
|
|
29
33
|
default_value = ast_variable.default_value
|
30
34
|
provided_value = @provided_variables[variable_name]
|
31
35
|
value_was_provided = @provided_variables.key?(variable_name)
|
36
|
+
max_errors = schema.validate_max_errors - @errors.count if schema.validate_max_errors
|
32
37
|
begin
|
33
|
-
validation_result = variable_type.validate_input(provided_value, ctx)
|
38
|
+
validation_result = variable_type.validate_input(provided_value, ctx, max_errors: max_errors)
|
34
39
|
if validation_result.valid?
|
35
40
|
if value_was_provided
|
36
41
|
# Add the variable if a value was provided
|
@@ -61,8 +66,7 @@ module GraphQL
|
|
61
66
|
# like InputValidationResults generated by validate_non_null_input but unfortunately we don't
|
62
67
|
# have this information available in the coerce_input call chain. Note this path is the path
|
63
68
|
# that appears under errors.extensions.problems.path and NOT the result path under errors.path.
|
64
|
-
validation_result = GraphQL::Query::InputValidationResult.
|
65
|
-
validation_result.add_problem(ex.message)
|
69
|
+
validation_result = GraphQL::Query::InputValidationResult.from_problem(ex.message)
|
66
70
|
end
|
67
71
|
|
68
72
|
if !validation_result.valid?
|
@@ -73,6 +77,29 @@ module GraphQL
|
|
73
77
|
end
|
74
78
|
|
75
79
|
def_delegators :@storage, :length, :key?, :[], :fetch, :to_h
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def deep_stringify(val)
|
84
|
+
case val
|
85
|
+
when Array
|
86
|
+
val.map { |v| deep_stringify(v) }
|
87
|
+
when Hash
|
88
|
+
new_val = {}
|
89
|
+
val.each do |k, v|
|
90
|
+
new_val[k.to_s] = deep_stringify(v)
|
91
|
+
end
|
92
|
+
new_val
|
93
|
+
else
|
94
|
+
val
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def add_max_errors_reached_message
|
99
|
+
message = "Too many errors processing variables, max validation error limit reached. Execution aborted"
|
100
|
+
validation_result = GraphQL::Query::InputValidationResult.from_problem(message)
|
101
|
+
errors << GraphQL::Query::VariableValidationError.new(nil, nil, nil, validation_result, msg: message)
|
102
|
+
end
|
76
103
|
end
|
77
104
|
end
|
78
105
|
end
|
data/lib/graphql/query.rb
CHANGED
@@ -5,9 +5,22 @@ module GraphQL
|
|
5
5
|
module ConnectionType
|
6
6
|
class << self
|
7
7
|
# @return [Boolean] If true, connection types get a `nodes` shortcut field
|
8
|
-
|
8
|
+
def default_nodes_field=(new_setting)
|
9
|
+
if new_setting
|
10
|
+
warn("GraphQL::Relay::ConnectionType will be removed in GraphQL 2.0.0; migrate to `GraphQL::Pagination::Connections` and remove this setting (`default_nodes_field = true`).")
|
11
|
+
end
|
12
|
+
@default_nodes_field = new_setting
|
13
|
+
end
|
14
|
+
attr_reader :default_nodes_field
|
15
|
+
|
9
16
|
# @return [Boolean] If true, connections check for reverse-direction `has*Page` values
|
10
|
-
|
17
|
+
def bidirectional_pagination=(new_setting)
|
18
|
+
if new_setting
|
19
|
+
warn("GraphQL::Relay::ConnectionType will be removed in GraphQL 2.0.0; migrate to `GraphQL::Pagination::Connections` and remove this setting (`bidirectional_pagination = true`).")
|
20
|
+
end
|
21
|
+
@bidirectional_pagination = new_setting
|
22
|
+
end
|
23
|
+
attr_reader :bidirectional_pagination
|
11
24
|
end
|
12
25
|
|
13
26
|
self.default_nodes_field = false
|
@@ -10,8 +10,7 @@ module GraphQL
|
|
10
10
|
if obj.is_a?(GraphQL::Schema::Object)
|
11
11
|
obj = obj.object
|
12
12
|
end
|
13
|
-
|
14
|
-
ctx.query.schema.id_from_object(obj, type, ctx)
|
13
|
+
ctx.query.schema.id_from_object(obj, @type, ctx)
|
15
14
|
end
|
16
15
|
end
|
17
16
|
end
|
@@ -35,6 +35,10 @@ module GraphQL
|
|
35
35
|
# @param context [GraphQL::Query::Context] The surrounding `ctx`, will be passed to the connection if provided (this is required for cursor encoders)
|
36
36
|
# @param edge_class [Class] The class to wrap `item` with (defaults to the connection's edge class)
|
37
37
|
def initialize(collection:, item:, parent: nil, context: nil, edge_class: nil)
|
38
|
+
if context.nil?
|
39
|
+
caller_loc = caller(2, 1).first
|
40
|
+
GraphQL::Deprecation.warn("`context: ...` will be required by `RangeAdd.new` in GraphQL-Ruby 2.0. Add `context: context` to the call at #{caller_loc}.")
|
41
|
+
end
|
38
42
|
if context && context.schema.new_connections?
|
39
43
|
conn_class = context.schema.connections.wrapper_for(collection)
|
40
44
|
# The rest will be added by ConnectionExtension
|
@@ -25,16 +25,16 @@ module GraphQL
|
|
25
25
|
|
26
26
|
def_node_matcher :argument_config_with_required_true?, <<-Pattern
|
27
27
|
(
|
28
|
-
send nil? :argument ... (hash <$(pair (sym :required) (true)) ...>)
|
28
|
+
send {nil? _} :argument ... (hash <$(pair (sym :required) (true)) ...>)
|
29
29
|
)
|
30
30
|
Pattern
|
31
31
|
|
32
32
|
def on_send(node)
|
33
33
|
argument_config_with_required_true?(node) do |required_config|
|
34
34
|
add_offense(required_config) do |corrector|
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
cleaned_node_source = source_without_keyword_argument(node, required_config)
|
36
|
+
corrector.replace(node, cleaned_node_source)
|
37
|
+
end
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
data/lib/graphql/scalar_type.rb
CHANGED
@@ -4,7 +4,7 @@ module GraphQL
|
|
4
4
|
class ScalarType < GraphQL::BaseType
|
5
5
|
extend Define::InstanceDefinable::DeprecatedDefine
|
6
6
|
|
7
|
-
|
7
|
+
deprecated_accepts_definitions :coerce, :coerce_input, :coerce_result
|
8
8
|
ensure_defined :coerce_non_null_input, :coerce_result
|
9
9
|
|
10
10
|
module NoOpCoerce
|
@@ -37,7 +37,7 @@ module GraphQL
|
|
37
37
|
# @param arg_name [Symbol]
|
38
38
|
# @param type_expr
|
39
39
|
# @param desc [String]
|
40
|
-
# @param required [Boolean] if true, this argument is non-null; if false, this argument is nullable
|
40
|
+
# @param required [Boolean, :nullable] if true, this argument is non-null; if false, this argument is nullable. If `:nullable`, then the argument must be provided, though it may be `null`.
|
41
41
|
# @param description [String]
|
42
42
|
# @param default_value [Object]
|
43
43
|
# @param as [Symbol] Override the keyword name when passed to a method
|
@@ -48,13 +48,22 @@ module GraphQL
|
|
48
48
|
# @param directives [Hash{Class => Hash}]
|
49
49
|
# @param deprecation_reason [String]
|
50
50
|
# @param validates [Hash, nil] Options for building validators, if any should be applied
|
51
|
-
|
51
|
+
# @param replace_null_with_default [Boolean] if `true`, incoming values of `null` will be replaced with the configured `default_value`
|
52
|
+
def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NO_DEFAULT, as: nil, from_resolver: false, camelize: true, prepare: nil, method_access: true, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block)
|
52
53
|
arg_name ||= name
|
53
54
|
@name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
|
55
|
+
NameValidator.validate!(@name)
|
54
56
|
@type_expr = type_expr || type
|
55
57
|
@description = desc || description
|
56
|
-
@null =
|
58
|
+
@null = required != true
|
57
59
|
@default_value = default_value
|
60
|
+
if replace_null_with_default
|
61
|
+
if !default_value?
|
62
|
+
raise ArgumentError, "`replace_null_with_default: true` requires a default value, please provide one with `default_value: ...`"
|
63
|
+
end
|
64
|
+
@replace_null_with_default = true
|
65
|
+
end
|
66
|
+
|
58
67
|
@owner = owner
|
59
68
|
@as = as
|
60
69
|
@loads = loads
|
@@ -72,13 +81,13 @@ module GraphQL
|
|
72
81
|
end
|
73
82
|
|
74
83
|
self.validates(validates)
|
84
|
+
if required == :nullable
|
85
|
+
self.owner.validates(required: { argument: arg_name })
|
86
|
+
end
|
75
87
|
|
76
88
|
if definition_block
|
77
|
-
|
78
|
-
|
79
|
-
else
|
80
|
-
instance_eval(&definition_block)
|
81
|
-
end
|
89
|
+
# `self` will still be self, it will also be the first argument to the block:
|
90
|
+
instance_exec(self, &definition_block)
|
82
91
|
end
|
83
92
|
end
|
84
93
|
|
@@ -94,6 +103,10 @@ module GraphQL
|
|
94
103
|
@default_value != NO_DEFAULT
|
95
104
|
end
|
96
105
|
|
106
|
+
def replace_null_with_default?
|
107
|
+
@replace_null_with_default
|
108
|
+
end
|
109
|
+
|
97
110
|
attr_writer :description
|
98
111
|
|
99
112
|
# @return [String] Documentation for this argument
|
@@ -147,20 +160,15 @@ module GraphQL
|
|
147
160
|
end
|
148
161
|
end
|
149
162
|
elsif as_type.kind.input_object?
|
150
|
-
as_type.
|
151
|
-
input_obj_arg = input_obj_arg.type_class
|
152
|
-
# TODO: this skips input objects whose values were alread replaced with application objects.
|
153
|
-
# See: https://github.com/rmosolgo/graphql-ruby/issues/2633
|
154
|
-
if value.is_a?(InputObject) && value.key?(input_obj_arg.keyword) && !input_obj_arg.authorized?(obj, value[input_obj_arg.keyword], ctx)
|
155
|
-
return false
|
156
|
-
end
|
157
|
-
end
|
163
|
+
return as_type.authorized?(obj, value, ctx)
|
158
164
|
end
|
159
165
|
# None of the early-return conditions were activated,
|
160
166
|
# so this is authorized.
|
161
167
|
true
|
162
168
|
end
|
163
169
|
|
170
|
+
prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
|
171
|
+
|
164
172
|
def to_graphql
|
165
173
|
argument = GraphQL::Argument.new
|
166
174
|
argument.name = @name
|
@@ -255,6 +263,11 @@ module GraphQL
|
|
255
263
|
return
|
256
264
|
end
|
257
265
|
|
266
|
+
if value.nil? && replace_null_with_default?
|
267
|
+
value = default_value
|
268
|
+
default_used = true
|
269
|
+
end
|
270
|
+
|
258
271
|
loaded_value = nil
|
259
272
|
coerced_value = context.schema.error_handler.with_error_handling(context) do
|
260
273
|
type.coerce_input(value, context)
|