graphql 2.4.13 → 2.5.11
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/graphql/analysis/query_complexity.rb +87 -7
- data/lib/graphql/backtrace/table.rb +37 -14
- data/lib/graphql/current.rb +1 -1
- data/lib/graphql/dashboard/detailed_traces.rb +47 -0
- data/lib/graphql/dashboard/installable.rb +22 -0
- data/lib/graphql/dashboard/limiters.rb +93 -0
- data/lib/graphql/dashboard/operation_store.rb +199 -0
- data/lib/graphql/dashboard/statics/charts.min.css +1 -0
- data/lib/graphql/dashboard/statics/dashboard.css +27 -0
- data/lib/graphql/dashboard/statics/dashboard.js +74 -9
- data/lib/graphql/dashboard/subscriptions.rb +96 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
- data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +49 -1
- data/lib/graphql/dashboard.rb +45 -29
- data/lib/graphql/dataloader/active_record_association_source.rb +28 -8
- data/lib/graphql/dataloader/active_record_source.rb +26 -5
- data/lib/graphql/dataloader/null_dataloader.rb +7 -0
- data/lib/graphql/dataloader/source.rb +16 -4
- data/lib/graphql/dig.rb +2 -1
- data/lib/graphql/execution/interpreter/resolve.rb +3 -3
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +34 -1
- data/lib/graphql/execution/interpreter/runtime.rb +163 -59
- data/lib/graphql/execution/interpreter.rb +5 -13
- data/lib/graphql/execution/multiplex.rb +6 -1
- data/lib/graphql/invalid_null_error.rb +15 -2
- data/lib/graphql/language/lexer.rb +9 -2
- data/lib/graphql/language/nodes.rb +5 -1
- data/lib/graphql/language/parser.rb +14 -6
- data/lib/graphql/query/context.rb +3 -8
- data/lib/graphql/query/partial.rb +179 -0
- data/lib/graphql/query.rb +59 -55
- data/lib/graphql/schema/addition.rb +3 -1
- data/lib/graphql/schema/always_visible.rb +1 -0
- data/lib/graphql/schema/argument.rb +9 -3
- data/lib/graphql/schema/build_from_definition.rb +96 -47
- data/lib/graphql/schema/directive/flagged.rb +2 -0
- data/lib/graphql/schema/directive.rb +33 -1
- data/lib/graphql/schema/field.rb +23 -1
- data/lib/graphql/schema/input_object.rb +38 -30
- data/lib/graphql/schema/list.rb +1 -1
- data/lib/graphql/schema/member/has_arguments.rb +2 -2
- data/lib/graphql/schema/member/has_dataloader.rb +4 -2
- data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
- data/lib/graphql/schema/member/has_interfaces.rb +2 -2
- data/lib/graphql/schema/member/type_system_helpers.rb +16 -2
- data/lib/graphql/schema/ractor_shareable.rb +79 -0
- data/lib/graphql/schema/resolver.rb +1 -0
- data/lib/graphql/schema/scalar.rb +1 -6
- data/lib/graphql/schema/timeout.rb +19 -2
- data/lib/graphql/schema/validator/required_validator.rb +15 -6
- data/lib/graphql/schema/visibility/migration.rb +2 -2
- data/lib/graphql/schema/visibility/profile.rb +107 -21
- data/lib/graphql/schema/visibility.rb +41 -29
- data/lib/graphql/schema/warden.rb +13 -5
- data/lib/graphql/schema.rb +228 -32
- data/lib/graphql/static_validation/all_rules.rb +2 -2
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +78 -16
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
- data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
- data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
- data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
- data/lib/graphql/testing/helpers.rb +5 -2
- data/lib/graphql/tracing/active_support_notifications_trace.rb +7 -0
- data/lib/graphql/tracing/appoptics_tracing.rb +5 -0
- data/lib/graphql/tracing/appsignal_trace.rb +26 -61
- data/lib/graphql/tracing/data_dog_trace.rb +41 -164
- data/lib/graphql/tracing/monitor_trace.rb +283 -0
- data/lib/graphql/tracing/new_relic_trace.rb +34 -164
- data/lib/graphql/tracing/notifications_trace.rb +183 -37
- data/lib/graphql/tracing/null_trace.rb +1 -1
- data/lib/graphql/tracing/perfetto_trace.rb +16 -19
- data/lib/graphql/tracing/prometheus_trace.rb +47 -74
- data/lib/graphql/tracing/scout_trace.rb +25 -59
- data/lib/graphql/tracing/sentry_trace.rb +56 -99
- data/lib/graphql/tracing/statsd_trace.rb +24 -47
- data/lib/graphql/tracing/trace.rb +0 -17
- data/lib/graphql/tracing.rb +1 -0
- data/lib/graphql/type_kinds.rb +1 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +1 -1
- metadata +35 -26
- data/lib/graphql/dashboard/views/graphql/dashboard/traces/index.html.erb +0 -63
- data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -23,21 +23,22 @@ module GraphQL
|
|
23
23
|
# @return [Array<GraphQL::Query::Result>] One result per query
|
24
24
|
def run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
|
25
25
|
queries = query_options.map do |opts|
|
26
|
-
case opts
|
26
|
+
query = case opts
|
27
27
|
when Hash
|
28
28
|
schema.query_class.new(schema, nil, **opts)
|
29
|
-
when GraphQL::Query
|
29
|
+
when GraphQL::Query, GraphQL::Query::Partial
|
30
30
|
opts
|
31
31
|
else
|
32
32
|
raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})"
|
33
33
|
end
|
34
|
+
query
|
34
35
|
end
|
35
36
|
|
37
|
+
return GraphQL::EmptyObjects::EMPTY_ARRAY if queries.empty?
|
36
38
|
|
37
39
|
multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
|
38
|
-
Fiber[:__graphql_current_multiplex] = multiplex
|
39
40
|
trace = multiplex.current_trace
|
40
|
-
|
41
|
+
Fiber[:__graphql_current_multiplex] = multiplex
|
41
42
|
trace.execute_multiplex(multiplex: multiplex) do
|
42
43
|
schema = multiplex.schema
|
43
44
|
queries = multiplex.queries
|
@@ -92,13 +93,6 @@ module GraphQL
|
|
92
93
|
# Then, work through lazy results in a breadth-first way
|
93
94
|
multiplex.dataloader.append_job {
|
94
95
|
query = multiplex.queries.length == 1 ? multiplex.queries[0] : nil
|
95
|
-
queries = multiplex ? multiplex.queries : [query]
|
96
|
-
final_values = queries.map do |query|
|
97
|
-
runtime = query.context.namespace(:interpreter_runtime)[:runtime]
|
98
|
-
# it might not be present if the query has an error
|
99
|
-
runtime ? runtime.final_result : nil
|
100
|
-
end
|
101
|
-
final_values.compact!
|
102
96
|
multiplex.current_trace.execute_query_lazy(multiplex: multiplex, query: query) do
|
103
97
|
Interpreter::Resolve.resolve_each_depth(lazies_at_depth, multiplex.dataloader)
|
104
98
|
end
|
@@ -154,8 +148,6 @@ module GraphQL
|
|
154
148
|
}
|
155
149
|
end
|
156
150
|
end
|
157
|
-
ensure
|
158
|
-
trace&.end_execute_multiplex(multiplex)
|
159
151
|
end
|
160
152
|
end
|
161
153
|
|
@@ -32,10 +32,15 @@ module GraphQL
|
|
32
32
|
@queries = queries
|
33
33
|
@queries.each { |q| q.multiplex = self }
|
34
34
|
@context = context
|
35
|
-
@current_trace = @context[:trace] || schema.new_trace(multiplex: self)
|
36
35
|
@dataloader = @context[:dataloader] ||= @schema.dataloader_class.new
|
37
36
|
@tracers = schema.tracers + (context[:tracers] || [])
|
38
37
|
@max_complexity = max_complexity
|
38
|
+
@current_trace = context[:trace] ||= schema.new_trace(multiplex: self)
|
39
|
+
@logger = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def logger
|
43
|
+
@logger ||= @schema.logger_for(context)
|
39
44
|
end
|
40
45
|
end
|
41
46
|
end
|
@@ -12,11 +12,24 @@ module GraphQL
|
|
12
12
|
# @return [GraphQL::Language::Nodes::Field] the field where the error occurred
|
13
13
|
attr_reader :ast_node
|
14
14
|
|
15
|
-
|
15
|
+
# @return [Boolean] indicates an array result caused the error
|
16
|
+
attr_reader :is_from_array
|
17
|
+
|
18
|
+
def initialize(parent_type, field, ast_node, is_from_array: false)
|
16
19
|
@parent_type = parent_type
|
17
20
|
@field = field
|
18
21
|
@ast_node = ast_node
|
19
|
-
|
22
|
+
@is_from_array = is_from_array
|
23
|
+
|
24
|
+
# For List elements, identify the non-null error is for an
|
25
|
+
# element and the required element type so it's not ambiguous
|
26
|
+
# whether it was caused by a null instead of the list or a
|
27
|
+
# null element.
|
28
|
+
if @is_from_array
|
29
|
+
super("Cannot return null for non-nullable element of type '#{@field.type.of_type.of_type.to_type_signature}' for #{@parent_type.graphql_name}.#{@field.graphql_name}")
|
30
|
+
else
|
31
|
+
super("Cannot return null for non-nullable field #{@parent_type.graphql_name}.#{@field.graphql_name}")
|
32
|
+
end
|
20
33
|
end
|
21
34
|
|
22
35
|
class << self
|
@@ -20,6 +20,11 @@ module GraphQL
|
|
20
20
|
@finished
|
21
21
|
end
|
22
22
|
|
23
|
+
def freeze
|
24
|
+
@scanner = nil
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
23
28
|
attr_reader :pos, :tokens_count
|
24
29
|
|
25
30
|
def advance
|
@@ -242,7 +247,7 @@ module GraphQL
|
|
242
247
|
:SCALAR,
|
243
248
|
nil,
|
244
249
|
:FRAGMENT
|
245
|
-
]
|
250
|
+
].freeze
|
246
251
|
|
247
252
|
# This produces a unique integer for bytes 2 and 3 of each keyword string
|
248
253
|
# See https://tenderlovemaking.com/2023/09/02/fast-tokenizers-with-stringscanner.html
|
@@ -271,7 +276,8 @@ module GraphQL
|
|
271
276
|
PUNCTUATION_NAME_FOR_BYTE = Punctuation.constants.each_with_object([]) { |name, arr|
|
272
277
|
punct = Punctuation.const_get(name)
|
273
278
|
arr[punct.ord] = name
|
274
|
-
}
|
279
|
+
}.freeze
|
280
|
+
|
275
281
|
|
276
282
|
QUOTE = '"'
|
277
283
|
UNICODE_DIGIT = /[0-9A-Za-z]/
|
@@ -321,6 +327,7 @@ module GraphQL
|
|
321
327
|
punct = Punctuation.const_get(punct_name)
|
322
328
|
FIRST_BYTES[punct.ord] = ByteFor::PUNCTUATION
|
323
329
|
end
|
330
|
+
FIRST_BYTES.freeze
|
324
331
|
|
325
332
|
|
326
333
|
# Replace any escaped unicode or whitespace with the _actual_ characters
|
@@ -83,7 +83,11 @@ module GraphQL
|
|
83
83
|
|
84
84
|
def to_query_string(printer: GraphQL::Language::Printer.new)
|
85
85
|
if printer.is_a?(GraphQL::Language::Printer)
|
86
|
-
|
86
|
+
if frozen?
|
87
|
+
@query_string || printer.print(self)
|
88
|
+
else
|
89
|
+
@query_string ||= printer.print(self)
|
90
|
+
end
|
87
91
|
else
|
88
92
|
printer.print(self)
|
89
93
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "strscan"
|
4
4
|
require "graphql/language/nodes"
|
5
|
+
require "graphql/tracing/null_trace"
|
5
6
|
|
6
7
|
module GraphQL
|
7
8
|
module Language
|
@@ -488,26 +489,33 @@ module GraphQL
|
|
488
489
|
end
|
489
490
|
|
490
491
|
def type
|
491
|
-
|
492
|
+
parsed_type = case token_name
|
492
493
|
when :IDENTIFIER
|
493
494
|
parse_type_name
|
494
495
|
when :LBRACKET
|
495
496
|
list_type
|
497
|
+
else
|
498
|
+
nil
|
496
499
|
end
|
497
500
|
|
498
|
-
if at?(:BANG)
|
499
|
-
|
501
|
+
if at?(:BANG) && parsed_type
|
502
|
+
parsed_type = Nodes::NonNullType.new(pos: pos, of_type: parsed_type, source: self)
|
500
503
|
expect_token(:BANG)
|
501
504
|
end
|
502
|
-
|
505
|
+
parsed_type
|
503
506
|
end
|
504
507
|
|
505
508
|
def list_type
|
506
509
|
loc = pos
|
507
510
|
expect_token(:LBRACKET)
|
508
|
-
|
511
|
+
inner_type = self.type
|
512
|
+
parsed_list_type = if inner_type
|
513
|
+
Nodes::ListType.new(pos: loc, of_type: inner_type, source: self)
|
514
|
+
else
|
515
|
+
nil
|
516
|
+
end
|
509
517
|
expect_token(:RBRACKET)
|
510
|
-
|
518
|
+
parsed_list_type
|
511
519
|
end
|
512
520
|
|
513
521
|
def parse_operation_type
|
@@ -39,9 +39,6 @@ module GraphQL
|
|
39
39
|
# @return [GraphQL::Schema]
|
40
40
|
attr_reader :schema
|
41
41
|
|
42
|
-
# @return [Array<String, Integer>] The current position in the result
|
43
|
-
attr_reader :path
|
44
|
-
|
45
42
|
# Make a new context which delegates key lookup to `values`
|
46
43
|
# @param query [GraphQL::Query] the query who owns this context
|
47
44
|
# @param values [Hash] A hash of arbitrary values which will be accessible at query-time
|
@@ -53,12 +50,10 @@ module GraphQL
|
|
53
50
|
@storage = Hash.new { |h, k| h[k] = {} }
|
54
51
|
@storage[nil] = @provided_values
|
55
52
|
@errors = []
|
56
|
-
@path = []
|
57
|
-
@value = nil
|
58
|
-
@context = self # for SharedMethods TODO delete sharedmethods
|
59
53
|
@scoped_context = ScopedContext.new(self)
|
60
54
|
end
|
61
55
|
|
56
|
+
# Modify this hash to return extensions to client.
|
62
57
|
# @return [Hash] A hash that will be added verbatim to the result hash, as `"extensions" => { ... }`
|
63
58
|
def response_extensions
|
64
59
|
namespace(:__query_result_extensions__)
|
@@ -89,7 +84,7 @@ module GraphQL
|
|
89
84
|
|
90
85
|
attr_writer :types
|
91
86
|
|
92
|
-
RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path])
|
87
|
+
RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path]).freeze
|
93
88
|
# @!method []=(key, value)
|
94
89
|
# Reassign `key` to the hash passed to {Schema#execute} as `context:`
|
95
90
|
|
@@ -244,7 +239,7 @@ module GraphQL
|
|
244
239
|
end
|
245
240
|
|
246
241
|
def inspect
|
247
|
-
"
|
242
|
+
"#<#{self.class} ...>"
|
248
243
|
end
|
249
244
|
|
250
245
|
def scoped_merge!(hash)
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
class Query
|
4
|
+
# This class is _like_ a {GraphQL::Query}, except it can run on an arbitrary path within a query string.
|
5
|
+
#
|
6
|
+
# It depends on a "parent" {Query}.
|
7
|
+
#
|
8
|
+
# During execution, it calls query-related tracing hooks but passes itself as `query:`.
|
9
|
+
#
|
10
|
+
# The {Partial} will use your {Schema.resolve_type} hook to find the right GraphQL type to use for
|
11
|
+
# `object` in some cases.
|
12
|
+
#
|
13
|
+
# @see Query#run_partials Run via {Query#run_partials}
|
14
|
+
class Partial
|
15
|
+
include Query::Runnable
|
16
|
+
|
17
|
+
# @param path [Array<String, Integer>] A path in `query.query_string` to start executing from
|
18
|
+
# @param object [Object] A starting object for execution
|
19
|
+
# @param query [GraphQL::Query] A full query instance that this partial is based on. Caches are shared.
|
20
|
+
# @param context [Hash] Extra context values to merge into `query.context`, if provided
|
21
|
+
# @param fragment_node [GraphQL::Language::Nodes::InlineFragment, GraphQL::Language::Nodes::FragmentDefinition]
|
22
|
+
def initialize(path: nil, object:, query:, context: nil, fragment_node: nil, type: nil)
|
23
|
+
@path = path
|
24
|
+
@object = object
|
25
|
+
@query = query
|
26
|
+
@schema = query.schema
|
27
|
+
context_vals = @query.context.to_h
|
28
|
+
if context
|
29
|
+
context_vals = context_vals.merge(context)
|
30
|
+
end
|
31
|
+
@context = GraphQL::Query::Context.new(query: self, schema: @query.schema, values: context_vals)
|
32
|
+
@multiplex = nil
|
33
|
+
@result_values = nil
|
34
|
+
@result = nil
|
35
|
+
|
36
|
+
if fragment_node
|
37
|
+
@ast_nodes = [fragment_node]
|
38
|
+
@root_type = type || raise(ArgumentError, "Pass `type:` when using `node:`")
|
39
|
+
# This is only used when `@leaf`
|
40
|
+
@field_definition = nil
|
41
|
+
elsif path.nil?
|
42
|
+
raise ArgumentError, "`path:` is required if `node:` is not given; add `path:`"
|
43
|
+
else
|
44
|
+
set_type_info_from_path
|
45
|
+
end
|
46
|
+
|
47
|
+
@leaf = @root_type.unwrap.kind.leaf?
|
48
|
+
end
|
49
|
+
|
50
|
+
def leaf?
|
51
|
+
@leaf
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_reader :context, :query, :ast_nodes, :root_type, :object, :field_definition, :path, :schema
|
55
|
+
|
56
|
+
attr_accessor :multiplex, :result_values
|
57
|
+
|
58
|
+
class Result < GraphQL::Query::Result
|
59
|
+
def path
|
60
|
+
@query.path
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [GraphQL::Query::Partial]
|
64
|
+
def partial
|
65
|
+
@query
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def result
|
70
|
+
@result ||= Result.new(query: self, values: result_values)
|
71
|
+
end
|
72
|
+
|
73
|
+
def current_trace
|
74
|
+
@query.current_trace
|
75
|
+
end
|
76
|
+
|
77
|
+
def types
|
78
|
+
@query.types
|
79
|
+
end
|
80
|
+
|
81
|
+
def resolve_type(...)
|
82
|
+
@query.resolve_type(...)
|
83
|
+
end
|
84
|
+
|
85
|
+
def variables
|
86
|
+
@query.variables
|
87
|
+
end
|
88
|
+
|
89
|
+
def fragments
|
90
|
+
@query.fragments
|
91
|
+
end
|
92
|
+
|
93
|
+
def valid?
|
94
|
+
@query.valid?
|
95
|
+
end
|
96
|
+
|
97
|
+
def analyzers
|
98
|
+
EmptyObjects::EMPTY_ARRAY
|
99
|
+
end
|
100
|
+
|
101
|
+
def analysis_errors=(_ignored)
|
102
|
+
# pass
|
103
|
+
end
|
104
|
+
|
105
|
+
def subscription?
|
106
|
+
@query.subscription?
|
107
|
+
end
|
108
|
+
|
109
|
+
def selected_operation
|
110
|
+
ast_nodes.first
|
111
|
+
end
|
112
|
+
|
113
|
+
def static_errors
|
114
|
+
@query.static_errors
|
115
|
+
end
|
116
|
+
|
117
|
+
def selected_operation_name
|
118
|
+
@query.selected_operation_name
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def set_type_info_from_path
|
124
|
+
selections = [@query.selected_operation]
|
125
|
+
type = @query.root_type
|
126
|
+
parent_type = nil
|
127
|
+
field_defn = nil
|
128
|
+
|
129
|
+
@path.each do |name_in_doc|
|
130
|
+
if name_in_doc.is_a?(Integer)
|
131
|
+
if type.list?
|
132
|
+
type = type.unwrap
|
133
|
+
next
|
134
|
+
else
|
135
|
+
raise ArgumentError, "Received path with index `#{name_in_doc}`, but type wasn't a list. Type: #{type.to_type_signature}, path: #{@path}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
next_selections = []
|
140
|
+
selections.each do |selection|
|
141
|
+
selections_to_check = []
|
142
|
+
selections_to_check.concat(selection.selections)
|
143
|
+
while (sel = selections_to_check.shift)
|
144
|
+
case sel
|
145
|
+
when GraphQL::Language::Nodes::InlineFragment
|
146
|
+
selections_to_check.concat(sel.selections)
|
147
|
+
when GraphQL::Language::Nodes::FragmentSpread
|
148
|
+
fragment = @query.fragments[sel.name]
|
149
|
+
selections_to_check.concat(fragment.selections)
|
150
|
+
when GraphQL::Language::Nodes::Field
|
151
|
+
if sel.alias == name_in_doc || sel.name == name_in_doc
|
152
|
+
next_selections << sel
|
153
|
+
end
|
154
|
+
else
|
155
|
+
raise "Unexpected selection in partial path: #{sel.class}, #{sel.inspect}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
if next_selections.empty?
|
161
|
+
raise ArgumentError, "Path `#{@path.inspect}` is not present in this query. `#{name_in_doc.inspect}` was not found. Try a different path or rewrite the query to include it."
|
162
|
+
end
|
163
|
+
field_name = next_selections.first.name
|
164
|
+
field_defn = @schema.get_field(type, field_name, @query.context) || raise("Invariant: no field called #{field_name} on #{type.graphql_name}")
|
165
|
+
parent_type = type
|
166
|
+
type = field_defn.type
|
167
|
+
if type.non_null?
|
168
|
+
type = type.of_type
|
169
|
+
end
|
170
|
+
selections = next_selections
|
171
|
+
end
|
172
|
+
|
173
|
+
@ast_nodes = selections
|
174
|
+
@root_type = type
|
175
|
+
@field_definition = field_defn
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
data/lib/graphql/query.rb
CHANGED
@@ -10,12 +10,47 @@ module GraphQL
|
|
10
10
|
autoload :Context, "graphql/query/context"
|
11
11
|
autoload :Fingerprint, "graphql/query/fingerprint"
|
12
12
|
autoload :NullContext, "graphql/query/null_context"
|
13
|
+
autoload :Partial, "graphql/query/partial"
|
13
14
|
autoload :Result, "graphql/query/result"
|
14
15
|
autoload :Variables, "graphql/query/variables"
|
15
16
|
autoload :InputValidationResult, "graphql/query/input_validation_result"
|
16
17
|
autoload :VariableValidationError, "graphql/query/variable_validation_error"
|
17
18
|
autoload :ValidationPipeline, "graphql/query/validation_pipeline"
|
18
19
|
|
20
|
+
# Code shared with {Partial}
|
21
|
+
module Runnable
|
22
|
+
def after_lazy(value, &block)
|
23
|
+
if !defined?(@runtime_instance)
|
24
|
+
@runtime_instance = context.namespace(:interpreter_runtime)[:runtime]
|
25
|
+
end
|
26
|
+
|
27
|
+
if @runtime_instance
|
28
|
+
@runtime_instance.minimal_after_lazy(value, &block)
|
29
|
+
else
|
30
|
+
@schema.after_lazy(value, &block)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Node-level cache for calculating arguments. Used during execution and query analysis.
|
35
|
+
# @param ast_node [GraphQL::Language::Nodes::AbstractNode]
|
36
|
+
# @param definition [GraphQL::Schema::Field]
|
37
|
+
# @param parent_object [GraphQL::Schema::Object]
|
38
|
+
# @return [Hash{Symbol => Object}]
|
39
|
+
def arguments_for(ast_node, definition, parent_object: nil)
|
40
|
+
arguments_cache.fetch(ast_node, definition, parent_object)
|
41
|
+
end
|
42
|
+
|
43
|
+
def arguments_cache
|
44
|
+
@arguments_cache ||= Execution::Interpreter::ArgumentsCache.new(self)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @api private
|
48
|
+
def handle_or_reraise(err)
|
49
|
+
@schema.handle_or_reraise(context, err)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
include Runnable
|
19
54
|
class OperationNameMissingError < GraphQL::ExecutionError
|
20
55
|
def initialize(name)
|
21
56
|
msg = if name.nil?
|
@@ -50,7 +85,7 @@ module GraphQL
|
|
50
85
|
# @return [GraphQL::StaticValidation::Validator] if present, the query will validate with these rules.
|
51
86
|
attr_reader :static_validator
|
52
87
|
|
53
|
-
# @param
|
88
|
+
# @param new_validator [GraphQL::StaticValidation::Validator] if present, the query will validate with these rules. This can't be reasssigned after validation.
|
54
89
|
def static_validator=(new_validator)
|
55
90
|
if defined?(@validation_pipeline) && @validation_pipeline && @validation_pipeline.has_validated?
|
56
91
|
raise ArgumentError, "Can't reassign Query#static_validator= after validation has run, remove this assignment."
|
@@ -98,9 +133,10 @@ module GraphQL
|
|
98
133
|
# @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value)
|
99
134
|
# @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
|
100
135
|
# @param visibility_profile [Symbol] Another way to assign `context[:visibility_profile]`
|
101
|
-
def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, visibility_profile: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil, use_visibility_profile: nil)
|
136
|
+
def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, multiplex: nil, validate: true, static_validator: nil, visibility_profile: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil, use_visibility_profile: nil)
|
102
137
|
# Even if `variables: nil` is passed, use an empty hash for simpler logic
|
103
138
|
variables ||= {}
|
139
|
+
@multiplex = multiplex
|
104
140
|
@schema = schema
|
105
141
|
@context = schema.context_class.new(query: self, values: context)
|
106
142
|
if visibility_profile
|
@@ -171,13 +207,7 @@ module GraphQL
|
|
171
207
|
@result_values = nil
|
172
208
|
@executed = false
|
173
209
|
|
174
|
-
@logger =
|
175
|
-
Logger.new(IO::NULL)
|
176
|
-
elsif context && (l = context[:logger])
|
177
|
-
l
|
178
|
-
else
|
179
|
-
schema.default_logger
|
180
|
-
end
|
210
|
+
@logger = schema.logger_for(context)
|
181
211
|
end
|
182
212
|
|
183
213
|
# If a document was provided to `GraphQL::Schema#execute` instead of the raw query string, we will need to get it from the document
|
@@ -203,19 +233,10 @@ module GraphQL
|
|
203
233
|
# @return [GraphQL::Execution::Lookahead]
|
204
234
|
def lookahead
|
205
235
|
@lookahead ||= begin
|
206
|
-
|
207
|
-
if ast_node.nil?
|
236
|
+
if selected_operation.nil?
|
208
237
|
GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
|
209
238
|
else
|
210
|
-
root_type
|
211
|
-
when nil, "query"
|
212
|
-
types.query_root # rubocop:disable Development/ContextIsPassedCop
|
213
|
-
when "mutation"
|
214
|
-
types.mutation_root # rubocop:disable Development/ContextIsPassedCop
|
215
|
-
when "subscription"
|
216
|
-
types.subscription_root # rubocop:disable Development/ContextIsPassedCop
|
217
|
-
end
|
218
|
-
GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [ast_node])
|
239
|
+
GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [selected_operation])
|
219
240
|
end
|
220
241
|
end
|
221
242
|
end
|
@@ -241,6 +262,18 @@ module GraphQL
|
|
241
262
|
with_prepared_ast { @operations }
|
242
263
|
end
|
243
264
|
|
265
|
+
# Run subtree partials of this query and return their results.
|
266
|
+
# Each partial is identified with a `path:` and `object:`
|
267
|
+
# where the path references a field in the AST and the object will be treated
|
268
|
+
# as the return value from that field. Subfields of the field named by `path`
|
269
|
+
# will be executed with `object` as the starting point
|
270
|
+
# @param partials_hashes [Array<Hash{Symbol => Object}>] Hashes with `path:` and `object:` keys
|
271
|
+
# @return [Array<GraphQL::Query::Result>]
|
272
|
+
def run_partials(partials_hashes)
|
273
|
+
partials = partials_hashes.map { |partial_options| Partial.new(query: self, **partial_options) }
|
274
|
+
Execution::Interpreter.run_all(@schema, partials, context: @context)
|
275
|
+
end
|
276
|
+
|
244
277
|
# Get the result for this query, executing it once
|
245
278
|
# @return [GraphQL::Query::Result] A Hash-like GraphQL response, with `"data"` and/or `"errors"` keys
|
246
279
|
def result
|
@@ -283,19 +316,6 @@ module GraphQL
|
|
283
316
|
end
|
284
317
|
end
|
285
318
|
|
286
|
-
# Node-level cache for calculating arguments. Used during execution and query analysis.
|
287
|
-
# @param ast_node [GraphQL::Language::Nodes::AbstractNode]
|
288
|
-
# @param definition [GraphQL::Schema::Field]
|
289
|
-
# @param parent_object [GraphQL::Schema::Object]
|
290
|
-
# @return Hash{Symbol => Object}
|
291
|
-
def arguments_for(ast_node, definition, parent_object: nil)
|
292
|
-
arguments_cache.fetch(ast_node, definition, parent_object)
|
293
|
-
end
|
294
|
-
|
295
|
-
def arguments_cache
|
296
|
-
@arguments_cache ||= Execution::Interpreter::ArgumentsCache.new(self)
|
297
|
-
end
|
298
|
-
|
299
319
|
# A version of the given query string, with:
|
300
320
|
# - Variables inlined to the query
|
301
321
|
# - Strings replaced with `<REDACTED>`
|
@@ -362,17 +382,21 @@ module GraphQL
|
|
362
382
|
|
363
383
|
def root_type_for_operation(op_type)
|
364
384
|
case op_type
|
365
|
-
when "query"
|
385
|
+
when "query", nil
|
366
386
|
types.query_root # rubocop:disable Development/ContextIsPassedCop
|
367
387
|
when "mutation"
|
368
388
|
types.mutation_root # rubocop:disable Development/ContextIsPassedCop
|
369
389
|
when "subscription"
|
370
390
|
types.subscription_root # rubocop:disable Development/ContextIsPassedCop
|
371
391
|
else
|
372
|
-
raise ArgumentError, "unexpected root type name: #{op_type.inspect}; expected 'query', 'mutation', or 'subscription'"
|
392
|
+
raise ArgumentError, "unexpected root type name: #{op_type.inspect}; expected nil, 'query', 'mutation', or 'subscription'"
|
373
393
|
end
|
374
394
|
end
|
375
395
|
|
396
|
+
def root_type
|
397
|
+
root_type_for_operation(selected_operation.operation_type)
|
398
|
+
end
|
399
|
+
|
376
400
|
def types
|
377
401
|
@visibility_profile || warden.visibility_profile
|
378
402
|
end
|
@@ -405,23 +429,6 @@ module GraphQL
|
|
405
429
|
with_prepared_ast { @subscription }
|
406
430
|
end
|
407
431
|
|
408
|
-
# @api private
|
409
|
-
def handle_or_reraise(err)
|
410
|
-
schema.handle_or_reraise(context, err)
|
411
|
-
end
|
412
|
-
|
413
|
-
def after_lazy(value, &block)
|
414
|
-
if !defined?(@runtime_instance)
|
415
|
-
@runtime_instance = context.namespace(:interpreter_runtime)[:runtime]
|
416
|
-
end
|
417
|
-
|
418
|
-
if @runtime_instance
|
419
|
-
@runtime_instance.minimal_after_lazy(value, &block)
|
420
|
-
else
|
421
|
-
@schema.after_lazy(value, &block)
|
422
|
-
end
|
423
|
-
end
|
424
|
-
|
425
432
|
attr_reader :logger
|
426
433
|
|
427
434
|
private
|
@@ -441,7 +448,6 @@ module GraphQL
|
|
441
448
|
@warden ||= @schema.warden_class.new(schema: @schema, context: @context)
|
442
449
|
parse_error = nil
|
443
450
|
@document ||= begin
|
444
|
-
current_trace.begin_parse(query_string)
|
445
451
|
if query_string
|
446
452
|
GraphQL.parse(query_string, trace: self.current_trace, max_tokens: @schema.max_query_string_tokens)
|
447
453
|
end
|
@@ -449,8 +455,6 @@ module GraphQL
|
|
449
455
|
parse_error = err
|
450
456
|
@schema.parse_error(err, @context)
|
451
457
|
nil
|
452
|
-
ensure
|
453
|
-
current_trace.end_parse(query_string)
|
454
458
|
end
|
455
459
|
|
456
460
|
@fragments = {}
|
@@ -258,7 +258,9 @@ module GraphQL
|
|
258
258
|
# We can get these now; we'll have to get late-bound types later
|
259
259
|
if interface_type.is_a?(Module) && type.is_a?(Class)
|
260
260
|
implementers = @possible_types[interface_type] ||= []
|
261
|
-
implementers
|
261
|
+
if !implementers.include?(type)
|
262
|
+
implementers << type
|
263
|
+
end
|
262
264
|
end
|
263
265
|
when String, Schema::LateBoundType
|
264
266
|
interface_type = interface_type_membership
|
@@ -212,12 +212,17 @@ module GraphQL
|
|
212
212
|
@statically_coercible = !requires_parent_object
|
213
213
|
end
|
214
214
|
|
215
|
+
def freeze
|
216
|
+
statically_coercible?
|
217
|
+
super
|
218
|
+
end
|
219
|
+
|
215
220
|
# Apply the {prepare} configuration to `value`, using methods from `obj`.
|
216
221
|
# Used by the runtime.
|
217
222
|
# @api private
|
218
223
|
def prepare_value(obj, value, context: nil)
|
219
224
|
if type.unwrap.kind.input_object?
|
220
|
-
value = recursively_prepare_input_object(value, type)
|
225
|
+
value = recursively_prepare_input_object(value, type, context)
|
221
226
|
end
|
222
227
|
|
223
228
|
Schema::Validator.validate!(validators, obj, context, value)
|
@@ -398,15 +403,16 @@ module GraphQL
|
|
398
403
|
|
399
404
|
private
|
400
405
|
|
401
|
-
def recursively_prepare_input_object(value, type)
|
406
|
+
def recursively_prepare_input_object(value, type, context)
|
402
407
|
if type.non_null?
|
403
408
|
type = type.of_type
|
404
409
|
end
|
405
410
|
|
406
411
|
if type.list? && !value.nil?
|
407
412
|
inner_type = type.of_type
|
408
|
-
value.map { |v| recursively_prepare_input_object(v, inner_type) }
|
413
|
+
value.map { |v| recursively_prepare_input_object(v, inner_type, context) }
|
409
414
|
elsif value.is_a?(GraphQL::Schema::InputObject)
|
415
|
+
value.validate_for(context)
|
410
416
|
value.prepare
|
411
417
|
else
|
412
418
|
value
|