graphql 2.0.31 → 2.6.1
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/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 +49 -0
- data/lib/generators/graphql/orm_mutations_base.rb +1 -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 +8 -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/create_graphql_detailed_traces.erb +10 -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 +5 -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/query_depth.rb +58 -0
- data/lib/graphql/analysis/visitor.rb +280 -0
- data/lib/graphql/analysis.rb +102 -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/coercion_error.rb +1 -9
- data/lib/graphql/current.rb +57 -0
- 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 +112 -0
- data/lib/graphql/dataloader/null_dataloader.rb +55 -10
- data/lib/graphql/dataloader/request.rb +5 -0
- data/lib/graphql/dataloader/source.rb +35 -12
- data/lib/graphql/dataloader.rb +224 -149
- data/lib/graphql/date_encoding_error.rb +1 -1
- data/lib/graphql/dig.rb +2 -1
- data/lib/graphql/duration_encoding_error.rb +16 -0
- data/lib/graphql/execution/field_resolve_step.rb +631 -0
- data/lib/graphql/execution/finalize.rb +217 -0
- data/lib/graphql/execution/input_values.rb +261 -0
- 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/handles_raw_value.rb +6 -0
- data/lib/graphql/execution/interpreter/resolve.rb +23 -25
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +228 -0
- data/lib/graphql/execution/interpreter/runtime.rb +365 -435
- data/lib/graphql/execution/interpreter.rb +87 -163
- data/lib/graphql/execution/lazy.rb +1 -1
- data/lib/graphql/execution/load_argument_step.rb +64 -0
- data/lib/graphql/execution/lookahead.rb +105 -31
- data/lib/graphql/execution/multiplex.rb +7 -6
- data/lib/graphql/execution/next.rb +90 -0
- data/lib/graphql/execution/prepare_object_step.rb +128 -0
- data/lib/graphql/execution/runner.rb +410 -0
- data/lib/graphql/execution/selections_step.rb +91 -0
- data/lib/graphql/execution.rb +8 -4
- data/lib/graphql/execution_error.rb +17 -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 +20 -6
- 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/schema_type.rb +8 -11
- 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 +26 -17
- data/lib/graphql/language/block_string.rb +34 -18
- data/lib/graphql/language/cache.rb +13 -0
- data/lib/graphql/language/comment.rb +18 -0
- data/lib/graphql/language/definition_slice.rb +1 -1
- data/lib/graphql/language/document_from_schema_definition.rb +90 -61
- data/lib/graphql/language/lexer.rb +323 -193
- data/lib/graphql/language/nodes.rb +139 -77
- data/lib/graphql/language/parser.rb +807 -1985
- data/lib/graphql/language/printer.rb +324 -151
- data/lib/graphql/language/sanitized_printer.rb +21 -23
- data/lib/graphql/language/static_visitor.rb +171 -0
- data/lib/graphql/language/visitor.rb +62 -119
- data/lib/graphql/language.rb +71 -1
- data/lib/graphql/load_application_object_failed_error.rb +5 -1
- data/lib/graphql/pagination/array_connection.rb +6 -6
- data/lib/graphql/pagination/connection.rb +30 -1
- data/lib/graphql/pagination/connections.rb +32 -0
- 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 +88 -144
- data/lib/graphql/query/null_context.rb +15 -18
- data/lib/graphql/query/partial.rb +194 -0
- data/lib/graphql/query/validation_pipeline.rb +4 -4
- data/lib/graphql/query/variable_validation_error.rb +1 -1
- data/lib/graphql/query/variables.rb +3 -3
- data/lib/graphql/query.rb +135 -81
- data/lib/graphql/railtie.rb +16 -6
- data/lib/graphql/rake_task.rb +3 -12
- 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/runtime_error.rb +6 -0
- data/lib/graphql/schema/addition.rb +26 -13
- data/lib/graphql/schema/always_visible.rb +7 -2
- data/lib/graphql/schema/argument.rb +78 -14
- data/lib/graphql/schema/base_64_encoder.rb +3 -5
- data/lib/graphql/schema/build_from_definition.rb +140 -66
- data/lib/graphql/schema/directive/flagged.rb +4 -2
- data/lib/graphql/schema/directive/one_of.rb +12 -0
- data/lib/graphql/schema/directive/specified_by.rb +14 -0
- data/lib/graphql/schema/directive.rb +78 -12
- data/lib/graphql/schema/enum.rb +110 -27
- data/lib/graphql/schema/enum_value.rb +11 -3
- data/lib/graphql/schema/field/connection_extension.rb +4 -51
- data/lib/graphql/schema/field/scope_extension.rb +19 -7
- data/lib/graphql/schema/field.rb +245 -119
- data/lib/graphql/schema/field_extension.rb +12 -9
- data/lib/graphql/schema/has_single_input_argument.rb +160 -0
- data/lib/graphql/schema/input_object.rb +123 -65
- data/lib/graphql/schema/interface.rb +60 -16
- data/lib/graphql/schema/introspection_system.rb +8 -17
- data/lib/graphql/schema/late_bound_type.rb +4 -0
- data/lib/graphql/schema/list.rb +8 -4
- data/lib/graphql/schema/loader.rb +3 -4
- data/lib/graphql/schema/member/base_dsl_methods.rb +18 -12
- data/lib/graphql/schema/member/has_arguments.rb +132 -100
- data/lib/graphql/schema/member/has_authorization.rb +35 -0
- data/lib/graphql/schema/member/has_dataloader.rb +99 -0
- data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
- data/lib/graphql/schema/member/has_directives.rb +5 -5
- data/lib/graphql/schema/member/has_fields.rb +121 -17
- data/lib/graphql/schema/member/has_interfaces.rb +27 -13
- 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/scoped.rb +19 -0
- data/lib/graphql/schema/member/type_system_helpers.rb +18 -5
- data/lib/graphql/schema/member/validates_input.rb +3 -3
- data/lib/graphql/schema/member.rb +6 -0
- data/lib/graphql/schema/mutation.rb +7 -0
- data/lib/graphql/schema/non_null.rb +1 -1
- data/lib/graphql/schema/object.rb +34 -8
- data/lib/graphql/schema/printer.rb +9 -7
- data/lib/graphql/schema/ractor_shareable.rb +79 -0
- data/lib/graphql/schema/relay_classic_mutation.rb +6 -129
- data/lib/graphql/schema/resolver.rb +128 -32
- data/lib/graphql/schema/scalar.rb +4 -9
- data/lib/graphql/schema/subscription.rb +63 -12
- data/lib/graphql/schema/timeout.rb +19 -2
- data/lib/graphql/schema/type_expression.rb +2 -2
- data/lib/graphql/schema/union.rb +2 -2
- data/lib/graphql/schema/unique_within_type.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 +464 -0
- data/lib/graphql/schema/visibility/visit.rb +190 -0
- data/lib/graphql/schema/visibility.rb +311 -0
- data/lib/graphql/schema/warden.rb +275 -103
- data/lib/graphql/schema/wrapper.rb +7 -1
- data/lib/graphql/schema.rb +954 -212
- data/lib/graphql/static_validation/all_rules.rb +3 -3
- data/lib/graphql/static_validation/base_visitor.rb +96 -71
- data/lib/graphql/static_validation/literal_validator.rb +6 -7
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +2 -2
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +6 -2
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +6 -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 +14 -3
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +59 -15
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +391 -262
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +6 -6
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +15 -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 +28 -8
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
- 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 +14 -2
- data/lib/graphql/static_validation/validation_context.rb +22 -6
- data/lib/graphql/static_validation/validator.rb +9 -1
- data/lib/graphql/static_validation.rb +0 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -5
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
- data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +45 -19
- data/lib/graphql/subscriptions/event.rb +22 -4
- data/lib/graphql/subscriptions/serialize.rb +3 -1
- data/lib/graphql/subscriptions.rb +56 -17
- data/lib/graphql/testing/helpers.rb +161 -0
- data/lib/graphql/testing/mock_action_cable.rb +111 -0
- data/lib/graphql/testing.rb +3 -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 +11 -3
- data/lib/graphql/tracing/appoptics_tracing.rb +9 -2
- 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 +75 -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/platform_tracing.rb +3 -1
- data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +5 -1
- 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 +82 -0
- data/lib/graphql/tracing/statsd_trace.rb +33 -41
- data/lib/graphql/tracing/statsd_tracing.rb +2 -0
- data/lib/graphql/tracing/trace.rb +118 -1
- data/lib/graphql/tracing.rb +31 -28
- data/lib/graphql/type_kinds.rb +2 -1
- data/lib/graphql/types/iso_8601_duration.rb +77 -0
- data/lib/graphql/types/relay/connection_behaviors.rb +45 -3
- data/lib/graphql/types/relay/edge_behaviors.rb +19 -1
- 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/types/relay/page_info_behaviors.rb +4 -0
- data/lib/graphql/types.rb +18 -10
- data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
- data/lib/graphql/unauthorized_error.rb +9 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +69 -54
- data/readme.md +12 -2
- metadata +236 -40
- 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 -276
- data/lib/graphql/analysis/ast.rb +0 -81
- data/lib/graphql/backtrace/inspect_result.rb +0 -50
- data/lib/graphql/backtrace/trace.rb +0 -96
- data/lib/graphql/backtrace/tracer.rb +0 -80
- data/lib/graphql/deprecation.rb +0 -9
- data/lib/graphql/filter.rb +0 -59
- data/lib/graphql/language/parser.y +0 -560
- data/lib/graphql/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/schema/null_mask.rb +0 -11
- data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
- data/lib/graphql/static_validation/type_stack.rb +0 -216
- data/lib/graphql/subscriptions/instrumentation.rb +0 -28
|
@@ -2,30 +2,64 @@
|
|
|
2
2
|
module GraphQL
|
|
3
3
|
class Subscriptions
|
|
4
4
|
class DefaultSubscriptionResolveExtension < GraphQL::Schema::FieldExtension
|
|
5
|
-
def resolve(context:, object
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
def resolve(context:, object: nil, objects: nil, arguments:)
|
|
6
|
+
if objects
|
|
7
|
+
has_override_implementation = @field.execution_mode != :direct_send
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
if !has_override_implementation
|
|
10
|
+
if context.query.subscription_update?
|
|
11
|
+
objects
|
|
12
|
+
else
|
|
13
|
+
objects.map { |o| context.skip }
|
|
14
|
+
end
|
|
12
15
|
else
|
|
13
|
-
|
|
16
|
+
yield(objects, arguments)
|
|
14
17
|
end
|
|
15
18
|
else
|
|
16
|
-
|
|
19
|
+
has_override_implementation = @field.resolver ||
|
|
20
|
+
object.respond_to?(@field.resolver_method)
|
|
21
|
+
|
|
22
|
+
if !has_override_implementation
|
|
23
|
+
if context.query.subscription_update?
|
|
24
|
+
object.object
|
|
25
|
+
else
|
|
26
|
+
context.skip
|
|
27
|
+
end
|
|
28
|
+
else
|
|
29
|
+
yield(object, arguments)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def after_resolve(values: nil, value: nil, context:, objects: nil, object: nil, arguments:, **rest)
|
|
35
|
+
if values
|
|
36
|
+
values.map do |value|
|
|
37
|
+
self.class.write_subscription(@field, value, arguments, context)
|
|
38
|
+
end
|
|
39
|
+
else
|
|
40
|
+
self.class.write_subscription(@field, value, arguments, context)
|
|
17
41
|
end
|
|
18
42
|
end
|
|
19
43
|
|
|
20
|
-
def
|
|
44
|
+
def self.write_subscription(field, value, arguments, context)
|
|
21
45
|
if value.is_a?(GraphQL::ExecutionError)
|
|
22
46
|
value
|
|
47
|
+
elsif field.resolver&.method_defined?(:subscription_written?) &&
|
|
48
|
+
(subscription_namespace = context.namespace(:subscriptions)) &&
|
|
49
|
+
(subscriptions_by_path = subscription_namespace[:subscriptions])
|
|
50
|
+
(subscription_instance = subscriptions_by_path[context.current_path])
|
|
51
|
+
# If it was already written, don't append this event to be written later
|
|
52
|
+
if !subscription_instance.subscription_written?
|
|
53
|
+
events = context.namespace(:subscriptions)[:events]
|
|
54
|
+
events << subscription_instance.event
|
|
55
|
+
end
|
|
56
|
+
value
|
|
23
57
|
elsif (events = context.namespace(:subscriptions)[:events])
|
|
24
58
|
# This is the first execution, so gather an Event
|
|
25
59
|
# for the backend to register:
|
|
26
60
|
event = Subscriptions::Event.new(
|
|
27
61
|
name: field.name,
|
|
28
|
-
arguments:
|
|
62
|
+
arguments: arguments,
|
|
29
63
|
context: context,
|
|
30
64
|
field: field,
|
|
31
65
|
)
|
|
@@ -33,7 +67,7 @@ module GraphQL
|
|
|
33
67
|
value
|
|
34
68
|
elsif context.query.subscription_topic == Subscriptions::Event.serialize(
|
|
35
69
|
field.name,
|
|
36
|
-
|
|
70
|
+
arguments,
|
|
37
71
|
field,
|
|
38
72
|
scope: (field.subscription_scope ? context[field.subscription_scope] : nil),
|
|
39
73
|
)
|
|
@@ -45,14 +79,6 @@ module GraphQL
|
|
|
45
79
|
context.skip
|
|
46
80
|
end
|
|
47
81
|
end
|
|
48
|
-
|
|
49
|
-
private
|
|
50
|
-
|
|
51
|
-
def arguments_without_field_extras(arguments:)
|
|
52
|
-
arguments.dup.tap do |event_args|
|
|
53
|
-
field.extras.each { |k| event_args.delete(k) }
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
82
|
end
|
|
57
83
|
end
|
|
58
84
|
end
|
|
@@ -20,7 +20,7 @@ module GraphQL
|
|
|
20
20
|
|
|
21
21
|
def initialize(name:, arguments:, field: nil, context: nil, scope: nil)
|
|
22
22
|
@name = name
|
|
23
|
-
@arguments = arguments
|
|
23
|
+
@arguments = self.class.arguments_without_field_extras(arguments: arguments, field: field)
|
|
24
24
|
@context = context
|
|
25
25
|
field ||= context.field
|
|
26
26
|
scope_key = field.subscription_scope
|
|
@@ -37,8 +37,9 @@ module GraphQL
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
# @return [String] an identifier for this unit of subscription
|
|
40
|
-
def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext)
|
|
40
|
+
def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext.instance)
|
|
41
41
|
subscription = field.resolver || GraphQL::Schema::Subscription
|
|
42
|
+
arguments = arguments_without_field_extras(field: field, arguments: arguments)
|
|
42
43
|
normalized_args = stringify_args(field, arguments.to_h, context)
|
|
43
44
|
subscription.topic_for(arguments: normalized_args, field: field, scope: scope)
|
|
44
45
|
end
|
|
@@ -60,6 +61,16 @@ module GraphQL
|
|
|
60
61
|
end
|
|
61
62
|
|
|
62
63
|
class << self
|
|
64
|
+
def arguments_without_field_extras(arguments:, field:)
|
|
65
|
+
if !field.extras.empty?
|
|
66
|
+
arguments = arguments.dup
|
|
67
|
+
field.extras.each do |extra_key|
|
|
68
|
+
arguments.delete(extra_key)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
arguments
|
|
72
|
+
end
|
|
73
|
+
|
|
63
74
|
private
|
|
64
75
|
|
|
65
76
|
# This method does not support cyclic references in the Hash,
|
|
@@ -93,6 +104,7 @@ module GraphQL
|
|
|
93
104
|
|
|
94
105
|
def stringify_args(arg_owner, args, context)
|
|
95
106
|
arg_owner = arg_owner.respond_to?(:unwrap) ? arg_owner.unwrap : arg_owner # remove list and non-null wrappers
|
|
107
|
+
|
|
96
108
|
case args
|
|
97
109
|
when Hash
|
|
98
110
|
next_args = {}
|
|
@@ -126,12 +138,18 @@ module GraphQL
|
|
|
126
138
|
when GraphQL::Schema::InputObject
|
|
127
139
|
stringify_args(arg_owner, args.to_h, context)
|
|
128
140
|
else
|
|
129
|
-
|
|
141
|
+
if arg_owner.is_a?(Class) && arg_owner < GraphQL::Schema::Enum
|
|
142
|
+
# `prepare:` may have made the value something other than
|
|
143
|
+
# a defined value of this enum -- use _that_ in this case.
|
|
144
|
+
arg_owner.coerce_isolated_input(args) || args
|
|
145
|
+
else
|
|
146
|
+
args
|
|
147
|
+
end
|
|
130
148
|
end
|
|
131
149
|
end
|
|
132
150
|
|
|
133
151
|
def get_arg_definition(arg_owner, arg_name, context)
|
|
134
|
-
|
|
152
|
+
context.types.argument(arg_owner, arg_name) || context.types.arguments(arg_owner).find { |v| v.keyword.to_s == arg_name }
|
|
135
153
|
end
|
|
136
154
|
end
|
|
137
155
|
end
|
|
@@ -146,8 +146,10 @@ module GraphQL
|
|
|
146
146
|
elsif obj.is_a?(Date) || obj.is_a?(Time)
|
|
147
147
|
# DateTime extends Date; for TimeWithZone, call `.utc` first.
|
|
148
148
|
{ TIMESTAMP_KEY => [obj.class.name, obj.strftime(TIMESTAMP_FORMAT)] }
|
|
149
|
-
elsif obj.is_a?(OpenStruct)
|
|
149
|
+
elsif defined?(OpenStruct) && obj.is_a?(OpenStruct)
|
|
150
150
|
{ OPEN_STRUCT_KEY => dump_value(obj.to_h) }
|
|
151
|
+
elsif defined?(ActiveRecord::Relation) && obj.is_a?(ActiveRecord::Relation)
|
|
152
|
+
dump_value(obj.to_a)
|
|
151
153
|
else
|
|
152
154
|
obj
|
|
153
155
|
end
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
require "securerandom"
|
|
3
3
|
require "graphql/subscriptions/broadcast_analyzer"
|
|
4
4
|
require "graphql/subscriptions/event"
|
|
5
|
-
require "graphql/subscriptions/instrumentation"
|
|
6
5
|
require "graphql/subscriptions/serialize"
|
|
7
6
|
require "graphql/subscriptions/action_cable_subscriptions"
|
|
8
7
|
require "graphql/subscriptions/default_subscription_resolve_extension"
|
|
@@ -30,8 +29,6 @@ module GraphQL
|
|
|
30
29
|
raise ArgumentError, "Can't reinstall subscriptions. #{schema} is using #{schema.subscriptions}, can't also add #{self}"
|
|
31
30
|
end
|
|
32
31
|
|
|
33
|
-
instrumentation = Subscriptions::Instrumentation.new(schema: schema)
|
|
34
|
-
defn.instrument(:query, instrumentation)
|
|
35
32
|
options[:schema] = schema
|
|
36
33
|
schema.subscriptions = self.new(**options)
|
|
37
34
|
schema.add_subscription_extension_if_necessary
|
|
@@ -62,17 +59,17 @@ module GraphQL
|
|
|
62
59
|
# @return [void]
|
|
63
60
|
def trigger(event_name, args, object, scope: nil, context: {})
|
|
64
61
|
# Make something as context-like as possible, even though there isn't a current query:
|
|
65
|
-
dummy_query =
|
|
62
|
+
dummy_query = @schema.query_class.new(@schema, "{ __typename }", validate: false, context: context)
|
|
66
63
|
context = dummy_query.context
|
|
67
64
|
event_name = event_name.to_s
|
|
68
65
|
|
|
69
66
|
# Try with the verbatim input first:
|
|
70
|
-
field =
|
|
67
|
+
field = dummy_query.types.field(@schema.subscription, event_name) # rubocop:disable Development/ContextIsPassedCop
|
|
71
68
|
|
|
72
69
|
if field.nil?
|
|
73
70
|
# And if it wasn't found, normalize it:
|
|
74
71
|
normalized_event_name = normalize_name(event_name)
|
|
75
|
-
field =
|
|
72
|
+
field = dummy_query.types.field(@schema.subscription, normalized_event_name) # rubocop:disable Development/ContextIsPassedCop
|
|
76
73
|
if field.nil?
|
|
77
74
|
raise InvalidTriggerError, "No subscription matching trigger: #{event_name} (looked for #{@schema.subscription.graphql_name}.#{normalized_event_name})"
|
|
78
75
|
end
|
|
@@ -83,7 +80,7 @@ module GraphQL
|
|
|
83
80
|
|
|
84
81
|
# Normalize symbol-keyed args to strings, try camelizing them
|
|
85
82
|
# Should this accept a real context somehow?
|
|
86
|
-
normalized_args = normalize_arguments(normalized_event_name, field, args,
|
|
83
|
+
normalized_args = normalize_arguments(normalized_event_name, field, args, @schema.null_context)
|
|
87
84
|
|
|
88
85
|
event = Subscriptions::Event.new(
|
|
89
86
|
name: normalized_event_name,
|
|
@@ -125,10 +122,10 @@ module GraphQL
|
|
|
125
122
|
variables: variables,
|
|
126
123
|
root_value: object,
|
|
127
124
|
}
|
|
128
|
-
|
|
125
|
+
|
|
129
126
|
# merge event's and query's context together
|
|
130
127
|
context.merge!(event.context) unless event.context.nil? || context.nil?
|
|
131
|
-
|
|
128
|
+
|
|
132
129
|
execute_options[:validate] = validate_update?(**execute_options)
|
|
133
130
|
result = @schema.execute(**execute_options)
|
|
134
131
|
subscriptions_context = result.context.namespace(:subscriptions)
|
|
@@ -136,11 +133,9 @@ module GraphQL
|
|
|
136
133
|
result = nil
|
|
137
134
|
end
|
|
138
135
|
|
|
139
|
-
unsubscribed
|
|
140
|
-
|
|
141
|
-
if unsubscribed
|
|
136
|
+
if subscriptions_context[:unsubscribed] && !subscriptions_context[:final_update]
|
|
142
137
|
# `unsubscribe` was called, clean up on our side
|
|
143
|
-
#
|
|
138
|
+
# The transport should also send `{more: false}` to client
|
|
144
139
|
delete_subscription(subscription_id)
|
|
145
140
|
result = nil
|
|
146
141
|
end
|
|
@@ -164,7 +159,14 @@ module GraphQL
|
|
|
164
159
|
res = execute_update(subscription_id, event, object)
|
|
165
160
|
if !res.nil?
|
|
166
161
|
deliver(subscription_id, res)
|
|
162
|
+
|
|
163
|
+
if res.context.namespace(:subscriptions)[:unsubscribed]
|
|
164
|
+
# `unsubscribe` was called, clean up on our side
|
|
165
|
+
# The transport should also send `{more: false}` to client
|
|
166
|
+
delete_subscription(subscription_id)
|
|
167
|
+
end
|
|
167
168
|
end
|
|
169
|
+
|
|
168
170
|
end
|
|
169
171
|
|
|
170
172
|
# Event `event` occurred on `object`,
|
|
@@ -229,14 +231,49 @@ module GraphQL
|
|
|
229
231
|
|
|
230
232
|
# @return [Boolean] if true, then a query like this one would be broadcasted
|
|
231
233
|
def broadcastable?(query_str, **query_options)
|
|
232
|
-
query =
|
|
234
|
+
query = @schema.query_class.new(@schema, query_str, **query_options)
|
|
233
235
|
if !query.valid?
|
|
234
236
|
raise "Invalid query: #{query.validation_errors.map(&:to_h).inspect}"
|
|
235
237
|
end
|
|
236
|
-
GraphQL::Analysis
|
|
238
|
+
GraphQL::Analysis.analyze_query(query, @schema.query_analyzers)
|
|
237
239
|
query.context.namespace(:subscriptions)[:subscription_broadcastable]
|
|
238
240
|
end
|
|
239
241
|
|
|
242
|
+
# Called during execution when a new `subscription ...` operation is received
|
|
243
|
+
# @param query [GraphQL::Query]
|
|
244
|
+
# @return [void]
|
|
245
|
+
def initialize_subscriptions(query)
|
|
246
|
+
subs_namespace = query.context.namespace(:subscriptions)
|
|
247
|
+
subs_namespace[:events] = []
|
|
248
|
+
subs_namespace[:subscriptions] = {}
|
|
249
|
+
nil
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Called during execution when a subscription operation has finished
|
|
253
|
+
# @param query [GraphQL::Query]
|
|
254
|
+
# @return [void]
|
|
255
|
+
def finish_subscriptions(query)
|
|
256
|
+
if (events = query.context.namespace(:subscriptions)[:events]) && !events.empty?
|
|
257
|
+
write_subscription(query, events)
|
|
258
|
+
end
|
|
259
|
+
nil
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def finalizer
|
|
263
|
+
Finalizer.new(self)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
class Finalizer
|
|
267
|
+
include Execution::Finalizer
|
|
268
|
+
def initialize(subscriptions)
|
|
269
|
+
@subscriptions = subscriptions
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def finalize_graphql_result(query, result_data, result_key)
|
|
273
|
+
@subscriptions.finish_subscriptions(query)
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
240
277
|
private
|
|
241
278
|
|
|
242
279
|
# Recursively normalize `args` as belonging to `arg_owner`:
|
|
@@ -248,6 +285,8 @@ module GraphQL
|
|
|
248
285
|
def normalize_arguments(event_name, arg_owner, args, context)
|
|
249
286
|
case arg_owner
|
|
250
287
|
when GraphQL::Schema::Field, Class
|
|
288
|
+
return args if args.nil?
|
|
289
|
+
|
|
251
290
|
if arg_owner.is_a?(Class) && !arg_owner.kind.input_object?
|
|
252
291
|
# it's a type, but not an input object
|
|
253
292
|
return args
|
|
@@ -287,7 +326,7 @@ module GraphQL
|
|
|
287
326
|
end
|
|
288
327
|
end
|
|
289
328
|
|
|
290
|
-
if missing_arg_names.
|
|
329
|
+
if !missing_arg_names.empty?
|
|
291
330
|
arg_owner_name = if arg_owner.is_a?(GraphQL::Schema::Field)
|
|
292
331
|
arg_owner.path
|
|
293
332
|
elsif arg_owner.is_a?(Class)
|
|
@@ -300,7 +339,7 @@ module GraphQL
|
|
|
300
339
|
|
|
301
340
|
normalized_args
|
|
302
341
|
when GraphQL::Schema::List
|
|
303
|
-
args
|
|
342
|
+
args&.map { |a| normalize_arguments(event_name, arg_owner.of_type, a, context) }
|
|
304
343
|
when GraphQL::Schema::NonNull
|
|
305
344
|
normalize_arguments(event_name, arg_owner.of_type, args, context)
|
|
306
345
|
else
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphQL
|
|
3
|
+
module Testing
|
|
4
|
+
module Helpers
|
|
5
|
+
# @param schema_class [Class<GraphQL::Schema>]
|
|
6
|
+
# @return [Module] A helpers module which always uses the given schema
|
|
7
|
+
def self.for(schema_class)
|
|
8
|
+
SchemaHelpers.for(schema_class)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class Error < GraphQL::Error
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class TypeNotVisibleError < Error
|
|
15
|
+
def initialize(type_name:)
|
|
16
|
+
message = "`#{type_name}` should be `visible?` this field resolution and `context`, but it was not"
|
|
17
|
+
super(message)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class FieldNotVisibleError < Error
|
|
22
|
+
def initialize(type_name:, field_name:)
|
|
23
|
+
message = "`#{type_name}.#{field_name}` should be `visible?` for this resolution, but it was not"
|
|
24
|
+
super(message)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class TypeNotDefinedError < Error
|
|
29
|
+
def initialize(type_name:)
|
|
30
|
+
message = "No type named `#{type_name}` is defined; choose another type name or define this type."
|
|
31
|
+
super(message)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class FieldNotDefinedError < Error
|
|
36
|
+
def initialize(type_name:, field_name:)
|
|
37
|
+
message = "`#{type_name}` has no field named `#{field_name}`; pick another name or define this field."
|
|
38
|
+
super(message)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def run_graphql_field(schema, field_path, object, arguments: {}, context: {}, ast_node: nil, lookahead: nil, visibility_profile: nil)
|
|
43
|
+
type_name, *field_names = field_path.split(".")
|
|
44
|
+
dummy_query = GraphQL::Query.new(schema, "{ __typename }", context: context, visibility_profile: visibility_profile)
|
|
45
|
+
query_context = dummy_query.context
|
|
46
|
+
dataloader = query_context.dataloader
|
|
47
|
+
object_type = dummy_query.types.type(type_name) # rubocop:disable Development/ContextIsPassedCop
|
|
48
|
+
if object_type
|
|
49
|
+
graphql_result = object
|
|
50
|
+
field_names.each do |field_name|
|
|
51
|
+
inner_object = graphql_result
|
|
52
|
+
dataloader.run_isolated {
|
|
53
|
+
graphql_result = object_type.wrap(inner_object, query_context)
|
|
54
|
+
}
|
|
55
|
+
if graphql_result.nil?
|
|
56
|
+
return nil
|
|
57
|
+
end
|
|
58
|
+
visible_field = dummy_query.types.field(object_type, field_name) # rubocop:disable Development/ContextIsPassedCop
|
|
59
|
+
if visible_field
|
|
60
|
+
dataloader.run_isolated {
|
|
61
|
+
query_context[:current_field] = visible_field
|
|
62
|
+
field_args = visible_field.coerce_arguments(graphql_result, arguments, query_context)
|
|
63
|
+
field_args = schema.sync_lazy(field_args)
|
|
64
|
+
if !visible_field.extras.empty?
|
|
65
|
+
extra_args = {}
|
|
66
|
+
visible_field.extras.each do |extra|
|
|
67
|
+
extra_args[extra] = case extra
|
|
68
|
+
when :ast_node
|
|
69
|
+
ast_node ||= GraphQL::Language::Nodes::Field.new(name: visible_field.graphql_name)
|
|
70
|
+
when :lookahead
|
|
71
|
+
lookahead ||= begin
|
|
72
|
+
ast_node ||= GraphQL::Language::Nodes::Field.new(name: visible_field.graphql_name)
|
|
73
|
+
Execution::Lookahead.new(
|
|
74
|
+
query: dummy_query,
|
|
75
|
+
ast_nodes: [ast_node],
|
|
76
|
+
field: visible_field,
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
raise ArgumentError, "This extra isn't supported in `run_graphql_field` yet: `#{extra.inspect}`. Open an issue on GitHub to request it: https://github.com/rmosolgo/graphql-ruby/issues/new"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
field_args = field_args.merge_extras(extra_args)
|
|
85
|
+
end
|
|
86
|
+
graphql_result = visible_field.resolve(graphql_result, field_args.keyword_arguments, query_context)
|
|
87
|
+
graphql_result = schema.sync_lazy(graphql_result)
|
|
88
|
+
}
|
|
89
|
+
object_type = visible_field.type.unwrap
|
|
90
|
+
elsif object_type.all_field_definitions.any? { |f| f.graphql_name == field_name }
|
|
91
|
+
raise FieldNotVisibleError.new(field_name: field_name, type_name: type_name)
|
|
92
|
+
else
|
|
93
|
+
raise FieldNotDefinedError.new(type_name: type_name, field_name: field_name)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
graphql_result
|
|
97
|
+
else
|
|
98
|
+
unfiltered_type = schema.use_visibility_profile? ? schema.visibility.get_type(type_name) : schema.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
|
|
99
|
+
if unfiltered_type
|
|
100
|
+
raise TypeNotVisibleError.new(type_name: type_name)
|
|
101
|
+
else
|
|
102
|
+
raise TypeNotDefinedError.new(type_name: type_name)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def with_resolution_context(schema, type:, object:, context:{}, visibility_profile: nil)
|
|
108
|
+
resolution_context = ResolutionAssertionContext.new(
|
|
109
|
+
self,
|
|
110
|
+
schema: schema,
|
|
111
|
+
type_name: type,
|
|
112
|
+
object: object,
|
|
113
|
+
context: context,
|
|
114
|
+
visibility_profile: visibility_profile,
|
|
115
|
+
)
|
|
116
|
+
yield(resolution_context)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class ResolutionAssertionContext
|
|
120
|
+
def initialize(test, type_name:, object:, schema:, context:, visibility_profile:)
|
|
121
|
+
@test = test
|
|
122
|
+
@type_name = type_name
|
|
123
|
+
@object = object
|
|
124
|
+
@schema = schema
|
|
125
|
+
@context = context
|
|
126
|
+
@visibility_profile = visibility_profile
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
attr_reader :visibility_profile
|
|
130
|
+
|
|
131
|
+
def run_graphql_field(field_name, arguments: {})
|
|
132
|
+
if @schema
|
|
133
|
+
@test.run_graphql_field(@schema, "#{@type_name}.#{field_name}", @object, arguments: arguments, context: @context, visibility_profile: @visibility_profile)
|
|
134
|
+
else
|
|
135
|
+
@test.run_graphql_field("#{@type_name}.#{field_name}", @object, arguments: arguments, context: @context, visibility_profile: @visibility_profile)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
module SchemaHelpers
|
|
141
|
+
include Helpers
|
|
142
|
+
|
|
143
|
+
def run_graphql_field(field_path, object, arguments: {}, context: {}, visibility_profile: nil)
|
|
144
|
+
super(@@schema_class_for_helpers, field_path, object, arguments: arguments, context: context, visibility_profile: visibility_profile)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def with_resolution_context(*args, **kwargs, &block)
|
|
148
|
+
# schema will be added later
|
|
149
|
+
super(nil, *args, **kwargs, &block)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def self.for(schema_class)
|
|
153
|
+
Module.new do
|
|
154
|
+
include SchemaHelpers
|
|
155
|
+
@@schema_class_for_helpers = schema_class
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphQL
|
|
3
|
+
module Testing
|
|
4
|
+
# A stub implementation of ActionCable.
|
|
5
|
+
# Any methods to support the mock backend have `mock` in the name.
|
|
6
|
+
#
|
|
7
|
+
# @example Configuring your schema to use MockActionCable in the test environment
|
|
8
|
+
# class MySchema < GraphQL::Schema
|
|
9
|
+
# # Use MockActionCable in test:
|
|
10
|
+
# use GraphQL::Subscriptions::ActionCableSubscriptions,
|
|
11
|
+
# action_cable: Rails.env.test? ? GraphQL::Testing::MockActionCable : ActionCable
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# @example Clearing old data before each test
|
|
15
|
+
# setup do
|
|
16
|
+
# GraphQL::Testing::MockActionCable.clear_mocks
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example Using MockActionCable in a test case
|
|
20
|
+
# # Create a channel to use in the test, pass it to GraphQL
|
|
21
|
+
# mock_channel = GraphQL::Testing::MockActionCable.get_mock_channel
|
|
22
|
+
# ActionCableTestSchema.execute("subscription { newsFlash { text } }", context: { channel: mock_channel })
|
|
23
|
+
#
|
|
24
|
+
# # Trigger a subscription update
|
|
25
|
+
# ActionCableTestSchema.subscriptions.trigger(:news_flash, {}, {text: "After yesterday's rain, someone stopped on Rio Road to help a box turtle across five lanes of traffic"})
|
|
26
|
+
#
|
|
27
|
+
# # Check messages on the channel
|
|
28
|
+
# expected_msg = {
|
|
29
|
+
# result: {
|
|
30
|
+
# "data" => {
|
|
31
|
+
# "newsFlash" => {
|
|
32
|
+
# "text" => "After yesterday's rain, someone stopped on Rio Road to help a box turtle across five lanes of traffic"
|
|
33
|
+
# }
|
|
34
|
+
# }
|
|
35
|
+
# },
|
|
36
|
+
# more: true,
|
|
37
|
+
# }
|
|
38
|
+
# assert_equal [expected_msg], mock_channel.mock_broadcasted_messages
|
|
39
|
+
#
|
|
40
|
+
class MockActionCable
|
|
41
|
+
class MockChannel
|
|
42
|
+
def initialize
|
|
43
|
+
@mock_broadcasted_messages = []
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [Array<Hash>] Payloads "sent" to this channel by GraphQL-Ruby
|
|
47
|
+
attr_reader :mock_broadcasted_messages
|
|
48
|
+
|
|
49
|
+
# Called by ActionCableSubscriptions. Implements a Rails API.
|
|
50
|
+
def stream_from(stream_name, coder: nil, &block)
|
|
51
|
+
# Rails uses `coder`, we don't
|
|
52
|
+
block ||= ->(msg) { @mock_broadcasted_messages << msg }
|
|
53
|
+
MockActionCable.mock_stream_for(stream_name).add_mock_channel(self, block)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Used by mock code
|
|
58
|
+
# @api private
|
|
59
|
+
class MockStream
|
|
60
|
+
def initialize
|
|
61
|
+
@mock_channels = {}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def add_mock_channel(channel, handler)
|
|
65
|
+
@mock_channels[channel] = handler
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def mock_broadcast(message)
|
|
69
|
+
@mock_channels.each do |channel, handler|
|
|
70
|
+
handler && handler.call(message)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class << self
|
|
76
|
+
# Call this before each test run to make sure that MockActionCable's data is empty
|
|
77
|
+
def clear_mocks
|
|
78
|
+
@mock_streams = {}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Implements Rails API
|
|
82
|
+
def server
|
|
83
|
+
self
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Implements Rails API
|
|
87
|
+
def broadcast(stream_name, message)
|
|
88
|
+
stream = @mock_streams[stream_name]
|
|
89
|
+
stream && stream.mock_broadcast(message)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Used by mock code
|
|
93
|
+
def mock_stream_for(stream_name)
|
|
94
|
+
@mock_streams[stream_name] ||= MockStream.new
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Use this as `context[:channel]` to simulate an ActionCable channel
|
|
98
|
+
#
|
|
99
|
+
# @return [GraphQL::Testing::MockActionCable::MockChannel]
|
|
100
|
+
def get_mock_channel
|
|
101
|
+
MockChannel.new
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @return [Array<String>] Streams that currently have subscribers
|
|
105
|
+
def mock_stream_names
|
|
106
|
+
@mock_streams.keys
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "graphql/tracing/notifications_trace"
|
|
4
4
|
|
|
5
5
|
module GraphQL
|
|
6
6
|
module Tracing
|
|
7
|
-
# This implementation forwards events to ActiveSupport::Notifications
|
|
8
|
-
#
|
|
7
|
+
# This implementation forwards events to ActiveSupport::Notifications with a `graphql` suffix.
|
|
8
|
+
#
|
|
9
|
+
# @example Sending execution events to ActiveSupport::Notifications
|
|
10
|
+
# class MySchema < GraphQL::Schema
|
|
11
|
+
# trace_with(GraphQL::Tracing::ActiveSupportNotificationsTrace)
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# @example Subscribing to GraphQL events with ActiveSupport::Notifications
|
|
15
|
+
# ActiveSupport::Notifications.subscribe(/graphql/) do |event|
|
|
16
|
+
# pp event.name
|
|
17
|
+
# pp event.payload
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
9
20
|
module ActiveSupportNotificationsTrace
|
|
10
21
|
include NotificationsTrace
|
|
11
22
|
def initialize(engine: ActiveSupport::Notifications, **rest)
|