graphql 2.5.9 → 2.5.26
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/detailed_trace_generator.rb +77 -0
- data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
- data/lib/graphql/analysis.rb +20 -13
- data/lib/graphql/dashboard/application_controller.rb +41 -0
- data/lib/graphql/dashboard/landings_controller.rb +9 -0
- data/lib/graphql/dashboard/statics_controller.rb +31 -0
- data/lib/graphql/dashboard/subscriptions.rb +2 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
- data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
- data/lib/graphql/dashboard.rb +11 -73
- data/lib/graphql/dataloader/active_record_association_source.rb +14 -2
- data/lib/graphql/dataloader/async_dataloader.rb +22 -11
- data/lib/graphql/dataloader/null_dataloader.rb +54 -9
- data/lib/graphql/dataloader.rb +75 -23
- data/lib/graphql/date_encoding_error.rb +1 -1
- data/lib/graphql/execution/field_resolve_step.rb +631 -0
- data/lib/graphql/execution/finalize.rb +217 -0
- data/lib/graphql/execution/input_values.rb +261 -0
- data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
- data/lib/graphql/execution/interpreter/resolve.rb +10 -16
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
- data/lib/graphql/execution/interpreter/runtime.rb +28 -33
- data/lib/graphql/execution/interpreter.rb +8 -22
- data/lib/graphql/execution/lazy.rb +1 -1
- data/lib/graphql/execution/load_argument_step.rb +64 -0
- data/lib/graphql/execution/multiplex.rb +1 -1
- data/lib/graphql/execution/next.rb +90 -0
- data/lib/graphql/execution/prepare_object_step.rb +128 -0
- data/lib/graphql/execution/runner.rb +410 -0
- data/lib/graphql/execution/selections_step.rb +91 -0
- data/lib/graphql/execution.rb +8 -4
- data/lib/graphql/execution_error.rb +17 -10
- data/lib/graphql/introspection/directive_type.rb +7 -3
- data/lib/graphql/introspection/dynamic_fields.rb +5 -1
- data/lib/graphql/introspection/entry_points.rb +11 -3
- data/lib/graphql/introspection/enum_value_type.rb +5 -5
- data/lib/graphql/introspection/field_type.rb +13 -5
- data/lib/graphql/introspection/input_value_type.rb +21 -13
- data/lib/graphql/introspection/type_type.rb +64 -28
- data/lib/graphql/invalid_null_error.rb +11 -5
- data/lib/graphql/language/document_from_schema_definition.rb +2 -1
- data/lib/graphql/language/lexer.rb +20 -9
- data/lib/graphql/language/nodes.rb +5 -1
- data/lib/graphql/language/parser.rb +1 -0
- data/lib/graphql/language.rb +21 -12
- data/lib/graphql/pagination/connection.rb +2 -0
- data/lib/graphql/pagination/connections.rb +32 -0
- data/lib/graphql/query/context.rb +11 -4
- data/lib/graphql/query/null_context.rb +9 -3
- data/lib/graphql/query/partial.rb +18 -3
- data/lib/graphql/query.rb +10 -1
- data/lib/graphql/runtime_error.rb +6 -0
- data/lib/graphql/schema/addition.rb +3 -1
- data/lib/graphql/schema/argument.rb +17 -0
- data/lib/graphql/schema/build_from_definition.rb +15 -2
- data/lib/graphql/schema/directive.rb +45 -13
- data/lib/graphql/schema/field/connection_extension.rb +4 -37
- data/lib/graphql/schema/field/scope_extension.rb +18 -13
- data/lib/graphql/schema/field.rb +87 -48
- data/lib/graphql/schema/field_extension.rb +11 -8
- data/lib/graphql/schema/interface.rb +26 -0
- data/lib/graphql/schema/list.rb +5 -1
- data/lib/graphql/schema/member/base_dsl_methods.rb +1 -11
- data/lib/graphql/schema/member/has_arguments.rb +43 -14
- data/lib/graphql/schema/member/has_authorization.rb +35 -0
- data/lib/graphql/schema/member/has_dataloader.rb +37 -0
- data/lib/graphql/schema/member/has_fields.rb +86 -5
- data/lib/graphql/schema/member/has_interfaces.rb +2 -2
- data/lib/graphql/schema/member/type_system_helpers.rb +16 -2
- data/lib/graphql/schema/member.rb +5 -0
- data/lib/graphql/schema/non_null.rb +1 -1
- data/lib/graphql/schema/object.rb +1 -0
- data/lib/graphql/schema/ractor_shareable.rb +79 -0
- data/lib/graphql/schema/resolver.rb +60 -1
- data/lib/graphql/schema/subscription.rb +0 -2
- data/lib/graphql/schema/validator/required_validator.rb +45 -5
- data/lib/graphql/schema/visibility/migration.rb +2 -2
- data/lib/graphql/schema/visibility/profile.rb +140 -56
- data/lib/graphql/schema/visibility.rb +31 -18
- data/lib/graphql/schema/wrapper.rb +7 -1
- data/lib/graphql/schema.rb +108 -32
- data/lib/graphql/static_validation/all_rules.rb +1 -1
- data/lib/graphql/static_validation/base_visitor.rb +90 -66
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -4
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +322 -256
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
- data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
- data/lib/graphql/static_validation/validation_context.rb +1 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
- data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +34 -10
- data/lib/graphql/subscriptions/event.rb +1 -0
- data/lib/graphql/subscriptions.rb +36 -1
- data/lib/graphql/testing/helpers.rb +12 -9
- data/lib/graphql/testing/mock_action_cable.rb +111 -0
- data/lib/graphql/testing.rb +1 -0
- data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
- data/lib/graphql/tracing/detailed_trace.rb +70 -7
- data/lib/graphql/tracing/null_trace.rb +1 -1
- data/lib/graphql/tracing/perfetto_trace.rb +209 -79
- data/lib/graphql/tracing/sentry_trace.rb +3 -1
- data/lib/graphql/tracing/trace.rb +6 -0
- data/lib/graphql/type_kinds.rb +1 -0
- data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
- data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
- data/lib/graphql/types/relay/has_node_field.rb +13 -8
- data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
- data/lib/graphql/types/relay/node_behaviors.rb +13 -2
- data/lib/graphql/unauthorized_error.rb +9 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +7 -3
- metadata +21 -3
|
@@ -258,7 +258,9 @@ module GraphQL
|
|
|
258
258
|
# We can get these now; we'll have to get late-bound types later
|
|
259
259
|
if interface_type.is_a?(Module) && type.is_a?(Class)
|
|
260
260
|
implementers = @possible_types[interface_type] ||= []
|
|
261
|
-
implementers
|
|
261
|
+
if !implementers.include?(type)
|
|
262
|
+
implementers << type
|
|
263
|
+
end
|
|
262
264
|
end
|
|
263
265
|
when String, Schema::LateBoundType
|
|
264
266
|
interface_type = interface_type_membership
|
|
@@ -4,6 +4,7 @@ module GraphQL
|
|
|
4
4
|
class Argument
|
|
5
5
|
include GraphQL::Schema::Member::HasPath
|
|
6
6
|
include GraphQL::Schema::Member::HasAstNode
|
|
7
|
+
include GraphQL::Schema::Member::HasAuthorization
|
|
7
8
|
include GraphQL::Schema::Member::HasDirectives
|
|
8
9
|
include GraphQL::Schema::Member::HasDeprecationReason
|
|
9
10
|
include GraphQL::Schema::Member::HasValidators
|
|
@@ -39,9 +40,14 @@ module GraphQL
|
|
|
39
40
|
# @param arg_name [Symbol]
|
|
40
41
|
# @param type_expr
|
|
41
42
|
# @param desc [String]
|
|
43
|
+
# @param type [Class, Array<Class>] Input type; positional argument also accepted
|
|
44
|
+
# @param name [Symbol] positional argument also accepted # @param loads [Class, Array<Class>] A GraphQL type to load for the given ID when one is present
|
|
45
|
+
# @param definition_block [Proc] Called with the newly-created {Argument}
|
|
46
|
+
# @param owner [Class] Private, used by GraphQL-Ruby during schema definition
|
|
42
47
|
# @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`.
|
|
43
48
|
# @param description [String]
|
|
44
49
|
# @param default_value [Object]
|
|
50
|
+
# @param loads [Class, Array<Class>] A GraphQL type to load for the given ID when one is present
|
|
45
51
|
# @param as [Symbol] Override the keyword name when passed to a method
|
|
46
52
|
# @param prepare [Symbol] A method to call to transform this argument's valuebefore sending it to field resolution
|
|
47
53
|
# @param camelize [Boolean] if true, the name will be camelized when building the schema
|
|
@@ -50,6 +56,8 @@ module GraphQL
|
|
|
50
56
|
# @param deprecation_reason [String]
|
|
51
57
|
# @param validates [Hash, nil] Options for building validators, if any should be applied
|
|
52
58
|
# @param replace_null_with_default [Boolean] if `true`, incoming values of `null` will be replaced with the configured `default_value`
|
|
59
|
+
# @param comment [String] Private, used by GraphQL-Ruby when parsing GraphQL schema files
|
|
60
|
+
# @param ast_node [GraphQL::Language::Nodes::InputValueDefinition] Private, used by GraphQL-Ruby when parsing schema files
|
|
53
61
|
def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, comment: nil, ast_node: nil, default_value: NOT_CONFIGURED, as: nil, from_resolver: false, camelize: true, prepare: nil, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block)
|
|
54
62
|
arg_name ||= name
|
|
55
63
|
@name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
|
|
@@ -157,6 +165,10 @@ module GraphQL
|
|
|
157
165
|
true
|
|
158
166
|
end
|
|
159
167
|
|
|
168
|
+
def authorizes?(_context)
|
|
169
|
+
self.method(:authorized?).owner != GraphQL::Schema::Argument
|
|
170
|
+
end
|
|
171
|
+
|
|
160
172
|
def authorized?(obj, value, ctx)
|
|
161
173
|
authorized_as_type?(obj, value, ctx, as_type: type)
|
|
162
174
|
end
|
|
@@ -212,6 +224,11 @@ module GraphQL
|
|
|
212
224
|
@statically_coercible = !requires_parent_object
|
|
213
225
|
end
|
|
214
226
|
|
|
227
|
+
def freeze
|
|
228
|
+
statically_coercible?
|
|
229
|
+
super
|
|
230
|
+
end
|
|
231
|
+
|
|
215
232
|
# Apply the {prepare} configuration to `value`, using methods from `obj`.
|
|
216
233
|
# Used by the runtime.
|
|
217
234
|
# @api private
|
|
@@ -187,6 +187,10 @@ module GraphQL
|
|
|
187
187
|
|
|
188
188
|
object_types.each do |t|
|
|
189
189
|
t.interfaces.each do |int_t|
|
|
190
|
+
if int_t.is_a?(LateBoundType)
|
|
191
|
+
int_t = types[int_t.graphql_name]
|
|
192
|
+
t.implements(int_t)
|
|
193
|
+
end
|
|
190
194
|
int_t.orphan_types(t)
|
|
191
195
|
end
|
|
192
196
|
end
|
|
@@ -262,6 +266,8 @@ module GraphQL
|
|
|
262
266
|
build_scalar_type(definition, type_resolver, base_types[:scalar], default_resolve: default_resolve)
|
|
263
267
|
when GraphQL::Language::Nodes::InputObjectTypeDefinition
|
|
264
268
|
build_input_object_type(definition, type_resolver, base_types[:input_object])
|
|
269
|
+
when GraphQL::Language::Nodes::DirectiveDefinition
|
|
270
|
+
build_directive(definition, type_resolver)
|
|
265
271
|
end
|
|
266
272
|
end
|
|
267
273
|
|
|
@@ -506,6 +512,7 @@ module GraphQL
|
|
|
506
512
|
camelize: false,
|
|
507
513
|
directives: prepare_directives(field_definition, type_resolver),
|
|
508
514
|
resolver_method: resolve_method_name,
|
|
515
|
+
resolve_batch: resolve_method_name,
|
|
509
516
|
)
|
|
510
517
|
|
|
511
518
|
builder.build_arguments(schema_field_defn, field_definition.arguments, type_resolver)
|
|
@@ -519,9 +526,15 @@ module GraphQL
|
|
|
519
526
|
|
|
520
527
|
def define_field_resolve_method(owner, method_name, field_name)
|
|
521
528
|
owner.define_method(method_name) { |**args|
|
|
522
|
-
field_instance =
|
|
529
|
+
field_instance = context.types.field(owner, field_name)
|
|
523
530
|
context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
|
|
524
531
|
}
|
|
532
|
+
owner.define_singleton_method(method_name) { |objects, context, **args|
|
|
533
|
+
field_instance = context.types.field(owner, field_name)
|
|
534
|
+
objects.map do |object|
|
|
535
|
+
context.schema.definition_default_resolve.call(self, field_instance, object, args, context)
|
|
536
|
+
end
|
|
537
|
+
}
|
|
525
538
|
end
|
|
526
539
|
|
|
527
540
|
def build_resolve_type(lookup_hash, directives, missing_type_handler)
|
|
@@ -540,7 +553,7 @@ module GraphQL
|
|
|
540
553
|
when GraphQL::Language::Nodes::ListType
|
|
541
554
|
resolve_type_proc.call(ast_node.of_type).to_list_type
|
|
542
555
|
when String
|
|
543
|
-
directives[ast_node]
|
|
556
|
+
directives[ast_node] ||= missing_type_handler.call(ast_node)
|
|
544
557
|
else
|
|
545
558
|
raise "Unexpected ast_node: #{ast_node.inspect}"
|
|
546
559
|
end
|
|
@@ -31,17 +31,25 @@ module GraphQL
|
|
|
31
31
|
|
|
32
32
|
def locations(*new_locations)
|
|
33
33
|
if !new_locations.empty?
|
|
34
|
+
is_runtime = false
|
|
34
35
|
new_locations.each do |new_loc|
|
|
35
|
-
|
|
36
|
+
loc_sym = new_loc.to_sym
|
|
37
|
+
if !LOCATIONS.include?(loc_sym)
|
|
36
38
|
raise ArgumentError, "#{self} (#{self.graphql_name}) has an invalid directive location: `locations #{new_loc}` "
|
|
37
39
|
end
|
|
40
|
+
is_runtime ||= RUNTIME_LOCATIONS.include?(loc_sym)
|
|
38
41
|
end
|
|
39
42
|
@locations = new_locations
|
|
43
|
+
@is_runtime = is_runtime
|
|
40
44
|
else
|
|
41
45
|
@locations ||= (superclass.respond_to?(:locations) ? superclass.locations : [])
|
|
42
46
|
end
|
|
43
47
|
end
|
|
44
48
|
|
|
49
|
+
def runtime?
|
|
50
|
+
@is_runtime
|
|
51
|
+
end
|
|
52
|
+
|
|
45
53
|
def default_directive(new_default_directive = nil)
|
|
46
54
|
if new_default_directive != nil
|
|
47
55
|
@default_directive = new_default_directive
|
|
@@ -104,8 +112,12 @@ module GraphQL
|
|
|
104
112
|
|
|
105
113
|
def inherited(subclass)
|
|
106
114
|
super
|
|
115
|
+
parent_class = self
|
|
107
116
|
subclass.class_exec do
|
|
108
117
|
@default_graphql_name ||= nil
|
|
118
|
+
@locations = parent_class.locations
|
|
119
|
+
@is_runtime = parent_class.runtime?
|
|
120
|
+
@repeatable = false
|
|
109
121
|
end
|
|
110
122
|
end
|
|
111
123
|
end
|
|
@@ -129,11 +141,29 @@ module GraphQL
|
|
|
129
141
|
# not runtime arguments.
|
|
130
142
|
context = Query::NullContext.instance
|
|
131
143
|
self.class.all_argument_definitions.each do |arg_defn|
|
|
132
|
-
|
|
133
|
-
|
|
144
|
+
keyword = arg_defn.keyword
|
|
145
|
+
arg_type = arg_defn.type
|
|
146
|
+
if arguments.key?(keyword)
|
|
147
|
+
value = arguments[keyword]
|
|
134
148
|
# This is a Ruby-land value; convert it to graphql for validation
|
|
135
149
|
graphql_value = begin
|
|
136
|
-
|
|
150
|
+
coerce_value = value
|
|
151
|
+
if arg_type.list? && (!coerce_value.nil?) && (!coerce_value.is_a?(Array))
|
|
152
|
+
# When validating inputs, GraphQL accepts a single item
|
|
153
|
+
# and implicitly converts it to a one-item list.
|
|
154
|
+
# However, we're using result coercion here to go from Ruby value
|
|
155
|
+
# to GraphQL value, so it doesn't have that feature.
|
|
156
|
+
# Keep the GraphQL-type behavior but implement it manually:
|
|
157
|
+
wrap_type = arg_type
|
|
158
|
+
while wrap_type.list?
|
|
159
|
+
if wrap_type.non_null?
|
|
160
|
+
wrap_type = wrap_type.of_type
|
|
161
|
+
end
|
|
162
|
+
wrap_type = wrap_type.of_type
|
|
163
|
+
coerce_value = [coerce_value]
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
arg_type.coerce_isolated_result(coerce_value)
|
|
137
167
|
rescue GraphQL::Schema::Enum::UnresolvedValueError
|
|
138
168
|
# Let validation handle this
|
|
139
169
|
value
|
|
@@ -142,7 +172,7 @@ module GraphQL
|
|
|
142
172
|
value = graphql_value = nil
|
|
143
173
|
end
|
|
144
174
|
|
|
145
|
-
result =
|
|
175
|
+
result = arg_type.validate_input(graphql_value, context)
|
|
146
176
|
if !result.valid?
|
|
147
177
|
raise InvalidArgumentError, "@#{graphql_name}.#{arg_defn.graphql_name} on #{owner.path} is invalid (#{value.inspect}): #{result.problems.first["explanation"]}"
|
|
148
178
|
end
|
|
@@ -159,13 +189,16 @@ module GraphQL
|
|
|
159
189
|
end
|
|
160
190
|
|
|
161
191
|
LOCATIONS = [
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
192
|
+
*(RUNTIME_LOCATIONS = [
|
|
193
|
+
QUERY = :QUERY,
|
|
194
|
+
MUTATION = :MUTATION,
|
|
195
|
+
SUBSCRIPTION = :SUBSCRIPTION,
|
|
196
|
+
FIELD = :FIELD,
|
|
197
|
+
FRAGMENT_DEFINITION = :FRAGMENT_DEFINITION,
|
|
198
|
+
FRAGMENT_SPREAD = :FRAGMENT_SPREAD,
|
|
199
|
+
INLINE_FRAGMENT = :INLINE_FRAGMENT,
|
|
200
|
+
VARIABLE_DEFINITION = :VARIABLE_DEFINITION,
|
|
201
|
+
]),
|
|
169
202
|
SCHEMA = :SCHEMA,
|
|
170
203
|
SCALAR = :SCALAR,
|
|
171
204
|
OBJECT = :OBJECT,
|
|
@@ -177,7 +210,6 @@ module GraphQL
|
|
|
177
210
|
ENUM_VALUE = :ENUM_VALUE,
|
|
178
211
|
INPUT_OBJECT = :INPUT_OBJECT,
|
|
179
212
|
INPUT_FIELD_DEFINITION = :INPUT_FIELD_DEFINITION,
|
|
180
|
-
VARIABLE_DEFINITION = :VARIABLE_DEFINITION,
|
|
181
213
|
]
|
|
182
214
|
|
|
183
215
|
DEFAULT_DEPRECATION_REASON = 'No longer supported'
|
|
@@ -12,52 +12,19 @@ module GraphQL
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
# Remove pagination args before passing it to a user method
|
|
15
|
-
def resolve(object
|
|
15
|
+
def resolve(object: nil, objects: nil, arguments:, context:)
|
|
16
16
|
next_args = arguments.dup
|
|
17
17
|
next_args.delete(:first)
|
|
18
18
|
next_args.delete(:last)
|
|
19
19
|
next_args.delete(:before)
|
|
20
20
|
next_args.delete(:after)
|
|
21
|
-
yield(object, next_args, arguments)
|
|
21
|
+
yield(object || objects, next_args, arguments)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def after_resolve(value:, object:, arguments:, context:, memo:)
|
|
25
25
|
original_arguments = memo
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
value = nil
|
|
29
|
-
context.query.after_lazy(maybe_lazy) do |resolved_value|
|
|
30
|
-
value = resolved_value
|
|
31
|
-
if value.is_a? GraphQL::ExecutionError
|
|
32
|
-
# This isn't even going to work because context doesn't have ast_node anymore
|
|
33
|
-
context.add_error(value)
|
|
34
|
-
nil
|
|
35
|
-
elsif value.nil?
|
|
36
|
-
nil
|
|
37
|
-
elsif value.is_a?(GraphQL::Pagination::Connection)
|
|
38
|
-
# update the connection with some things that may not have been provided
|
|
39
|
-
value.context ||= context
|
|
40
|
-
value.parent ||= object.object
|
|
41
|
-
value.first_value ||= original_arguments[:first]
|
|
42
|
-
value.after_value ||= original_arguments[:after]
|
|
43
|
-
value.last_value ||= original_arguments[:last]
|
|
44
|
-
value.before_value ||= original_arguments[:before]
|
|
45
|
-
value.arguments ||= original_arguments # rubocop:disable Development/ContextIsPassedCop -- unrelated .arguments method
|
|
46
|
-
value.field ||= field
|
|
47
|
-
if field.has_max_page_size? && !value.has_max_page_size_override?
|
|
48
|
-
value.max_page_size = field.max_page_size
|
|
49
|
-
end
|
|
50
|
-
if field.has_default_page_size? && !value.has_default_page_size_override?
|
|
51
|
-
value.default_page_size = field.default_page_size
|
|
52
|
-
end
|
|
53
|
-
if (custom_t = context.schema.connections.edge_class_for_field(@field))
|
|
54
|
-
value.edge_class = custom_t
|
|
55
|
-
end
|
|
56
|
-
value
|
|
57
|
-
else
|
|
58
|
-
context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers
|
|
59
|
-
context.schema.connections.wrap(field, object.object, value, original_arguments, context)
|
|
60
|
-
end
|
|
26
|
+
context.query.after_lazy(value) do |resolved_value|
|
|
27
|
+
context.schema.connections.populate_connection(field, object.object, resolved_value, original_arguments, context)
|
|
61
28
|
end
|
|
62
29
|
end
|
|
63
30
|
end
|
|
@@ -5,22 +5,27 @@ module GraphQL
|
|
|
5
5
|
class Field
|
|
6
6
|
class ScopeExtension < GraphQL::Schema::FieldExtension
|
|
7
7
|
def after_resolve(object:, arguments:, context:, value:, memo:)
|
|
8
|
-
if
|
|
9
|
-
value
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
if object.is_a?(GraphQL::Schema::Object)
|
|
9
|
+
if value.nil?
|
|
10
|
+
value
|
|
11
|
+
else
|
|
12
|
+
return_type = field.type.unwrap
|
|
13
|
+
if return_type.respond_to?(:scope_items)
|
|
14
|
+
scoped_items = return_type.scope_items(value, context)
|
|
15
|
+
if !scoped_items.equal?(value) && !return_type.reauthorize_scoped_objects
|
|
16
|
+
if (current_runtime_state = Fiber[:__graphql_runtime_info]) &&
|
|
17
|
+
(query_runtime_state = current_runtime_state[context.query])
|
|
18
|
+
query_runtime_state.was_authorized_by_scope_items = true
|
|
19
|
+
end
|
|
18
20
|
end
|
|
21
|
+
scoped_items
|
|
22
|
+
else
|
|
23
|
+
value
|
|
19
24
|
end
|
|
20
|
-
scoped_items
|
|
21
|
-
else
|
|
22
|
-
value
|
|
23
25
|
end
|
|
26
|
+
else
|
|
27
|
+
# TODO skip this entirely?
|
|
28
|
+
value
|
|
24
29
|
end
|
|
25
30
|
end
|
|
26
31
|
end
|
data/lib/graphql/schema/field.rb
CHANGED
|
@@ -8,6 +8,7 @@ module GraphQL
|
|
|
8
8
|
include GraphQL::Schema::Member::HasArguments
|
|
9
9
|
include GraphQL::Schema::Member::HasArguments::FieldConfigured
|
|
10
10
|
include GraphQL::Schema::Member::HasAstNode
|
|
11
|
+
include GraphQL::Schema::Member::HasAuthorization
|
|
11
12
|
include GraphQL::Schema::Member::HasPath
|
|
12
13
|
include GraphQL::Schema::Member::HasValidators
|
|
13
14
|
extend GraphQL::Schema::FindInheritedValue
|
|
@@ -109,52 +110,6 @@ module GraphQL
|
|
|
109
110
|
end
|
|
110
111
|
attr_writer :subscription_scope
|
|
111
112
|
|
|
112
|
-
# Create a field instance from a list of arguments, keyword arguments, and a block.
|
|
113
|
-
#
|
|
114
|
-
# This method implements prioritization between the `resolver` or `mutation` defaults
|
|
115
|
-
# and the local overrides via other keywords.
|
|
116
|
-
#
|
|
117
|
-
# It also normalizes positional arguments into keywords for {Schema::Field#initialize}.
|
|
118
|
-
# @param resolver [Class] A {GraphQL::Schema::Resolver} class to use for field configuration
|
|
119
|
-
# @param mutation [Class] A {GraphQL::Schema::Mutation} class to use for field configuration
|
|
120
|
-
# @param subscription [Class] A {GraphQL::Schema::Subscription} class to use for field configuration
|
|
121
|
-
# @return [GraphQL::Schema:Field] an instance of `self`
|
|
122
|
-
# @see {.initialize} for other options
|
|
123
|
-
def self.from_options(name = nil, type = nil, desc = nil, comment: nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
|
|
124
|
-
if (resolver_class = resolver || mutation || subscription)
|
|
125
|
-
# Add a reference to that parent class
|
|
126
|
-
kwargs[:resolver_class] = resolver_class
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
if name
|
|
130
|
-
kwargs[:name] = name
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
if comment
|
|
134
|
-
kwargs[:comment] = comment
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
if !type.nil?
|
|
138
|
-
if desc
|
|
139
|
-
if kwargs[:description]
|
|
140
|
-
raise ArgumentError, "Provide description as a positional argument or `description:` keyword, but not both (#{desc.inspect}, #{kwargs[:description].inspect})"
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
kwargs[:description] = desc
|
|
144
|
-
kwargs[:type] = type
|
|
145
|
-
elsif (resolver || mutation) && type.is_a?(String)
|
|
146
|
-
# The return type should be copied from the resolver, and the second positional argument is the description
|
|
147
|
-
kwargs[:description] = type
|
|
148
|
-
else
|
|
149
|
-
kwargs[:type] = type
|
|
150
|
-
end
|
|
151
|
-
if type.is_a?(Class) && type < GraphQL::Schema::Mutation
|
|
152
|
-
raise ArgumentError, "Use `field #{name.inspect}, mutation: Mutation, ...` to provide a mutation to this field instead"
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
new(**kwargs, &block)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
113
|
# Can be set with `connection: true|false` or inferred from a type name ending in `*Connection`
|
|
159
114
|
# @return [Boolean] if true, this field will be wrapped with Relay connection behavior
|
|
160
115
|
def connection?
|
|
@@ -238,6 +193,11 @@ module GraphQL
|
|
|
238
193
|
# @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`)
|
|
239
194
|
# @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
|
|
240
195
|
# @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added.
|
|
196
|
+
# @param resolve_static [Symbol, true, nil] Used by {Schema.execute_next} to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments`
|
|
197
|
+
# @param resolve_batch [Symbol, true, nil] Used by {Schema.execute_next} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`.
|
|
198
|
+
# @param resolve_each [Symbol, true, nil] Used by {Schema.execute_next} to get a value value for each item. Called on the owner type class with `object, context, **arguments`.
|
|
199
|
+
# @param resolve_legacy_instance_method [Symbol, true, nil] Used by {Schema.execute_next} to get a value value for each item. Calls an instance method on the object type class.
|
|
200
|
+
# @param dataload [Class, Hash] Shorthand for making dataloader calls
|
|
241
201
|
# @param max_page_size [Integer, nil] For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
|
|
242
202
|
# @param default_page_size [Integer, nil] For connections, the default number of items to return from this field, or `nil` to return unlimited results.
|
|
243
203
|
# @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
|
|
@@ -255,7 +215,12 @@ module GraphQL
|
|
|
255
215
|
# @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
|
|
256
216
|
# @param validates [Array<Hash>] Configurations for validating this field
|
|
257
217
|
# @param fallback_value [Object] A fallback value if the method is not defined
|
|
258
|
-
|
|
218
|
+
# @param dynamic_introspection [Boolean] (Private, used by GraphQL-Ruby)
|
|
219
|
+
# @param relay_node_field [Boolean] (Private, used by GraphQL-Ruby)
|
|
220
|
+
# @param relay_nodes_field [Boolean] (Private, used by GraphQL-Ruby)
|
|
221
|
+
# @param extras [Array<:ast_node, :parent, :lookahead, :owner, :execution_errors, :graphql_name, :argument_details, Symbol>] Extra arguments to be injected into the resolver for this field
|
|
222
|
+
# @param definition_block [Proc] an additional block for configuring the field. Receive the field as a block param, or, if no block params are defined, then the block is `instance_eval`'d on the new {Field}.
|
|
223
|
+
def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: nil, resolve_legacy_instance_method: nil, resolve_static: nil, resolve_each: nil, resolve_batch: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, dataload: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
|
|
259
224
|
if name.nil?
|
|
260
225
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
|
261
226
|
end
|
|
@@ -303,6 +268,36 @@ module GraphQL
|
|
|
303
268
|
@method_str = -method_name.to_s
|
|
304
269
|
@method_sym = method_name.to_sym
|
|
305
270
|
@resolver_method = (resolver_method || name_s).to_sym
|
|
271
|
+
|
|
272
|
+
if resolve_static
|
|
273
|
+
@execution_mode = :resolve_static
|
|
274
|
+
@execution_mode_key = resolve_static == true ? @method_sym : resolve_static
|
|
275
|
+
elsif resolve_batch
|
|
276
|
+
@execution_mode = :resolve_batch
|
|
277
|
+
@execution_mode_key = resolve_batch == true ? @method_sym : resolve_batch
|
|
278
|
+
elsif resolve_each
|
|
279
|
+
@execution_mode = :resolve_each
|
|
280
|
+
@execution_mode_key = resolve_each == true ? @method_sym : resolve_each
|
|
281
|
+
elsif hash_key
|
|
282
|
+
@execution_mode = :hash_key
|
|
283
|
+
@execution_mode_key = hash_key
|
|
284
|
+
elsif dig
|
|
285
|
+
@execution_mode = :dig
|
|
286
|
+
@execution_mode_key = dig
|
|
287
|
+
elsif resolver_class
|
|
288
|
+
@execution_mode = :resolver_class
|
|
289
|
+
@execution_mode_key = resolver_class
|
|
290
|
+
elsif resolve_legacy_instance_method
|
|
291
|
+
@execution_mode = :resolve_legacy_instance_method
|
|
292
|
+
@execution_mode_key = resolve_legacy_instance_method == true ? @method_sym : resolve_legacy_instance_method
|
|
293
|
+
elsif dataload
|
|
294
|
+
@execution_mode = :dataload
|
|
295
|
+
@execution_mode_key = dataload
|
|
296
|
+
else
|
|
297
|
+
@execution_mode = :direct_send
|
|
298
|
+
@execution_mode_key = @method_sym
|
|
299
|
+
end
|
|
300
|
+
|
|
306
301
|
@complexity = complexity
|
|
307
302
|
@dynamic_introspection = dynamic_introspection
|
|
308
303
|
@return_type_expr = type
|
|
@@ -347,7 +342,7 @@ module GraphQL
|
|
|
347
342
|
|
|
348
343
|
@extensions = EMPTY_ARRAY
|
|
349
344
|
@call_after_define = false
|
|
350
|
-
set_pagination_extensions(connection_extension: connection_extension)
|
|
345
|
+
set_pagination_extensions(connection_extension: NOT_CONFIGURED.equal?(connection_extension) ? self.class.connection_extension : connection_extension)
|
|
351
346
|
# Do this last so we have as much context as possible when initializing them:
|
|
352
347
|
if !extensions.empty?
|
|
353
348
|
self.extensions(extensions)
|
|
@@ -373,6 +368,9 @@ module GraphQL
|
|
|
373
368
|
end
|
|
374
369
|
end
|
|
375
370
|
|
|
371
|
+
# @api private
|
|
372
|
+
attr_reader :execution_mode_key, :execution_mode
|
|
373
|
+
|
|
376
374
|
# Calls the definition block, if one was given.
|
|
377
375
|
# This is deferred so that references to the return type
|
|
378
376
|
# can be lazily evaluated, reducing Rails boot time.
|
|
@@ -616,6 +614,14 @@ module GraphQL
|
|
|
616
614
|
end
|
|
617
615
|
end
|
|
618
616
|
|
|
617
|
+
def freeze
|
|
618
|
+
type
|
|
619
|
+
owner_type
|
|
620
|
+
arguments_statically_coercible?
|
|
621
|
+
connection?
|
|
622
|
+
super
|
|
623
|
+
end
|
|
624
|
+
|
|
619
625
|
class MissingReturnTypeError < GraphQL::Error; end
|
|
620
626
|
attr_writer :type
|
|
621
627
|
|
|
@@ -658,6 +664,12 @@ module GraphQL
|
|
|
658
664
|
end
|
|
659
665
|
end
|
|
660
666
|
|
|
667
|
+
def authorizes?(context)
|
|
668
|
+
method(:authorized?).owner != GraphQL::Schema::Field ||
|
|
669
|
+
((args = context.types.arguments(self)) && (args.any? { |a| a.authorizes?(context) })) ||
|
|
670
|
+
(@resolver_class&.authorizes?(context)) || false
|
|
671
|
+
end
|
|
672
|
+
|
|
661
673
|
def authorized?(object, args, context)
|
|
662
674
|
if @resolver_class
|
|
663
675
|
# The resolver _instance_ will check itself during `resolve()`
|
|
@@ -912,6 +924,33 @@ ERR
|
|
|
912
924
|
end
|
|
913
925
|
end
|
|
914
926
|
|
|
927
|
+
public
|
|
928
|
+
|
|
929
|
+
def run_next_extensions_before_resolve(objs, args, ctx, extended, idx: 0, &block)
|
|
930
|
+
extension = @extensions[idx]
|
|
931
|
+
if extension
|
|
932
|
+
extension.resolve(objects: objs, arguments: args, context: ctx) do |extended_objs, extended_args, memo|
|
|
933
|
+
if memo
|
|
934
|
+
memos = extended.memos ||= {}
|
|
935
|
+
memos[idx] = memo
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
if (extras = extension.added_extras)
|
|
939
|
+
ae = extended.added_extras ||= []
|
|
940
|
+
ae.concat(extras)
|
|
941
|
+
end
|
|
942
|
+
|
|
943
|
+
extended.object = extended_objs
|
|
944
|
+
extended.arguments = extended_args
|
|
945
|
+
run_next_extensions_before_resolve(extended_objs, extended_args, ctx, extended, idx: idx + 1, &block)
|
|
946
|
+
end
|
|
947
|
+
else
|
|
948
|
+
yield(objs, args)
|
|
949
|
+
end
|
|
950
|
+
end
|
|
951
|
+
|
|
952
|
+
private
|
|
953
|
+
|
|
915
954
|
def run_extensions_before_resolve(obj, args, ctx, extended, idx: 0)
|
|
916
955
|
extension = @extensions[idx]
|
|
917
956
|
if extension
|
|
@@ -123,15 +123,16 @@ module GraphQL
|
|
|
123
123
|
#
|
|
124
124
|
# Whatever this method returns will be used for execution.
|
|
125
125
|
#
|
|
126
|
-
# @param object [Object] The object the field is being resolved on
|
|
126
|
+
# @param object [Object] The object the field is being resolved on (not passed by new execution)
|
|
127
|
+
# @param objects [Array<Object>] The objects the field is being resolved on (passed by new execution)
|
|
127
128
|
# @param arguments [Hash] Ruby keyword arguments for resolving this field
|
|
128
129
|
# @param context [Query::Context] the context for this query
|
|
129
|
-
# @yieldparam
|
|
130
|
+
# @yieldparam object_or_objects [Object, Array<Object>] The object or objects (new execution) to continue resolving the field on
|
|
130
131
|
# @yieldparam arguments [Hash] The keyword arguments to continue resolving with
|
|
131
132
|
# @yieldparam memo [Object] Any extension-specific value which will be passed to {#after_resolve} later
|
|
132
133
|
# @return [Object] The return value for this field.
|
|
133
|
-
def resolve(object
|
|
134
|
-
yield(object, arguments, nil)
|
|
134
|
+
def resolve(object: nil, objects: nil, arguments:, context:)
|
|
135
|
+
yield(object.nil? ? objects : object, arguments, nil)
|
|
135
136
|
end
|
|
136
137
|
|
|
137
138
|
# Called after {#field} was resolved, and after any lazy values (like `Promise`s) were synced,
|
|
@@ -139,14 +140,16 @@ module GraphQL
|
|
|
139
140
|
#
|
|
140
141
|
# Whatever this hook returns will be used as the return value.
|
|
141
142
|
#
|
|
142
|
-
# @param object [Object] The object the field is being resolved on
|
|
143
|
+
# @param object [Object] The object the field is being resolved on (not passed by new execution)
|
|
144
|
+
# @param objects [Array<Object>] The object the field is being resolved on (passed by new execution)
|
|
143
145
|
# @param arguments [Hash] Ruby keyword arguments for resolving this field
|
|
144
146
|
# @param context [Query::Context] the context for this query
|
|
145
|
-
# @param value [Object] Whatever the field previously returned
|
|
147
|
+
# @param value [Object] Whatever the field previously returned (not passed by new execution)
|
|
148
|
+
# @param values [Array<Object>] Whatever the field previously returned (passed by new execution)
|
|
146
149
|
# @param memo [Object] The third value yielded by {#resolve}, or `nil` if there wasn't one
|
|
147
150
|
# @return [Object] The return value for this field.
|
|
148
|
-
def after_resolve(object
|
|
149
|
-
value
|
|
151
|
+
def after_resolve(object: nil, objects: nil, arguments:, context:, values: nil, value: nil, memo:)
|
|
152
|
+
value.nil? ? values : value
|
|
150
153
|
end
|
|
151
154
|
end
|
|
152
155
|
end
|
|
@@ -33,6 +33,26 @@ module GraphQL
|
|
|
33
33
|
self::DefinitionMethods.module_exec(&block)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
# Instance methods defined in this block will become class methods on objects that implement this interface.
|
|
37
|
+
# Use it to implement `resolve_each:`, `resolve_batch:`, and `resolve_static:` fields.
|
|
38
|
+
# @example
|
|
39
|
+
# field :thing, String, resolve_static: true
|
|
40
|
+
#
|
|
41
|
+
# resolver_methods do
|
|
42
|
+
# def thing
|
|
43
|
+
# Somehow.get.thing
|
|
44
|
+
# end
|
|
45
|
+
# end
|
|
46
|
+
def resolver_methods(&block)
|
|
47
|
+
if !defined?(@_resolver_methods)
|
|
48
|
+
resolver_methods_module = Module.new
|
|
49
|
+
@_resolver_methods = resolver_methods_module
|
|
50
|
+
const_set(:ResolverMethods, resolver_methods_module)
|
|
51
|
+
extend(self::ResolverMethods)
|
|
52
|
+
end
|
|
53
|
+
self::ResolverMethods.module_exec(&block)
|
|
54
|
+
end
|
|
55
|
+
|
|
36
56
|
# @see {Schema::Warden} hides interfaces without visible implementations
|
|
37
57
|
def visible?(context)
|
|
38
58
|
true
|
|
@@ -79,6 +99,12 @@ module GraphQL
|
|
|
79
99
|
if !backtrace_line
|
|
80
100
|
raise "Attach interfaces using `implements(#{self})`, not `include(#{self})`"
|
|
81
101
|
end
|
|
102
|
+
|
|
103
|
+
child_class.ancestors.reverse_each do |ancestor|
|
|
104
|
+
if ancestor.const_defined?(:ResolverMethods)
|
|
105
|
+
child_class.extend(ancestor::ResolverMethods)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
82
108
|
end
|
|
83
109
|
|
|
84
110
|
super
|
data/lib/graphql/schema/list.rb
CHANGED
|
@@ -19,7 +19,11 @@ module GraphQL
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def to_type_signature
|
|
22
|
-
"[#{@of_type.to_type_signature}]"
|
|
22
|
+
@type_signature ||= -"[#{@of_type.to_type_signature}]"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def authorizes?(ctx)
|
|
26
|
+
of_type.authorizes?(ctx)
|
|
23
27
|
end
|
|
24
28
|
|
|
25
29
|
# This is for introspection, where it's expected the name will be `null`
|
|
@@ -26,16 +26,6 @@ module GraphQL
|
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
# Just a convenience method to point out that people should use graphql_name instead
|
|
30
|
-
def name(new_name = nil)
|
|
31
|
-
return super() if new_name.nil?
|
|
32
|
-
|
|
33
|
-
fail(
|
|
34
|
-
"The new name override method is `graphql_name`, not `name`. Usage: "\
|
|
35
|
-
"graphql_name \"#{new_name}\""
|
|
36
|
-
)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
29
|
# Call this method to provide a new description; OR
|
|
40
30
|
# call it without an argument to get the description
|
|
41
31
|
# @param new_description [String]
|
|
@@ -130,7 +120,7 @@ module GraphQL
|
|
|
130
120
|
true
|
|
131
121
|
end
|
|
132
122
|
|
|
133
|
-
def default_relay
|
|
123
|
+
def default_relay?
|
|
134
124
|
false
|
|
135
125
|
end
|
|
136
126
|
|