graphql 1.13.2 → 1.13.6
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/graphql/analysis/ast/field_usage.rb +6 -2
- data/lib/graphql/date_encoding_error.rb +16 -0
- data/lib/graphql/execution/interpreter/arguments_cache.rb +4 -2
- data/lib/graphql/execution/interpreter/runtime.rb +33 -17
- data/lib/graphql/introspection/directive_location_enum.rb +2 -2
- data/lib/graphql/introspection/directive_type.rb +2 -0
- data/lib/graphql/introspection/schema_type.rb +5 -0
- data/lib/graphql/introspection/type_type.rb +9 -3
- data/lib/graphql/introspection.rb +3 -0
- 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 +2 -2
- 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/pagination/active_record_relation_connection.rb +43 -6
- data/lib/graphql/pagination/relation_connection.rb +59 -29
- data/lib/graphql/schema/argument.rb +6 -10
- data/lib/graphql/schema/build_from_definition.rb +1 -0
- data/lib/graphql/schema/directive.rb +15 -0
- data/lib/graphql/schema/field.rb +103 -39
- data/lib/graphql/schema/field_extension.rb +37 -0
- data/lib/graphql/schema/input_object.rb +15 -0
- data/lib/graphql/schema/loader.rb +3 -0
- data/lib/graphql/schema/non_null.rb +4 -0
- data/lib/graphql/schema/scalar.rb +12 -0
- data/lib/graphql/schema/validator/required_validator.rb +29 -15
- data/lib/graphql/schema.rb +16 -1
- 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/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 +1 -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/subscriptions/serialize.rb +22 -2
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +6 -20
- data/lib/graphql/tracing/data_dog_tracing.rb +6 -1
- data/lib/graphql/tracing/notifications_tracing.rb +59 -0
- data/lib/graphql/tracing/platform_tracing.rb +11 -6
- data/lib/graphql/types/iso_8601_date.rb +13 -5
- data/lib/graphql/types/relay/node_field.rb +2 -3
- data/lib/graphql/types/relay/nodes_field.rb +19 -3
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +1 -0
- metadata +10 -6
@@ -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]) }
|
@@ -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
|
@@ -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
|
@@ -53,7 +53,7 @@ module GraphQL
|
|
53
53
|
@name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
|
54
54
|
@type_expr = type_expr || type
|
55
55
|
@description = desc || description
|
56
|
-
@null =
|
56
|
+
@null = required != true
|
57
57
|
@default_value = default_value
|
58
58
|
@owner = owner
|
59
59
|
@as = as
|
@@ -72,6 +72,9 @@ module GraphQL
|
|
72
72
|
end
|
73
73
|
|
74
74
|
self.validates(validates)
|
75
|
+
if required == :nullable
|
76
|
+
self.owner.validates(required: { argument: arg_name })
|
77
|
+
end
|
75
78
|
|
76
79
|
if definition_block
|
77
80
|
if definition_block.arity == 1
|
@@ -147,14 +150,7 @@ module GraphQL
|
|
147
150
|
end
|
148
151
|
end
|
149
152
|
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
|
153
|
+
return as_type.authorized?(obj, value, ctx)
|
158
154
|
end
|
159
155
|
# None of the early-return conditions were activated,
|
160
156
|
# so this is authorized.
|
@@ -377,6 +377,7 @@ module GraphQL
|
|
377
377
|
Class.new(GraphQL::Schema::Directive) do
|
378
378
|
graphql_name(directive_definition.name)
|
379
379
|
description(directive_definition.description)
|
380
|
+
repeatable(directive_definition.repeatable)
|
380
381
|
locations(*directive_definition.locations.map { |location| location.name.to_sym })
|
381
382
|
ast_node(directive_definition)
|
382
383
|
builder.build_arguments(self, directive_definition.arguments, type_resolver)
|
@@ -90,6 +90,11 @@ module GraphQL
|
|
90
90
|
yield
|
91
91
|
end
|
92
92
|
|
93
|
+
# Continuing is passed as a block, yield to continue.
|
94
|
+
def resolve_each(object, arguments, context)
|
95
|
+
yield
|
96
|
+
end
|
97
|
+
|
93
98
|
def on_field?
|
94
99
|
locations.include?(FIELD)
|
95
100
|
end
|
@@ -101,6 +106,14 @@ module GraphQL
|
|
101
106
|
def on_operation?
|
102
107
|
locations.include?(QUERY) && locations.include?(MUTATION) && locations.include?(SUBSCRIPTION)
|
103
108
|
end
|
109
|
+
|
110
|
+
def repeatable?
|
111
|
+
!!@repeatable
|
112
|
+
end
|
113
|
+
|
114
|
+
def repeatable(new_value)
|
115
|
+
@repeatable = new_value
|
116
|
+
end
|
104
117
|
end
|
105
118
|
|
106
119
|
# @return [GraphQL::Schema::Field, GraphQL::Schema::Argument, Class, Module]
|
@@ -139,6 +152,7 @@ module GraphQL
|
|
139
152
|
ENUM_VALUE = :ENUM_VALUE,
|
140
153
|
INPUT_OBJECT = :INPUT_OBJECT,
|
141
154
|
INPUT_FIELD_DEFINITION = :INPUT_FIELD_DEFINITION,
|
155
|
+
VARIABLE_DEFINITION = :VARIABLE_DEFINITION,
|
142
156
|
]
|
143
157
|
|
144
158
|
DEFAULT_DEPRECATION_REASON = 'No longer supported'
|
@@ -161,6 +175,7 @@ module GraphQL
|
|
161
175
|
ENUM_VALUE: 'Location adjacent to an enum value definition.',
|
162
176
|
INPUT_OBJECT: 'Location adjacent to an input object type definition.',
|
163
177
|
INPUT_FIELD_DEFINITION: 'Location adjacent to an input object field definition.',
|
178
|
+
VARIABLE_DEFINITION: 'Location adjacent to a variable definition.',
|
164
179
|
}
|
165
180
|
|
166
181
|
private
|
data/lib/graphql/schema/field.rb
CHANGED
@@ -16,6 +16,8 @@ module GraphQL
|
|
16
16
|
include GraphQL::Schema::Member::HasDirectives
|
17
17
|
include GraphQL::Schema::Member::HasDeprecationReason
|
18
18
|
|
19
|
+
class FieldImplementationFailed < GraphQL::Error; end
|
20
|
+
|
19
21
|
# @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided
|
20
22
|
attr_reader :name
|
21
23
|
alias :graphql_name :name
|
@@ -792,51 +794,103 @@ module GraphQL
|
|
792
794
|
|
793
795
|
def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx)
|
794
796
|
with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs|
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
#
|
804
|
-
# - A method on the type instance;
|
805
|
-
# - Hash keys, if the wrapped object is a hash;
|
806
|
-
# - A method on the wrapped object;
|
807
|
-
# - Or, raise not implemented.
|
808
|
-
#
|
809
|
-
if obj.respond_to?(@resolver_method)
|
810
|
-
# Call the method with kwargs, if there are any
|
811
|
-
if ruby_kwargs.any?
|
812
|
-
obj.public_send(@resolver_method, **ruby_kwargs)
|
813
|
-
else
|
814
|
-
obj.public_send(@resolver_method)
|
797
|
+
begin
|
798
|
+
method_receiver = nil
|
799
|
+
method_to_call = nil
|
800
|
+
if @resolver_class
|
801
|
+
if obj.is_a?(GraphQL::Schema::Object)
|
802
|
+
obj = obj.object
|
803
|
+
end
|
804
|
+
obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
|
815
805
|
end
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
806
|
+
|
807
|
+
# Find a way to resolve this field, checking:
|
808
|
+
#
|
809
|
+
# - A method on the type instance;
|
810
|
+
# - Hash keys, if the wrapped object is a hash;
|
811
|
+
# - A method on the wrapped object;
|
812
|
+
# - Or, raise not implemented.
|
813
|
+
#
|
814
|
+
if obj.respond_to?(@resolver_method)
|
815
|
+
method_to_call = @resolver_method
|
816
|
+
method_receiver = obj
|
817
|
+
# Call the method with kwargs, if there are any
|
818
|
+
if ruby_kwargs.any?
|
819
|
+
obj.public_send(@resolver_method, **ruby_kwargs)
|
820
|
+
else
|
821
|
+
obj.public_send(@resolver_method)
|
822
|
+
end
|
823
|
+
elsif obj.object.is_a?(Hash)
|
824
|
+
inner_object = obj.object
|
825
|
+
if inner_object.key?(@method_sym)
|
826
|
+
inner_object[@method_sym]
|
827
|
+
else
|
828
|
+
inner_object[@method_str]
|
829
|
+
end
|
830
|
+
elsif obj.object.respond_to?(@method_sym)
|
831
|
+
method_to_call = @method_sym
|
832
|
+
method_receiver = obj.object
|
833
|
+
if ruby_kwargs.any?
|
834
|
+
obj.object.public_send(@method_sym, **ruby_kwargs)
|
835
|
+
else
|
836
|
+
obj.object.public_send(@method_sym)
|
837
|
+
end
|
820
838
|
else
|
821
|
-
|
839
|
+
raise <<-ERR
|
840
|
+
Failed to implement #{@owner.graphql_name}.#{@name}, tried:
|
841
|
+
|
842
|
+
- `#{obj.class}##{@resolver_method}`, which did not exist
|
843
|
+
- `#{obj.object.class}##{@method_sym}`, which did not exist
|
844
|
+
- Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
|
845
|
+
|
846
|
+
To implement this field, define one of the methods above (and check for typos)
|
847
|
+
ERR
|
822
848
|
end
|
823
|
-
|
824
|
-
|
825
|
-
|
849
|
+
rescue ArgumentError
|
850
|
+
assert_satisfactory_implementation(method_receiver, method_to_call, ruby_kwargs)
|
851
|
+
# if the line above doesn't raise, re-raise
|
852
|
+
raise
|
853
|
+
end
|
854
|
+
end
|
855
|
+
end
|
856
|
+
|
857
|
+
def assert_satisfactory_implementation(receiver, method_name, ruby_kwargs)
|
858
|
+
method_defn = receiver.method(method_name)
|
859
|
+
unsatisfied_ruby_kwargs = ruby_kwargs.dup
|
860
|
+
unsatisfied_method_params = []
|
861
|
+
encountered_keyrest = false
|
862
|
+
method_defn.parameters.each do |(param_type, param_name)|
|
863
|
+
case param_type
|
864
|
+
when :key
|
865
|
+
unsatisfied_ruby_kwargs.delete(param_name)
|
866
|
+
when :keyreq
|
867
|
+
if unsatisfied_ruby_kwargs.key?(param_name)
|
868
|
+
unsatisfied_ruby_kwargs.delete(param_name)
|
826
869
|
else
|
827
|
-
|
870
|
+
unsatisfied_method_params << "- `#{param_name}:` is required by Ruby, but not by GraphQL. Consider `#{param_name}: nil` instead, or making this argument required in GraphQL."
|
828
871
|
end
|
829
|
-
|
830
|
-
|
831
|
-
|
872
|
+
when :keyrest
|
873
|
+
encountered_keyrest = true
|
874
|
+
when :req
|
875
|
+
unsatisfied_method_params << "- `#{param_name}` is required by Ruby, but GraphQL doesn't pass positional arguments. If it's meant to be a GraphQL argument, use `#{param_name}:` instead. Otherwise, remove it."
|
876
|
+
when :opt, :rest
|
877
|
+
# This is fine, although it will never be present
|
878
|
+
end
|
879
|
+
end
|
832
880
|
|
833
|
-
|
834
|
-
|
835
|
-
|
881
|
+
if encountered_keyrest
|
882
|
+
unsatisfied_ruby_kwargs.clear
|
883
|
+
end
|
836
884
|
|
837
|
-
|
838
|
-
ERR
|
839
|
-
|
885
|
+
if unsatisfied_ruby_kwargs.any? || unsatisfied_method_params.any?
|
886
|
+
raise FieldImplementationFailed.new, <<-ERR
|
887
|
+
Failed to call #{method_name} on #{receiver.inspect} because the Ruby method params were incompatible with the GraphQL arguments:
|
888
|
+
|
889
|
+
#{ unsatisfied_ruby_kwargs
|
890
|
+
.map { |key, value| "- `#{key}: #{value}` was given by GraphQL but not defined in the Ruby method. Add `#{key}:` to the method parameters." }
|
891
|
+
.concat(unsatisfied_method_params)
|
892
|
+
.join("\n") }
|
893
|
+
ERR
|
840
894
|
end
|
841
895
|
end
|
842
896
|
|
@@ -850,8 +904,12 @@ module GraphQL
|
|
850
904
|
# This is a hack to get the _last_ value for extended obj and args,
|
851
905
|
# in case one of the extensions doesn't `yield`.
|
852
906
|
# (There's another implementation that uses multiple-return, but I'm wary of the perf cost of the extra arrays)
|
853
|
-
extended = { args: args, obj: obj, memos: nil }
|
907
|
+
extended = { args: args, obj: obj, memos: nil, added_extras: nil }
|
854
908
|
value = run_extensions_before_resolve(obj, args, ctx, extended) do |obj, args|
|
909
|
+
if (added_extras = extended[:added_extras])
|
910
|
+
args = args.dup
|
911
|
+
added_extras.each { |e| args.delete(e) }
|
912
|
+
end
|
855
913
|
yield(obj, args)
|
856
914
|
end
|
857
915
|
|
@@ -880,6 +938,12 @@ module GraphQL
|
|
880
938
|
memos = extended[:memos] ||= {}
|
881
939
|
memos[idx] = memo
|
882
940
|
end
|
941
|
+
|
942
|
+
if (extras = extension.added_extras)
|
943
|
+
ae = extended[:added_extras] ||= []
|
944
|
+
ae.concat(extras)
|
945
|
+
end
|
946
|
+
|
883
947
|
extended[:obj] = extended_obj
|
884
948
|
extended[:args] = extended_args
|
885
949
|
run_extensions_before_resolve(extended_obj, extended_args, ctx, extended, idx: idx + 1) { |o, a| yield(o, a) }
|
@@ -49,8 +49,36 @@ module GraphQL
|
|
49
49
|
configs = @own_default_argument_configurations ||= []
|
50
50
|
configs << [argument_args, argument_kwargs]
|
51
51
|
end
|
52
|
+
|
53
|
+
# If configured, these `extras` will be added to the field if they aren't already present,
|
54
|
+
# but removed by from `arguments` before the field's `resolve` is called.
|
55
|
+
# (The extras _will_ be present for other extensions, though.)
|
56
|
+
#
|
57
|
+
# @param new_extras [Array<Symbol>] If provided, assign extras used by this extension
|
58
|
+
# @return [Array<Symbol>] any extras assigned to this extension
|
59
|
+
def extras(new_extras = nil)
|
60
|
+
if new_extras
|
61
|
+
@own_extras = new_extras
|
62
|
+
end
|
63
|
+
|
64
|
+
inherited_extras = self.superclass.respond_to?(:extras) ? superclass.extras : nil
|
65
|
+
if @own_extras
|
66
|
+
if inherited_extras
|
67
|
+
inherited_extras + @own_extras
|
68
|
+
else
|
69
|
+
@own_extras
|
70
|
+
end
|
71
|
+
elsif inherited_extras
|
72
|
+
inherited_extras
|
73
|
+
else
|
74
|
+
NO_EXTRAS
|
75
|
+
end
|
76
|
+
end
|
52
77
|
end
|
53
78
|
|
79
|
+
NO_EXTRAS = [].freeze
|
80
|
+
private_constant :NO_EXTRAS
|
81
|
+
|
54
82
|
# Called when this extension is attached to a field.
|
55
83
|
# The field definition may be extended during this method.
|
56
84
|
# @return [void]
|
@@ -79,9 +107,18 @@ module GraphQL
|
|
79
107
|
end
|
80
108
|
end
|
81
109
|
end
|
110
|
+
if (extras = self.class.extras).any?
|
111
|
+
@added_extras = extras - field.extras
|
112
|
+
field.extras(@added_extras)
|
113
|
+
else
|
114
|
+
@added_extras = nil
|
115
|
+
end
|
82
116
|
freeze
|
83
117
|
end
|
84
118
|
|
119
|
+
# @api private
|
120
|
+
attr_reader :added_extras
|
121
|
+
|
85
122
|
# Called before resolving {#field}. It should either:
|
86
123
|
#
|
87
124
|
# - `yield` values to continue execution; OR
|
@@ -79,6 +79,21 @@ module GraphQL
|
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
82
|
+
def self.authorized?(obj, value, ctx)
|
83
|
+
# Authorize each argument (but this doesn't apply if `prepare` is implemented):
|
84
|
+
if value.is_a?(InputObject)
|
85
|
+
arguments(ctx).each do |_name, input_obj_arg|
|
86
|
+
input_obj_arg = input_obj_arg.type_class
|
87
|
+
if value.key?(input_obj_arg.keyword) &&
|
88
|
+
!input_obj_arg.authorized?(obj, value[input_obj_arg.keyword], ctx)
|
89
|
+
return false
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
# It didn't early-return false:
|
94
|
+
true
|
95
|
+
end
|
96
|
+
|
82
97
|
def unwrap_value(value)
|
83
98
|
case value
|
84
99
|
when Array
|
@@ -34,6 +34,7 @@ module GraphQL
|
|
34
34
|
Class.new(GraphQL::Schema) do
|
35
35
|
orphan_types(types.values)
|
36
36
|
directives(directives)
|
37
|
+
description(schema["description"])
|
37
38
|
|
38
39
|
def self.resolve_type(*)
|
39
40
|
raise(GraphQL::RequiredImplementationMissingError, "This schema was loaded from string, so it can't resolve types for objects")
|
@@ -141,6 +142,7 @@ module GraphQL
|
|
141
142
|
Class.new(GraphQL::Schema::Scalar) do
|
142
143
|
graphql_name(type["name"])
|
143
144
|
description(type["description"])
|
145
|
+
specified_by_url(type["specifiedByUrl"])
|
144
146
|
end
|
145
147
|
end
|
146
148
|
when "UNION"
|
@@ -160,6 +162,7 @@ module GraphQL
|
|
160
162
|
graphql_name(directive["name"])
|
161
163
|
description(directive["description"])
|
162
164
|
locations(*directive["locations"].map(&:to_sym))
|
165
|
+
repeatable(directive["isRepeatable"])
|
163
166
|
loader.build_arguments(self, directive["args"], type_resolver)
|
164
167
|
end
|
165
168
|
end
|
@@ -53,6 +53,10 @@ module GraphQL
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def coerce_input(value, ctx)
|
56
|
+
# `.validate_input` above is used for variables, but this method is used for arguments
|
57
|
+
if value.nil?
|
58
|
+
raise GraphQL::ExecutionError, "`null` is not a valid input for `#{to_type_signature}`, please provide a value for this argument."
|
59
|
+
end
|
56
60
|
of_type.coerce_input(value, ctx)
|
57
61
|
end
|
58
62
|
|
@@ -32,6 +32,18 @@ module GraphQL
|
|
32
32
|
GraphQL::TypeKinds::SCALAR
|
33
33
|
end
|
34
34
|
|
35
|
+
def specified_by_url(new_url = nil)
|
36
|
+
if new_url
|
37
|
+
@specified_by_url = new_url
|
38
|
+
elsif defined?(@specified_by_url)
|
39
|
+
@specified_by_url
|
40
|
+
elsif superclass.respond_to?(:specified_by_url)
|
41
|
+
superclass.specified_by_url
|
42
|
+
else
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
35
47
|
def default_scalar(is_default = nil)
|
36
48
|
if !is_default.nil?
|
37
49
|
@default_scalar = is_default
|