graphql 2.0.28 → 2.2.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/generators/graphql/install/templates/base_mutation.erb +2 -0
- data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
- data/lib/generators/graphql/install_generator.rb +3 -0
- data/lib/generators/graphql/templates/base_argument.erb +2 -0
- data/lib/generators/graphql/templates/base_connection.erb +2 -0
- data/lib/generators/graphql/templates/base_edge.erb +2 -0
- data/lib/generators/graphql/templates/base_enum.erb +2 -0
- data/lib/generators/graphql/templates/base_field.erb +2 -0
- data/lib/generators/graphql/templates/base_input_object.erb +2 -0
- data/lib/generators/graphql/templates/base_interface.erb +2 -0
- data/lib/generators/graphql/templates/base_object.erb +2 -0
- data/lib/generators/graphql/templates/base_resolver.erb +6 -0
- data/lib/generators/graphql/templates/base_scalar.erb +2 -0
- data/lib/generators/graphql/templates/base_union.erb +2 -0
- data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
- data/lib/generators/graphql/templates/loader.erb +2 -0
- data/lib/generators/graphql/templates/mutation.erb +2 -0
- data/lib/generators/graphql/templates/node_type.erb +2 -0
- data/lib/generators/graphql/templates/query_type.erb +2 -0
- data/lib/generators/graphql/templates/schema.erb +2 -0
- data/lib/graphql/analysis/ast/analyzer.rb +7 -0
- data/lib/graphql/analysis/ast/field_usage.rb +32 -7
- data/lib/graphql/analysis/ast/query_complexity.rb +80 -128
- data/lib/graphql/analysis/ast/query_depth.rb +7 -2
- data/lib/graphql/analysis/ast/visitor.rb +2 -2
- data/lib/graphql/analysis/ast.rb +21 -11
- data/lib/graphql/backtrace/trace.rb +12 -15
- data/lib/graphql/coercion_error.rb +1 -9
- data/lib/graphql/dataloader/async_dataloader.rb +85 -0
- data/lib/graphql/dataloader/request.rb +5 -0
- data/lib/graphql/dataloader/source.rb +11 -3
- data/lib/graphql/dataloader.rb +109 -142
- data/lib/graphql/duration_encoding_error.rb +16 -0
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +170 -0
- data/lib/graphql/execution/interpreter/runtime.rb +79 -248
- data/lib/graphql/execution/interpreter.rb +91 -157
- data/lib/graphql/execution/lookahead.rb +88 -21
- data/lib/graphql/introspection/dynamic_fields.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +11 -5
- data/lib/graphql/introspection/schema_type.rb +3 -1
- data/lib/graphql/language/block_string.rb +34 -18
- data/lib/graphql/language/definition_slice.rb +1 -1
- data/lib/graphql/language/document_from_schema_definition.rb +37 -37
- data/lib/graphql/language/lexer.rb +271 -177
- data/lib/graphql/language/nodes.rb +75 -57
- data/lib/graphql/language/parser.rb +707 -1986
- data/lib/graphql/language/printer.rb +303 -146
- data/lib/graphql/language/sanitized_printer.rb +20 -22
- data/lib/graphql/language/static_visitor.rb +167 -0
- data/lib/graphql/language/visitor.rb +20 -81
- data/lib/graphql/language.rb +1 -0
- data/lib/graphql/load_application_object_failed_error.rb +5 -1
- data/lib/graphql/pagination/array_connection.rb +3 -3
- data/lib/graphql/pagination/connection.rb +28 -1
- data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
- data/lib/graphql/pagination/relation_connection.rb +3 -3
- data/lib/graphql/query/context/scoped_context.rb +101 -0
- data/lib/graphql/query/context.rb +36 -98
- data/lib/graphql/query/null_context.rb +4 -11
- data/lib/graphql/query/validation_pipeline.rb +2 -2
- data/lib/graphql/query/variables.rb +3 -3
- data/lib/graphql/query.rb +13 -22
- data/lib/graphql/railtie.rb +9 -6
- data/lib/graphql/rake_task.rb +3 -12
- data/lib/graphql/schema/argument.rb +6 -1
- data/lib/graphql/schema/base_64_encoder.rb +3 -5
- data/lib/graphql/schema/build_from_definition.rb +0 -11
- data/lib/graphql/schema/directive/one_of.rb +12 -0
- data/lib/graphql/schema/directive/specified_by.rb +14 -0
- data/lib/graphql/schema/directive.rb +1 -1
- data/lib/graphql/schema/enum.rb +3 -3
- data/lib/graphql/schema/field/connection_extension.rb +1 -15
- data/lib/graphql/schema/field/scope_extension.rb +8 -1
- data/lib/graphql/schema/field.rb +39 -35
- data/lib/graphql/schema/has_single_input_argument.rb +156 -0
- data/lib/graphql/schema/input_object.rb +2 -2
- data/lib/graphql/schema/interface.rb +15 -11
- data/lib/graphql/schema/introspection_system.rb +2 -0
- data/lib/graphql/schema/loader.rb +0 -2
- data/lib/graphql/schema/member/base_dsl_methods.rb +2 -1
- data/lib/graphql/schema/member/has_arguments.rb +61 -38
- data/lib/graphql/schema/member/has_fields.rb +8 -5
- data/lib/graphql/schema/member/has_interfaces.rb +23 -9
- data/lib/graphql/schema/member/scoped.rb +19 -0
- data/lib/graphql/schema/member/validates_input.rb +3 -3
- data/lib/graphql/schema/object.rb +8 -0
- data/lib/graphql/schema/printer.rb +8 -7
- data/lib/graphql/schema/relay_classic_mutation.rb +6 -128
- data/lib/graphql/schema/resolver.rb +16 -8
- data/lib/graphql/schema/scalar.rb +3 -3
- data/lib/graphql/schema/subscription.rb +11 -4
- data/lib/graphql/schema/union.rb +1 -1
- data/lib/graphql/schema/unique_within_type.rb +1 -1
- data/lib/graphql/schema/warden.rb +96 -94
- data/lib/graphql/schema.rb +252 -78
- data/lib/graphql/static_validation/all_rules.rb +1 -1
- data/lib/graphql/static_validation/base_visitor.rb +1 -1
- data/lib/graphql/static_validation/literal_validator.rb +2 -3
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +2 -2
- data/lib/graphql/static_validation/validation_context.rb +5 -5
- data/lib/graphql/static_validation/validator.rb +3 -0
- data/lib/graphql/static_validation.rb +0 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +3 -2
- data/lib/graphql/subscriptions/event.rb +8 -2
- data/lib/graphql/subscriptions/serialize.rb +2 -0
- data/lib/graphql/subscriptions.rb +14 -12
- data/lib/graphql/testing/helpers.rb +129 -0
- data/lib/graphql/testing.rb +2 -0
- data/lib/graphql/tracing/appoptics_trace.rb +2 -2
- data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
- data/lib/graphql/tracing/legacy_hooks_trace.rb +74 -0
- data/lib/graphql/tracing/platform_tracing.rb +2 -0
- data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +3 -1
- data/lib/graphql/tracing/sentry_trace.rb +112 -0
- data/lib/graphql/tracing/trace.rb +1 -0
- data/lib/graphql/tracing.rb +3 -1
- data/lib/graphql/types/iso_8601_duration.rb +77 -0
- data/lib/graphql/types/relay/connection_behaviors.rb +32 -2
- data/lib/graphql/types/relay/edge_behaviors.rb +7 -0
- data/lib/graphql/types.rb +1 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +6 -5
- data/readme.md +12 -2
- metadata +46 -38
- data/lib/graphql/deprecation.rb +0 -9
- data/lib/graphql/filter.rb +0 -59
- data/lib/graphql/language/parser.y +0 -560
- data/lib/graphql/schema/base_64_bp.rb +0 -26
- data/lib/graphql/static_validation/type_stack.rb +0 -216
- data/lib/graphql/subscriptions/instrumentation.rb +0 -28
|
@@ -25,7 +25,7 @@ module GraphQL
|
|
|
25
25
|
queries = query_options.map do |opts|
|
|
26
26
|
case opts
|
|
27
27
|
when Hash
|
|
28
|
-
|
|
28
|
+
schema.query_class.new(schema, nil, **opts)
|
|
29
29
|
when GraphQL::Query
|
|
30
30
|
opts
|
|
31
31
|
else
|
|
@@ -37,179 +37,113 @@ module GraphQL
|
|
|
37
37
|
multiplex.current_trace.execute_multiplex(multiplex: multiplex) do
|
|
38
38
|
schema = multiplex.schema
|
|
39
39
|
queries = multiplex.queries
|
|
40
|
-
query_instrumenters = schema.instrumenters[:query]
|
|
41
|
-
multiplex_instrumenters = schema.instrumenters[:multiplex]
|
|
42
40
|
lazies_at_depth = Hash.new { |h, k| h[k] = [] }
|
|
41
|
+
multiplex_analyzers = schema.multiplex_analyzers
|
|
42
|
+
if multiplex.max_complexity
|
|
43
|
+
multiplex_analyzers += [GraphQL::Analysis::AST::MaxQueryComplexity]
|
|
44
|
+
end
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers)
|
|
47
|
+
begin
|
|
48
|
+
# Since this is basically the batching context,
|
|
49
|
+
# share it for a whole multiplex
|
|
50
|
+
multiplex.context[:interpreter_instance] ||= multiplex.schema.query_execution_strategy.new
|
|
51
|
+
# Do as much eager evaluation of the query as possible
|
|
52
|
+
results = []
|
|
53
|
+
queries.each_with_index do |query, idx|
|
|
54
|
+
if query.subscription? && !query.subscription_update?
|
|
55
|
+
query.context.namespace(:subscriptions)[:events] = []
|
|
52
56
|
end
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
begin
|
|
68
|
-
# Although queries in a multiplex _share_ an Interpreter instance,
|
|
69
|
-
# they also have another item of state, which is private to that query
|
|
70
|
-
# in particular, assign it here:
|
|
71
|
-
runtime = Runtime.new(query: query, lazies_at_depth: lazies_at_depth)
|
|
72
|
-
query.context.namespace(:interpreter_runtime)[:runtime] = runtime
|
|
73
|
-
|
|
74
|
-
query.current_trace.execute_query(query: query) do
|
|
75
|
-
runtime.run_eager
|
|
76
|
-
end
|
|
77
|
-
rescue GraphQL::ExecutionError => err
|
|
78
|
-
query.context.errors << err
|
|
79
|
-
NO_OPERATION
|
|
80
|
-
end
|
|
57
|
+
multiplex.dataloader.append_job {
|
|
58
|
+
operation = query.selected_operation
|
|
59
|
+
result = if operation.nil? || !query.valid? || query.context.errors.any?
|
|
60
|
+
NO_OPERATION
|
|
61
|
+
else
|
|
62
|
+
begin
|
|
63
|
+
# Although queries in a multiplex _share_ an Interpreter instance,
|
|
64
|
+
# they also have another item of state, which is private to that query
|
|
65
|
+
# in particular, assign it here:
|
|
66
|
+
runtime = Runtime.new(query: query, lazies_at_depth: lazies_at_depth)
|
|
67
|
+
query.context.namespace(:interpreter_runtime)[:runtime] = runtime
|
|
68
|
+
|
|
69
|
+
query.current_trace.execute_query(query: query) do
|
|
70
|
+
runtime.run_eager
|
|
81
71
|
end
|
|
82
|
-
|
|
83
|
-
|
|
72
|
+
rescue GraphQL::ExecutionError => err
|
|
73
|
+
query.context.errors << err
|
|
74
|
+
NO_OPERATION
|
|
75
|
+
end
|
|
84
76
|
end
|
|
77
|
+
results[idx] = result
|
|
78
|
+
}
|
|
79
|
+
end
|
|
85
80
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
# Then, work through lazy results in a breadth-first way
|
|
89
|
-
multiplex.dataloader.append_job {
|
|
90
|
-
query = multiplex.queries.length == 1 ? multiplex.queries[0] : nil
|
|
91
|
-
queries = multiplex ? multiplex.queries : [query]
|
|
92
|
-
final_values = queries.map do |query|
|
|
93
|
-
runtime = query.context.namespace(:interpreter_runtime)[:runtime]
|
|
94
|
-
# it might not be present if the query has an error
|
|
95
|
-
runtime ? runtime.final_result : nil
|
|
96
|
-
end
|
|
97
|
-
final_values.compact!
|
|
98
|
-
multiplex.current_trace.execute_query_lazy(multiplex: multiplex, query: query) do
|
|
99
|
-
Interpreter::Resolve.resolve_each_depth(lazies_at_depth, multiplex.dataloader)
|
|
100
|
-
end
|
|
101
|
-
queries.each do |query|
|
|
102
|
-
runtime = query.context.namespace(:interpreter_runtime)[:runtime]
|
|
103
|
-
if runtime
|
|
104
|
-
runtime.delete_all_interpreter_context
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
}
|
|
108
|
-
multiplex.dataloader.run
|
|
81
|
+
multiplex.dataloader.run
|
|
109
82
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
83
|
+
# Then, work through lazy results in a breadth-first way
|
|
84
|
+
multiplex.dataloader.append_job {
|
|
85
|
+
query = multiplex.queries.length == 1 ? multiplex.queries[0] : nil
|
|
86
|
+
queries = multiplex ? multiplex.queries : [query]
|
|
87
|
+
final_values = queries.map do |query|
|
|
88
|
+
runtime = query.context.namespace(:interpreter_runtime)[:runtime]
|
|
89
|
+
# it might not be present if the query has an error
|
|
90
|
+
runtime ? runtime.final_result : nil
|
|
91
|
+
end
|
|
92
|
+
final_values.compact!
|
|
93
|
+
multiplex.current_trace.execute_query_lazy(multiplex: multiplex, query: query) do
|
|
94
|
+
Interpreter::Resolve.resolve_each_depth(lazies_at_depth, multiplex.dataloader)
|
|
95
|
+
end
|
|
96
|
+
}
|
|
97
|
+
multiplex.dataloader.run
|
|
125
98
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
99
|
+
# Then, find all errors and assign the result to the query object
|
|
100
|
+
results.each_with_index do |data_result, idx|
|
|
101
|
+
query = queries[idx]
|
|
102
|
+
if (events = query.context.namespace(:subscriptions)[:events]) && events.any?
|
|
103
|
+
schema.subscriptions.write_subscription(query, events)
|
|
104
|
+
end
|
|
105
|
+
# Assign the result so that it can be accessed in instrumentation
|
|
106
|
+
query.result_values = if data_result.equal?(NO_OPERATION)
|
|
107
|
+
if !query.valid? || query.context.errors.any?
|
|
108
|
+
# A bit weird, but `Query#static_errors` _includes_ `query.context.errors`
|
|
109
|
+
{ "errors" => query.static_errors.map(&:to_h) }
|
|
110
|
+
else
|
|
111
|
+
data_result
|
|
112
|
+
end
|
|
113
|
+
else
|
|
114
|
+
result = {}
|
|
130
115
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
query.result_values["extensions"] = query.context.namespace(:__query_result_extensions__)
|
|
135
|
-
end
|
|
136
|
-
# Get the Query::Result, not the Hash
|
|
137
|
-
results[idx] = query.result
|
|
116
|
+
if query.context.errors.any?
|
|
117
|
+
error_result = query.context.errors.map(&:to_h)
|
|
118
|
+
result["errors"] = error_result
|
|
138
119
|
end
|
|
139
120
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
ensure
|
|
147
|
-
queries.map { |query|
|
|
148
|
-
runtime = query.context.namespace(:interpreter_runtime)[:runtime]
|
|
149
|
-
if runtime
|
|
150
|
-
runtime.delete_all_interpreter_context
|
|
151
|
-
end
|
|
152
|
-
}
|
|
121
|
+
result["data"] = query.context.namespace(:interpreter_runtime)[:runtime].final_result
|
|
122
|
+
|
|
123
|
+
result
|
|
124
|
+
end
|
|
125
|
+
if query.context.namespace?(:__query_result_extensions__)
|
|
126
|
+
query.result_values["extensions"] = query.context.namespace(:__query_result_extensions__)
|
|
153
127
|
end
|
|
128
|
+
# Get the Query::Result, not the Hash
|
|
129
|
+
results[idx] = query.result
|
|
154
130
|
end
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
private
|
|
160
131
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
132
|
+
results
|
|
133
|
+
rescue Exception
|
|
134
|
+
# TODO rescue at a higher level so it will catch errors in analysis, too
|
|
135
|
+
# Assign values here so that the query's `@executed` becomes true
|
|
136
|
+
queries.map { |q| q.result_values ||= {} }
|
|
137
|
+
raise
|
|
138
|
+
ensure
|
|
139
|
+
queries.map { |query|
|
|
140
|
+
runtime = query.context.namespace(:interpreter_runtime)[:runtime]
|
|
141
|
+
if runtime
|
|
142
|
+
runtime.delete_all_interpreter_context
|
|
143
|
+
end
|
|
172
144
|
}
|
|
173
|
-
}
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# Call each before hook, and if they all succeed, yield.
|
|
178
|
-
# If they don't all succeed, call after_ for each one that succeeded.
|
|
179
|
-
def call_hooks(instrumenters, object, before_hook_name, after_hook_name)
|
|
180
|
-
begin
|
|
181
|
-
successful = []
|
|
182
|
-
instrumenters.each do |instrumenter|
|
|
183
|
-
instrumenter.public_send(before_hook_name, object)
|
|
184
|
-
successful << instrumenter
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
# if any before hooks raise an exception, quit calling before hooks,
|
|
188
|
-
# but call the after hooks on anything that succeeded but also
|
|
189
|
-
# raise the exception that came from the before hook.
|
|
190
|
-
rescue GraphQL::ExecutionError => err
|
|
191
|
-
object.context.errors << err
|
|
192
|
-
rescue => e
|
|
193
|
-
raise call_after_hooks(successful, object, after_hook_name, e)
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
begin
|
|
197
|
-
yield # Call the user code
|
|
198
|
-
ensure
|
|
199
|
-
ex = call_after_hooks(successful, object, after_hook_name, nil)
|
|
200
|
-
raise ex if ex
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
def call_after_hooks(instrumenters, object, after_hook_name, ex)
|
|
205
|
-
instrumenters.reverse_each do |instrumenter|
|
|
206
|
-
begin
|
|
207
|
-
instrumenter.public_send(after_hook_name, object)
|
|
208
|
-
rescue => e
|
|
209
|
-
ex = e
|
|
210
145
|
end
|
|
211
146
|
end
|
|
212
|
-
ex
|
|
213
147
|
end
|
|
214
148
|
end
|
|
215
149
|
|
|
@@ -80,6 +80,22 @@ module GraphQL
|
|
|
80
80
|
selection(field_name, selected_type: selected_type, arguments: arguments).selected?
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
+
# True if this node has a selection with alias matching `alias_name`.
|
|
84
|
+
# If `alias_name` is a String, it is treated as a GraphQL-style (camelized)
|
|
85
|
+
# field name and used verbatim. If `alias_name` is a Symbol, it is
|
|
86
|
+
# treated as a Ruby-style (underscored) name and camelized before comparing.
|
|
87
|
+
#
|
|
88
|
+
# If `arguments:` is provided, each provided key/value will be matched
|
|
89
|
+
# against the arguments in the next selection. This method will return false
|
|
90
|
+
# if any of the given `arguments:` are not present and matching in the next selection.
|
|
91
|
+
# (But, the next selection may contain _more_ than the given arguments.)
|
|
92
|
+
# @param alias_name [String, Symbol]
|
|
93
|
+
# @param arguments [Hash] Arguments which must match in the selection
|
|
94
|
+
# @return [Boolean]
|
|
95
|
+
def selects_alias?(alias_name, arguments: nil)
|
|
96
|
+
alias_selection(alias_name, arguments: arguments).selected?
|
|
97
|
+
end
|
|
98
|
+
|
|
83
99
|
# @return [Boolean] True if this lookahead represents a field that was requested
|
|
84
100
|
def selected?
|
|
85
101
|
true
|
|
@@ -102,9 +118,10 @@ module GraphQL
|
|
|
102
118
|
@query.warden
|
|
103
119
|
.possible_types(selected_type)
|
|
104
120
|
.map { |t| @query.warden.fields(t) }
|
|
105
|
-
.flatten
|
|
121
|
+
.tap(&:flatten!)
|
|
106
122
|
end
|
|
107
123
|
|
|
124
|
+
|
|
108
125
|
if (match_by_orig_name = all_fields.find { |f| f.original_name == field_name })
|
|
109
126
|
match_by_orig_name
|
|
110
127
|
else
|
|
@@ -114,23 +131,29 @@ module GraphQL
|
|
|
114
131
|
@query.get_field(selected_type, guessed_name)
|
|
115
132
|
end
|
|
116
133
|
end
|
|
134
|
+
lookahead_for_selection(next_field_defn, selected_type, arguments)
|
|
135
|
+
end
|
|
117
136
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
end
|
|
137
|
+
# Like {#selection}, but for aliases.
|
|
138
|
+
# It returns a null object (check with {#selected?})
|
|
139
|
+
# @return [GraphQL::Execution::Lookahead]
|
|
140
|
+
def alias_selection(alias_name, selected_type: @selected_type, arguments: nil)
|
|
141
|
+
alias_cache_key = [alias_name, arguments]
|
|
142
|
+
return alias_selections[key] if alias_selections.key?(alias_name)
|
|
125
143
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
144
|
+
alias_node = lookup_alias_node(ast_nodes, alias_name)
|
|
145
|
+
return NULL_LOOKAHEAD unless alias_node
|
|
146
|
+
|
|
147
|
+
next_field_defn = @query.get_field(selected_type, alias_node.name)
|
|
148
|
+
|
|
149
|
+
alias_arguments = @query.arguments_for(alias_node, next_field_defn)
|
|
150
|
+
if alias_arguments.is_a?(::GraphQL::Execution::Interpreter::Arguments)
|
|
151
|
+
alias_arguments = alias_arguments.keyword_arguments
|
|
133
152
|
end
|
|
153
|
+
|
|
154
|
+
return NULL_LOOKAHEAD if arguments && arguments != alias_arguments
|
|
155
|
+
|
|
156
|
+
alias_selections[alias_cache_key] = lookahead_for_selection(next_field_defn, selected_type, alias_arguments, alias_name)
|
|
134
157
|
end
|
|
135
158
|
|
|
136
159
|
# Like {#selection}, but for all nodes.
|
|
@@ -258,7 +281,7 @@ module GraphQL
|
|
|
258
281
|
end
|
|
259
282
|
find_selections(subselections_by_type, subselections_on_type, on_type, ast_selection.selections, arguments)
|
|
260
283
|
when GraphQL::Language::Nodes::FragmentSpread
|
|
261
|
-
frag_defn =
|
|
284
|
+
frag_defn = lookup_fragment(ast_selection)
|
|
262
285
|
# Again, assuming a valid AST
|
|
263
286
|
on_type = @query.get_type(frag_defn.type.name)
|
|
264
287
|
subselections_on_type = subselections_by_type[on_type] ||= {}
|
|
@@ -271,11 +294,11 @@ module GraphQL
|
|
|
271
294
|
|
|
272
295
|
# If a selection on `node` matches `field_name` (which is backed by `field_defn`)
|
|
273
296
|
# and matches the `arguments:` constraints, then add that node to `matches`
|
|
274
|
-
def find_selected_nodes(node, field_defn, arguments:, matches:)
|
|
297
|
+
def find_selected_nodes(node, field_name, field_defn, arguments:, matches:, alias_name: NOT_CONFIGURED)
|
|
275
298
|
return if skipped_by_directive?(node)
|
|
276
299
|
case node
|
|
277
300
|
when GraphQL::Language::Nodes::Field
|
|
278
|
-
if node.name ==
|
|
301
|
+
if node.name == field_name && (NOT_CONFIGURED.equal?(alias_name) || node.alias == alias_name)
|
|
279
302
|
if arguments.nil? || arguments.empty?
|
|
280
303
|
# No constraint applied
|
|
281
304
|
matches << node
|
|
@@ -284,10 +307,10 @@ module GraphQL
|
|
|
284
307
|
end
|
|
285
308
|
end
|
|
286
309
|
when GraphQL::Language::Nodes::InlineFragment
|
|
287
|
-
node.selections.each { |s| find_selected_nodes(s, field_defn, arguments: arguments, matches: matches) }
|
|
310
|
+
node.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches, alias_name: alias_name) }
|
|
288
311
|
when GraphQL::Language::Nodes::FragmentSpread
|
|
289
|
-
frag_defn =
|
|
290
|
-
frag_defn.selections.each { |s| find_selected_nodes(s, field_defn, arguments: arguments, matches: matches) }
|
|
312
|
+
frag_defn = lookup_fragment(node)
|
|
313
|
+
frag_defn.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches, alias_name: alias_name) }
|
|
291
314
|
else
|
|
292
315
|
raise "Unexpected selection comparison on #{node.class.name} (#{node})"
|
|
293
316
|
end
|
|
@@ -306,6 +329,50 @@ module GraphQL
|
|
|
306
329
|
query_kwargs.key?(arg_name_sym) && query_kwargs[arg_name_sym] == arg_value
|
|
307
330
|
end
|
|
308
331
|
end
|
|
332
|
+
|
|
333
|
+
def lookahead_for_selection(field_defn, selected_type, arguments, alias_name = NOT_CONFIGURED)
|
|
334
|
+
return NULL_LOOKAHEAD unless field_defn
|
|
335
|
+
|
|
336
|
+
next_nodes = []
|
|
337
|
+
field_name = field_defn.name
|
|
338
|
+
@ast_nodes.each do |ast_node|
|
|
339
|
+
ast_node.selections.each do |selection|
|
|
340
|
+
find_selected_nodes(selection, field_name, field_defn, arguments: arguments, matches: next_nodes, alias_name: alias_name)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
return NULL_LOOKAHEAD if next_nodes.empty?
|
|
345
|
+
|
|
346
|
+
Lookahead.new(query: @query, ast_nodes: next_nodes, field: field_defn, owner_type: selected_type)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def alias_selections
|
|
350
|
+
return @alias_selections if defined?(@alias_selections)
|
|
351
|
+
@alias_selections ||= {}
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def lookup_alias_node(nodes, name)
|
|
355
|
+
return if nodes.empty?
|
|
356
|
+
|
|
357
|
+
nodes.flat_map(&:children)
|
|
358
|
+
.flat_map { |child| unwrap_fragments(child) }
|
|
359
|
+
.find { |child| child.is_a?(GraphQL::Language::Nodes::Field) && child.alias == name }
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def unwrap_fragments(node)
|
|
363
|
+
case node
|
|
364
|
+
when GraphQL::Language::Nodes::InlineFragment
|
|
365
|
+
node.children
|
|
366
|
+
when GraphQL::Language::Nodes::FragmentSpread
|
|
367
|
+
lookup_fragment(node).children
|
|
368
|
+
else
|
|
369
|
+
[node]
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def lookup_fragment(ast_selection)
|
|
374
|
+
@query.fragments[ast_selection.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{ast_selection.name} (found: #{@query.fragments.keys})")
|
|
375
|
+
end
|
|
309
376
|
end
|
|
310
377
|
end
|
|
311
378
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
module GraphQL
|
|
3
3
|
module Introspection
|
|
4
4
|
class DynamicFields < Introspection::BaseObject
|
|
5
|
-
field :__typename, String, "The name of this type", null: false
|
|
5
|
+
field :__typename, String, "The name of this type", null: false, dynamic_introspection: true
|
|
6
6
|
|
|
7
7
|
def __typename
|
|
8
8
|
object.class.graphql_name
|
|
@@ -2,20 +2,26 @@
|
|
|
2
2
|
module GraphQL
|
|
3
3
|
module Introspection
|
|
4
4
|
class EntryPoints < Introspection::BaseObject
|
|
5
|
-
field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false
|
|
6
|
-
field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system" do
|
|
5
|
+
field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false, dynamic_introspection: true
|
|
6
|
+
field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system", dynamic_introspection: true do
|
|
7
7
|
argument :name, String
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def __schema
|
|
11
11
|
# Apply wrapping manually since this field isn't wrapped by instrumentation
|
|
12
|
-
schema =
|
|
12
|
+
schema = context.schema
|
|
13
13
|
schema_type = schema.introspection_system.types["__Schema"]
|
|
14
|
-
schema_type.wrap(schema,
|
|
14
|
+
schema_type.wrap(schema, context)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def __type(name:)
|
|
18
|
-
context.warden.reachable_type?(name)
|
|
18
|
+
if context.warden.reachable_type?(name)
|
|
19
|
+
context.warden.get_type(name)
|
|
20
|
+
elsif (type = context.schema.extra_types.find { |t| t.graphql_name == name })
|
|
21
|
+
type
|
|
22
|
+
else
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
19
25
|
end
|
|
20
26
|
end
|
|
21
27
|
end
|
|
@@ -47,10 +47,10 @@ module GraphQL
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
# Remove leading & trailing blank lines
|
|
50
|
-
while lines.size > 0 && lines
|
|
50
|
+
while lines.size > 0 && contains_only_whitespace?(lines.first)
|
|
51
51
|
lines.shift
|
|
52
52
|
end
|
|
53
|
-
while lines.size > 0 && lines
|
|
53
|
+
while lines.size > 0 && contains_only_whitespace?(lines.last)
|
|
54
54
|
lines.pop
|
|
55
55
|
end
|
|
56
56
|
|
|
@@ -59,40 +59,56 @@ module GraphQL
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def self.print(str, indent: '')
|
|
62
|
-
|
|
62
|
+
line_length = 120 - indent.length
|
|
63
|
+
block_str = "".dup
|
|
64
|
+
triple_quotes = "\"\"\"\n"
|
|
65
|
+
block_str << indent
|
|
66
|
+
block_str << triple_quotes
|
|
63
67
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
if str.include?("\n")
|
|
69
|
+
str.split("\n") do |line|
|
|
70
|
+
if line == ''
|
|
71
|
+
block_str << "\n"
|
|
72
|
+
else
|
|
73
|
+
break_line(line, line_length) do |subline|
|
|
74
|
+
block_str << indent
|
|
75
|
+
block_str << subline
|
|
76
|
+
block_str << "\n"
|
|
77
|
+
end
|
|
73
78
|
end
|
|
74
79
|
end
|
|
80
|
+
else
|
|
81
|
+
break_line(str, line_length) do |subline|
|
|
82
|
+
block_str << indent
|
|
83
|
+
block_str << subline
|
|
84
|
+
block_str << "\n"
|
|
85
|
+
end
|
|
75
86
|
end
|
|
76
87
|
|
|
77
|
-
block_str <<
|
|
88
|
+
block_str << indent
|
|
89
|
+
block_str << triple_quotes
|
|
78
90
|
end
|
|
79
91
|
|
|
80
92
|
private
|
|
81
93
|
|
|
82
94
|
def self.break_line(line, length)
|
|
83
|
-
return
|
|
95
|
+
return yield(line) if line.length < length + 5
|
|
84
96
|
|
|
85
97
|
parts = line.split(Regexp.new("((?: |^).{15,#{length - 40}}(?= |$))"))
|
|
86
|
-
return
|
|
98
|
+
return yield(line) if parts.length < 4
|
|
87
99
|
|
|
88
|
-
|
|
100
|
+
yield(parts.slice!(0, 3).join)
|
|
89
101
|
|
|
90
102
|
parts.each_with_index do |part, i|
|
|
91
103
|
next if i % 2 == 1
|
|
92
|
-
|
|
104
|
+
yield "#{part[1..-1]}#{parts[i + 1]}"
|
|
93
105
|
end
|
|
94
106
|
|
|
95
|
-
|
|
107
|
+
nil
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def self.contains_only_whitespace?(line)
|
|
111
|
+
line.match?(/^\s*$/)
|
|
96
112
|
end
|
|
97
113
|
end
|
|
98
114
|
end
|