graphql 2.2.17 → 2.5.16
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/mutation_root_generator.rb +2 -2
- data/lib/generators/graphql/install_generator.rb +46 -0
- data/lib/generators/graphql/orm_mutations_base.rb +1 -1
- data/lib/generators/graphql/templates/base_resolver.erb +2 -0
- data/lib/generators/graphql/templates/schema.erb +3 -0
- data/lib/generators/graphql/type_generator.rb +1 -1
- data/lib/graphql/analysis/analyzer.rb +90 -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 +263 -0
- data/lib/graphql/analysis/{ast/query_depth.rb → query_depth.rb} +23 -25
- data/lib/graphql/analysis/visitor.rb +280 -0
- data/lib/graphql/analysis.rb +95 -1
- data/lib/graphql/autoload.rb +38 -0
- data/lib/graphql/backtrace/table.rb +118 -55
- data/lib/graphql/backtrace.rb +1 -19
- data/lib/graphql/current.rb +57 -0
- 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/bootstrap-5.3.3.min.css +6 -0
- data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
- data/lib/graphql/dashboard/statics/charts.min.css +1 -0
- data/lib/graphql/dashboard/statics/dashboard.css +30 -0
- data/lib/graphql/dashboard/statics/dashboard.js +143 -0
- data/lib/graphql/dashboard/statics/header-icon.png +0 -0
- data/lib/graphql/dashboard/statics/icon.png +0 -0
- 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/landings/show.html.erb +18 -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 +108 -0
- data/lib/graphql/dashboard.rb +158 -0
- data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
- data/lib/graphql/dataloader/active_record_source.rb +47 -0
- data/lib/graphql/dataloader/async_dataloader.rb +46 -19
- data/lib/graphql/dataloader/null_dataloader.rb +51 -10
- data/lib/graphql/dataloader/source.rb +20 -9
- data/lib/graphql/dataloader.rb +153 -45
- data/lib/graphql/date_encoding_error.rb +1 -1
- data/lib/graphql/dig.rb +2 -1
- data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
- data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
- data/lib/graphql/execution/interpreter/resolve.rb +23 -25
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +63 -5
- data/lib/graphql/execution/interpreter/runtime.rb +321 -222
- data/lib/graphql/execution/interpreter.rb +23 -30
- data/lib/graphql/execution/lookahead.rb +18 -11
- data/lib/graphql/execution/multiplex.rb +6 -5
- data/lib/graphql/introspection/directive_location_enum.rb +1 -1
- data/lib/graphql/introspection/directive_type.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/introspection/field_type.rb +1 -1
- data/lib/graphql/introspection/schema_type.rb +6 -11
- data/lib/graphql/introspection/type_type.rb +5 -5
- data/lib/graphql/invalid_name_error.rb +1 -1
- data/lib/graphql/invalid_null_error.rb +20 -17
- data/lib/graphql/language/cache.rb +13 -0
- data/lib/graphql/language/comment.rb +18 -0
- data/lib/graphql/language/document_from_schema_definition.rb +64 -35
- data/lib/graphql/language/lexer.rb +72 -42
- data/lib/graphql/language/nodes.rb +93 -52
- data/lib/graphql/language/parser.rb +168 -61
- data/lib/graphql/language/printer.rb +31 -15
- data/lib/graphql/language/sanitized_printer.rb +1 -1
- data/lib/graphql/language.rb +61 -1
- data/lib/graphql/pagination/connection.rb +1 -1
- data/lib/graphql/query/context/scoped_context.rb +1 -1
- data/lib/graphql/query/context.rb +46 -47
- data/lib/graphql/query/null_context.rb +3 -5
- data/lib/graphql/query/partial.rb +179 -0
- data/lib/graphql/query/validation_pipeline.rb +2 -2
- data/lib/graphql/query/variable_validation_error.rb +1 -1
- data/lib/graphql/query.rb +123 -69
- data/lib/graphql/railtie.rb +7 -0
- data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
- data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
- data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
- data/lib/graphql/rubocop.rb +2 -0
- data/lib/graphql/schema/addition.rb +26 -13
- data/lib/graphql/schema/always_visible.rb +7 -2
- data/lib/graphql/schema/argument.rb +57 -8
- data/lib/graphql/schema/build_from_definition.rb +116 -49
- data/lib/graphql/schema/directive/flagged.rb +4 -2
- data/lib/graphql/schema/directive.rb +54 -2
- data/lib/graphql/schema/enum.rb +107 -24
- data/lib/graphql/schema/enum_value.rb +10 -2
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field/scope_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +134 -45
- data/lib/graphql/schema/field_extension.rb +1 -1
- data/lib/graphql/schema/has_single_input_argument.rb +6 -2
- data/lib/graphql/schema/input_object.rb +122 -64
- data/lib/graphql/schema/interface.rb +23 -5
- data/lib/graphql/schema/introspection_system.rb +6 -17
- data/lib/graphql/schema/late_bound_type.rb +4 -0
- data/lib/graphql/schema/list.rb +3 -3
- data/lib/graphql/schema/loader.rb +3 -2
- data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
- data/lib/graphql/schema/member/has_arguments.rb +44 -58
- data/lib/graphql/schema/member/has_dataloader.rb +62 -0
- data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
- data/lib/graphql/schema/member/has_directives.rb +4 -4
- data/lib/graphql/schema/member/has_fields.rb +26 -6
- data/lib/graphql/schema/member/has_interfaces.rb +6 -6
- data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
- data/lib/graphql/schema/member/has_validators.rb +1 -1
- data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
- data/lib/graphql/schema/member/type_system_helpers.rb +17 -4
- data/lib/graphql/schema/member.rb +1 -0
- data/lib/graphql/schema/mutation.rb +7 -0
- data/lib/graphql/schema/object.rb +25 -8
- data/lib/graphql/schema/printer.rb +1 -0
- data/lib/graphql/schema/ractor_shareable.rb +79 -0
- data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
- data/lib/graphql/schema/resolver.rb +29 -23
- data/lib/graphql/schema/scalar.rb +1 -6
- data/lib/graphql/schema/subscription.rb +52 -6
- data/lib/graphql/schema/timeout.rb +19 -2
- data/lib/graphql/schema/type_expression.rb +2 -2
- data/lib/graphql/schema/union.rb +1 -1
- data/lib/graphql/schema/validator/all_validator.rb +62 -0
- data/lib/graphql/schema/validator/required_validator.rb +92 -11
- data/lib/graphql/schema/validator.rb +3 -1
- data/lib/graphql/schema/visibility/migration.rb +188 -0
- data/lib/graphql/schema/visibility/profile.rb +445 -0
- data/lib/graphql/schema/visibility/visit.rb +190 -0
- data/lib/graphql/schema/visibility.rb +311 -0
- data/lib/graphql/schema/warden.rb +190 -20
- data/lib/graphql/schema.rb +695 -167
- data/lib/graphql/static_validation/all_rules.rb +2 -2
- data/lib/graphql/static_validation/base_visitor.rb +6 -5
- data/lib/graphql/static_validation/literal_validator.rb +4 -4
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +88 -25
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
- 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/no_definitions_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -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 +3 -3
- 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 +7 -3
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
- data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
- data/lib/graphql/static_validation/validation_context.rb +18 -2
- data/lib/graphql/static_validation/validator.rb +6 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +5 -3
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
- data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
- data/lib/graphql/subscriptions/event.rb +13 -2
- data/lib/graphql/subscriptions/serialize.rb +1 -1
- data/lib/graphql/subscriptions.rb +7 -5
- data/lib/graphql/testing/helpers.rb +48 -16
- data/lib/graphql/testing/mock_action_cable.rb +111 -0
- data/lib/graphql/testing.rb +1 -0
- data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
- data/lib/graphql/tracing/appoptics_trace.rb +5 -1
- data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
- data/lib/graphql/tracing/appsignal_trace.rb +32 -59
- data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
- data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
- data/lib/graphql/tracing/data_dog_trace.rb +46 -162
- data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
- data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
- data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
- data/lib/graphql/tracing/detailed_trace.rb +141 -0
- data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
- data/lib/graphql/tracing/legacy_trace.rb +4 -61
- data/lib/graphql/tracing/monitor_trace.rb +283 -0
- data/lib/graphql/tracing/new_relic_trace.rb +47 -54
- data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
- data/lib/graphql/tracing/notifications_trace.rb +183 -37
- data/lib/graphql/tracing/notifications_tracing.rb +2 -0
- data/lib/graphql/tracing/null_trace.rb +9 -0
- data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
- data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
- data/lib/graphql/tracing/perfetto_trace.rb +818 -0
- data/lib/graphql/tracing/platform_tracing.rb +1 -1
- data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
- data/lib/graphql/tracing/prometheus_trace.rb +73 -73
- data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
- data/lib/graphql/tracing/scout_trace.rb +32 -58
- data/lib/graphql/tracing/scout_tracing.rb +2 -0
- data/lib/graphql/tracing/sentry_trace.rb +64 -98
- data/lib/graphql/tracing/statsd_trace.rb +33 -45
- data/lib/graphql/tracing/statsd_tracing.rb +2 -0
- data/lib/graphql/tracing/trace.rb +111 -1
- data/lib/graphql/tracing.rb +31 -30
- data/lib/graphql/type_kinds.rb +2 -1
- data/lib/graphql/types/relay/connection_behaviors.rb +12 -2
- data/lib/graphql/types/relay/edge_behaviors.rb +11 -1
- data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
- data/lib/graphql/types.rb +18 -11
- data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +64 -54
- metadata +197 -22
- data/lib/graphql/analysis/ast/analyzer.rb +0 -91
- data/lib/graphql/analysis/ast/field_usage.rb +0 -82
- 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 -182
- data/lib/graphql/analysis/ast/visitor.rb +0 -276
- data/lib/graphql/analysis/ast.rb +0 -94
- data/lib/graphql/backtrace/inspect_result.rb +0 -50
- data/lib/graphql/backtrace/trace.rb +0 -93
- data/lib/graphql/backtrace/tracer.rb +0 -80
- data/lib/graphql/language/token.rb +0 -34
- data/lib/graphql/schema/invalid_type_error.rb +0 -7
- data/lib/graphql/schema/null_mask.rb +0 -11
- data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
|
@@ -0,0 +1,280 @@
|
|
|
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:, timeout:)
|
|
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
|
+
@timeout_time = if timeout
|
|
28
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) + timeout
|
|
29
|
+
else
|
|
30
|
+
Float::INFINITY
|
|
31
|
+
end
|
|
32
|
+
super(query.selected_operation)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [GraphQL::Query] the query being visited
|
|
36
|
+
attr_reader :query
|
|
37
|
+
|
|
38
|
+
# @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
|
|
39
|
+
attr_reader :object_types
|
|
40
|
+
|
|
41
|
+
# @return [Array<GraphQL::AnalysisError]
|
|
42
|
+
attr_reader :rescued_errors
|
|
43
|
+
|
|
44
|
+
def visit
|
|
45
|
+
return unless @document
|
|
46
|
+
super
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Visit Helpers
|
|
50
|
+
|
|
51
|
+
# @return [GraphQL::Execution::Interpreter::Arguments] Arguments for this node, merging default values, literal values and query variables
|
|
52
|
+
# @see {GraphQL::Query#arguments_for}
|
|
53
|
+
def arguments_for(ast_node, field_definition)
|
|
54
|
+
@query.arguments_for(ast_node, field_definition)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [Boolean] If the visitor is currently inside a fragment definition
|
|
58
|
+
def visiting_fragment_definition?
|
|
59
|
+
@in_fragment_def
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @return [Boolean] If the current node should be skipped because of a skip or include directive
|
|
63
|
+
def skipping?
|
|
64
|
+
@skipping
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @return [Array<String>] The path to the response key for the current field
|
|
68
|
+
def response_path
|
|
69
|
+
@response_path.dup
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
|
|
73
|
+
# Visitor Hooks
|
|
74
|
+
[
|
|
75
|
+
:operation_definition, :fragment_definition,
|
|
76
|
+
:inline_fragment, :field, :directive, :argument, :fragment_spread
|
|
77
|
+
].each do |node_type|
|
|
78
|
+
module_eval <<-RUBY, __FILE__, __LINE__
|
|
79
|
+
def call_on_enter_#{node_type}(node, parent)
|
|
80
|
+
@analyzers.each do |a|
|
|
81
|
+
a.on_enter_#{node_type}(node, parent, self)
|
|
82
|
+
rescue AnalysisError => err
|
|
83
|
+
@rescued_errors << err
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def call_on_leave_#{node_type}(node, parent)
|
|
88
|
+
@analyzers.each do |a|
|
|
89
|
+
a.on_leave_#{node_type}(node, parent, self)
|
|
90
|
+
rescue AnalysisError => err
|
|
91
|
+
@rescued_errors << err
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
RUBY
|
|
96
|
+
end
|
|
97
|
+
# rubocop:enable Development/NoEvalCop
|
|
98
|
+
|
|
99
|
+
def on_operation_definition(node, parent)
|
|
100
|
+
check_timeout
|
|
101
|
+
object_type = @schema.root_type_for_operation(node.operation_type)
|
|
102
|
+
@object_types.push(object_type)
|
|
103
|
+
@path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
|
|
104
|
+
call_on_enter_operation_definition(node, parent)
|
|
105
|
+
super
|
|
106
|
+
call_on_leave_operation_definition(node, parent)
|
|
107
|
+
@object_types.pop
|
|
108
|
+
@path.pop
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def on_inline_fragment(node, parent)
|
|
112
|
+
check_timeout
|
|
113
|
+
object_type = if node.type
|
|
114
|
+
@types.type(node.type.name)
|
|
115
|
+
else
|
|
116
|
+
@object_types.last
|
|
117
|
+
end
|
|
118
|
+
@object_types.push(object_type)
|
|
119
|
+
@path.push("...#{node.type ? " on #{node.type.name}" : ""}")
|
|
120
|
+
@skipping = @skip_stack.last || skip?(node)
|
|
121
|
+
@skip_stack << @skipping
|
|
122
|
+
call_on_enter_inline_fragment(node, parent)
|
|
123
|
+
super
|
|
124
|
+
@skipping = @skip_stack.pop
|
|
125
|
+
call_on_leave_inline_fragment(node, parent)
|
|
126
|
+
@object_types.pop
|
|
127
|
+
@path.pop
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def on_field(node, parent)
|
|
131
|
+
check_timeout
|
|
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
|
+
check_timeout
|
|
160
|
+
directive_defn = @schema.directives[node.name]
|
|
161
|
+
@directive_definitions.push(directive_defn)
|
|
162
|
+
call_on_enter_directive(node, parent)
|
|
163
|
+
super
|
|
164
|
+
call_on_leave_directive(node, parent)
|
|
165
|
+
@directive_definitions.pop
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def on_argument(node, parent)
|
|
169
|
+
check_timeout
|
|
170
|
+
argument_defn = if (arg = @argument_definitions.last)
|
|
171
|
+
arg_type = arg.type.unwrap
|
|
172
|
+
if arg_type.kind.input_object?
|
|
173
|
+
@types.argument(arg_type, node.name)
|
|
174
|
+
else
|
|
175
|
+
nil
|
|
176
|
+
end
|
|
177
|
+
elsif (directive_defn = @directive_definitions.last)
|
|
178
|
+
@types.argument(directive_defn, node.name)
|
|
179
|
+
elsif (field_defn = @field_definitions.last)
|
|
180
|
+
@types.argument(field_defn, node.name)
|
|
181
|
+
else
|
|
182
|
+
nil
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
@argument_definitions.push(argument_defn)
|
|
186
|
+
@path.push(node.name)
|
|
187
|
+
call_on_enter_argument(node, parent)
|
|
188
|
+
super
|
|
189
|
+
call_on_leave_argument(node, parent)
|
|
190
|
+
@argument_definitions.pop
|
|
191
|
+
@path.pop
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def on_fragment_spread(node, parent)
|
|
195
|
+
check_timeout
|
|
196
|
+
@path.push("... #{node.name}")
|
|
197
|
+
@skipping = @skip_stack.last || skip?(node)
|
|
198
|
+
@skip_stack << @skipping
|
|
199
|
+
|
|
200
|
+
call_on_enter_fragment_spread(node, parent)
|
|
201
|
+
enter_fragment_spread_inline(node)
|
|
202
|
+
super
|
|
203
|
+
@skipping = @skip_stack.pop
|
|
204
|
+
leave_fragment_spread_inline(node)
|
|
205
|
+
call_on_leave_fragment_spread(node, parent)
|
|
206
|
+
@path.pop
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# @return [GraphQL::BaseType] The current object type
|
|
210
|
+
def type_definition
|
|
211
|
+
@object_types.last
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# @return [GraphQL::BaseType] The type which the current type came from
|
|
215
|
+
def parent_type_definition
|
|
216
|
+
@object_types[-2]
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
|
|
220
|
+
def field_definition
|
|
221
|
+
@field_definitions.last
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# @return [GraphQL::Field, nil] The GraphQL field which returned the object that the current field belongs to
|
|
225
|
+
def previous_field_definition
|
|
226
|
+
@field_definitions[-2]
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
|
|
230
|
+
def directive_definition
|
|
231
|
+
@directive_definitions.last
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
|
|
235
|
+
def argument_definition
|
|
236
|
+
@argument_definitions.last
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# @return [GraphQL::Argument, nil] The previous GraphQL argument
|
|
240
|
+
def previous_argument_definition
|
|
241
|
+
@argument_definitions[-2]
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
private
|
|
245
|
+
|
|
246
|
+
# Visit a fragment spread inline instead of visiting the definition
|
|
247
|
+
# by itself.
|
|
248
|
+
def enter_fragment_spread_inline(fragment_spread)
|
|
249
|
+
fragment_def = query.fragments[fragment_spread.name]
|
|
250
|
+
|
|
251
|
+
object_type = if fragment_def.type
|
|
252
|
+
@types.type(fragment_def.type.name)
|
|
253
|
+
else
|
|
254
|
+
object_types.last
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
object_types << object_type
|
|
258
|
+
|
|
259
|
+
on_fragment_definition_children(fragment_def)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Visit a fragment spread inline instead of visiting the definition
|
|
263
|
+
# by itself.
|
|
264
|
+
def leave_fragment_spread_inline(_fragment_spread)
|
|
265
|
+
object_types.pop
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def skip?(ast_node)
|
|
269
|
+
dir = ast_node.directives
|
|
270
|
+
!dir.empty? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def check_timeout
|
|
274
|
+
if Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) > @timeout_time
|
|
275
|
+
raise GraphQL::Analysis::TimeoutError
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
data/lib/graphql/analysis.rb
CHANGED
|
@@ -1,2 +1,96 @@
|
|
|
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
|
+
module GraphQL
|
|
10
|
+
module Analysis
|
|
11
|
+
AST = self
|
|
12
|
+
|
|
13
|
+
class TimeoutError < AnalysisError
|
|
14
|
+
def initialize(...)
|
|
15
|
+
super("Timeout on validation of query")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module_function
|
|
20
|
+
# Analyze a multiplex, and all queries within.
|
|
21
|
+
# Multiplex analyzers are ran for all queries, keeping state.
|
|
22
|
+
# Query analyzers are ran per query, without carrying state between queries.
|
|
23
|
+
#
|
|
24
|
+
# @param multiplex [GraphQL::Execution::Multiplex]
|
|
25
|
+
# @param analyzers [Array<GraphQL::Analysis::Analyzer>]
|
|
26
|
+
# @return [Array<Any>] Results from multiplex analyzers
|
|
27
|
+
def analyze_multiplex(multiplex, analyzers)
|
|
28
|
+
multiplex_analyzers = analyzers.map { |analyzer| analyzer.new(multiplex) }
|
|
29
|
+
|
|
30
|
+
multiplex.current_trace.analyze_multiplex(multiplex: multiplex) do
|
|
31
|
+
query_results = multiplex.queries.map do |query|
|
|
32
|
+
if query.valid?
|
|
33
|
+
analyze_query(
|
|
34
|
+
query,
|
|
35
|
+
query.analyzers,
|
|
36
|
+
multiplex_analyzers: multiplex_analyzers
|
|
37
|
+
)
|
|
38
|
+
else
|
|
39
|
+
[]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
multiplex_results = multiplex_analyzers.map(&:result)
|
|
44
|
+
multiplex_errors = analysis_errors(multiplex_results)
|
|
45
|
+
|
|
46
|
+
multiplex.queries.each_with_index do |query, idx|
|
|
47
|
+
query.analysis_errors = multiplex_errors + analysis_errors(query_results[idx])
|
|
48
|
+
end
|
|
49
|
+
multiplex_results
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @param query [GraphQL::Query]
|
|
54
|
+
# @param analyzers [Array<GraphQL::Analysis::Analyzer>]
|
|
55
|
+
# @return [Array<Any>] Results from those analyzers
|
|
56
|
+
def analyze_query(query, analyzers, multiplex_analyzers: [])
|
|
57
|
+
query.current_trace.analyze_query(query: query) do
|
|
58
|
+
query_analyzers = analyzers
|
|
59
|
+
.map { |analyzer| analyzer.new(query) }
|
|
60
|
+
.tap { _1.select!(&:analyze?) }
|
|
61
|
+
|
|
62
|
+
analyzers_to_run = query_analyzers + multiplex_analyzers
|
|
63
|
+
if !analyzers_to_run.empty?
|
|
64
|
+
|
|
65
|
+
analyzers_to_run.select!(&:visit?)
|
|
66
|
+
if !analyzers_to_run.empty?
|
|
67
|
+
visitor = GraphQL::Analysis::Visitor.new(
|
|
68
|
+
query: query,
|
|
69
|
+
analyzers: analyzers_to_run,
|
|
70
|
+
timeout: query.validate_timeout_remaining,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
visitor.visit
|
|
74
|
+
|
|
75
|
+
if !visitor.rescued_errors.empty?
|
|
76
|
+
return visitor.rescued_errors
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
query_analyzers.map(&:result)
|
|
81
|
+
else
|
|
82
|
+
[]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
rescue TimeoutError => err
|
|
86
|
+
[err]
|
|
87
|
+
rescue GraphQL::UnauthorizedError, GraphQL::ExecutionError
|
|
88
|
+
# This error was raised during analysis and will be returned the client before execution
|
|
89
|
+
[]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def analysis_errors(results)
|
|
93
|
+
results.flatten.tap { _1.select! { |r| r.is_a?(GraphQL::AnalysisError) } }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphQL
|
|
4
|
+
# @see GraphQL::Railtie for automatic Rails integration
|
|
5
|
+
module Autoload
|
|
6
|
+
# Register a constant named `const_name` to be loaded from `path`.
|
|
7
|
+
# This is like `Kernel#autoload` but it tracks the constants so they can be eager-loaded with {#eager_load!}
|
|
8
|
+
# @param const_name [Symbol]
|
|
9
|
+
# @param path [String]
|
|
10
|
+
# @return [void]
|
|
11
|
+
def autoload(const_name, path)
|
|
12
|
+
@_eagerloaded_constants ||= []
|
|
13
|
+
@_eagerloaded_constants << const_name
|
|
14
|
+
|
|
15
|
+
super const_name, path
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Call this to load this constant's `autoload` dependents and continue calling recursively
|
|
19
|
+
# @return [void]
|
|
20
|
+
def eager_load!
|
|
21
|
+
@_eager_loading = true
|
|
22
|
+
if @_eagerloaded_constants
|
|
23
|
+
@_eagerloaded_constants.each { |const_name| const_get(const_name) }
|
|
24
|
+
@_eagerloaded_constants = nil
|
|
25
|
+
end
|
|
26
|
+
nil
|
|
27
|
+
ensure
|
|
28
|
+
@_eager_loading = false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# @return [Boolean] `true` if GraphQL-Ruby is currently eager-loading its constants
|
|
34
|
+
def eager_loading?
|
|
35
|
+
@_eager_loading ||= false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -36,7 +36,93 @@ module GraphQL
|
|
|
36
36
|
private
|
|
37
37
|
|
|
38
38
|
def rows
|
|
39
|
-
@rows ||=
|
|
39
|
+
@rows ||= begin
|
|
40
|
+
query = @context.query
|
|
41
|
+
query_ctx = @context
|
|
42
|
+
runtime_inst = query_ctx.namespace(:interpreter_runtime)[:runtime]
|
|
43
|
+
result = runtime_inst.instance_variable_get(:@response)
|
|
44
|
+
rows = []
|
|
45
|
+
result_path = []
|
|
46
|
+
last_part = nil
|
|
47
|
+
path = @context.current_path
|
|
48
|
+
path.each do |path_part|
|
|
49
|
+
value = value_at(runtime_inst, result_path)
|
|
50
|
+
|
|
51
|
+
if result_path.empty?
|
|
52
|
+
name = query.selected_operation.operation_type || "query"
|
|
53
|
+
if (n = query.selected_operation_name)
|
|
54
|
+
name += " #{n}"
|
|
55
|
+
end
|
|
56
|
+
args = query.variables
|
|
57
|
+
else
|
|
58
|
+
name = result.graphql_field.path
|
|
59
|
+
args = result.graphql_arguments
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
object = result.graphql_parent ? result.graphql_parent.graphql_application_value : result.graphql_application_value
|
|
63
|
+
object = object.object.inspect
|
|
64
|
+
|
|
65
|
+
rows << [
|
|
66
|
+
result.ast_node.position.join(":"),
|
|
67
|
+
name,
|
|
68
|
+
"#{object}",
|
|
69
|
+
args.to_h.inspect,
|
|
70
|
+
inspect_result(value),
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
result_path << path_part
|
|
74
|
+
if path_part == path.last
|
|
75
|
+
last_part = path_part
|
|
76
|
+
else
|
|
77
|
+
result = result[path_part]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
object = result.graphql_application_value.object.inspect
|
|
82
|
+
ast_node = nil
|
|
83
|
+
result.graphql_selections.each do |s|
|
|
84
|
+
found_ast_node = find_ast_node(s, last_part)
|
|
85
|
+
if found_ast_node
|
|
86
|
+
ast_node = found_ast_node
|
|
87
|
+
break
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
if ast_node
|
|
92
|
+
field_defn = query.get_field(result.graphql_result_type, ast_node.name)
|
|
93
|
+
args = query.arguments_for(ast_node, field_defn).to_h
|
|
94
|
+
field_path = field_defn.path
|
|
95
|
+
if ast_node.alias
|
|
96
|
+
field_path += " as #{ast_node.alias}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
rows << [
|
|
100
|
+
ast_node.position.join(":"),
|
|
101
|
+
field_path,
|
|
102
|
+
"#{object}",
|
|
103
|
+
args.inspect,
|
|
104
|
+
inspect_result(@override_value)
|
|
105
|
+
]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
rows << HEADERS
|
|
109
|
+
rows.reverse!
|
|
110
|
+
rows
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def find_ast_node(node, last_part)
|
|
115
|
+
return nil unless node
|
|
116
|
+
return node if node.respond_to?(:alias) && node.respond_to?(:name) && (node.alias == last_part || node.name == last_part)
|
|
117
|
+
return nil unless node.respond_to?(:selections)
|
|
118
|
+
return nil if node.selections.nil? || node.selections.empty?
|
|
119
|
+
|
|
120
|
+
node.selections.each do |child|
|
|
121
|
+
child_ast_node = find_ast_node(child, last_part)
|
|
122
|
+
return child_ast_node if child_ast_node
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
nil
|
|
40
126
|
end
|
|
41
127
|
|
|
42
128
|
# @return [String]
|
|
@@ -75,67 +161,44 @@ module GraphQL
|
|
|
75
161
|
table
|
|
76
162
|
end
|
|
77
163
|
|
|
78
|
-
# @return [Array] 5 items for a backtrace table (not `key`)
|
|
79
|
-
def build_rows(context_entry, rows:, top: false)
|
|
80
|
-
case context_entry
|
|
81
|
-
when Backtrace::Frame
|
|
82
|
-
field_alias = context_entry.ast_node.respond_to?(:alias) && context_entry.ast_node.alias
|
|
83
|
-
value = if top && @override_value
|
|
84
|
-
@override_value
|
|
85
|
-
else
|
|
86
|
-
value_at(@context.query.context.namespace(:interpreter_runtime)[:runtime], context_entry.path)
|
|
87
|
-
end
|
|
88
|
-
rows << [
|
|
89
|
-
"#{context_entry.ast_node ? context_entry.ast_node.position.join(":") : ""}",
|
|
90
|
-
"#{context_entry.field.path}#{field_alias ? " as #{field_alias}" : ""}",
|
|
91
|
-
"#{context_entry.object.object.inspect}",
|
|
92
|
-
context_entry.arguments.to_h.inspect, # rubocop:disable Development/ContextIsPassedCop -- unrelated method
|
|
93
|
-
Backtrace::InspectResult.inspect_result(value),
|
|
94
|
-
]
|
|
95
|
-
if (parent = context_entry.parent_frame)
|
|
96
|
-
build_rows(parent, rows: rows)
|
|
97
|
-
else
|
|
98
|
-
rows
|
|
99
|
-
end
|
|
100
|
-
when GraphQL::Query::Context
|
|
101
|
-
query = context_entry.query
|
|
102
|
-
op = query.selected_operation
|
|
103
|
-
if op
|
|
104
|
-
op_type = op.operation_type
|
|
105
|
-
position = "#{op.line}:#{op.col}"
|
|
106
|
-
else
|
|
107
|
-
op_type = "query"
|
|
108
|
-
position = "?:?"
|
|
109
|
-
end
|
|
110
|
-
op_name = query.selected_operation_name
|
|
111
|
-
object = query.root_value
|
|
112
|
-
if object.is_a?(GraphQL::Schema::Object)
|
|
113
|
-
object = object.object
|
|
114
|
-
end
|
|
115
|
-
value = value_at(context_entry.namespace(:interpreter_runtime)[:runtime], [])
|
|
116
|
-
rows << [
|
|
117
|
-
"#{position}",
|
|
118
|
-
"#{op_type}#{op_name ? " #{op_name}" : ""}",
|
|
119
|
-
"#{object.inspect}",
|
|
120
|
-
query.variables.to_h.inspect,
|
|
121
|
-
Backtrace::InspectResult.inspect_result(value),
|
|
122
|
-
]
|
|
123
|
-
else
|
|
124
|
-
raise "Unexpected get_rows subject #{context_entry.class} (#{context_entry.inspect})"
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
164
|
|
|
128
165
|
def value_at(runtime, path)
|
|
129
166
|
response = runtime.final_result
|
|
130
167
|
path.each do |key|
|
|
131
|
-
|
|
132
|
-
next
|
|
133
|
-
else
|
|
134
|
-
break
|
|
135
|
-
end
|
|
168
|
+
response && (response = response[key])
|
|
136
169
|
end
|
|
137
170
|
response
|
|
138
171
|
end
|
|
172
|
+
|
|
173
|
+
def inspect_result(obj)
|
|
174
|
+
case obj
|
|
175
|
+
when Hash
|
|
176
|
+
"{" +
|
|
177
|
+
obj.map do |key, val|
|
|
178
|
+
"#{key}: #{inspect_truncated(val)}"
|
|
179
|
+
end.join(", ") +
|
|
180
|
+
"}"
|
|
181
|
+
when Array
|
|
182
|
+
"[" +
|
|
183
|
+
obj.map { |v| inspect_truncated(v) }.join(", ") +
|
|
184
|
+
"]"
|
|
185
|
+
else
|
|
186
|
+
inspect_truncated(obj)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def inspect_truncated(obj)
|
|
191
|
+
case obj
|
|
192
|
+
when Hash
|
|
193
|
+
"{...}"
|
|
194
|
+
when Array
|
|
195
|
+
"[...]"
|
|
196
|
+
when GraphQL::Execution::Lazy
|
|
197
|
+
"(unresolved)"
|
|
198
|
+
else
|
|
199
|
+
"#{obj.inspect}"
|
|
200
|
+
end
|
|
201
|
+
end
|
|
139
202
|
end
|
|
140
203
|
end
|
|
141
204
|
end
|
data/lib/graphql/backtrace.rb
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require "graphql/backtrace/inspect_result"
|
|
3
2
|
require "graphql/backtrace/table"
|
|
4
3
|
require "graphql/backtrace/traced_error"
|
|
5
|
-
require "graphql/backtrace/tracer"
|
|
6
|
-
require "graphql/backtrace/trace"
|
|
7
4
|
module GraphQL
|
|
8
5
|
# Wrap unhandled errors with {TracedError}.
|
|
9
6
|
#
|
|
@@ -24,7 +21,7 @@ module GraphQL
|
|
|
24
21
|
def_delegators :to_a, :each, :[]
|
|
25
22
|
|
|
26
23
|
def self.use(schema_defn)
|
|
27
|
-
schema_defn.
|
|
24
|
+
schema_defn.using_backtrace = true
|
|
28
25
|
end
|
|
29
26
|
|
|
30
27
|
def initialize(context, value: nil)
|
|
@@ -40,20 +37,5 @@ module GraphQL
|
|
|
40
37
|
def to_a
|
|
41
38
|
@table.to_backtrace
|
|
42
39
|
end
|
|
43
|
-
|
|
44
|
-
# Used for internal bookkeeping
|
|
45
|
-
# @api private
|
|
46
|
-
class Frame
|
|
47
|
-
attr_reader :path, :query, :ast_node, :object, :field, :arguments, :parent_frame
|
|
48
|
-
def initialize(path:, query:, ast_node:, object:, field:, arguments:, parent_frame:)
|
|
49
|
-
@path = path
|
|
50
|
-
@query = query
|
|
51
|
-
@ast_node = ast_node
|
|
52
|
-
@field = field
|
|
53
|
-
@object = object
|
|
54
|
-
@arguments = arguments
|
|
55
|
-
@parent_frame = parent_frame
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
40
|
end
|
|
59
41
|
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphQL
|
|
4
|
+
# This module exposes Fiber-level runtime information.
|
|
5
|
+
#
|
|
6
|
+
# It won't work across unrelated fibers, although it will work in child Fibers.
|
|
7
|
+
#
|
|
8
|
+
# @example Setting Up ActiveRecord::QueryLogs
|
|
9
|
+
#
|
|
10
|
+
# config.active_record.query_log_tags = [
|
|
11
|
+
# :namespaced_controller,
|
|
12
|
+
# :action,
|
|
13
|
+
# :job,
|
|
14
|
+
# # ...
|
|
15
|
+
# {
|
|
16
|
+
# # GraphQL runtime info:
|
|
17
|
+
# current_graphql_operation: -> { GraphQL::Current.operation_name },
|
|
18
|
+
# current_graphql_field: -> { GraphQL::Current.field&.path },
|
|
19
|
+
# current_dataloader_source: -> { GraphQL::Current.dataloader_source_class },
|
|
20
|
+
# # ...
|
|
21
|
+
# },
|
|
22
|
+
# ]
|
|
23
|
+
#
|
|
24
|
+
module Current
|
|
25
|
+
# @return [String, nil] Comma-joined operation names for the currently-running {Execution::Multiplex}. `nil` if all operations are anonymous.
|
|
26
|
+
def self.operation_name
|
|
27
|
+
if (m = Fiber[:__graphql_current_multiplex])
|
|
28
|
+
m.context[:__graphql_current_operation_name] ||= begin
|
|
29
|
+
names = m.queries.map { |q| q.selected_operation_name }
|
|
30
|
+
if names.all?(&:nil?)
|
|
31
|
+
nil
|
|
32
|
+
else
|
|
33
|
+
names.join(",")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
else
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @see GraphQL::Field#path for a string identifying this field
|
|
42
|
+
# @return [GraphQL::Field, nil] The currently-running field, if there is one.
|
|
43
|
+
def self.field
|
|
44
|
+
Fiber[:__graphql_runtime_info]&.values&.first&.current_field
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [Class, nil] The currently-running {Dataloader::Source} class, if there is one.
|
|
48
|
+
def self.dataloader_source_class
|
|
49
|
+
Fiber[:__graphql_current_dataloader_source]&.class
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [GraphQL::Dataloader::Source, nil] The currently-running source, if there is one
|
|
53
|
+
def self.dataloader_source
|
|
54
|
+
Fiber[:__graphql_current_dataloader_source]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|