graphql 2.5.18 → 2.5.20
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.rb +9 -74
- data/lib/graphql/dataloader/null_dataloader.rb +7 -3
- data/lib/graphql/execution/batching/field_compatibility.rb +150 -0
- data/lib/graphql/execution/batching/field_resolve_step.rb +408 -0
- data/lib/graphql/execution/batching/prepare_object_step.rb +112 -0
- data/lib/graphql/execution/batching/runner.rb +352 -0
- data/lib/graphql/execution/batching/selections_step.rb +37 -0
- data/lib/graphql/execution/batching.rb +62 -0
- data/lib/graphql/execution_error.rb +13 -10
- data/lib/graphql/introspection/directive_type.rb +7 -3
- data/lib/graphql/introspection/entry_points.rb +6 -2
- 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/query/context.rb +3 -2
- data/lib/graphql/query/null_context.rb +9 -3
- data/lib/graphql/schema/argument.rb +4 -0
- data/lib/graphql/schema/build_from_definition.rb +26 -6
- data/lib/graphql/schema/field.rb +76 -1
- data/lib/graphql/schema/member/has_dataloader.rb +9 -0
- data/lib/graphql/schema/member/has_fields.rb +3 -0
- data/lib/graphql/schema/resolver.rb +3 -1
- data/lib/graphql/schema/visibility.rb +1 -1
- data/lib/graphql/schema.rb +33 -7
- data/lib/graphql/subscriptions.rb +1 -1
- data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
- data/lib/graphql/tracing/detailed_trace.rb +20 -5
- data/lib/graphql/tracing/perfetto_trace.rb +48 -2
- 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 +5 -8
- data/lib/graphql/types/relay/has_nodes_field.rb +5 -8
- data/lib/graphql/unauthorized_error.rb +5 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +3 -0
- metadata +14 -2
|
@@ -9,53 +9,61 @@ module GraphQL
|
|
|
9
9
|
field :name, String, null: false
|
|
10
10
|
field :description, String
|
|
11
11
|
field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false
|
|
12
|
-
field :default_value, String, "A GraphQL-formatted string representing the default value for this input value."
|
|
13
|
-
field :is_deprecated, Boolean, null: false
|
|
12
|
+
field :default_value, String, "A GraphQL-formatted string representing the default value for this input value.", resolve_each: :resolve_default_value
|
|
13
|
+
field :is_deprecated, Boolean, null: false, resolve_each: :resolve_is_deprecated
|
|
14
14
|
field :deprecation_reason, String
|
|
15
15
|
|
|
16
|
+
def self.resolve_is_deprecated(object, context)
|
|
17
|
+
!!object.deprecation_reason
|
|
18
|
+
end
|
|
19
|
+
|
|
16
20
|
def is_deprecated
|
|
17
|
-
|
|
21
|
+
self.class.resolve_is_deprecated(object, context)
|
|
18
22
|
end
|
|
19
23
|
|
|
20
|
-
def
|
|
21
|
-
if
|
|
22
|
-
value =
|
|
24
|
+
def self.resolve_default_value(object, context)
|
|
25
|
+
if object.default_value?
|
|
26
|
+
value = object.default_value
|
|
23
27
|
if value.nil?
|
|
24
28
|
'null'
|
|
25
29
|
else
|
|
26
|
-
if (
|
|
30
|
+
if (object.type.kind.list? || (object.type.kind.non_null? && object.type.of_type.kind.list?)) && !value.respond_to?(:map)
|
|
27
31
|
# This is a bit odd -- we expect the default value to be an application-style value, so we use coerce result below.
|
|
28
32
|
# But coerce_result doesn't wrap single-item lists, which are valid inputs to list types.
|
|
29
33
|
# So, apply that wrapper here if needed.
|
|
30
34
|
value = [value]
|
|
31
35
|
end
|
|
32
|
-
coerced_default_value =
|
|
33
|
-
serialize_default_value(coerced_default_value,
|
|
36
|
+
coerced_default_value = object.type.coerce_result(value, context)
|
|
37
|
+
serialize_default_value(coerced_default_value, object.type, context)
|
|
34
38
|
end
|
|
35
39
|
else
|
|
36
40
|
nil
|
|
37
41
|
end
|
|
38
42
|
end
|
|
39
43
|
|
|
44
|
+
def default_value
|
|
45
|
+
self.class.resolve_default_value(object, context)
|
|
46
|
+
end
|
|
47
|
+
|
|
40
48
|
|
|
41
49
|
private
|
|
42
50
|
|
|
43
51
|
# Recursively serialize, taking care not to add quotes to enum values
|
|
44
|
-
def serialize_default_value(value, type)
|
|
52
|
+
def self.serialize_default_value(value, type, context)
|
|
45
53
|
if value.nil?
|
|
46
54
|
'null'
|
|
47
55
|
elsif type.kind.list?
|
|
48
56
|
inner_type = type.of_type
|
|
49
|
-
"[" + value.map { |v| serialize_default_value(v, inner_type) }.join(", ") + "]"
|
|
57
|
+
"[" + value.map { |v| serialize_default_value(v, inner_type, context) }.join(", ") + "]"
|
|
50
58
|
elsif type.kind.non_null?
|
|
51
|
-
serialize_default_value(value, type.of_type)
|
|
59
|
+
serialize_default_value(value, type.of_type, context)
|
|
52
60
|
elsif type.kind.enum?
|
|
53
61
|
value
|
|
54
62
|
elsif type.kind.input_object?
|
|
55
63
|
"{" +
|
|
56
64
|
value.map do |k, v|
|
|
57
65
|
arg_defn = type.get_argument(k, context)
|
|
58
|
-
"#{k}: #{serialize_default_value(v, arg_defn.type)}"
|
|
66
|
+
"#{k}: #{serialize_default_value(v, arg_defn.type, context)}"
|
|
59
67
|
end.join(", ") +
|
|
60
68
|
"}"
|
|
61
69
|
else
|
|
@@ -11,32 +11,36 @@ module GraphQL
|
|
|
11
11
|
"they describe. Abstract types, Union and Interface, provide the Object types "\
|
|
12
12
|
"possible at runtime. List and NonNull types compose other types."
|
|
13
13
|
|
|
14
|
-
field :kind, GraphQL::Schema::LateBoundType.new("__TypeKind"), null: false
|
|
14
|
+
field :kind, GraphQL::Schema::LateBoundType.new("__TypeKind"), null: false, resolve_each: :resolve_kind
|
|
15
15
|
field :name, String, method: :graphql_name
|
|
16
16
|
field :description, String
|
|
17
|
-
field :fields, [GraphQL::Schema::LateBoundType.new("__Field")], scope: false do
|
|
17
|
+
field :fields, [GraphQL::Schema::LateBoundType.new("__Field")], scope: false, resolve_each: :resolve_fields do
|
|
18
18
|
argument :include_deprecated, Boolean, required: false, default_value: false
|
|
19
19
|
end
|
|
20
|
-
field :interfaces, [GraphQL::Schema::LateBoundType.new("__Type")], scope: false
|
|
21
|
-
field :possible_types, [GraphQL::Schema::LateBoundType.new("__Type")], scope: false
|
|
22
|
-
field :enum_values, [GraphQL::Schema::LateBoundType.new("__EnumValue")], scope: false do
|
|
20
|
+
field :interfaces, [GraphQL::Schema::LateBoundType.new("__Type")], scope: false, resolve_each: :resolve_interfaces
|
|
21
|
+
field :possible_types, [GraphQL::Schema::LateBoundType.new("__Type")], scope: false, resolve_each: :resolve_possible_types
|
|
22
|
+
field :enum_values, [GraphQL::Schema::LateBoundType.new("__EnumValue")], scope: false, resolve_each: :resolve_enum_values do
|
|
23
23
|
argument :include_deprecated, Boolean, required: false, default_value: false
|
|
24
24
|
end
|
|
25
|
-
field :input_fields, [GraphQL::Schema::LateBoundType.new("__InputValue")], scope: false do
|
|
25
|
+
field :input_fields, [GraphQL::Schema::LateBoundType.new("__InputValue")], scope: false, resolve_each: :resolve_input_fields do
|
|
26
26
|
argument :include_deprecated, Boolean, required: false, default_value: false
|
|
27
27
|
end
|
|
28
|
-
field :of_type, GraphQL::Schema::LateBoundType.new("__Type")
|
|
28
|
+
field :of_type, GraphQL::Schema::LateBoundType.new("__Type"), resolve_each: :resolve_of_type
|
|
29
29
|
|
|
30
|
-
field :specifiedByURL, String, resolver_method: :specified_by_url
|
|
30
|
+
field :specifiedByURL, String, resolve_each: :resolve_specified_by_url, resolver_method: :specified_by_url
|
|
31
31
|
|
|
32
|
-
field :is_one_of, Boolean, null: false
|
|
32
|
+
field :is_one_of, Boolean, null: false, resolve_each: :resolve_is_one_of
|
|
33
33
|
|
|
34
|
-
def
|
|
34
|
+
def self.resolve_is_one_of(object, _ctx)
|
|
35
35
|
object.kind.input_object? &&
|
|
36
36
|
object.directives.any? { |d| d.graphql_name == "oneOf" }
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def
|
|
39
|
+
def is_one_of
|
|
40
|
+
self.class.resolve_is_one_of(object, context)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.resolve_specified_by_url(object, _ctx)
|
|
40
44
|
if object.kind.scalar?
|
|
41
45
|
object.specified_by_url
|
|
42
46
|
else
|
|
@@ -44,15 +48,23 @@ module GraphQL
|
|
|
44
48
|
end
|
|
45
49
|
end
|
|
46
50
|
|
|
51
|
+
def specified_by_url
|
|
52
|
+
self.class.resolve_specified_by_url(object, context)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.resolve_kind(object, context)
|
|
56
|
+
object.kind.name
|
|
57
|
+
end
|
|
58
|
+
|
|
47
59
|
def kind
|
|
48
|
-
|
|
60
|
+
self.class.resolve_kind(object, context)
|
|
49
61
|
end
|
|
50
62
|
|
|
51
|
-
def
|
|
52
|
-
if
|
|
63
|
+
def self.resolve_enum_values(object, context, include_deprecated:)
|
|
64
|
+
if !object.kind.enum?
|
|
53
65
|
nil
|
|
54
66
|
else
|
|
55
|
-
enum_values =
|
|
67
|
+
enum_values = context.types.enum_values(object)
|
|
56
68
|
|
|
57
69
|
if !include_deprecated
|
|
58
70
|
enum_values = enum_values.select {|f| !f.deprecation_reason }
|
|
@@ -62,17 +74,25 @@ module GraphQL
|
|
|
62
74
|
end
|
|
63
75
|
end
|
|
64
76
|
|
|
65
|
-
def
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
def enum_values(include_deprecated:)
|
|
78
|
+
self.class.resolve_enum_values(object, context, include_deprecated: include_deprecated)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def self.resolve_interfaces(object, context)
|
|
82
|
+
if object.kind.object? || object.kind.interface?
|
|
83
|
+
context.types.interfaces(object).sort_by(&:graphql_name)
|
|
68
84
|
else
|
|
69
85
|
nil
|
|
70
86
|
end
|
|
71
87
|
end
|
|
72
88
|
|
|
73
|
-
def
|
|
74
|
-
|
|
75
|
-
|
|
89
|
+
def interfaces
|
|
90
|
+
self.class.resolve_interfaces(object, context)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def self.resolve_input_fields(object, context, include_deprecated:)
|
|
94
|
+
if object.kind.input_object?
|
|
95
|
+
args = context.types.arguments(object)
|
|
76
96
|
args = args.reject(&:deprecation_reason) unless include_deprecated
|
|
77
97
|
args
|
|
78
98
|
else
|
|
@@ -80,19 +100,27 @@ module GraphQL
|
|
|
80
100
|
end
|
|
81
101
|
end
|
|
82
102
|
|
|
83
|
-
def
|
|
84
|
-
|
|
85
|
-
|
|
103
|
+
def input_fields(include_deprecated:)
|
|
104
|
+
self.class.resolve_input_fields(object, context, include_deprecated: include_deprecated)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def self.resolve_possible_types(object, context)
|
|
108
|
+
if object.kind.abstract?
|
|
109
|
+
context.types.possible_types(object).sort_by(&:graphql_name)
|
|
86
110
|
else
|
|
87
111
|
nil
|
|
88
112
|
end
|
|
89
113
|
end
|
|
90
114
|
|
|
91
|
-
def
|
|
92
|
-
|
|
115
|
+
def possible_types
|
|
116
|
+
self.class.resolve_possible_types(object, context)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def self.resolve_fields(object, context, include_deprecated:)
|
|
120
|
+
if !object.kind.fields?
|
|
93
121
|
nil
|
|
94
122
|
else
|
|
95
|
-
fields =
|
|
123
|
+
fields = context.types.fields(object)
|
|
96
124
|
if !include_deprecated
|
|
97
125
|
fields = fields.select {|f| !f.deprecation_reason }
|
|
98
126
|
end
|
|
@@ -100,8 +128,16 @@ module GraphQL
|
|
|
100
128
|
end
|
|
101
129
|
end
|
|
102
130
|
|
|
131
|
+
def fields(include_deprecated:)
|
|
132
|
+
self.class.resolve_fields(object, context, include_deprecated: include_deprecated)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def self.resolve_of_type(object, _ctx)
|
|
136
|
+
object.kind.wraps? ? object.of_type : nil
|
|
137
|
+
end
|
|
138
|
+
|
|
103
139
|
def of_type
|
|
104
|
-
|
|
140
|
+
self.class.resolve_of_type(object, context)
|
|
105
141
|
end
|
|
106
142
|
end
|
|
107
143
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
module GraphQL
|
|
3
3
|
# Raised automatically when a field's resolve function returns `nil`
|
|
4
4
|
# for a non-null field.
|
|
5
|
-
class InvalidNullError < GraphQL::
|
|
5
|
+
class InvalidNullError < GraphQL::RuntimeError
|
|
6
6
|
# @return [GraphQL::BaseType] The owner of {#field}
|
|
7
7
|
attr_reader :parent_type
|
|
8
8
|
|
|
@@ -10,17 +10,23 @@ module GraphQL
|
|
|
10
10
|
attr_reader :field
|
|
11
11
|
|
|
12
12
|
# @return [GraphQL::Language::Nodes::Field] the field where the error occurred
|
|
13
|
-
|
|
13
|
+
def ast_node
|
|
14
|
+
@ast_nodes.first
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_reader :ast_nodes
|
|
14
18
|
|
|
15
19
|
# @return [Boolean] indicates an array result caused the error
|
|
16
20
|
attr_reader :is_from_array
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
attr_accessor :path
|
|
23
|
+
|
|
24
|
+
def initialize(parent_type, field, ast_node_or_nodes, is_from_array: false, path: nil)
|
|
19
25
|
@parent_type = parent_type
|
|
20
26
|
@field = field
|
|
21
|
-
@
|
|
27
|
+
@ast_nodes = Array(ast_node_or_nodes)
|
|
22
28
|
@is_from_array = is_from_array
|
|
23
|
-
|
|
29
|
+
@path = path
|
|
24
30
|
# For List elements, identify the non-null error is for an
|
|
25
31
|
# element and the required element type so it's not ambiguous
|
|
26
32
|
# whether it was caused by a null instead of the list or a
|
|
@@ -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
|
|
@@ -118,8 +119,8 @@ module GraphQL
|
|
|
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
|
|
@@ -512,22 +512,42 @@ module GraphQL
|
|
|
512
512
|
camelize: false,
|
|
513
513
|
directives: prepare_directives(field_definition, type_resolver),
|
|
514
514
|
resolver_method: resolve_method_name,
|
|
515
|
+
resolve_batch: resolve_method_name,
|
|
515
516
|
)
|
|
516
517
|
|
|
517
518
|
builder.build_arguments(schema_field_defn, field_definition.arguments, type_resolver)
|
|
518
519
|
|
|
519
520
|
# Don't do this for interfaces
|
|
520
521
|
if default_resolve
|
|
521
|
-
define_field_resolve_method(owner, resolve_method_name, field_definition.name)
|
|
522
|
+
define_field_resolve_method(owner, resolve_method_name, field_definition.name, field_definition.arguments.empty?)
|
|
522
523
|
end
|
|
523
524
|
end
|
|
524
525
|
end
|
|
525
526
|
|
|
526
|
-
def define_field_resolve_method(owner, method_name, field_name)
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
527
|
+
def define_field_resolve_method(owner, method_name, field_name, empty_arguments)
|
|
528
|
+
if empty_arguments
|
|
529
|
+
owner.define_method(method_name) {
|
|
530
|
+
field_instance = context.types.field(owner, field_name)
|
|
531
|
+
context.schema.definition_default_resolve.call(self.class, field_instance, object, EmptyObjects::EMPTY_HASH, context)
|
|
532
|
+
}
|
|
533
|
+
owner.define_singleton_method(method_name) { |objects, context|
|
|
534
|
+
field_instance = context.types.field(owner, field_name)
|
|
535
|
+
objects.map do |object|
|
|
536
|
+
context.schema.definition_default_resolve.call(self, field_instance, object, EmptyObjects::EMPTY_HASH, context)
|
|
537
|
+
end
|
|
538
|
+
}
|
|
539
|
+
else
|
|
540
|
+
owner.define_method(method_name) { |**args|
|
|
541
|
+
field_instance = context.types.field(owner, field_name)
|
|
542
|
+
context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
|
|
543
|
+
}
|
|
544
|
+
owner.define_singleton_method(method_name) { |objects, context, **args|
|
|
545
|
+
field_instance = context.types.field(owner, field_name)
|
|
546
|
+
objects.map do |object|
|
|
547
|
+
context.schema.definition_default_resolve.call(self, field_instance, object, args, context)
|
|
548
|
+
end
|
|
549
|
+
}
|
|
550
|
+
end
|
|
531
551
|
end
|
|
532
552
|
|
|
533
553
|
def build_resolve_type(lookup_hash, directives, missing_type_handler)
|
data/lib/graphql/schema/field.rb
CHANGED
|
@@ -192,6 +192,9 @@ module GraphQL
|
|
|
192
192
|
# @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`)
|
|
193
193
|
# @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
|
|
194
194
|
# @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added.
|
|
195
|
+
# @param resolve_static [Symbol, nil] Used by {Schema.execute_batching} to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments`
|
|
196
|
+
# @param resolve_batch [Symbol, nil] Used by {Schema.execute_batching} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`.
|
|
197
|
+
# @param resolve_each [Symbol, nil] Used by {Schema.execute_batching} to get a value value for each item. Called on the owner type class with `object, context, **arguments`.
|
|
195
198
|
# @param max_page_size [Integer, nil] For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
|
|
196
199
|
# @param default_page_size [Integer, nil] For connections, the default number of items to return from this field, or `nil` to return unlimited results.
|
|
197
200
|
# @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
|
|
@@ -214,7 +217,7 @@ module GraphQL
|
|
|
214
217
|
# @param relay_nodes_field [Boolean] (Private, used by GraphQL-Ruby)
|
|
215
218
|
# @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
|
|
216
219
|
# @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}.
|
|
217
|
-
def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: 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, 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)
|
|
220
|
+
def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, 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, 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)
|
|
218
221
|
if name.nil?
|
|
219
222
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
|
220
223
|
end
|
|
@@ -262,6 +265,27 @@ module GraphQL
|
|
|
262
265
|
@method_str = -method_name.to_s
|
|
263
266
|
@method_sym = method_name.to_sym
|
|
264
267
|
@resolver_method = (resolver_method || name_s).to_sym
|
|
268
|
+
|
|
269
|
+
if resolve_static
|
|
270
|
+
@batch_mode = :resolve_static
|
|
271
|
+
@batch_mode_key = resolve_static == true ? @method_sym : resolve_static
|
|
272
|
+
elsif resolve_batch
|
|
273
|
+
@batch_mode = :resolve_batch
|
|
274
|
+
@batch_mode_key = resolve_batch == true ? @method_sym : resolve_batch
|
|
275
|
+
elsif resolve_each
|
|
276
|
+
@batch_mode = :resolve_each
|
|
277
|
+
@batch_mode_key = resolve_each == true ? @method_sym : resolve_each
|
|
278
|
+
elsif hash_key
|
|
279
|
+
@batch_mode = :hash_key
|
|
280
|
+
@batch_mode_key = hash_key
|
|
281
|
+
elsif dig
|
|
282
|
+
@batch_mode = :dig
|
|
283
|
+
@batch_mode_key = dig
|
|
284
|
+
else
|
|
285
|
+
@batch_mode = :direct_send
|
|
286
|
+
@batch_mode_key = @method_sym
|
|
287
|
+
end
|
|
288
|
+
|
|
265
289
|
@complexity = complexity
|
|
266
290
|
@dynamic_introspection = dynamic_introspection
|
|
267
291
|
@return_type_expr = type
|
|
@@ -332,6 +356,50 @@ module GraphQL
|
|
|
332
356
|
end
|
|
333
357
|
end
|
|
334
358
|
|
|
359
|
+
# Called by {Execution::Batching} to resolve this field for each of `objects`
|
|
360
|
+
# @param field_resolve_step [Execution::Batching::FieldResolveStep] an internal metadata object from execution code
|
|
361
|
+
# @param objects [Array<Object>] Objects returned from previously-executed fields
|
|
362
|
+
# @param context [GraphQL::Query::Context]
|
|
363
|
+
# @param args_hash [Hash<Symbol => Object>] Ruby-style arguments for this field
|
|
364
|
+
# @return [Array<Object>] One field result for each of `objects`; must have the same length as `objects`
|
|
365
|
+
# @see #initialize Use `resolve_static:`, `resolve_batch:`, `resolve_each:`, `hash_key:`, or `method:`
|
|
366
|
+
# @api private
|
|
367
|
+
def resolve_batch(field_resolve_step, objects, context, args_hash)
|
|
368
|
+
case @batch_mode
|
|
369
|
+
when :resolve_batch
|
|
370
|
+
if args_hash.empty?
|
|
371
|
+
@owner.public_send(@batch_mode_key, objects, context)
|
|
372
|
+
else
|
|
373
|
+
@owner.public_send(@batch_mode_key, objects, context, **args_hash)
|
|
374
|
+
end
|
|
375
|
+
when :resolve_static
|
|
376
|
+
result = if args_hash.empty?
|
|
377
|
+
@owner.public_send(@batch_mode_key, context)
|
|
378
|
+
else
|
|
379
|
+
@owner.public_send(@batch_mode_key, context, **args_hash)
|
|
380
|
+
end
|
|
381
|
+
Array.new(objects.size, result)
|
|
382
|
+
when :resolve_each
|
|
383
|
+
if args_hash.empty?
|
|
384
|
+
objects.map { |o| @owner.public_send(@batch_mode_key, o, context) }
|
|
385
|
+
else
|
|
386
|
+
objects.map { |o| @owner.public_send(@batch_mode_key, o, context, **args_hash) }
|
|
387
|
+
end
|
|
388
|
+
when :hash_key
|
|
389
|
+
objects.map { |o| o[@batch_mode_key] }
|
|
390
|
+
when :direct_send
|
|
391
|
+
if args_hash.empty?
|
|
392
|
+
objects.map { |o| o.public_send(@batch_mode_key) }
|
|
393
|
+
else
|
|
394
|
+
objects.map { |o| o.public_send(@batch_mode_key, **args_hash) }
|
|
395
|
+
end
|
|
396
|
+
when :dig
|
|
397
|
+
objects.map { |o| o.dig(*@batch_mode_key) }
|
|
398
|
+
else
|
|
399
|
+
raise "Batching execution for #{path} not implemented; provide `resolve_static:`, `resolve_batch:`, `hash_key:`, `method:`, or use a compatibility plug-in"
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
335
403
|
# Calls the definition block, if one was given.
|
|
336
404
|
# This is deferred so that references to the return type
|
|
337
405
|
# can be lazily evaluated, reducing Rails boot time.
|
|
@@ -625,6 +693,13 @@ module GraphQL
|
|
|
625
693
|
end
|
|
626
694
|
end
|
|
627
695
|
|
|
696
|
+
def authorizes?(context)
|
|
697
|
+
method(:authorized?).owner != GraphQL::Schema::Field || (
|
|
698
|
+
(args = context.types.arguments(self)) &&
|
|
699
|
+
(args.any? { |a| a.authorizes?(context) })
|
|
700
|
+
)
|
|
701
|
+
end
|
|
702
|
+
|
|
628
703
|
def authorized?(object, args, context)
|
|
629
704
|
if @resolver_class
|
|
630
705
|
# The resolver _instance_ will check itself during `resolve()`
|
|
@@ -20,6 +20,15 @@ module GraphQL
|
|
|
20
20
|
dataloader.with(source_class, *source_args).load(load_key)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
# A shortcut method for loading many keys from a source.
|
|
24
|
+
# Identical to `dataloader.with(source_class, *source_args).load_all(load_keys)`
|
|
25
|
+
# @param source_class [Class<GraphQL::Dataloader::Source>]
|
|
26
|
+
# @param source_args [Array<Object>] Any extra parameters defined in `source_class`'s `initialize` method
|
|
27
|
+
# @param load_keys [Array<Object>] The keys to look up using `def fetch`
|
|
28
|
+
def dataload_all(source_class, *source_args, load_keys)
|
|
29
|
+
dataloader.with(source_class, *source_args).load_all(load_keys)
|
|
30
|
+
end
|
|
31
|
+
|
|
23
32
|
# Find an object with ActiveRecord via {Dataloader::ActiveRecordSource}.
|
|
24
33
|
# @param model [Class<ActiveRecord::Base>]
|
|
25
34
|
# @param find_by_value [Object] Usually an `id`, might be another value if `find_by:` is also provided
|
|
@@ -20,6 +20,9 @@ module GraphQL
|
|
|
20
20
|
# @option kwargs [String, Symbol] :hash_key The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`)
|
|
21
21
|
# @option kwargs [Array<String, Symbol>] :dig The nested hash keys to lookup on the underlying hash to resolve this field using dig
|
|
22
22
|
# @option kwargs [Symbol] :resolver_method The method on the type to call to resolve this field (defaults to `name`)
|
|
23
|
+
# @option kwargs [Symbol] :resolve_static Used by {Schema.execute_batching} to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments`
|
|
24
|
+
# @option kwargs [Symbol] :resolve_batch Used by {Schema.execute_batching} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`.
|
|
25
|
+
# @option kwargs [Symbol] :resolve_each Used by {Schema.execute_batching} to get a value value for each item. Called on the owner type class with `object, context, **arguments`.
|
|
23
26
|
# @option kwargs [Boolean] :connection `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
|
|
24
27
|
# @option kwargs [Class] :connection_extension The extension to add, to implement connections. If `nil`, no extension is added.
|
|
25
28
|
# @option kwargs [Integer, nil] :max_page_size For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
|
|
@@ -46,7 +46,7 @@ module GraphQL
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
# @return [Object] The application object this field is being resolved on
|
|
49
|
-
|
|
49
|
+
attr_accessor :object
|
|
50
50
|
|
|
51
51
|
# @return [GraphQL::Query::Context]
|
|
52
52
|
attr_reader :context
|
|
@@ -54,6 +54,8 @@ module GraphQL
|
|
|
54
54
|
# @return [GraphQL::Schema::Field]
|
|
55
55
|
attr_reader :field
|
|
56
56
|
|
|
57
|
+
attr_writer :prepared_arguments
|
|
58
|
+
|
|
57
59
|
def arguments
|
|
58
60
|
@prepared_arguments || raise("Arguments have not been prepared yet, still waiting for #load_arguments to resolve. (Call `.arguments` later in the code.)")
|
|
59
61
|
end
|
|
@@ -191,7 +191,7 @@ module GraphQL
|
|
|
191
191
|
if refresh
|
|
192
192
|
@top_level_profile = nil
|
|
193
193
|
end
|
|
194
|
-
@top_level_profile ||= @schema.visibility_profile_class.new(context:
|
|
194
|
+
@top_level_profile ||= @schema.visibility_profile_class.new(context: @schema.null_context, schema: @schema, visibility: self)
|
|
195
195
|
end
|
|
196
196
|
|
|
197
197
|
private
|
data/lib/graphql/schema.rb
CHANGED
|
@@ -330,10 +330,16 @@ module GraphQL
|
|
|
330
330
|
find_inherited_value(:plugins, EMPTY_ARRAY) + own_plugins
|
|
331
331
|
end
|
|
332
332
|
|
|
333
|
+
attr_writer :null_context
|
|
334
|
+
|
|
335
|
+
def null_context
|
|
336
|
+
@null_context || GraphQL::Query::NullContext.instance
|
|
337
|
+
end
|
|
338
|
+
|
|
333
339
|
# Build a map of `{ name => type }` and return it
|
|
334
340
|
# @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
|
|
335
341
|
# @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
|
|
336
|
-
def types(context =
|
|
342
|
+
def types(context = null_context)
|
|
337
343
|
if use_visibility_profile?
|
|
338
344
|
types = Visibility::Profile.from_context(context, self)
|
|
339
345
|
return types.all_types_h
|
|
@@ -366,7 +372,7 @@ module GraphQL
|
|
|
366
372
|
# @param context [GraphQL::Query::Context] Used for filtering definitions at query-time
|
|
367
373
|
# @param use_visibility_profile Private, for migration to {Schema::Visibility}
|
|
368
374
|
# @return [Module, nil] A type, or nil if there's no type called `type_name`
|
|
369
|
-
def get_type(type_name, context =
|
|
375
|
+
def get_type(type_name, context = null_context, use_visibility_profile = use_visibility_profile?)
|
|
370
376
|
if use_visibility_profile
|
|
371
377
|
profile = Visibility::Profile.from_context(context, self)
|
|
372
378
|
return profile.type(type_name)
|
|
@@ -617,7 +623,7 @@ module GraphQL
|
|
|
617
623
|
# @param use_visibility_profile Private, for migration to {Schema::Visibility}
|
|
618
624
|
# @return [Hash<String, Module>] All possible types, if no `type` is given.
|
|
619
625
|
# @return [Array<Module>] Possible types for `type`, if it's given.
|
|
620
|
-
def possible_types(type = nil, context =
|
|
626
|
+
def possible_types(type = nil, context = null_context, use_visibility_profile = use_visibility_profile?)
|
|
621
627
|
if use_visibility_profile
|
|
622
628
|
if type
|
|
623
629
|
return Visibility::Profile.from_context(context, self).possible_types(type)
|
|
@@ -701,7 +707,7 @@ module GraphQL
|
|
|
701
707
|
GraphQL::Schema::TypeExpression.build_type(context.query.types, ast_node)
|
|
702
708
|
end
|
|
703
709
|
|
|
704
|
-
def get_field(type_or_name, field_name, context =
|
|
710
|
+
def get_field(type_or_name, field_name, context = null_context, use_visibility_profile = use_visibility_profile?)
|
|
705
711
|
if use_visibility_profile
|
|
706
712
|
profile = Visibility::Profile.from_context(context, self)
|
|
707
713
|
parent_type = case type_or_name
|
|
@@ -738,7 +744,7 @@ module GraphQL
|
|
|
738
744
|
end
|
|
739
745
|
end
|
|
740
746
|
|
|
741
|
-
def get_fields(type, context =
|
|
747
|
+
def get_fields(type, context = null_context)
|
|
742
748
|
type.fields(context)
|
|
743
749
|
end
|
|
744
750
|
|
|
@@ -1228,6 +1234,7 @@ module GraphQL
|
|
|
1228
1234
|
vis = self.visibility
|
|
1229
1235
|
child_class.visibility = vis.dup_for(child_class)
|
|
1230
1236
|
end
|
|
1237
|
+
child_class.null_context = Query::NullContext.new(schema: child_class)
|
|
1231
1238
|
super
|
|
1232
1239
|
end
|
|
1233
1240
|
|
|
@@ -1329,10 +1336,11 @@ module GraphQL
|
|
|
1329
1336
|
def type_error(type_error, context)
|
|
1330
1337
|
case type_error
|
|
1331
1338
|
when GraphQL::InvalidNullError
|
|
1332
|
-
execution_error = GraphQL::ExecutionError.new(type_error.message,
|
|
1333
|
-
execution_error.path = context[:current_path]
|
|
1339
|
+
execution_error = GraphQL::ExecutionError.new(type_error.message, ast_nodes: type_error.ast_nodes)
|
|
1340
|
+
execution_error.path = type_error.path || context[:current_path]
|
|
1334
1341
|
|
|
1335
1342
|
context.errors << execution_error
|
|
1343
|
+
execution_error
|
|
1336
1344
|
when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
|
|
1337
1345
|
raise type_error
|
|
1338
1346
|
when GraphQL::IntegerDecodingError
|
|
@@ -1354,6 +1362,24 @@ module GraphQL
|
|
|
1354
1362
|
lazy_methods.set(lazy_class, value_method)
|
|
1355
1363
|
end
|
|
1356
1364
|
|
|
1365
|
+
def uses_raw_value?
|
|
1366
|
+
!!@uses_raw_value
|
|
1367
|
+
end
|
|
1368
|
+
|
|
1369
|
+
def uses_raw_value(new_val)
|
|
1370
|
+
@uses_raw_value = new_val
|
|
1371
|
+
end
|
|
1372
|
+
|
|
1373
|
+
def resolves_lazies?
|
|
1374
|
+
lazy_method_count = 0
|
|
1375
|
+
lazy_methods.each do |k, v|
|
|
1376
|
+
if !v.nil?
|
|
1377
|
+
lazy_method_count += 1
|
|
1378
|
+
end
|
|
1379
|
+
end
|
|
1380
|
+
lazy_method_count > 2
|
|
1381
|
+
end
|
|
1382
|
+
|
|
1357
1383
|
def instrument(instrument_step, instrumenter, options = {})
|
|
1358
1384
|
warn <<~WARN
|
|
1359
1385
|
Schema.instrument is deprecated, use `trace_with` instead: https://graphql-ruby.org/queries/tracing.html"
|
|
@@ -80,7 +80,7 @@ module GraphQL
|
|
|
80
80
|
|
|
81
81
|
# Normalize symbol-keyed args to strings, try camelizing them
|
|
82
82
|
# Should this accept a real context somehow?
|
|
83
|
-
normalized_args = normalize_arguments(normalized_event_name, field, args,
|
|
83
|
+
normalized_args = normalize_arguments(normalized_event_name, field, args, @schema.null_context)
|
|
84
84
|
|
|
85
85
|
event = Subscriptions::Event.new(
|
|
86
86
|
name: normalized_event_name,
|