graphql 2.4.5 → 2.5.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
- data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
- data/lib/graphql/analysis/analyzer.rb +2 -1
- data/lib/graphql/analysis/query_complexity.rb +87 -7
- data/lib/graphql/analysis/visitor.rb +37 -40
- data/lib/graphql/analysis.rb +12 -9
- data/lib/graphql/autoload.rb +1 -0
- data/lib/graphql/backtrace/table.rb +118 -55
- data/lib/graphql/backtrace.rb +1 -19
- data/lib/graphql/current.rb +6 -1
- data/lib/graphql/dashboard/application_controller.rb +41 -0
- data/lib/graphql/dashboard/detailed_traces.rb +47 -0
- data/lib/graphql/dashboard/installable.rb +22 -0
- data/lib/graphql/dashboard/landings_controller.rb +9 -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/statics_controller.rb +31 -0
- data/lib/graphql/dashboard/subscriptions.rb +97 -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 +24 -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 +96 -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 +38 -15
- data/lib/graphql/dataloader/null_dataloader.rb +55 -10
- data/lib/graphql/dataloader/source.rb +18 -6
- data/lib/graphql/dataloader.rb +110 -26
- data/lib/graphql/date_encoding_error.rb +1 -1
- data/lib/graphql/dig.rb +2 -1
- data/lib/graphql/execution/interpreter/resolve.rb +10 -16
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +58 -5
- data/lib/graphql/execution/interpreter/runtime.rb +229 -93
- data/lib/graphql/execution/interpreter.rb +15 -24
- data/lib/graphql/execution/multiplex.rb +7 -6
- data/lib/graphql/execution/next/field_resolve_step.rb +690 -0
- data/lib/graphql/execution/next/load_argument_step.rb +60 -0
- data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
- data/lib/graphql/execution/next/runner.rb +389 -0
- data/lib/graphql/execution/next/selections_step.rb +37 -0
- data/lib/graphql/execution/next.rb +69 -0
- data/lib/graphql/execution.rb +1 -0
- data/lib/graphql/execution_error.rb +13 -10
- data/lib/graphql/introspection/directive_location_enum.rb +1 -1
- data/lib/graphql/introspection/directive_type.rb +7 -3
- data/lib/graphql/introspection/dynamic_fields.rb +5 -1
- data/lib/graphql/introspection/entry_points.rb +11 -3
- data/lib/graphql/introspection/enum_value_type.rb +5 -5
- data/lib/graphql/introspection/field_type.rb +13 -5
- data/lib/graphql/introspection/input_value_type.rb +21 -13
- data/lib/graphql/introspection/type_type.rb +64 -28
- data/lib/graphql/invalid_name_error.rb +1 -1
- data/lib/graphql/invalid_null_error.rb +25 -16
- data/lib/graphql/language/document_from_schema_definition.rb +2 -1
- data/lib/graphql/language/lexer.rb +16 -5
- data/lib/graphql/language/nodes.rb +8 -1
- data/lib/graphql/language/parser.rb +16 -8
- data/lib/graphql/language/static_visitor.rb +37 -33
- data/lib/graphql/language/visitor.rb +59 -55
- data/lib/graphql/language.rb +21 -12
- data/lib/graphql/pagination/connection.rb +2 -0
- data/lib/graphql/pagination/connections.rb +32 -0
- data/lib/graphql/query/context.rb +6 -10
- data/lib/graphql/query/null_context.rb +9 -3
- data/lib/graphql/query/partial.rb +179 -0
- data/lib/graphql/query.rb +64 -64
- data/lib/graphql/railtie.rb +1 -1
- data/lib/graphql/schema/addition.rb +3 -1
- data/lib/graphql/schema/always_visible.rb +1 -0
- data/lib/graphql/schema/argument.rb +24 -8
- data/lib/graphql/schema/build_from_definition.rb +113 -54
- data/lib/graphql/schema/directive/flagged.rb +2 -0
- data/lib/graphql/schema/directive.rb +52 -2
- data/lib/graphql/schema/enum.rb +36 -1
- data/lib/graphql/schema/enum_value.rb +1 -1
- data/lib/graphql/schema/field/connection_extension.rb +15 -35
- data/lib/graphql/schema/field/scope_extension.rb +22 -13
- data/lib/graphql/schema/field.rb +101 -51
- data/lib/graphql/schema/field_extension.rb +33 -0
- data/lib/graphql/schema/input_object.rb +45 -38
- data/lib/graphql/schema/interface.rb +2 -1
- data/lib/graphql/schema/list.rb +1 -1
- data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
- data/lib/graphql/schema/member/has_arguments.rb +56 -19
- data/lib/graphql/schema/member/has_authorization.rb +35 -0
- data/lib/graphql/schema/member/has_dataloader.rb +79 -0
- data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
- data/lib/graphql/schema/member/has_directives.rb +1 -1
- data/lib/graphql/schema/member/has_fields.rb +81 -5
- data/lib/graphql/schema/member/has_interfaces.rb +3 -3
- data/lib/graphql/schema/member/scoped.rb +1 -1
- data/lib/graphql/schema/member/type_system_helpers.rb +17 -3
- data/lib/graphql/schema/member.rb +6 -0
- data/lib/graphql/schema/object.rb +18 -8
- data/lib/graphql/schema/ractor_shareable.rb +79 -0
- data/lib/graphql/schema/resolver.rb +52 -6
- data/lib/graphql/schema/scalar.rb +1 -6
- data/lib/graphql/schema/subscription.rb +50 -4
- data/lib/graphql/schema/timeout.rb +19 -2
- data/lib/graphql/schema/validator/required_validator.rb +71 -14
- data/lib/graphql/schema/visibility/migration.rb +3 -2
- data/lib/graphql/schema/visibility/profile.rb +115 -23
- data/lib/graphql/schema/visibility.rb +49 -32
- data/lib/graphql/schema/warden.rb +23 -2
- data/lib/graphql/schema.rb +333 -68
- data/lib/graphql/static_validation/all_rules.rb +2 -2
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +79 -17
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
- data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
- data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
- data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
- data/lib/graphql/static_validation/validator.rb +6 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
- data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
- data/lib/graphql/subscriptions/event.rb +12 -1
- data/lib/graphql/subscriptions/serialize.rb +1 -1
- data/lib/graphql/subscriptions.rb +1 -1
- data/lib/graphql/testing/helpers.rb +17 -11
- 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 +9 -1
- data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
- data/lib/graphql/tracing/appsignal_trace.rb +32 -55
- 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 -158
- data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
- data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -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 +156 -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 +184 -34
- 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 +864 -0
- data/lib/graphql/tracing/platform_trace.rb +5 -0
- data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
- data/lib/graphql/tracing/prometheus_trace.rb +72 -68
- data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
- data/lib/graphql/tracing/scout_trace.rb +32 -55
- data/lib/graphql/tracing/scout_tracing.rb +2 -0
- data/lib/graphql/tracing/sentry_trace.rb +64 -94
- data/lib/graphql/tracing/statsd_trace.rb +33 -41
- 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 +1 -0
- data/lib/graphql/types/relay/connection_behaviors.rb +9 -7
- data/lib/graphql/types/relay/edge_behaviors.rb +5 -4
- data/lib/graphql/types/relay/has_node_field.rb +13 -8
- data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
- data/lib/graphql/types/relay/node_behaviors.rb +13 -2
- data/lib/graphql/unauthorized_error.rb +5 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +12 -31
- metadata +174 -11
- data/lib/graphql/backtrace/inspect_result.rb +0 -38
- data/lib/graphql/backtrace/trace.rb +0 -93
- data/lib/graphql/backtrace/tracer.rb +0 -80
- data/lib/graphql/schema/null_mask.rb +0 -11
- data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
|
@@ -5,8 +5,10 @@ require "graphql/query/null_context"
|
|
|
5
5
|
module GraphQL
|
|
6
6
|
class Schema
|
|
7
7
|
class Object < GraphQL::Schema::Member
|
|
8
|
+
extend GraphQL::Schema::Member::HasAuthorization
|
|
8
9
|
extend GraphQL::Schema::Member::HasFields
|
|
9
10
|
extend GraphQL::Schema::Member::HasInterfaces
|
|
11
|
+
include Member::HasDataloader
|
|
10
12
|
|
|
11
13
|
# Raised when an Object doesn't have any field defined and hasn't explicitly opted out of this requirement
|
|
12
14
|
class FieldsAreRequiredError < GraphQL::Error
|
|
@@ -65,20 +67,28 @@ module GraphQL
|
|
|
65
67
|
# @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy]
|
|
66
68
|
# @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false`
|
|
67
69
|
def authorized_new(object, context)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
context.query.current_trace.begin_authorized(self, object, context)
|
|
71
|
+
begin
|
|
72
|
+
maybe_lazy_auth_val = context.query.current_trace.authorized(query: context.query, type: self, object: object) do
|
|
73
|
+
begin
|
|
74
|
+
authorized?(object, context)
|
|
75
|
+
rescue GraphQL::UnauthorizedError => err
|
|
76
|
+
context.schema.unauthorized_object(err)
|
|
77
|
+
rescue StandardError => err
|
|
78
|
+
context.query.handle_or_reraise(err)
|
|
79
|
+
end
|
|
75
80
|
end
|
|
81
|
+
ensure
|
|
82
|
+
context.query.current_trace.end_authorized(self, object, context, maybe_lazy_auth_val)
|
|
76
83
|
end
|
|
77
84
|
|
|
78
85
|
auth_val = if context.schema.lazy?(maybe_lazy_auth_val)
|
|
79
86
|
GraphQL::Execution::Lazy.new do
|
|
87
|
+
context.query.current_trace.begin_authorized(self, object, context)
|
|
80
88
|
context.query.current_trace.authorized_lazy(query: context.query, type: self, object: object) do
|
|
81
|
-
context.schema.sync_lazy(maybe_lazy_auth_val)
|
|
89
|
+
res = context.schema.sync_lazy(maybe_lazy_auth_val)
|
|
90
|
+
context.query.current_trace.end_authorized(self, object, context, res)
|
|
91
|
+
res
|
|
82
92
|
end
|
|
83
93
|
end
|
|
84
94
|
else
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphQL
|
|
3
|
+
class Schema
|
|
4
|
+
module RactorShareable
|
|
5
|
+
def self.extended(schema_class)
|
|
6
|
+
schema_class.extend(SchemaExtension)
|
|
7
|
+
schema_class.freeze_schema
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module SchemaExtension
|
|
11
|
+
|
|
12
|
+
def freeze_error_handlers(handlers)
|
|
13
|
+
handlers[:subclass_handlers].default_proc = nil
|
|
14
|
+
handlers[:subclass_handlers].each do |_class, subclass_handlers|
|
|
15
|
+
freeze_error_handlers(subclass_handlers)
|
|
16
|
+
end
|
|
17
|
+
Ractor.make_shareable(handlers)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def freeze_schema
|
|
21
|
+
# warm some ivars:
|
|
22
|
+
default_analysis_engine
|
|
23
|
+
default_execution_strategy
|
|
24
|
+
GraphQL.default_parser
|
|
25
|
+
default_logger
|
|
26
|
+
freeze_error_handlers(error_handlers)
|
|
27
|
+
# TODO: this freezes errors of parent classes which could cause trouble
|
|
28
|
+
parent_class = superclass
|
|
29
|
+
while parent_class.respond_to?(:error_handlers)
|
|
30
|
+
freeze_error_handlers(parent_class.error_handlers)
|
|
31
|
+
parent_class = parent_class.superclass
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
own_tracers.freeze
|
|
35
|
+
@frozen_tracers = tracers.freeze
|
|
36
|
+
own_trace_modes.each do |m|
|
|
37
|
+
trace_options_for(m)
|
|
38
|
+
build_trace_mode(m)
|
|
39
|
+
end
|
|
40
|
+
build_trace_mode(:default)
|
|
41
|
+
Ractor.make_shareable(@trace_options_for_mode)
|
|
42
|
+
Ractor.make_shareable(own_trace_modes)
|
|
43
|
+
Ractor.make_shareable(own_multiplex_analyzers)
|
|
44
|
+
@frozen_multiplex_analyzers = Ractor.make_shareable(multiplex_analyzers)
|
|
45
|
+
Ractor.make_shareable(own_query_analyzers)
|
|
46
|
+
@frozen_query_analyzers = Ractor.make_shareable(query_analyzers)
|
|
47
|
+
Ractor.make_shareable(own_plugins)
|
|
48
|
+
own_plugins.each do |(plugin, options)|
|
|
49
|
+
Ractor.make_shareable(plugin)
|
|
50
|
+
Ractor.make_shareable(options)
|
|
51
|
+
end
|
|
52
|
+
@frozen_plugins = Ractor.make_shareable(plugins)
|
|
53
|
+
Ractor.make_shareable(own_references_to)
|
|
54
|
+
@frozen_directives = Ractor.make_shareable(directives)
|
|
55
|
+
|
|
56
|
+
Ractor.make_shareable(visibility)
|
|
57
|
+
Ractor.make_shareable(introspection_system)
|
|
58
|
+
extend(FrozenMethods)
|
|
59
|
+
|
|
60
|
+
Ractor.make_shareable(self)
|
|
61
|
+
superclass.respond_to?(:freeze_schema) && superclass.freeze_schema
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
module FrozenMethods
|
|
65
|
+
def tracers; @frozen_tracers; end
|
|
66
|
+
def multiplex_analyzers; @frozen_multiplex_analyzers; end
|
|
67
|
+
def query_analyzers; @frozen_query_analyzers; end
|
|
68
|
+
def plugins; @frozen_plugins; end
|
|
69
|
+
def directives; @frozen_directives; end
|
|
70
|
+
|
|
71
|
+
# This actually accumulates info during execution...
|
|
72
|
+
# How to support it?
|
|
73
|
+
def lazy?(_obj); false; end
|
|
74
|
+
def sync_lazy(obj); obj; end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -23,10 +23,13 @@ module GraphQL
|
|
|
23
23
|
# Really we only need description & comment from here, but:
|
|
24
24
|
extend Schema::Member::BaseDSLMethods
|
|
25
25
|
extend GraphQL::Schema::Member::HasArguments
|
|
26
|
+
extend GraphQL::Schema::Member::HasAuthorization
|
|
26
27
|
extend GraphQL::Schema::Member::HasValidators
|
|
27
28
|
include Schema::Member::HasPath
|
|
28
29
|
extend Schema::Member::HasPath
|
|
29
30
|
extend Schema::Member::HasDirectives
|
|
31
|
+
include Schema::Member::HasDataloader
|
|
32
|
+
extend Schema::Member::HasDeprecationReason
|
|
30
33
|
|
|
31
34
|
# @param object [Object] The application object that this field is being resolved on
|
|
32
35
|
# @param context [GraphQL::Query::Context]
|
|
@@ -43,20 +46,58 @@ module GraphQL
|
|
|
43
46
|
@prepared_arguments = nil
|
|
44
47
|
end
|
|
45
48
|
|
|
49
|
+
attr_accessor :exec_result, :exec_index, :field_resolve_step
|
|
50
|
+
|
|
46
51
|
# @return [Object] The application object this field is being resolved on
|
|
47
|
-
|
|
52
|
+
attr_accessor :object
|
|
48
53
|
|
|
49
54
|
# @return [GraphQL::Query::Context]
|
|
50
55
|
attr_reader :context
|
|
51
56
|
|
|
52
|
-
# @return [GraphQL::Dataloader]
|
|
53
|
-
def dataloader
|
|
54
|
-
context.dataloader
|
|
55
|
-
end
|
|
56
|
-
|
|
57
57
|
# @return [GraphQL::Schema::Field]
|
|
58
58
|
attr_reader :field
|
|
59
59
|
|
|
60
|
+
attr_writer :prepared_arguments
|
|
61
|
+
|
|
62
|
+
def call
|
|
63
|
+
if self.class < Schema::HasSingleInputArgument
|
|
64
|
+
@prepared_arguments = @prepared_arguments[:input]
|
|
65
|
+
end
|
|
66
|
+
q = context.query
|
|
67
|
+
trace_objs = [object]
|
|
68
|
+
q.current_trace.begin_execute_field(field, @prepared_arguments, trace_objs, q)
|
|
69
|
+
is_authed, new_return_value = authorized?(**@prepared_arguments)
|
|
70
|
+
|
|
71
|
+
if (runner = @field_resolve_step.runner).resolves_lazies && runner.schema.lazy?(is_authed)
|
|
72
|
+
is_authed, new_return_value = runner.schema.sync_lazy(is_authed)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
result = if is_authed
|
|
76
|
+
Schema::Validator.validate!(self.class.validators, object, context, @prepared_arguments, as: @field)
|
|
77
|
+
call_resolve(@prepared_arguments)
|
|
78
|
+
else
|
|
79
|
+
new_return_value
|
|
80
|
+
end
|
|
81
|
+
q = context.query
|
|
82
|
+
q.current_trace.end_execute_field(field, @prepared_arguments, trace_objs, q, [result])
|
|
83
|
+
|
|
84
|
+
exec_result[exec_index] = result
|
|
85
|
+
rescue RuntimeError => err
|
|
86
|
+
exec_result[exec_index] = err
|
|
87
|
+
rescue StandardError => stderr
|
|
88
|
+
exec_result[exec_index] = begin
|
|
89
|
+
context.query.handle_or_reraise(stderr)
|
|
90
|
+
rescue GraphQL::ExecutionError => ex_err
|
|
91
|
+
ex_err
|
|
92
|
+
end
|
|
93
|
+
ensure
|
|
94
|
+
field_pending_steps = field_resolve_step.pending_steps
|
|
95
|
+
field_pending_steps.delete(self)
|
|
96
|
+
if field_pending_steps.size == 0 && field_resolve_step.field_results
|
|
97
|
+
field_resolve_step.runner.add_step(field_resolve_step)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
60
101
|
def arguments
|
|
61
102
|
@prepared_arguments || raise("Arguments have not been prepared yet, still waiting for #load_arguments to resolve. (Call `.arguments` later in the code.)")
|
|
62
103
|
end
|
|
@@ -407,6 +448,11 @@ module GraphQL
|
|
|
407
448
|
end
|
|
408
449
|
end
|
|
409
450
|
|
|
451
|
+
def inherited(child_class)
|
|
452
|
+
child_class.description(description)
|
|
453
|
+
super
|
|
454
|
+
end
|
|
455
|
+
|
|
410
456
|
private
|
|
411
457
|
|
|
412
458
|
attr_reader :own_extensions
|
|
@@ -50,12 +50,7 @@ module GraphQL
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
if coerced_result.nil?
|
|
53
|
-
|
|
54
|
-
""
|
|
55
|
-
else
|
|
56
|
-
" #{GraphQL::Language.serialize(value)}"
|
|
57
|
-
end
|
|
58
|
-
Query::InputValidationResult.from_problem("Could not coerce value#{str_value} to #{graphql_name}")
|
|
53
|
+
Query::InputValidationResult.from_problem("Could not coerce value #{GraphQL::Language.serialize(value)} to #{graphql_name}")
|
|
59
54
|
elsif coerced_result.is_a?(GraphQL::CoercionError)
|
|
60
55
|
Query::InputValidationResult.from_problem(coerced_result.message, message: coerced_result.message, extensions: coerced_result.extensions)
|
|
61
56
|
else
|
|
@@ -19,13 +19,22 @@ module GraphQL
|
|
|
19
19
|
# propagate null.
|
|
20
20
|
null false
|
|
21
21
|
|
|
22
|
+
# @api private
|
|
22
23
|
def initialize(object:, context:, field:)
|
|
23
24
|
super
|
|
24
25
|
# Figure out whether this is an update or an initial subscription
|
|
25
26
|
@mode = context.query.subscription_update? ? :update : :subscribe
|
|
27
|
+
@subscription_written = false
|
|
28
|
+
@original_arguments = nil
|
|
29
|
+
if (subs_ns = context.namespace(:subscriptions)) &&
|
|
30
|
+
(sub_insts = subs_ns[:subscriptions])
|
|
31
|
+
sub_insts[context.current_path] = self
|
|
32
|
+
end
|
|
26
33
|
end
|
|
27
34
|
|
|
35
|
+
# @api private
|
|
28
36
|
def resolve_with_support(**args)
|
|
37
|
+
@original_arguments = args # before `loads:` have been run
|
|
29
38
|
result = nil
|
|
30
39
|
unsubscribed = true
|
|
31
40
|
unsubscribed_result = catch :graphql_subscription_unsubscribed do
|
|
@@ -46,7 +55,9 @@ module GraphQL
|
|
|
46
55
|
end
|
|
47
56
|
end
|
|
48
57
|
|
|
49
|
-
# Implement the {Resolve} API
|
|
58
|
+
# Implement the {Resolve} API.
|
|
59
|
+
# You can implement this if you want code to run for _both_ the initial subscription
|
|
60
|
+
# and for later updates. Or, implement {#subscribe} and {#update}
|
|
50
61
|
def resolve(**args)
|
|
51
62
|
# Dispatch based on `@mode`, which will raise a `NoMethodError` if we ever
|
|
52
63
|
# have an unexpected `@mode`
|
|
@@ -54,6 +65,7 @@ module GraphQL
|
|
|
54
65
|
end
|
|
55
66
|
|
|
56
67
|
# Wrap the user-defined `#subscribe` hook
|
|
68
|
+
# @api private
|
|
57
69
|
def resolve_subscribe(**args)
|
|
58
70
|
ret_val = !args.empty? ? subscribe(**args) : subscribe
|
|
59
71
|
if ret_val == :no_response
|
|
@@ -71,6 +83,7 @@ module GraphQL
|
|
|
71
83
|
end
|
|
72
84
|
|
|
73
85
|
# Wrap the user-provided `#update` hook
|
|
86
|
+
# @api private
|
|
74
87
|
def resolve_update(**args)
|
|
75
88
|
ret_val = !args.empty? ? update(**args) : update
|
|
76
89
|
if ret_val == NO_UPDATE
|
|
@@ -106,14 +119,13 @@ module GraphQL
|
|
|
106
119
|
throw :graphql_subscription_unsubscribed, update_value
|
|
107
120
|
end
|
|
108
121
|
|
|
109
|
-
READING_SCOPE = ::Object.new
|
|
110
122
|
# Call this method to provide a new subscription_scope; OR
|
|
111
123
|
# call it without an argument to get the subscription_scope
|
|
112
124
|
# @param new_scope [Symbol]
|
|
113
125
|
# @param optional [Boolean] If true, then don't require `scope:` to be provided to updates to this subscription.
|
|
114
126
|
# @return [Symbol]
|
|
115
|
-
def self.subscription_scope(new_scope =
|
|
116
|
-
if new_scope !=
|
|
127
|
+
def self.subscription_scope(new_scope = NOT_CONFIGURED, optional: false)
|
|
128
|
+
if new_scope != NOT_CONFIGURED
|
|
117
129
|
@subscription_scope = new_scope
|
|
118
130
|
@subscription_scope_optional = optional
|
|
119
131
|
elsif defined?(@subscription_scope)
|
|
@@ -150,6 +162,40 @@ module GraphQL
|
|
|
150
162
|
def self.topic_for(arguments:, field:, scope:)
|
|
151
163
|
Subscriptions::Serialize.dump_recursive([scope, field.graphql_name, arguments])
|
|
152
164
|
end
|
|
165
|
+
|
|
166
|
+
# Calls through to `schema.subscriptions` to register this subscription with the backend.
|
|
167
|
+
# This is automatically called by GraphQL-Ruby after a query finishes successfully,
|
|
168
|
+
# but if you need to commit the subscription during `#subscribe`, you can call it there.
|
|
169
|
+
# (This method also sets a flag showing that this subscription was already written.)
|
|
170
|
+
#
|
|
171
|
+
# If you call this method yourself, you may also need to {#unsubscribe}
|
|
172
|
+
# or call `subscriptions.delete_subscription` to clean up the database if the query crashes with an error
|
|
173
|
+
# later in execution.
|
|
174
|
+
# @return [void]
|
|
175
|
+
def write_subscription
|
|
176
|
+
if subscription_written?
|
|
177
|
+
raise GraphQL::Error, "`write_subscription` was called but `#{self.class}#subscription_written?` is already true. Remove a call to `write subscription`."
|
|
178
|
+
else
|
|
179
|
+
@subscription_written = true
|
|
180
|
+
context.schema.subscriptions.write_subscription(context.query, [event])
|
|
181
|
+
end
|
|
182
|
+
nil
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# @return [Boolean] `true` if {#write_subscription} was called already
|
|
186
|
+
def subscription_written?
|
|
187
|
+
@subscription_written
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# @return [Subscriptions::Event] This object is used as a representation of this subscription for the backend
|
|
191
|
+
def event
|
|
192
|
+
@event ||= Subscriptions::Event.new(
|
|
193
|
+
name: field.name,
|
|
194
|
+
arguments: @original_arguments,
|
|
195
|
+
context: context,
|
|
196
|
+
field: field,
|
|
197
|
+
)
|
|
198
|
+
end
|
|
153
199
|
end
|
|
154
200
|
end
|
|
155
201
|
end
|
|
@@ -71,15 +71,23 @@ module GraphQL
|
|
|
71
71
|
def execute_field(query:, field:, **_rest)
|
|
72
72
|
timeout_state = query.context.namespace(@timeout).fetch(:state)
|
|
73
73
|
# If the `:state` is `false`, then `max_seconds(query)` opted out of timeout for this query.
|
|
74
|
-
if timeout_state
|
|
74
|
+
if timeout_state == false
|
|
75
|
+
super
|
|
76
|
+
elsif Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at)
|
|
75
77
|
error = GraphQL::Schema::Timeout::TimeoutError.new(field)
|
|
76
78
|
# Only invoke the timeout callback for the first timeout
|
|
77
79
|
if !timeout_state[:timed_out]
|
|
78
80
|
timeout_state[:timed_out] = true
|
|
79
81
|
@timeout.handle_timeout(error, query)
|
|
82
|
+
timeout_state = query.context.namespace(@timeout).fetch(:state)
|
|
80
83
|
end
|
|
81
84
|
|
|
82
|
-
|
|
85
|
+
# `handle_timeout` may have set this to be `false`
|
|
86
|
+
if timeout_state != false
|
|
87
|
+
error
|
|
88
|
+
else
|
|
89
|
+
super
|
|
90
|
+
end
|
|
83
91
|
else
|
|
84
92
|
super
|
|
85
93
|
end
|
|
@@ -102,6 +110,15 @@ module GraphQL
|
|
|
102
110
|
# override to do something interesting
|
|
103
111
|
end
|
|
104
112
|
|
|
113
|
+
# Call this method (eg, from {#handle_timeout}) to disable timeout tracking
|
|
114
|
+
# for the given query.
|
|
115
|
+
# @param query [GraphQL::Query]
|
|
116
|
+
# @return [void]
|
|
117
|
+
def disable_timeout(query)
|
|
118
|
+
query.context.namespace(self)[:state] = false
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
|
|
105
122
|
# This error is raised when a query exceeds `max_seconds`.
|
|
106
123
|
# Since it's a child of {GraphQL::ExecutionError},
|
|
107
124
|
# its message will be added to the response's `errors` key.
|
|
@@ -8,6 +8,13 @@ module GraphQL
|
|
|
8
8
|
#
|
|
9
9
|
# (This is for specifying mutually exclusive sets of arguments.)
|
|
10
10
|
#
|
|
11
|
+
# If you use {GraphQL::Schema::Visibility} to hide all the arguments in a `one_of: [..]` set,
|
|
12
|
+
# then a developer-facing {GraphQL::Error} will be raised during execution. Pass `allow_all_hidden: true` to
|
|
13
|
+
# skip validation in this case instead.
|
|
14
|
+
#
|
|
15
|
+
# This validator also implements `argument ... required: :nullable`. If an argument has `required: :nullable`
|
|
16
|
+
# but it's hidden with {GraphQL::Schema::Visibility}, then this validator doesn't run.
|
|
17
|
+
#
|
|
11
18
|
# @example Require exactly one of these arguments
|
|
12
19
|
#
|
|
13
20
|
# field :update_amount, IngredientAmount, null: false do
|
|
@@ -37,33 +44,62 @@ module GraphQL
|
|
|
37
44
|
class RequiredValidator < Validator
|
|
38
45
|
# @param one_of [Array<Symbol>] A list of arguments, exactly one of which is required for this field
|
|
39
46
|
# @param argument [Symbol] An argument that is required for this field
|
|
47
|
+
# @param allow_all_hidden [Boolean] If `true`, then this validator won't run if all the `one_of: ...` arguments have been hidden
|
|
40
48
|
# @param message [String]
|
|
41
|
-
def initialize(one_of: nil, argument: nil, message: nil, **default_options)
|
|
49
|
+
def initialize(one_of: nil, argument: nil, allow_all_hidden: nil, message: nil, **default_options)
|
|
42
50
|
@one_of = if one_of
|
|
43
51
|
one_of
|
|
44
52
|
elsif argument
|
|
45
|
-
[argument]
|
|
53
|
+
[ argument ]
|
|
46
54
|
else
|
|
47
55
|
raise ArgumentError, "`one_of:` or `argument:` must be given in `validates required: {...}`"
|
|
48
56
|
end
|
|
57
|
+
@allow_all_hidden = allow_all_hidden.nil? ? !!argument : allow_all_hidden
|
|
49
58
|
@message = message
|
|
50
59
|
super(**default_options)
|
|
51
60
|
end
|
|
52
61
|
|
|
53
62
|
def validate(_object, context, value)
|
|
54
|
-
|
|
63
|
+
fully_matched_conditions = 0
|
|
64
|
+
partially_matched_conditions = 0
|
|
65
|
+
|
|
66
|
+
visible_keywords = context.types.arguments(@validated).map(&:keyword)
|
|
67
|
+
no_visible_conditions = true
|
|
55
68
|
|
|
56
69
|
if !value.nil?
|
|
57
70
|
@one_of.each do |one_of_condition|
|
|
58
71
|
case one_of_condition
|
|
59
72
|
when Symbol
|
|
73
|
+
if no_visible_conditions && visible_keywords.include?(one_of_condition)
|
|
74
|
+
no_visible_conditions = false
|
|
75
|
+
end
|
|
76
|
+
|
|
60
77
|
if value.key?(one_of_condition)
|
|
61
|
-
|
|
78
|
+
fully_matched_conditions += 1
|
|
62
79
|
end
|
|
63
80
|
when Array
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
81
|
+
any_match = false
|
|
82
|
+
full_match = true
|
|
83
|
+
|
|
84
|
+
one_of_condition.each do |k|
|
|
85
|
+
if no_visible_conditions && visible_keywords.include?(k)
|
|
86
|
+
no_visible_conditions = false
|
|
87
|
+
end
|
|
88
|
+
if value.key?(k)
|
|
89
|
+
any_match = true
|
|
90
|
+
else
|
|
91
|
+
full_match = false
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
partial_match = !full_match && any_match
|
|
96
|
+
|
|
97
|
+
if full_match
|
|
98
|
+
fully_matched_conditions += 1
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
if partial_match
|
|
102
|
+
partially_matched_conditions += 1
|
|
67
103
|
end
|
|
68
104
|
else
|
|
69
105
|
raise ArgumentError, "Unknown one_of condition: #{one_of_condition.inspect}"
|
|
@@ -71,7 +107,19 @@ module GraphQL
|
|
|
71
107
|
end
|
|
72
108
|
end
|
|
73
109
|
|
|
74
|
-
if
|
|
110
|
+
if no_visible_conditions
|
|
111
|
+
if @allow_all_hidden
|
|
112
|
+
return nil
|
|
113
|
+
else
|
|
114
|
+
raise GraphQL::Error, <<~ERR
|
|
115
|
+
#{@validated.path} validates `required: ...` but all required arguments were hidden.
|
|
116
|
+
|
|
117
|
+
Update your schema definition to allow the client to see some fields or skip validation by adding `required: { ..., allow_all_hidden: true }`
|
|
118
|
+
ERR
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
if fully_matched_conditions == 1 && partially_matched_conditions == 0
|
|
75
123
|
nil # OK
|
|
76
124
|
else
|
|
77
125
|
@message || build_message(context)
|
|
@@ -79,26 +127,35 @@ module GraphQL
|
|
|
79
127
|
end
|
|
80
128
|
|
|
81
129
|
def build_message(context)
|
|
82
|
-
argument_definitions =
|
|
130
|
+
argument_definitions = context.types.arguments(@validated)
|
|
131
|
+
|
|
83
132
|
required_names = @one_of.map do |arg_keyword|
|
|
84
133
|
if arg_keyword.is_a?(Array)
|
|
85
|
-
names = arg_keyword.map { |arg|
|
|
134
|
+
names = arg_keyword.map { |arg| arg_keyword_to_graphql_name(argument_definitions, arg) }
|
|
135
|
+
names.compact! # hidden arguments are `nil`
|
|
86
136
|
"(" + names.join(" and ") + ")"
|
|
87
137
|
else
|
|
88
|
-
|
|
138
|
+
arg_keyword_to_graphql_name(argument_definitions, arg_keyword)
|
|
89
139
|
end
|
|
90
140
|
end
|
|
141
|
+
required_names.compact! # remove entries for hidden arguments
|
|
142
|
+
|
|
91
143
|
|
|
92
|
-
|
|
144
|
+
case required_names.size
|
|
145
|
+
when 0
|
|
146
|
+
# The required definitions were hidden from the client.
|
|
147
|
+
# Another option here would be to raise an error in the application....
|
|
148
|
+
"%{validated} is missing a required argument."
|
|
149
|
+
when 1
|
|
93
150
|
"%{validated} must include the following argument: #{required_names.first}."
|
|
94
151
|
else
|
|
95
152
|
"%{validated} must include exactly one of the following arguments: #{required_names.join(", ")}."
|
|
96
153
|
end
|
|
97
154
|
end
|
|
98
155
|
|
|
99
|
-
def
|
|
156
|
+
def arg_keyword_to_graphql_name(argument_definitions, arg_keyword)
|
|
100
157
|
argument_definition = argument_definitions.find { |defn| defn.keyword == arg_keyword }
|
|
101
|
-
argument_definition
|
|
158
|
+
argument_definition&.graphql_name
|
|
102
159
|
end
|
|
103
160
|
end
|
|
104
161
|
end
|
|
@@ -76,10 +76,10 @@ module GraphQL
|
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
-
def initialize(context:, schema:, name: nil)
|
|
79
|
+
def initialize(context:, schema:, name: nil, visibility:)
|
|
80
80
|
@name = name
|
|
81
81
|
@skip_error = context[:skip_visibility_migration_error] || context.is_a?(Query::NullContext) || context.is_a?(Hash)
|
|
82
|
-
@profile_types = GraphQL::Schema::Visibility::Profile.new(context: context, schema: schema)
|
|
82
|
+
@profile_types = GraphQL::Schema::Visibility::Profile.new(context: context, schema: schema, visibility: visibility)
|
|
83
83
|
if !@skip_error
|
|
84
84
|
context[:visibility_migration_running] = true
|
|
85
85
|
warden_ctx_vals = context.to_h.dup
|
|
@@ -112,6 +112,7 @@ module GraphQL
|
|
|
112
112
|
:all_types_h,
|
|
113
113
|
:fields,
|
|
114
114
|
:loadable?,
|
|
115
|
+
:loadable_possible_types,
|
|
115
116
|
:type,
|
|
116
117
|
:arguments,
|
|
117
118
|
:argument,
|