graphql 2.5.11 → 2.5.23
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/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/async_dataloader.rb +22 -11
- data/lib/graphql/dataloader/null_dataloader.rb +48 -10
- data/lib/graphql/dataloader.rb +75 -23
- data/lib/graphql/date_encoding_error.rb +1 -1
- 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 +24 -18
- data/lib/graphql/execution/interpreter.rb +8 -22
- data/lib/graphql/execution/lazy.rb +1 -1
- data/lib/graphql/execution/multiplex.rb +1 -1
- data/lib/graphql/execution/next/field_resolve_step.rb +743 -0
- data/lib/graphql/execution/next/load_argument_step.rb +64 -0
- data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
- data/lib/graphql/execution/next/runner.rb +411 -0
- data/lib/graphql/execution/next/selections_step.rb +37 -0
- data/lib/graphql/execution/next.rb +72 -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.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 +4 -3
- data/lib/graphql/query/null_context.rb +9 -3
- data/lib/graphql/schema/argument.rb +12 -0
- data/lib/graphql/schema/build_from_definition.rb +10 -1
- data/lib/graphql/schema/directive.rb +22 -4
- data/lib/graphql/schema/field/connection_extension.rb +15 -35
- data/lib/graphql/schema/field/scope_extension.rb +22 -13
- data/lib/graphql/schema/field.rb +79 -48
- data/lib/graphql/schema/field_extension.rb +33 -0
- data/lib/graphql/schema/list.rb +1 -1
- data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
- 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/resolver.rb +60 -1
- data/lib/graphql/schema/subscription.rb +0 -2
- data/lib/graphql/schema/validator/required_validator.rb +33 -2
- data/lib/graphql/schema/visibility/profile.rb +68 -49
- data/lib/graphql/schema/visibility.rb +3 -3
- data/lib/graphql/schema/wrapper.rb +7 -1
- data/lib/graphql/schema.rb +53 -10
- 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/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 +25 -1
- data/lib/graphql/subscriptions/event.rb +1 -0
- data/lib/graphql/subscriptions.rb +21 -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 +209 -79
- data/lib/graphql/tracing/sentry_trace.rb +3 -1
- 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 +17 -3
|
@@ -29,6 +29,7 @@ module GraphQL
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
extend Forwardable
|
|
32
|
+
include Schema::Member::HasDataloader
|
|
32
33
|
|
|
33
34
|
# @return [Array<GraphQL::ExecutionError>] errors returned during execution
|
|
34
35
|
attr_reader :errors
|
|
@@ -111,15 +112,15 @@ module GraphQL
|
|
|
111
112
|
# Return this value to tell the runtime
|
|
112
113
|
# to exclude this field from the response altogether
|
|
113
114
|
def skip
|
|
114
|
-
GraphQL::Execution::
|
|
115
|
+
GraphQL::Execution::Skip.new
|
|
115
116
|
end
|
|
116
117
|
|
|
117
118
|
# Add error at query-level.
|
|
118
119
|
# @param error [GraphQL::ExecutionError] an execution error
|
|
119
120
|
# @return [void]
|
|
120
121
|
def add_error(error)
|
|
121
|
-
if !error.is_a?(
|
|
122
|
-
raise TypeError, "expected error to be a
|
|
122
|
+
if !error.is_a?(GraphQL::RuntimeError)
|
|
123
|
+
raise TypeError, "expected error to be a GraphQL::RuntimeError, but was #{error.class}"
|
|
123
124
|
end
|
|
124
125
|
errors << error
|
|
125
126
|
nil
|
|
@@ -4,7 +4,13 @@ module GraphQL
|
|
|
4
4
|
class Query
|
|
5
5
|
# This object can be `ctx` in places where there is no query
|
|
6
6
|
class NullContext < Context
|
|
7
|
-
|
|
7
|
+
def self.instance
|
|
8
|
+
@instance ||= self.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.instance=(new_inst)
|
|
12
|
+
@instance = new_inst
|
|
13
|
+
end
|
|
8
14
|
|
|
9
15
|
class NullQuery
|
|
10
16
|
def after_lazy(value)
|
|
@@ -20,10 +26,10 @@ module GraphQL
|
|
|
20
26
|
attr_reader :schema, :query, :warden, :dataloader
|
|
21
27
|
def_delegators GraphQL::EmptyObjects::EMPTY_HASH, :[], :fetch, :dig, :key?, :to_h
|
|
22
28
|
|
|
23
|
-
def initialize
|
|
29
|
+
def initialize(schema: NullSchema)
|
|
24
30
|
@query = NullQuery.new
|
|
25
31
|
@dataloader = GraphQL::Dataloader::NullDataloader.new
|
|
26
|
-
@schema =
|
|
32
|
+
@schema = schema
|
|
27
33
|
@warden = Schema::Warden::NullWarden.new(context: self, schema: @schema)
|
|
28
34
|
@types = @warden.visibility_profile
|
|
29
35
|
freeze
|
|
@@ -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
|
|
@@ -266,6 +266,8 @@ module GraphQL
|
|
|
266
266
|
build_scalar_type(definition, type_resolver, base_types[:scalar], default_resolve: default_resolve)
|
|
267
267
|
when GraphQL::Language::Nodes::InputObjectTypeDefinition
|
|
268
268
|
build_input_object_type(definition, type_resolver, base_types[:input_object])
|
|
269
|
+
when GraphQL::Language::Nodes::DirectiveDefinition
|
|
270
|
+
build_directive(definition, type_resolver)
|
|
269
271
|
end
|
|
270
272
|
end
|
|
271
273
|
|
|
@@ -510,6 +512,7 @@ module GraphQL
|
|
|
510
512
|
camelize: false,
|
|
511
513
|
directives: prepare_directives(field_definition, type_resolver),
|
|
512
514
|
resolver_method: resolve_method_name,
|
|
515
|
+
resolve_batch: resolve_method_name,
|
|
513
516
|
)
|
|
514
517
|
|
|
515
518
|
builder.build_arguments(schema_field_defn, field_definition.arguments, type_resolver)
|
|
@@ -526,6 +529,12 @@ module GraphQL
|
|
|
526
529
|
field_instance = context.types.field(owner, field_name)
|
|
527
530
|
context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
|
|
528
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
|
+
}
|
|
529
538
|
end
|
|
530
539
|
|
|
531
540
|
def build_resolve_type(lookup_hash, directives, missing_type_handler)
|
|
@@ -544,7 +553,7 @@ module GraphQL
|
|
|
544
553
|
when GraphQL::Language::Nodes::ListType
|
|
545
554
|
resolve_type_proc.call(ast_node.of_type).to_list_type
|
|
546
555
|
when String
|
|
547
|
-
directives[ast_node]
|
|
556
|
+
directives[ast_node] ||= missing_type_handler.call(ast_node)
|
|
548
557
|
else
|
|
549
558
|
raise "Unexpected ast_node: #{ast_node.inspect}"
|
|
550
559
|
end
|
|
@@ -129,11 +129,29 @@ module GraphQL
|
|
|
129
129
|
# not runtime arguments.
|
|
130
130
|
context = Query::NullContext.instance
|
|
131
131
|
self.class.all_argument_definitions.each do |arg_defn|
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
keyword = arg_defn.keyword
|
|
133
|
+
arg_type = arg_defn.type
|
|
134
|
+
if arguments.key?(keyword)
|
|
135
|
+
value = arguments[keyword]
|
|
134
136
|
# This is a Ruby-land value; convert it to graphql for validation
|
|
135
137
|
graphql_value = begin
|
|
136
|
-
|
|
138
|
+
coerce_value = value
|
|
139
|
+
if arg_type.list? && (!coerce_value.nil?) && (!coerce_value.is_a?(Array))
|
|
140
|
+
# When validating inputs, GraphQL accepts a single item
|
|
141
|
+
# and implicitly converts it to a one-item list.
|
|
142
|
+
# However, we're using result coercion here to go from Ruby value
|
|
143
|
+
# to GraphQL value, so it doesn't have that feature.
|
|
144
|
+
# Keep the GraphQL-type behavior but implement it manually:
|
|
145
|
+
wrap_type = arg_type
|
|
146
|
+
while wrap_type.list?
|
|
147
|
+
if wrap_type.non_null?
|
|
148
|
+
wrap_type = wrap_type.of_type
|
|
149
|
+
end
|
|
150
|
+
wrap_type = wrap_type.of_type
|
|
151
|
+
coerce_value = [coerce_value]
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
arg_type.coerce_isolated_result(coerce_value)
|
|
137
155
|
rescue GraphQL::Schema::Enum::UnresolvedValueError
|
|
138
156
|
# Let validation handle this
|
|
139
157
|
value
|
|
@@ -142,7 +160,7 @@ module GraphQL
|
|
|
142
160
|
value = graphql_value = nil
|
|
143
161
|
end
|
|
144
162
|
|
|
145
|
-
result =
|
|
163
|
+
result = arg_type.validate_input(graphql_value, context)
|
|
146
164
|
if !result.valid?
|
|
147
165
|
raise InvalidArgumentError, "@#{graphql_name}.#{arg_defn.graphql_name} on #{owner.path} is invalid (#{value.inspect}): #{result.problems.first["explanation"]}"
|
|
148
166
|
end
|
|
@@ -21,45 +21,25 @@ module GraphQL
|
|
|
21
21
|
yield(object, next_args, arguments)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
def resolve_next(objects:, arguments:, context:)
|
|
25
|
+
next_args = arguments.dup
|
|
26
|
+
next_args.delete(:first)
|
|
27
|
+
next_args.delete(:last)
|
|
28
|
+
next_args.delete(:before)
|
|
29
|
+
next_args.delete(:after)
|
|
30
|
+
yield(objects, next_args, arguments)
|
|
31
|
+
end
|
|
32
|
+
|
|
24
33
|
def after_resolve(value:, object:, arguments:, context:, memo:)
|
|
25
34
|
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
|
|
35
|
+
context.query.after_lazy(value) do |resolved_value|
|
|
36
|
+
context.schema.connections.populate_connection(field, object.object, resolved_value, original_arguments, context)
|
|
61
37
|
end
|
|
62
38
|
end
|
|
39
|
+
|
|
40
|
+
def after_resolve_next(**kwargs)
|
|
41
|
+
raise "This should never be called -- it's hardcoded in execution instead."
|
|
42
|
+
end
|
|
63
43
|
end
|
|
64
44
|
end
|
|
65
45
|
end
|
|
@@ -5,24 +5,33 @@ 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
|
|
31
|
+
|
|
32
|
+
def after_resolve_next(**kwargs)
|
|
33
|
+
raise "This should never be called -- it's hardcoded in execution instead."
|
|
34
|
+
end
|
|
26
35
|
end
|
|
27
36
|
end
|
|
28
37
|
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_next_mode = :resolve_static
|
|
274
|
+
@execution_next_mode_key = resolve_static == true ? @method_sym : resolve_static
|
|
275
|
+
elsif resolve_batch
|
|
276
|
+
@execution_next_mode = :resolve_batch
|
|
277
|
+
@execution_next_mode_key = resolve_batch == true ? @method_sym : resolve_batch
|
|
278
|
+
elsif resolve_each
|
|
279
|
+
@execution_next_mode = :resolve_each
|
|
280
|
+
@execution_next_mode_key = resolve_each == true ? @method_sym : resolve_each
|
|
281
|
+
elsif hash_key
|
|
282
|
+
@execution_next_mode = :hash_key
|
|
283
|
+
@execution_next_mode_key = hash_key
|
|
284
|
+
elsif dig
|
|
285
|
+
@execution_next_mode = :dig
|
|
286
|
+
@execution_next_mode_key = dig
|
|
287
|
+
elsif resolver_class
|
|
288
|
+
@execution_next_mode = :resolver_class
|
|
289
|
+
@execution_next_mode_key = resolver_class
|
|
290
|
+
elsif resolve_legacy_instance_method
|
|
291
|
+
@execution_next_mode = :resolve_legacy_instance_method
|
|
292
|
+
@execution_next_mode_key = resolve_legacy_instance_method == true ? @method_sym : resolve_legacy_instance_method
|
|
293
|
+
elsif dataload
|
|
294
|
+
@execution_next_mode = :dataload
|
|
295
|
+
@execution_next_mode_key = dataload
|
|
296
|
+
else
|
|
297
|
+
@execution_next_mode = :direct_send
|
|
298
|
+
@execution_next_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_next_mode_key, :execution_next_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_next(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
|
|
@@ -134,6 +134,24 @@ module GraphQL
|
|
|
134
134
|
yield(object, arguments, nil)
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
+
# Called before batch-resolving {#field}. It should either:
|
|
138
|
+
#
|
|
139
|
+
# - `yield` values to continue execution; OR
|
|
140
|
+
# - return something else to shortcut field execution.
|
|
141
|
+
#
|
|
142
|
+
# Whatever this method returns will be used for execution.
|
|
143
|
+
#
|
|
144
|
+
# @param objects [Array<Object>] The objects the field is being resolved on
|
|
145
|
+
# @param arguments [Hash] Ruby keyword arguments for resolving this field
|
|
146
|
+
# @param context [Query::Context] the context for this query
|
|
147
|
+
# @yieldparam objects [Array<Object>] The objects to continue resolving the field on. Length must be the same as passed-in `objects:`
|
|
148
|
+
# @yieldparam arguments [Hash] The keyword arguments to continue resolving with
|
|
149
|
+
# @yieldparam memo [Object] Any extension-specific value which will be passed to {#after_resolve} later
|
|
150
|
+
# @return [Array<Object>] The return value for this field, length matching passed-in `objects:`.
|
|
151
|
+
def resolve_next(objects:, arguments:, context:)
|
|
152
|
+
yield(objects, arguments, nil)
|
|
153
|
+
end
|
|
154
|
+
|
|
137
155
|
# Called after {#field} was resolved, and after any lazy values (like `Promise`s) were synced,
|
|
138
156
|
# but before the value was added to the GraphQL response.
|
|
139
157
|
#
|
|
@@ -148,6 +166,21 @@ module GraphQL
|
|
|
148
166
|
def after_resolve(object:, arguments:, context:, value:, memo:)
|
|
149
167
|
value
|
|
150
168
|
end
|
|
169
|
+
|
|
170
|
+
# Called after {#field} was batch-resolved, and after any lazy values (like `Promise`s) were synced,
|
|
171
|
+
# but before the value was added to the GraphQL response.
|
|
172
|
+
#
|
|
173
|
+
# Whatever this hook returns will be used as the return value.
|
|
174
|
+
#
|
|
175
|
+
# @param objects [Array<Object>] The objects the field is being resolved on
|
|
176
|
+
# @param arguments [Hash] Ruby keyword arguments for resolving this field
|
|
177
|
+
# @param context [Query::Context] the context for this query
|
|
178
|
+
# @param values [Array<Object>] Whatever the field returned, one for each of `objects`
|
|
179
|
+
# @param memo [Object] The third value yielded by {#resolve}, or `nil` if there wasn't one
|
|
180
|
+
# @return [Array<Object>] The return values for this field, length matching `objects:`.
|
|
181
|
+
def after_resolve_next(objects:, arguments:, context:, values:, memo:)
|
|
182
|
+
values
|
|
183
|
+
end
|
|
151
184
|
end
|
|
152
185
|
end
|
|
153
186
|
end
|
data/lib/graphql/schema/list.rb
CHANGED
|
@@ -14,29 +14,52 @@ module GraphQL
|
|
|
14
14
|
cls.extend(ClassConfigured)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
# @
|
|
18
|
-
# @
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
# @param arg_name [Symbol] The underscore-cased name of this argument, `name:` keyword also accepted
|
|
18
|
+
# @param type_expr The GraphQL type of this argument; `type:` keyword also accepted
|
|
19
|
+
# @param desc [String] Argument description, `description:` keyword also accepted
|
|
20
|
+
# @option kwargs [Boolean, :nullable] :required 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`.
|
|
21
|
+
# @option kwargs [String] :description Positional argument also accepted
|
|
22
|
+
# @option kwargs [Class, Array<Class>] :type Input type; positional argument also accepted
|
|
23
|
+
# @option kwargs [Symbol] :name positional argument also accepted
|
|
24
|
+
# @option kwargs [Object] :default_value
|
|
25
|
+
# @option kwargs [Class, Array<Class>] :loads A GraphQL type to load for the given ID when one is present
|
|
26
|
+
# @option kwargs [Symbol] :as Override the keyword name when passed to a method
|
|
27
|
+
# @option kwargs [Symbol] :prepare A method to call to transform this argument's valuebefore sending it to field resolution
|
|
28
|
+
# @option kwargs [Boolean] :camelize if true, the name will be camelized when building the schema
|
|
29
|
+
# @option kwargs [Boolean] :from_resolver if true, a Resolver class defined this argument
|
|
30
|
+
# @option kwargs [Hash{Class => Hash}] :directives
|
|
31
|
+
# @option kwargs [String] :deprecation_reason
|
|
32
|
+
# @option kwargs [String] :comment Private, used by GraphQL-Ruby when parsing GraphQL schema files
|
|
33
|
+
# @option kwargs [GraphQL::Language::Nodes::InputValueDefinition] :ast_node Private, used by GraphQL-Ruby when parsing schema files
|
|
34
|
+
# @option kwargs [Hash, nil] :validates Options for building validators, if any should be applied
|
|
35
|
+
# @option kwargs [Boolean] :replace_null_with_default if `true`, incoming values of `null` will be replaced with the configured `default_value`
|
|
36
|
+
# @param definition_block [Proc] Called with the newly-created {Argument}
|
|
37
|
+
# @param kwargs [Hash] Keywords for defining an argument. Any keywords not documented here must be handled by your base Argument class.
|
|
38
|
+
# @return [GraphQL::Schema::Argument] An instance of {argument_class} created from these arguments
|
|
39
|
+
def argument(arg_name = nil, type_expr = nil, desc = nil, **kwargs, &definition_block)
|
|
40
|
+
if kwargs[:loads]
|
|
41
|
+
loads_name = arg_name || kwargs[:name]
|
|
42
|
+
loads_name_as_string = loads_name.to_s
|
|
43
|
+
|
|
44
|
+
inferred_arg_name = case loads_name_as_string
|
|
27
45
|
when /_id$/
|
|
28
|
-
|
|
46
|
+
loads_name_as_string.sub(/_id$/, "").to_sym
|
|
29
47
|
when /_ids$/
|
|
30
|
-
|
|
48
|
+
loads_name_as_string.sub(/_ids$/, "")
|
|
31
49
|
.sub(/([^s])$/, "\\1s")
|
|
32
50
|
.to_sym
|
|
33
51
|
else
|
|
34
|
-
|
|
52
|
+
loads_name
|
|
35
53
|
end
|
|
36
54
|
|
|
37
55
|
kwargs[:as] ||= inferred_arg_name
|
|
38
56
|
end
|
|
39
|
-
|
|
57
|
+
kwargs[:owner] = self
|
|
58
|
+
arg_defn = self.argument_class.new(
|
|
59
|
+
arg_name, type_expr, desc,
|
|
60
|
+
**kwargs,
|
|
61
|
+
&definition_block
|
|
62
|
+
)
|
|
40
63
|
add_argument(arg_defn)
|
|
41
64
|
arg_defn
|
|
42
65
|
end
|
|
@@ -413,6 +436,12 @@ module GraphQL
|
|
|
413
436
|
end
|
|
414
437
|
end
|
|
415
438
|
|
|
439
|
+
# Called when an argument's `loads:` configuration fails to fetch an application object.
|
|
440
|
+
# By default, this method raises the given error, but you can override it to handle failures differently.
|
|
441
|
+
#
|
|
442
|
+
# @param err [GraphQL::LoadApplicationObjectFailedError] The error that occurred
|
|
443
|
+
# @return [Object, nil] If a value is returned, it will be used instead of the failed load
|
|
444
|
+
# @api public
|
|
416
445
|
def load_application_object_failed(err)
|
|
417
446
|
raise err
|
|
418
447
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphQL
|
|
3
|
+
class Schema
|
|
4
|
+
class Member
|
|
5
|
+
module HasAuthorization
|
|
6
|
+
def self.included(child_class)
|
|
7
|
+
child_class.include(InstanceConfigured)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.extended(child_class)
|
|
11
|
+
child_class.extend(ClassConfigured)
|
|
12
|
+
child_class.class_exec do
|
|
13
|
+
@authorizes = false
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def authorized?(object, context)
|
|
18
|
+
true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
module InstanceConfigured
|
|
22
|
+
def authorizes?(context)
|
|
23
|
+
raise RequiredImplementationMissingError, "#{self.class} must implement #authorizes?(context)"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module ClassConfigured
|
|
28
|
+
def authorizes?(context)
|
|
29
|
+
method(:authorized?).owner != GraphQL::Schema::Member::HasAuthorization
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|