graphql 2.5.11 → 2.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
- data/lib/generators/graphql/field_extractor.rb +17 -1
- data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
- data/lib/generators/graphql/templates/schema.erb +2 -1
- data/lib/graphql/analysis/query_complexity.rb +31 -15
- data/lib/graphql/analysis.rb +20 -13
- data/lib/graphql/backtrace/table.rb +10 -1
- data/lib/graphql/current.rb +7 -1
- data/lib/graphql/dashboard/application_controller.rb +45 -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 +14 -73
- data/lib/graphql/dataloader/async_dataloader.rb +336 -60
- data/lib/graphql/dataloader/null_dataloader.rb +48 -10
- data/lib/graphql/dataloader/source.rb +32 -19
- data/lib/graphql/dataloader.rb +107 -39
- data/lib/graphql/date_encoding_error.rb +1 -1
- data/lib/graphql/execution/directive_checks.rb +2 -0
- data/lib/graphql/execution/field_resolve_step.rb +804 -0
- data/lib/graphql/execution/finalize.rb +229 -0
- data/lib/graphql/execution/input_values.rb +333 -0
- data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -0
- data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
- data/lib/graphql/execution/interpreter/resolve.rb +7 -13
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
- data/lib/graphql/execution/interpreter/runtime.rb +60 -40
- data/lib/graphql/execution/interpreter.rb +17 -23
- data/lib/graphql/execution/lazy.rb +1 -1
- data/lib/graphql/execution/load_argument_step.rb +102 -0
- data/lib/graphql/execution/multiplex.rb +1 -1
- data/lib/graphql/execution/next.rb +98 -0
- data/lib/graphql/execution/prepare_object_step.rb +151 -0
- data/lib/graphql/execution/resolve_type_step.rb +27 -0
- data/lib/graphql/execution/runner.rb +457 -0
- data/lib/graphql/execution/selections_step.rb +95 -0
- data/lib/graphql/execution.rb +15 -4
- data/lib/graphql/execution_error.rb +17 -12
- data/lib/graphql/float_decoding_error.rb +13 -0
- data/lib/graphql/float_encoding_error.rb +28 -0
- 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/schema_type.rb +6 -2
- data/lib/graphql/introspection/type_type.rb +64 -28
- data/lib/graphql/invalid_null_error.rb +11 -5
- data/lib/graphql/language/block_string.rb +6 -11
- data/lib/graphql/language/cache.rb +84 -14
- data/lib/graphql/language/document_from_schema_definition.rb +2 -1
- data/lib/graphql/language/lexer.rb +27 -17
- data/lib/graphql/language/nodes.rb +2 -1
- data/lib/graphql/language/parser.rb +1 -1
- data/lib/graphql/language.rb +32 -17
- data/lib/graphql/pagination/connection.rb +2 -0
- data/lib/graphql/pagination/connections.rb +30 -0
- data/lib/graphql/pagination/relation_connection.rb +10 -1
- data/lib/graphql/query/context.rb +10 -3
- data/lib/graphql/query/null_context.rb +9 -3
- data/lib/graphql/query/partial.rb +18 -3
- data/lib/graphql/query/variable_validation_error.rb +16 -1
- data/lib/graphql/query/variables.rb +1 -1
- data/lib/graphql/query.rb +12 -3
- data/lib/graphql/railtie.rb +2 -1
- data/lib/graphql/runtime_error.rb +6 -0
- data/lib/graphql/schema/argument.rb +13 -1
- data/lib/graphql/schema/build_from_definition.rb +28 -1
- data/lib/graphql/schema/directive/feature.rb +4 -0
- data/lib/graphql/schema/directive/transform.rb +20 -0
- data/lib/graphql/schema/directive.rb +48 -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 +79 -48
- data/lib/graphql/schema/field_extension.rb +11 -8
- data/lib/graphql/schema/has_single_input_argument.rb +24 -13
- data/lib/graphql/schema/input_object.rb +6 -2
- data/lib/graphql/schema/interface.rb +26 -0
- data/lib/graphql/schema/introspection_system.rb +6 -21
- 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.rb +5 -0
- data/lib/graphql/schema/non_null.rb +1 -1
- data/lib/graphql/schema/object.rb +1 -0
- data/lib/graphql/schema/printer.rb +1 -1
- data/lib/graphql/schema/ractor_shareable.rb +1 -0
- data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
- data/lib/graphql/schema/resolver.rb +76 -1
- data/lib/graphql/schema/subscription.rb +53 -10
- data/lib/graphql/schema/timeout.rb +2 -2
- data/lib/graphql/schema/validator/allow_blank_validator.rb +3 -3
- data/lib/graphql/schema/validator/allow_null_validator.rb +3 -3
- data/lib/graphql/schema/validator/exclusion_validator.rb +2 -2
- data/lib/graphql/schema/validator/format_validator.rb +3 -3
- data/lib/graphql/schema/validator/inclusion_validator.rb +2 -2
- data/lib/graphql/schema/validator/length_validator.rb +6 -6
- data/lib/graphql/schema/validator/numericality_validator.rb +19 -19
- data/lib/graphql/schema/validator/required_validator.rb +38 -5
- data/lib/graphql/schema/validator.rb +9 -0
- data/lib/graphql/schema/visibility/profile.rb +74 -53
- data/lib/graphql/schema/visibility/visit.rb +1 -1
- data/lib/graphql/schema/visibility.rb +32 -24
- data/lib/graphql/schema/wrapper.rb +11 -1
- data/lib/graphql/schema.rb +93 -27
- 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 +351 -248
- 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/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 +40 -10
- data/lib/graphql/subscriptions/serialize.rb +10 -4
- 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/perfetto_trace.rb +213 -81
- data/lib/graphql/tracing/sentry_trace.rb +3 -1
- data/lib/graphql/tracing/trace.rb +6 -0
- data/lib/graphql/types/float.rb +18 -4
- 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 +8 -2
- metadata +23 -59
data/lib/graphql/railtie.rb
CHANGED
|
@@ -15,7 +15,8 @@ module GraphQL
|
|
|
15
15
|
initializer("graphql.cache") do |app|
|
|
16
16
|
if config.graphql.parser_cache
|
|
17
17
|
Language::Parser.cache ||= Language::Cache.new(
|
|
18
|
-
app.root.join("tmp/cache/graphql")
|
|
18
|
+
app.root.join("tmp/cache/graphql"),
|
|
19
|
+
secret: app.secret_key_base
|
|
19
20
|
)
|
|
20
21
|
end
|
|
21
22
|
end
|
|
@@ -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)
|
|
@@ -86,7 +94,7 @@ module GraphQL
|
|
|
86
94
|
end
|
|
87
95
|
|
|
88
96
|
if required == :nullable
|
|
89
|
-
self.owner.validates(required: { argument:
|
|
97
|
+
self.owner.validates(required: { argument: @keyword })
|
|
90
98
|
end
|
|
91
99
|
|
|
92
100
|
if definition_block
|
|
@@ -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 || type.unwrap.authorizes?(context)
|
|
170
|
+
end
|
|
171
|
+
|
|
160
172
|
def authorized?(obj, value, ctx)
|
|
161
173
|
authorized_as_type?(obj, value, ctx, as_type: type)
|
|
162
174
|
end
|
|
@@ -82,6 +82,7 @@ module GraphQL
|
|
|
82
82
|
replace_late_bound_types_with_built_in(types)
|
|
83
83
|
|
|
84
84
|
schema_extensions = nil
|
|
85
|
+
definitions_by_name = nil
|
|
85
86
|
document.definitions.each do |definition|
|
|
86
87
|
case definition
|
|
87
88
|
when GraphQL::Language::Nodes::SchemaDefinition, GraphQL::Language::Nodes::DirectiveDefinition
|
|
@@ -99,6 +100,23 @@ module GraphQL
|
|
|
99
100
|
# It's possible that this was already loaded by the directives
|
|
100
101
|
prev_type = types[definition.name]
|
|
101
102
|
if prev_type.nil? || prev_type.is_a?(Schema::LateBoundType)
|
|
103
|
+
if definition.is_a?(GraphQL::Language::Nodes::ObjectTypeDefinition) || definition.is_a?(Language::Nodes::InterfaceTypeDefinition)
|
|
104
|
+
interface_names = definition.interfaces.map(&:name)
|
|
105
|
+
if !interface_names.empty?
|
|
106
|
+
definitions_by_name ||= document.definitions.each_with_object({}) do |d, by_name|
|
|
107
|
+
by_name[d.name] ||= d if d.respond_to?(:name)
|
|
108
|
+
end
|
|
109
|
+
transitive_names = interface_names.map { |n| definitions_by_name[n]&.interfaces&.map(&:name) }
|
|
110
|
+
transitive_names.flatten!
|
|
111
|
+
transitive_names.compact!
|
|
112
|
+
else
|
|
113
|
+
transitive_names = interface_names
|
|
114
|
+
end
|
|
115
|
+
if !(missing_transitive_interfaces = transitive_names - interface_names).empty?
|
|
116
|
+
raise GraphQL::Schema::InvalidDocumentError, "type #{definition.name} is missing one or more transitive interface names: #{missing_transitive_interfaces.join(", ")}. Add them to the type's `implements` list and try again."
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
102
120
|
types[definition.name] = build_definition_from_node(definition, type_resolver, default_resolve, base_types)
|
|
103
121
|
end
|
|
104
122
|
end
|
|
@@ -266,6 +284,8 @@ module GraphQL
|
|
|
266
284
|
build_scalar_type(definition, type_resolver, base_types[:scalar], default_resolve: default_resolve)
|
|
267
285
|
when GraphQL::Language::Nodes::InputObjectTypeDefinition
|
|
268
286
|
build_input_object_type(definition, type_resolver, base_types[:input_object])
|
|
287
|
+
when GraphQL::Language::Nodes::DirectiveDefinition
|
|
288
|
+
build_directive(definition, type_resolver)
|
|
269
289
|
end
|
|
270
290
|
end
|
|
271
291
|
|
|
@@ -510,6 +530,7 @@ module GraphQL
|
|
|
510
530
|
camelize: false,
|
|
511
531
|
directives: prepare_directives(field_definition, type_resolver),
|
|
512
532
|
resolver_method: resolve_method_name,
|
|
533
|
+
resolve_batch: resolve_method_name,
|
|
513
534
|
)
|
|
514
535
|
|
|
515
536
|
builder.build_arguments(schema_field_defn, field_definition.arguments, type_resolver)
|
|
@@ -526,6 +547,12 @@ module GraphQL
|
|
|
526
547
|
field_instance = context.types.field(owner, field_name)
|
|
527
548
|
context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
|
|
528
549
|
}
|
|
550
|
+
owner.define_singleton_method(method_name) { |objects, context, **args|
|
|
551
|
+
field_instance = context.types.field(owner, field_name)
|
|
552
|
+
objects.map do |object|
|
|
553
|
+
context.schema.definition_default_resolve.call(self, field_instance, object, args, context)
|
|
554
|
+
end
|
|
555
|
+
}
|
|
529
556
|
end
|
|
530
557
|
|
|
531
558
|
def build_resolve_type(lookup_hash, directives, missing_type_handler)
|
|
@@ -544,7 +571,7 @@ module GraphQL
|
|
|
544
571
|
when GraphQL::Language::Nodes::ListType
|
|
545
572
|
resolve_type_proc.call(ast_node.of_type).to_list_type
|
|
546
573
|
when String
|
|
547
|
-
directives[ast_node]
|
|
574
|
+
directives[ast_node] ||= missing_type_handler.call(ast_node)
|
|
548
575
|
else
|
|
549
576
|
raise "Unexpected ast_node: #{ast_node.inspect}"
|
|
550
577
|
end
|
|
@@ -60,6 +60,10 @@ module GraphQL
|
|
|
60
60
|
def self.enabled?(flag_name, object, context)
|
|
61
61
|
raise GraphQL::RequiredImplementationMissingError, "Implement `.enabled?(flag_name, object, context)` to return true or false for the feature flag (#{flag_name.inspect})"
|
|
62
62
|
end
|
|
63
|
+
|
|
64
|
+
def self.resolve_field(...); end
|
|
65
|
+
def self.resolve_fragment_spread(...); end
|
|
66
|
+
def self.resolve_inline_fragment(...); end
|
|
63
67
|
end
|
|
64
68
|
end
|
|
65
69
|
end
|
|
@@ -54,6 +54,26 @@ module GraphQL
|
|
|
54
54
|
nil
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
|
+
|
|
58
|
+
def self.resolve_field(ast_nodes, parent_type, field_defn, objects, arguments, context)
|
|
59
|
+
transform_name = arguments[:by]
|
|
60
|
+
if TRANSFORMS.include?(transform_name)
|
|
61
|
+
Transformer.new(transform_name)
|
|
62
|
+
else
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class Transformer
|
|
68
|
+
include Execution::PostProcessor
|
|
69
|
+
def initialize(transform)
|
|
70
|
+
@transform = transform
|
|
71
|
+
end
|
|
72
|
+
def after_resolve(field_results)
|
|
73
|
+
field_results.map! { |r| r.respond_to?(@transform) ? r.public_send(@transform) : r }
|
|
74
|
+
field_results
|
|
75
|
+
end
|
|
76
|
+
end
|
|
57
77
|
end
|
|
58
78
|
end
|
|
59
79
|
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,20 +141,41 @@ 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
|
|
140
170
|
end
|
|
171
|
+
elsif arg_defn.default_value?
|
|
172
|
+
value = arg_defn.default_value
|
|
173
|
+
graphql_value = arg_type.coerce_isolated_result(value) unless value.nil?
|
|
141
174
|
else
|
|
142
175
|
value = graphql_value = nil
|
|
143
176
|
end
|
|
144
177
|
|
|
145
|
-
result =
|
|
178
|
+
result = arg_type.validate_input(graphql_value, context)
|
|
146
179
|
if !result.valid?
|
|
147
180
|
raise InvalidArgumentError, "@#{graphql_name}.#{arg_defn.graphql_name} on #{owner.path} is invalid (#{value.inspect}): #{result.problems.first["explanation"]}"
|
|
148
181
|
end
|
|
@@ -159,13 +192,16 @@ module GraphQL
|
|
|
159
192
|
end
|
|
160
193
|
|
|
161
194
|
LOCATIONS = [
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
195
|
+
*(RUNTIME_LOCATIONS = [
|
|
196
|
+
QUERY = :QUERY,
|
|
197
|
+
MUTATION = :MUTATION,
|
|
198
|
+
SUBSCRIPTION = :SUBSCRIPTION,
|
|
199
|
+
FIELD = :FIELD,
|
|
200
|
+
FRAGMENT_DEFINITION = :FRAGMENT_DEFINITION,
|
|
201
|
+
FRAGMENT_SPREAD = :FRAGMENT_SPREAD,
|
|
202
|
+
INLINE_FRAGMENT = :INLINE_FRAGMENT,
|
|
203
|
+
VARIABLE_DEFINITION = :VARIABLE_DEFINITION,
|
|
204
|
+
]),
|
|
169
205
|
SCHEMA = :SCHEMA,
|
|
170
206
|
SCALAR = :SCALAR,
|
|
171
207
|
OBJECT = :OBJECT,
|
|
@@ -177,7 +213,6 @@ module GraphQL
|
|
|
177
213
|
ENUM_VALUE = :ENUM_VALUE,
|
|
178
214
|
INPUT_OBJECT = :INPUT_OBJECT,
|
|
179
215
|
INPUT_FIELD_DEFINITION = :INPUT_FIELD_DEFINITION,
|
|
180
|
-
VARIABLE_DEFINITION = :VARIABLE_DEFINITION,
|
|
181
216
|
]
|
|
182
217
|
|
|
183
218
|
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.
|
|
@@ -666,6 +664,12 @@ module GraphQL
|
|
|
666
664
|
end
|
|
667
665
|
end
|
|
668
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
|
+
|
|
669
673
|
def authorized?(object, args, context)
|
|
670
674
|
if @resolver_class
|
|
671
675
|
# The resolver _instance_ will check itself during `resolve()`
|
|
@@ -920,6 +924,33 @@ ERR
|
|
|
920
924
|
end
|
|
921
925
|
end
|
|
922
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
|
+
|
|
923
954
|
def run_extensions_before_resolve(obj, args, ctx, extended, idx: 0)
|
|
924
955
|
extension = @extensions[idx]
|
|
925
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
|