graphql 2.0.13 → 2.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
- 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/mutation_delete_generator.rb +1 -1
- data/lib/generators/graphql/mutation_update_generator.rb +1 -1
- data/lib/generators/graphql/relay.rb +18 -1
- 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 +8 -0
- data/lib/graphql/analysis/analyzer.rb +89 -0
- data/lib/graphql/analysis/field_usage.rb +82 -0
- data/lib/graphql/analysis/max_query_complexity.rb +20 -0
- data/lib/graphql/analysis/max_query_depth.rb +20 -0
- data/lib/graphql/analysis/query_complexity.rb +183 -0
- data/lib/graphql/analysis/query_depth.rb +58 -0
- data/lib/graphql/analysis/visitor.rb +283 -0
- data/lib/graphql/analysis.rb +92 -1
- data/lib/graphql/backtrace/inspect_result.rb +0 -12
- data/lib/graphql/backtrace/table.rb +2 -2
- data/lib/graphql/backtrace/trace.rb +93 -0
- data/lib/graphql/backtrace/tracer.rb +1 -1
- data/lib/graphql/backtrace.rb +2 -1
- data/lib/graphql/coercion_error.rb +1 -9
- data/lib/graphql/dataloader/async_dataloader.rb +88 -0
- data/lib/graphql/dataloader/null_dataloader.rb +1 -1
- data/lib/graphql/dataloader/request.rb +5 -0
- data/lib/graphql/dataloader/source.rb +89 -45
- data/lib/graphql/dataloader.rb +115 -142
- data/lib/graphql/duration_encoding_error.rb +16 -0
- data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
- data/lib/graphql/execution/interpreter/arguments.rb +1 -1
- data/lib/graphql/execution/interpreter/arguments_cache.rb +33 -33
- data/lib/graphql/execution/interpreter/resolve.rb +19 -0
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +175 -0
- data/lib/graphql/execution/interpreter/runtime.rb +331 -455
- data/lib/graphql/execution/interpreter.rb +125 -61
- data/lib/graphql/execution/lazy.rb +6 -12
- data/lib/graphql/execution/lookahead.rb +124 -46
- data/lib/graphql/execution/multiplex.rb +3 -117
- data/lib/graphql/execution.rb +0 -1
- data/lib/graphql/introspection/directive_type.rb +3 -3
- data/lib/graphql/introspection/dynamic_fields.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +11 -5
- data/lib/graphql/introspection/field_type.rb +2 -2
- data/lib/graphql/introspection/schema_type.rb +10 -13
- data/lib/graphql/introspection/type_type.rb +17 -10
- data/lib/graphql/introspection.rb +3 -2
- 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 +75 -59
- data/lib/graphql/language/lexer.rb +358 -1506
- data/lib/graphql/language/nodes.rb +166 -93
- data/lib/graphql/language/parser.rb +795 -1953
- data/lib/graphql/language/printer.rb +340 -160
- data/lib/graphql/language/sanitized_printer.rb +21 -23
- data/lib/graphql/language/static_visitor.rb +167 -0
- data/lib/graphql/language/visitor.rb +188 -141
- data/lib/graphql/language.rb +61 -1
- data/lib/graphql/load_application_object_failed_error.rb +5 -1
- data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
- data/lib/graphql/pagination/array_connection.rb +6 -6
- data/lib/graphql/pagination/connection.rb +33 -6
- data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
- data/lib/graphql/query/context/scoped_context.rb +101 -0
- data/lib/graphql/query/context.rb +117 -112
- data/lib/graphql/query/null_context.rb +12 -25
- data/lib/graphql/query/validation_pipeline.rb +6 -5
- data/lib/graphql/query/variables.rb +3 -3
- data/lib/graphql/query.rb +86 -30
- data/lib/graphql/railtie.rb +9 -6
- data/lib/graphql/rake_task.rb +29 -11
- data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
- data/lib/graphql/schema/addition.rb +59 -23
- data/lib/graphql/schema/always_visible.rb +11 -0
- data/lib/graphql/schema/argument.rb +55 -26
- data/lib/graphql/schema/base_64_encoder.rb +3 -5
- data/lib/graphql/schema/build_from_definition.rb +56 -32
- data/lib/graphql/schema/directive/one_of.rb +24 -0
- data/lib/graphql/schema/directive/specified_by.rb +14 -0
- data/lib/graphql/schema/directive/transform.rb +1 -1
- data/lib/graphql/schema/directive.rb +15 -3
- data/lib/graphql/schema/enum.rb +35 -24
- data/lib/graphql/schema/enum_value.rb +2 -3
- data/lib/graphql/schema/field/connection_extension.rb +2 -16
- data/lib/graphql/schema/field/scope_extension.rb +8 -1
- data/lib/graphql/schema/field.rb +147 -107
- data/lib/graphql/schema/field_extension.rb +1 -4
- data/lib/graphql/schema/find_inherited_value.rb +2 -7
- data/lib/graphql/schema/has_single_input_argument.rb +158 -0
- data/lib/graphql/schema/input_object.rb +47 -11
- data/lib/graphql/schema/interface.rb +15 -21
- data/lib/graphql/schema/introspection_system.rb +7 -17
- data/lib/graphql/schema/late_bound_type.rb +10 -0
- data/lib/graphql/schema/list.rb +2 -2
- data/lib/graphql/schema/loader.rb +2 -3
- data/lib/graphql/schema/member/base_dsl_methods.rb +18 -14
- data/lib/graphql/schema/member/build_type.rb +11 -3
- data/lib/graphql/schema/member/has_arguments.rb +170 -130
- data/lib/graphql/schema/member/has_ast_node.rb +12 -0
- data/lib/graphql/schema/member/has_deprecation_reason.rb +3 -4
- data/lib/graphql/schema/member/has_directives.rb +81 -61
- data/lib/graphql/schema/member/has_fields.rb +100 -38
- data/lib/graphql/schema/member/has_interfaces.rb +65 -10
- data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
- data/lib/graphql/schema/member/has_validators.rb +32 -6
- data/lib/graphql/schema/member/relay_shortcuts.rb +19 -0
- data/lib/graphql/schema/member/scoped.rb +19 -0
- data/lib/graphql/schema/member/type_system_helpers.rb +16 -0
- data/lib/graphql/schema/member/validates_input.rb +3 -3
- data/lib/graphql/schema/mutation.rb +7 -0
- data/lib/graphql/schema/object.rb +16 -5
- data/lib/graphql/schema/printer.rb +11 -8
- data/lib/graphql/schema/relay_classic_mutation.rb +7 -129
- data/lib/graphql/schema/resolver/has_payload_type.rb +9 -9
- data/lib/graphql/schema/resolver.rb +47 -32
- data/lib/graphql/schema/scalar.rb +3 -3
- data/lib/graphql/schema/subscription.rb +11 -4
- data/lib/graphql/schema/subset.rb +397 -0
- data/lib/graphql/schema/timeout.rb +25 -29
- data/lib/graphql/schema/type_expression.rb +2 -2
- data/lib/graphql/schema/type_membership.rb +3 -0
- data/lib/graphql/schema/union.rb +11 -2
- data/lib/graphql/schema/unique_within_type.rb +1 -1
- data/lib/graphql/schema/validator/all_validator.rb +60 -0
- data/lib/graphql/schema/validator.rb +4 -2
- data/lib/graphql/schema/warden.rb +238 -93
- data/lib/graphql/schema.rb +498 -103
- data/lib/graphql/static_validation/all_rules.rb +2 -1
- data/lib/graphql/static_validation/base_visitor.rb +7 -6
- data/lib/graphql/static_validation/definition_dependencies.rb +7 -1
- data/lib/graphql/static_validation/literal_validator.rb +24 -7
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -2
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -4
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +10 -10
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
- data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb +66 -0
- data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb +29 -0
- data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
- data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
- data/lib/graphql/static_validation/validation_context.rb +5 -5
- data/lib/graphql/static_validation/validator.rb +4 -1
- data/lib/graphql/static_validation.rb +0 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +11 -4
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
- data/lib/graphql/subscriptions/event.rb +11 -10
- data/lib/graphql/subscriptions/serialize.rb +2 -0
- data/lib/graphql/subscriptions.rb +20 -13
- data/lib/graphql/testing/helpers.rb +151 -0
- data/lib/graphql/testing.rb +2 -0
- data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
- data/lib/graphql/tracing/appoptics_trace.rb +251 -0
- data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
- data/lib/graphql/tracing/appsignal_trace.rb +77 -0
- data/lib/graphql/tracing/data_dog_trace.rb +183 -0
- data/lib/graphql/tracing/data_dog_tracing.rb +9 -21
- data/lib/graphql/{execution/instrumentation.rb → tracing/legacy_hooks_trace.rb} +10 -28
- data/lib/graphql/tracing/legacy_trace.rb +69 -0
- data/lib/graphql/tracing/new_relic_trace.rb +75 -0
- data/lib/graphql/tracing/notifications_trace.rb +45 -0
- data/lib/graphql/tracing/platform_trace.rb +118 -0
- data/lib/graphql/tracing/platform_tracing.rb +17 -3
- data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +4 -2
- data/lib/graphql/tracing/prometheus_trace.rb +89 -0
- data/lib/graphql/tracing/prometheus_tracing.rb +3 -3
- data/lib/graphql/tracing/scout_trace.rb +72 -0
- data/lib/graphql/tracing/sentry_trace.rb +112 -0
- data/lib/graphql/tracing/statsd_trace.rb +56 -0
- data/lib/graphql/tracing/trace.rb +76 -0
- data/lib/graphql/tracing.rb +20 -40
- data/lib/graphql/type_kinds.rb +7 -4
- data/lib/graphql/types/iso_8601_duration.rb +77 -0
- data/lib/graphql/types/relay/base_connection.rb +1 -1
- data/lib/graphql/types/relay/connection_behaviors.rb +68 -6
- data/lib/graphql/types/relay/edge_behaviors.rb +33 -5
- data/lib/graphql/types/relay/node_behaviors.rb +8 -2
- data/lib/graphql/types/relay/page_info_behaviors.rb +11 -2
- data/lib/graphql/types/relay.rb +0 -1
- data/lib/graphql/types/string.rb +1 -1
- data/lib/graphql/types.rb +1 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +27 -20
- data/readme.md +13 -3
- metadata +96 -47
- data/lib/graphql/analysis/ast/analyzer.rb +0 -84
- data/lib/graphql/analysis/ast/field_usage.rb +0 -57
- data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
- data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
- data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
- data/lib/graphql/analysis/ast/query_depth.rb +0 -55
- data/lib/graphql/analysis/ast/visitor.rb +0 -269
- data/lib/graphql/analysis/ast.rb +0 -81
- data/lib/graphql/deprecation.rb +0 -9
- data/lib/graphql/filter.rb +0 -53
- data/lib/graphql/language/lexer.rl +0 -280
- data/lib/graphql/language/parser.y +0 -554
- data/lib/graphql/language/token.rb +0 -34
- data/lib/graphql/schema/base_64_bp.rb +0 -26
- data/lib/graphql/schema/invalid_type_error.rb +0 -7
- data/lib/graphql/static_validation/type_stack.rb +0 -216
- data/lib/graphql/subscriptions/instrumentation.rb +0 -28
- data/lib/graphql/types/relay/default_relay.rb +0 -21
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
module Analysis
|
4
|
+
# A query reducer for measuring the depth of a given query.
|
5
|
+
#
|
6
|
+
# See https://graphql-ruby.org/queries/ast_analysis.html for more examples.
|
7
|
+
#
|
8
|
+
# @example Logging the depth of a query
|
9
|
+
# class LogQueryDepth < GraphQL::Analysis::QueryDepth
|
10
|
+
# def result
|
11
|
+
# log("GraphQL query depth: #{@max_depth}")
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# # In your Schema file:
|
16
|
+
#
|
17
|
+
# class MySchema < GraphQL::Schema
|
18
|
+
# query_analyzer LogQueryDepth
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # When you run the query, the depth will get logged:
|
22
|
+
#
|
23
|
+
# Schema.execute(query_str)
|
24
|
+
# # GraphQL query depth: 8
|
25
|
+
#
|
26
|
+
class QueryDepth < Analyzer
|
27
|
+
def initialize(query)
|
28
|
+
@max_depth = 0
|
29
|
+
@current_depth = 0
|
30
|
+
@count_introspection_fields = query.schema.count_introspection_fields
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
def on_enter_field(node, parent, visitor)
|
35
|
+
return if visitor.skipping? ||
|
36
|
+
visitor.visiting_fragment_definition? ||
|
37
|
+
(@count_introspection_fields == false && visitor.field_definition.introspection?)
|
38
|
+
|
39
|
+
@current_depth += 1
|
40
|
+
end
|
41
|
+
|
42
|
+
def on_leave_field(node, parent, visitor)
|
43
|
+
return if visitor.skipping? ||
|
44
|
+
visitor.visiting_fragment_definition? ||
|
45
|
+
(@count_introspection_fields == false && visitor.field_definition.introspection?)
|
46
|
+
|
47
|
+
if @max_depth < @current_depth
|
48
|
+
@max_depth = @current_depth
|
49
|
+
end
|
50
|
+
@current_depth -= 1
|
51
|
+
end
|
52
|
+
|
53
|
+
def result
|
54
|
+
@max_depth
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,283 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
module Analysis
|
4
|
+
# Depth first traversal through a query AST, calling AST analyzers
|
5
|
+
# along the way.
|
6
|
+
#
|
7
|
+
# The visitor is a special case of GraphQL::Language::StaticVisitor, visiting
|
8
|
+
# only the selected operation, providing helpers for common use cases such
|
9
|
+
# as skipped fields and visiting fragment spreads.
|
10
|
+
#
|
11
|
+
# @see {GraphQL::Analysis::Analyzer} AST Analyzers for queries
|
12
|
+
class Visitor < GraphQL::Language::StaticVisitor
|
13
|
+
def initialize(query:, analyzers:)
|
14
|
+
@analyzers = analyzers
|
15
|
+
@path = []
|
16
|
+
@object_types = []
|
17
|
+
@directives = []
|
18
|
+
@field_definitions = []
|
19
|
+
@argument_definitions = []
|
20
|
+
@directive_definitions = []
|
21
|
+
@rescued_errors = []
|
22
|
+
@query = query
|
23
|
+
@schema = query.schema
|
24
|
+
@types = query.types
|
25
|
+
@response_path = []
|
26
|
+
@skip_stack = [false]
|
27
|
+
super(query.selected_operation)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [GraphQL::Query] the query being visited
|
31
|
+
attr_reader :query
|
32
|
+
|
33
|
+
# @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
|
34
|
+
attr_reader :object_types
|
35
|
+
|
36
|
+
# @return [Array<GraphQL::AnalysisError]
|
37
|
+
attr_reader :rescued_errors
|
38
|
+
|
39
|
+
def visit
|
40
|
+
return unless @document
|
41
|
+
super
|
42
|
+
end
|
43
|
+
|
44
|
+
# Visit Helpers
|
45
|
+
|
46
|
+
# @return [GraphQL::Execution::Interpreter::Arguments] Arguments for this node, merging default values, literal values and query variables
|
47
|
+
# @see {GraphQL::Query#arguments_for}
|
48
|
+
def arguments_for(ast_node, field_definition)
|
49
|
+
@query.arguments_for(ast_node, field_definition)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [Boolean] If the visitor is currently inside a fragment definition
|
53
|
+
def visiting_fragment_definition?
|
54
|
+
@in_fragment_def
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Boolean] If the current node should be skipped because of a skip or include directive
|
58
|
+
def skipping?
|
59
|
+
@skipping
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [Array<String>] The path to the response key for the current field
|
63
|
+
def response_path
|
64
|
+
@response_path.dup
|
65
|
+
end
|
66
|
+
|
67
|
+
# Visitor Hooks
|
68
|
+
[
|
69
|
+
:operation_definition, :fragment_definition,
|
70
|
+
:inline_fragment, :field, :directive, :argument, :fragment_spread
|
71
|
+
].each do |node_type|
|
72
|
+
module_eval <<-RUBY, __FILE__, __LINE__
|
73
|
+
def call_on_enter_#{node_type}(node, parent)
|
74
|
+
@analyzers.each do |a|
|
75
|
+
begin
|
76
|
+
a.on_enter_#{node_type}(node, parent, self)
|
77
|
+
rescue AnalysisError => err
|
78
|
+
@rescued_errors << err
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def call_on_leave_#{node_type}(node, parent)
|
84
|
+
@analyzers.each do |a|
|
85
|
+
begin
|
86
|
+
a.on_leave_#{node_type}(node, parent, self)
|
87
|
+
rescue AnalysisError => err
|
88
|
+
@rescued_errors << err
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
RUBY
|
94
|
+
end
|
95
|
+
|
96
|
+
def on_operation_definition(node, parent)
|
97
|
+
object_type = @schema.root_type_for_operation(node.operation_type)
|
98
|
+
@object_types.push(object_type)
|
99
|
+
@path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
|
100
|
+
call_on_enter_operation_definition(node, parent)
|
101
|
+
super
|
102
|
+
call_on_leave_operation_definition(node, parent)
|
103
|
+
@object_types.pop
|
104
|
+
@path.pop
|
105
|
+
end
|
106
|
+
|
107
|
+
def on_fragment_definition(node, parent)
|
108
|
+
on_fragment_with_type(node) do
|
109
|
+
@path.push("fragment #{node.name}")
|
110
|
+
@in_fragment_def = false
|
111
|
+
call_on_enter_fragment_definition(node, parent)
|
112
|
+
super
|
113
|
+
@in_fragment_def = false
|
114
|
+
call_on_leave_fragment_definition(node, parent)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def on_inline_fragment(node, parent)
|
119
|
+
on_fragment_with_type(node) do
|
120
|
+
@path.push("...#{node.type ? " on #{node.type.name}" : ""}")
|
121
|
+
@skipping = @skip_stack.last || skip?(node)
|
122
|
+
@skip_stack << @skipping
|
123
|
+
|
124
|
+
call_on_enter_inline_fragment(node, parent)
|
125
|
+
super
|
126
|
+
@skipping = @skip_stack.pop
|
127
|
+
call_on_leave_inline_fragment(node, parent)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def on_field(node, parent)
|
132
|
+
@response_path.push(node.alias || node.name)
|
133
|
+
parent_type = @object_types.last
|
134
|
+
# This could be nil if the previous field wasn't found:
|
135
|
+
field_definition = parent_type && @types.field(parent_type, node.name)
|
136
|
+
@field_definitions.push(field_definition)
|
137
|
+
if !field_definition.nil?
|
138
|
+
next_object_type = field_definition.type.unwrap
|
139
|
+
@object_types.push(next_object_type)
|
140
|
+
else
|
141
|
+
@object_types.push(nil)
|
142
|
+
end
|
143
|
+
@path.push(node.alias || node.name)
|
144
|
+
|
145
|
+
@skipping = @skip_stack.last || skip?(node)
|
146
|
+
@skip_stack << @skipping
|
147
|
+
|
148
|
+
call_on_enter_field(node, parent)
|
149
|
+
super
|
150
|
+
@skipping = @skip_stack.pop
|
151
|
+
call_on_leave_field(node, parent)
|
152
|
+
@response_path.pop
|
153
|
+
@field_definitions.pop
|
154
|
+
@object_types.pop
|
155
|
+
@path.pop
|
156
|
+
end
|
157
|
+
|
158
|
+
def on_directive(node, parent)
|
159
|
+
directive_defn = @schema.directives[node.name]
|
160
|
+
@directive_definitions.push(directive_defn)
|
161
|
+
call_on_enter_directive(node, parent)
|
162
|
+
super
|
163
|
+
call_on_leave_directive(node, parent)
|
164
|
+
@directive_definitions.pop
|
165
|
+
end
|
166
|
+
|
167
|
+
def on_argument(node, parent)
|
168
|
+
argument_defn = if (arg = @argument_definitions.last)
|
169
|
+
arg_type = arg.type.unwrap
|
170
|
+
if arg_type.kind.input_object?
|
171
|
+
@types.argument(arg_type, node.name)
|
172
|
+
else
|
173
|
+
nil
|
174
|
+
end
|
175
|
+
elsif (directive_defn = @directive_definitions.last)
|
176
|
+
@types.argument(directive_defn, node.name)
|
177
|
+
elsif (field_defn = @field_definitions.last)
|
178
|
+
@types.argument(field_defn, node.name)
|
179
|
+
else
|
180
|
+
nil
|
181
|
+
end
|
182
|
+
|
183
|
+
@argument_definitions.push(argument_defn)
|
184
|
+
@path.push(node.name)
|
185
|
+
call_on_enter_argument(node, parent)
|
186
|
+
super
|
187
|
+
call_on_leave_argument(node, parent)
|
188
|
+
@argument_definitions.pop
|
189
|
+
@path.pop
|
190
|
+
end
|
191
|
+
|
192
|
+
def on_fragment_spread(node, parent)
|
193
|
+
@path.push("... #{node.name}")
|
194
|
+
@skipping = @skip_stack.last || skip?(node)
|
195
|
+
@skip_stack << @skipping
|
196
|
+
|
197
|
+
call_on_enter_fragment_spread(node, parent)
|
198
|
+
enter_fragment_spread_inline(node)
|
199
|
+
super
|
200
|
+
@skipping = @skip_stack.pop
|
201
|
+
leave_fragment_spread_inline(node)
|
202
|
+
call_on_leave_fragment_spread(node, parent)
|
203
|
+
@path.pop
|
204
|
+
end
|
205
|
+
|
206
|
+
# @return [GraphQL::BaseType] The current object type
|
207
|
+
def type_definition
|
208
|
+
@object_types.last
|
209
|
+
end
|
210
|
+
|
211
|
+
# @return [GraphQL::BaseType] The type which the current type came from
|
212
|
+
def parent_type_definition
|
213
|
+
@object_types[-2]
|
214
|
+
end
|
215
|
+
|
216
|
+
# @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
|
217
|
+
def field_definition
|
218
|
+
@field_definitions.last
|
219
|
+
end
|
220
|
+
|
221
|
+
# @return [GraphQL::Field, nil] The GraphQL field which returned the object that the current field belongs to
|
222
|
+
def previous_field_definition
|
223
|
+
@field_definitions[-2]
|
224
|
+
end
|
225
|
+
|
226
|
+
# @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
|
227
|
+
def directive_definition
|
228
|
+
@directive_definitions.last
|
229
|
+
end
|
230
|
+
|
231
|
+
# @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
|
232
|
+
def argument_definition
|
233
|
+
@argument_definitions.last
|
234
|
+
end
|
235
|
+
|
236
|
+
# @return [GraphQL::Argument, nil] The previous GraphQL argument
|
237
|
+
def previous_argument_definition
|
238
|
+
@argument_definitions[-2]
|
239
|
+
end
|
240
|
+
|
241
|
+
private
|
242
|
+
|
243
|
+
# Visit a fragment spread inline instead of visiting the definition
|
244
|
+
# by itself.
|
245
|
+
def enter_fragment_spread_inline(fragment_spread)
|
246
|
+
fragment_def = query.fragments[fragment_spread.name]
|
247
|
+
|
248
|
+
object_type = if fragment_def.type
|
249
|
+
@types.type(fragment_def.type.name)
|
250
|
+
else
|
251
|
+
object_types.last
|
252
|
+
end
|
253
|
+
|
254
|
+
object_types << object_type
|
255
|
+
|
256
|
+
on_fragment_definition_children(fragment_def)
|
257
|
+
end
|
258
|
+
|
259
|
+
# Visit a fragment spread inline instead of visiting the definition
|
260
|
+
# by itself.
|
261
|
+
def leave_fragment_spread_inline(_fragment_spread)
|
262
|
+
object_types.pop
|
263
|
+
end
|
264
|
+
|
265
|
+
def skip?(ast_node)
|
266
|
+
dir = ast_node.directives
|
267
|
+
dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
|
268
|
+
end
|
269
|
+
|
270
|
+
def on_fragment_with_type(node)
|
271
|
+
object_type = if node.type
|
272
|
+
@types.type(node.type.name)
|
273
|
+
else
|
274
|
+
@object_types.last
|
275
|
+
end
|
276
|
+
@object_types.push(object_type)
|
277
|
+
yield(node)
|
278
|
+
@object_types.pop
|
279
|
+
@path.pop
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
data/lib/graphql/analysis.rb
CHANGED
@@ -1,2 +1,93 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require "graphql/analysis/
|
2
|
+
require "graphql/analysis/visitor"
|
3
|
+
require "graphql/analysis/analyzer"
|
4
|
+
require "graphql/analysis/field_usage"
|
5
|
+
require "graphql/analysis/query_complexity"
|
6
|
+
require "graphql/analysis/max_query_complexity"
|
7
|
+
require "graphql/analysis/query_depth"
|
8
|
+
require "graphql/analysis/max_query_depth"
|
9
|
+
require "timeout"
|
10
|
+
|
11
|
+
module GraphQL
|
12
|
+
module Analysis
|
13
|
+
AST = self
|
14
|
+
module_function
|
15
|
+
# Analyze a multiplex, and all queries within.
|
16
|
+
# Multiplex analyzers are ran for all queries, keeping state.
|
17
|
+
# Query analyzers are ran per query, without carrying state between queries.
|
18
|
+
#
|
19
|
+
# @param multiplex [GraphQL::Execution::Multiplex]
|
20
|
+
# @param analyzers [Array<GraphQL::Analysis::Analyzer>]
|
21
|
+
# @return [Array<Any>] Results from multiplex analyzers
|
22
|
+
def analyze_multiplex(multiplex, analyzers)
|
23
|
+
multiplex_analyzers = analyzers.map { |analyzer| analyzer.new(multiplex) }
|
24
|
+
|
25
|
+
multiplex.current_trace.analyze_multiplex(multiplex: multiplex) do
|
26
|
+
query_results = multiplex.queries.map do |query|
|
27
|
+
if query.valid?
|
28
|
+
analyze_query(
|
29
|
+
query,
|
30
|
+
query.analyzers,
|
31
|
+
multiplex_analyzers: multiplex_analyzers
|
32
|
+
)
|
33
|
+
else
|
34
|
+
[]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
multiplex_results = multiplex_analyzers.map(&:result)
|
39
|
+
multiplex_errors = analysis_errors(multiplex_results)
|
40
|
+
|
41
|
+
multiplex.queries.each_with_index do |query, idx|
|
42
|
+
query.analysis_errors = multiplex_errors + analysis_errors(query_results[idx])
|
43
|
+
end
|
44
|
+
multiplex_results
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# @param query [GraphQL::Query]
|
49
|
+
# @param analyzers [Array<GraphQL::Analysis::Analyzer>]
|
50
|
+
# @return [Array<Any>] Results from those analyzers
|
51
|
+
def analyze_query(query, analyzers, multiplex_analyzers: [])
|
52
|
+
query.current_trace.analyze_query(query: query) do
|
53
|
+
query_analyzers = analyzers
|
54
|
+
.map { |analyzer| analyzer.new(query) }
|
55
|
+
.tap { _1.select!(&:analyze?) }
|
56
|
+
|
57
|
+
analyzers_to_run = query_analyzers + multiplex_analyzers
|
58
|
+
if analyzers_to_run.any?
|
59
|
+
|
60
|
+
analyzers_to_run.select!(&:visit?)
|
61
|
+
if analyzers_to_run.any?
|
62
|
+
visitor = GraphQL::Analysis::Visitor.new(
|
63
|
+
query: query,
|
64
|
+
analyzers: analyzers_to_run
|
65
|
+
)
|
66
|
+
|
67
|
+
# `nil` or `0` causes no timeout
|
68
|
+
Timeout::timeout(query.validate_timeout_remaining) do
|
69
|
+
visitor.visit
|
70
|
+
end
|
71
|
+
|
72
|
+
if visitor.rescued_errors.any?
|
73
|
+
return visitor.rescued_errors
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
query_analyzers.map(&:result)
|
78
|
+
else
|
79
|
+
[]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
rescue Timeout::Error
|
83
|
+
[GraphQL::AnalysisError.new("Timeout on validation of query")]
|
84
|
+
rescue GraphQL::UnauthorizedError
|
85
|
+
# This error was raised during analysis and will be returned the client before execution
|
86
|
+
[]
|
87
|
+
end
|
88
|
+
|
89
|
+
def analysis_errors(results)
|
90
|
+
results.flatten.tap { _1.select! { |r| r.is_a?(GraphQL::AnalysisError) } }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -16,12 +16,6 @@ module GraphQL
|
|
16
16
|
"[" +
|
17
17
|
obj.map { |v| inspect_truncated(v) }.join(", ") +
|
18
18
|
"]"
|
19
|
-
when Query::Context::SharedMethods
|
20
|
-
if obj.invalid_null?
|
21
|
-
"nil"
|
22
|
-
else
|
23
|
-
inspect_truncated(obj.value)
|
24
|
-
end
|
25
19
|
else
|
26
20
|
inspect_truncated(obj)
|
27
21
|
end
|
@@ -33,12 +27,6 @@ module GraphQL
|
|
33
27
|
"{...}"
|
34
28
|
when Array
|
35
29
|
"[...]"
|
36
|
-
when Query::Context::SharedMethods
|
37
|
-
if obj.invalid_null?
|
38
|
-
"nil"
|
39
|
-
else
|
40
|
-
inspect_truncated(obj.value)
|
41
|
-
end
|
42
30
|
when GraphQL::Execution::Lazy
|
43
31
|
"(unresolved)"
|
44
32
|
else
|
@@ -83,7 +83,7 @@ module GraphQL
|
|
83
83
|
value = if top && @override_value
|
84
84
|
@override_value
|
85
85
|
else
|
86
|
-
value_at(@context.query.context.namespace(:
|
86
|
+
value_at(@context.query.context.namespace(:interpreter_runtime)[:runtime], context_entry.path)
|
87
87
|
end
|
88
88
|
rows << [
|
89
89
|
"#{context_entry.ast_node ? context_entry.ast_node.position.join(":") : ""}",
|
@@ -112,7 +112,7 @@ module GraphQL
|
|
112
112
|
if object.is_a?(GraphQL::Schema::Object)
|
113
113
|
object = object.object
|
114
114
|
end
|
115
|
-
value = value_at(context_entry.namespace(:
|
115
|
+
value = value_at(context_entry.namespace(:interpreter_runtime)[:runtime], [])
|
116
116
|
rows << [
|
117
117
|
"#{position}",
|
118
118
|
"#{op_type}#{op_name ? " #{op_name}" : ""}",
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
class Backtrace
|
4
|
+
module Trace
|
5
|
+
def initialize(*args, **kwargs, &block)
|
6
|
+
@__backtrace_contexts = {}
|
7
|
+
@__backtrace_last_context = nil
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate(query:, validate:)
|
12
|
+
if query.multiplex
|
13
|
+
push_query_backtrace_context(query)
|
14
|
+
end
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def analyze_query(query:)
|
19
|
+
if query.multiplex # missing for stand-alone static validation
|
20
|
+
push_query_backtrace_context(query)
|
21
|
+
end
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
def execute_query(query:)
|
26
|
+
push_query_backtrace_context(query)
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def execute_query_lazy(query:, multiplex:)
|
31
|
+
query ||= multiplex.queries.first
|
32
|
+
push_query_backtrace_context(query)
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def execute_field(field:, query:, ast_node:, arguments:, object:)
|
37
|
+
push_field_backtrace_context(field, query, ast_node, arguments, object)
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
def execute_field_lazy(field:, query:, ast_node:, arguments:, object:)
|
42
|
+
push_field_backtrace_context(field, query, ast_node, arguments, object)
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute_multiplex(multiplex:)
|
47
|
+
super
|
48
|
+
rescue StandardError => err
|
49
|
+
# This is an unhandled error from execution,
|
50
|
+
# Re-raise it with a GraphQL trace.
|
51
|
+
potential_context = @__backtrace_last_context
|
52
|
+
if potential_context.is_a?(GraphQL::Query::Context) ||
|
53
|
+
potential_context.is_a?(Backtrace::Frame)
|
54
|
+
raise TracedError.new(err, potential_context)
|
55
|
+
else
|
56
|
+
raise
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def push_query_backtrace_context(query)
|
63
|
+
push_data = query
|
64
|
+
push_key = []
|
65
|
+
@__backtrace_contexts[push_key] = push_data
|
66
|
+
@__backtrace_last_context = push_data
|
67
|
+
end
|
68
|
+
|
69
|
+
def push_field_backtrace_context(field, query, ast_node, arguments, object)
|
70
|
+
push_key = query.context[:current_path]
|
71
|
+
push_storage = @__backtrace_contexts
|
72
|
+
parent_frame = push_storage[push_key[0..-2]]
|
73
|
+
|
74
|
+
if parent_frame.is_a?(GraphQL::Query)
|
75
|
+
parent_frame = parent_frame.context
|
76
|
+
end
|
77
|
+
|
78
|
+
push_data = Frame.new(
|
79
|
+
query: query,
|
80
|
+
path: push_key,
|
81
|
+
ast_node: ast_node,
|
82
|
+
field: field,
|
83
|
+
object: object,
|
84
|
+
arguments: arguments,
|
85
|
+
parent_frame: parent_frame,
|
86
|
+
)
|
87
|
+
push_storage[push_key] = push_data
|
88
|
+
@__backtrace_last_context = push_data
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -25,7 +25,7 @@ module GraphQL
|
|
25
25
|
when "execute_field", "execute_field_lazy"
|
26
26
|
query = metadata[:query]
|
27
27
|
multiplex = query.multiplex
|
28
|
-
push_key =
|
28
|
+
push_key = query.context[:current_path]
|
29
29
|
parent_frame = multiplex.context[:graphql_backtrace_contexts][push_key[0..-2]]
|
30
30
|
|
31
31
|
if parent_frame.is_a?(GraphQL::Query)
|
data/lib/graphql/backtrace.rb
CHANGED
@@ -3,6 +3,7 @@ require "graphql/backtrace/inspect_result"
|
|
3
3
|
require "graphql/backtrace/table"
|
4
4
|
require "graphql/backtrace/traced_error"
|
5
5
|
require "graphql/backtrace/tracer"
|
6
|
+
require "graphql/backtrace/trace"
|
6
7
|
module GraphQL
|
7
8
|
# Wrap unhandled errors with {TracedError}.
|
8
9
|
#
|
@@ -23,7 +24,7 @@ module GraphQL
|
|
23
24
|
def_delegators :to_a, :each, :[]
|
24
25
|
|
25
26
|
def self.use(schema_defn)
|
26
|
-
schema_defn.
|
27
|
+
schema_defn.trace_with(self::Trace)
|
27
28
|
end
|
28
29
|
|
29
30
|
def initialize(context, value: nil)
|
@@ -1,13 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module GraphQL
|
3
|
-
class CoercionError < GraphQL::
|
4
|
-
# @return [Hash] Optional custom data for error objects which will be added
|
5
|
-
# under the `extensions` key.
|
6
|
-
attr_accessor :extensions
|
7
|
-
|
8
|
-
def initialize(message, extensions: nil)
|
9
|
-
@extensions = extensions
|
10
|
-
super(message)
|
11
|
-
end
|
3
|
+
class CoercionError < GraphQL::ExecutionError
|
12
4
|
end
|
13
5
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
class Dataloader
|
4
|
+
class AsyncDataloader < Dataloader
|
5
|
+
def yield
|
6
|
+
if (condition = Thread.current[:graphql_dataloader_next_tick])
|
7
|
+
condition.wait
|
8
|
+
else
|
9
|
+
Fiber.yield
|
10
|
+
end
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
job_fibers = []
|
16
|
+
next_job_fibers = []
|
17
|
+
source_tasks = []
|
18
|
+
next_source_tasks = []
|
19
|
+
first_pass = true
|
20
|
+
sources_condition = Async::Condition.new
|
21
|
+
manager = spawn_fiber do
|
22
|
+
while first_pass || job_fibers.any?
|
23
|
+
first_pass = false
|
24
|
+
fiber_vars = get_fiber_variables
|
25
|
+
|
26
|
+
while (f = (job_fibers.shift || spawn_job_fiber))
|
27
|
+
if f.alive?
|
28
|
+
finished = run_fiber(f)
|
29
|
+
if !finished
|
30
|
+
next_job_fibers << f
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
job_fibers.concat(next_job_fibers)
|
35
|
+
next_job_fibers.clear
|
36
|
+
|
37
|
+
Sync do |root_task|
|
38
|
+
set_fiber_variables(fiber_vars)
|
39
|
+
while source_tasks.any? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }
|
40
|
+
while (task = source_tasks.shift || spawn_source_task(root_task, sources_condition))
|
41
|
+
if task.alive?
|
42
|
+
root_task.yield # give the source task a chance to run
|
43
|
+
next_source_tasks << task
|
44
|
+
end
|
45
|
+
end
|
46
|
+
sources_condition.signal
|
47
|
+
source_tasks.concat(next_source_tasks)
|
48
|
+
next_source_tasks.clear
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
manager.resume
|
55
|
+
if manager.alive?
|
56
|
+
raise "Invariant: Manager didn't terminate successfully: #{manager}"
|
57
|
+
end
|
58
|
+
|
59
|
+
rescue UncaughtThrowError => e
|
60
|
+
throw e.tag, e.value
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def spawn_source_task(parent_task, condition)
|
66
|
+
pending_sources = nil
|
67
|
+
@source_cache.each_value do |source_by_batch_params|
|
68
|
+
source_by_batch_params.each_value do |source|
|
69
|
+
if source.pending?
|
70
|
+
pending_sources ||= []
|
71
|
+
pending_sources << source
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
if pending_sources
|
77
|
+
fiber_vars = get_fiber_variables
|
78
|
+
parent_task.async do
|
79
|
+
set_fiber_variables(fiber_vars)
|
80
|
+
Thread.current[:graphql_dataloader_next_tick] = condition
|
81
|
+
pending_sources.each(&:run_pending_keys)
|
82
|
+
cleanup_fiber
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|