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
|
@@ -14,9 +14,11 @@ module GraphQL
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def message
|
|
17
|
-
"Field '#{field_name}' has #{kind == :argument ? 'an' : 'a'} #{kind} conflict: #{conflicts}?"
|
|
17
|
+
@message || "Field '#{field_name}' has #{kind == :argument ? 'an' : 'a'} #{kind} conflict: #{conflicts}?"
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
attr_writer :message
|
|
21
|
+
|
|
20
22
|
def path
|
|
21
23
|
[]
|
|
22
24
|
end
|
|
@@ -26,7 +28,13 @@ module GraphQL
|
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
def add_conflict(node, conflict_str)
|
|
29
|
-
|
|
31
|
+
# Check if we already have an error for this exact node.
|
|
32
|
+
# Use object identity first (fast path), then fall back to
|
|
33
|
+
# value + location comparison for duplicate AST nodes.
|
|
34
|
+
if nodes.any? { |n| n.equal?(node) || (n.line == node.line && n.col == node.col && n == node) }
|
|
35
|
+
# already have an error for this node
|
|
36
|
+
return
|
|
37
|
+
end
|
|
30
38
|
|
|
31
39
|
@nodes << node
|
|
32
40
|
@conflicts << conflict_str
|
|
@@ -8,8 +8,8 @@ module GraphQL
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def on_inline_fragment(node, parent)
|
|
11
|
-
fragment_parent =
|
|
12
|
-
fragment_child =
|
|
11
|
+
fragment_parent = @parent_object_type
|
|
12
|
+
fragment_child = @current_object_type
|
|
13
13
|
if fragment_child
|
|
14
14
|
validate_fragment_in_scope(fragment_parent, fragment_child, node, context, context.path)
|
|
15
15
|
end
|
|
@@ -17,7 +17,7 @@ module GraphQL
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def on_fragment_spread(node, parent)
|
|
20
|
-
fragment_parent =
|
|
20
|
+
fragment_parent = @current_object_type
|
|
21
21
|
@spreads_to_validate << FragmentSpread.new(node: node, parent_type: fragment_parent, path: context.path)
|
|
22
22
|
super
|
|
23
23
|
end
|
|
@@ -28,7 +28,7 @@ module GraphQL
|
|
|
28
28
|
frag_node = context.fragments[frag_spread.node.name]
|
|
29
29
|
if frag_node
|
|
30
30
|
fragment_child_name = frag_node.type.name
|
|
31
|
-
fragment_child =
|
|
31
|
+
fragment_child = @types.type(fragment_child_name)
|
|
32
32
|
# Might be non-existent type name
|
|
33
33
|
if fragment_child
|
|
34
34
|
validate_fragment_in_scope(frag_spread.parent_type, fragment_child, frag_spread.node, context, frag_spread.path)
|
|
@@ -44,8 +44,8 @@ module GraphQL
|
|
|
44
44
|
# It's not a valid fragment type, this error was handled someplace else
|
|
45
45
|
return
|
|
46
46
|
end
|
|
47
|
-
parent_types =
|
|
48
|
-
child_types =
|
|
47
|
+
parent_types = @types.possible_types(parent_type.unwrap)
|
|
48
|
+
child_types = @types.possible_types(child_type.unwrap)
|
|
49
49
|
|
|
50
50
|
if child_types.none? { |c| parent_types.include?(c) }
|
|
51
51
|
name = node.respond_to?(:name) ? " #{node.name}" : ""
|
|
@@ -21,10 +21,23 @@ module GraphQL
|
|
|
21
21
|
true
|
|
22
22
|
else
|
|
23
23
|
type_name = fragment_node.type.name
|
|
24
|
-
type =
|
|
24
|
+
type = @types.type(type_name)
|
|
25
25
|
if type.nil?
|
|
26
|
+
suggestion = if @schema.did_you_mean
|
|
27
|
+
@all_possible_fragment_type_names ||= begin
|
|
28
|
+
names = []
|
|
29
|
+
context.types.all_types.each do |type|
|
|
30
|
+
if type.kind.fields?
|
|
31
|
+
names << type.graphql_name
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
names
|
|
35
|
+
end
|
|
36
|
+
context.did_you_mean_suggestion(type_name, @all_possible_fragment_type_names)
|
|
37
|
+
end
|
|
38
|
+
|
|
26
39
|
add_error(GraphQL::StaticValidation::FragmentTypesExistError.new(
|
|
27
|
-
"No such type #{type_name}, so it can't be a fragment condition",
|
|
40
|
+
"No such type #{type_name}, so it can't be a fragment condition#{suggestion}",
|
|
28
41
|
nodes: fragment_node,
|
|
29
42
|
type: type_name
|
|
30
43
|
))
|
|
@@ -19,7 +19,7 @@ module GraphQL
|
|
|
19
19
|
true
|
|
20
20
|
else
|
|
21
21
|
type_name = node_type.to_query_string
|
|
22
|
-
type_def =
|
|
22
|
+
type_def = @types.type(type_name)
|
|
23
23
|
if type_def.nil? || !type_def.kind.composite?
|
|
24
24
|
add_error(GraphQL::StaticValidation::FragmentsAreOnCompositeTypesError.new(
|
|
25
25
|
"Invalid fragment on type #{type_name} (must be Union, Interface or Object)",
|
|
@@ -3,7 +3,7 @@ module GraphQL
|
|
|
3
3
|
module StaticValidation
|
|
4
4
|
module MutationRootExists
|
|
5
5
|
def on_operation_definition(node, _parent)
|
|
6
|
-
if node.operation_type == 'mutation' && context.
|
|
6
|
+
if node.operation_type == 'mutation' && context.query.types.mutation_root.nil?
|
|
7
7
|
add_error(GraphQL::StaticValidation::MutationRootExistsError.new(
|
|
8
8
|
'Schema is not configured for mutations',
|
|
9
9
|
nodes: node
|
|
@@ -32,7 +32,7 @@ module GraphQL
|
|
|
32
32
|
|
|
33
33
|
def on_document(node, parent)
|
|
34
34
|
super
|
|
35
|
-
if
|
|
35
|
+
if !@schema_definition_nodes.empty?
|
|
36
36
|
add_error(GraphQL::StaticValidation::NoDefinitionsArePresentError.new(%|Query cannot contain schema definitions|, nodes: @schema_definition_nodes))
|
|
37
37
|
end
|
|
38
38
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphQL
|
|
3
|
+
module StaticValidation
|
|
4
|
+
class NotSingleSubscriptionError < StaticValidation::Error
|
|
5
|
+
def initialize(message, path: nil, nodes: [])
|
|
6
|
+
super(message, path: path, nodes: nodes)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# A hash representation of this Message
|
|
10
|
+
def to_h
|
|
11
|
+
extensions = {
|
|
12
|
+
"code" => code,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
super.merge({
|
|
16
|
+
"extensions" => extensions
|
|
17
|
+
})
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def code
|
|
21
|
+
"notSingleSubscription"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -3,7 +3,7 @@ module GraphQL
|
|
|
3
3
|
module StaticValidation
|
|
4
4
|
module QueryRootExists
|
|
5
5
|
def on_operation_definition(node, _parent)
|
|
6
|
-
if (node.operation_type == 'query' || node.operation_type.nil?) && context.
|
|
6
|
+
if (node.operation_type == 'query' || node.operation_type.nil?) && context.query.types.query_root.nil?
|
|
7
7
|
add_error(GraphQL::StaticValidation::QueryRootExistsError.new(
|
|
8
8
|
'Schema is not configured for queries',
|
|
9
9
|
nodes: node
|
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
module GraphQL
|
|
3
3
|
module StaticValidation
|
|
4
4
|
module RequiredArgumentsArePresent
|
|
5
|
+
def initialize(*)
|
|
6
|
+
super
|
|
7
|
+
@required_args_cache = {}.compare_by_identity
|
|
8
|
+
end
|
|
9
|
+
|
|
5
10
|
def on_field(node, _parent)
|
|
6
|
-
assert_required_args(node,
|
|
11
|
+
assert_required_args(node, @current_field_definition)
|
|
7
12
|
super
|
|
8
13
|
end
|
|
9
14
|
|
|
@@ -16,15 +21,30 @@ module GraphQL
|
|
|
16
21
|
private
|
|
17
22
|
|
|
18
23
|
def assert_required_args(ast_node, defn)
|
|
19
|
-
|
|
20
|
-
return if args.empty?
|
|
21
|
-
present_argument_names = ast_node.arguments.map(&:name)
|
|
22
|
-
required_argument_names = context.warden.arguments(defn)
|
|
23
|
-
.select { |a| a.type.kind.non_null? && !a.default_value? && context.warden.get_argument(defn, a.name) }
|
|
24
|
-
.map(&:name)
|
|
24
|
+
return unless defn
|
|
25
25
|
|
|
26
|
+
# Cache required argument names per definition to avoid re-iterating
|
|
27
|
+
# arguments for the same definition across field instances
|
|
28
|
+
if @required_args_cache.key?(defn)
|
|
29
|
+
required_argument_names = @required_args_cache[defn]
|
|
30
|
+
else
|
|
31
|
+
args = @types.arguments(defn)
|
|
32
|
+
required_argument_names = nil
|
|
33
|
+
if !args.empty?
|
|
34
|
+
args.each do |a|
|
|
35
|
+
if a.type.kind.non_null? && !a.default_value? && @types.argument(defn, a.name)
|
|
36
|
+
(required_argument_names ||= []) << a.graphql_name
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
@required_args_cache[defn] = required_argument_names
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
return if required_argument_names.nil?
|
|
44
|
+
|
|
45
|
+
present_argument_names = ast_node.arguments.map(&:name)
|
|
26
46
|
missing_names = required_argument_names - present_argument_names
|
|
27
|
-
if missing_names.
|
|
47
|
+
if !missing_names.empty?
|
|
28
48
|
add_error(GraphQL::StaticValidation::RequiredArgumentsArePresentError.new(
|
|
29
49
|
"#{ast_node.class.name.split("::").last} '#{ast_node.name}' is missing required arguments: #{missing_names.join(", ")}",
|
|
30
50
|
nodes: ast_node,
|
|
@@ -26,7 +26,7 @@ module GraphQL
|
|
|
26
26
|
context.directive_definition || context.field_definition
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
parent_type = context.
|
|
29
|
+
parent_type = context.types.argument(defn, parent_name(parent, defn))
|
|
30
30
|
parent_type ? parent_type.type.unwrap : nil
|
|
31
31
|
end
|
|
32
32
|
|
|
@@ -34,16 +34,16 @@ module GraphQL
|
|
|
34
34
|
parent_type = get_parent_type(context, parent)
|
|
35
35
|
return unless parent_type && parent_type.kind.input_object?
|
|
36
36
|
|
|
37
|
-
required_fields = context.
|
|
38
|
-
.select{|arg| arg.type.kind.non_null?}
|
|
39
|
-
.map(&:graphql_name)
|
|
37
|
+
required_fields = context.types.arguments(parent_type)
|
|
38
|
+
.select{ |arg| arg.type.kind.non_null? && !arg.default_value? }
|
|
39
|
+
.map!(&:graphql_name)
|
|
40
40
|
|
|
41
41
|
present_fields = ast_node.arguments.map(&:name)
|
|
42
42
|
missing_fields = required_fields - present_fields
|
|
43
43
|
|
|
44
44
|
missing_fields.each do |missing_field|
|
|
45
45
|
path = [*context.path, missing_field]
|
|
46
|
-
missing_field_type = context.
|
|
46
|
+
missing_field_type = context.types.argument(parent_type, missing_field).type
|
|
47
47
|
add_error(RequiredInputObjectAttributesArePresentError.new(
|
|
48
48
|
"Argument '#{missing_field}' on InputObject '#{parent_type.to_type_signature}' is required. Expected type #{missing_field_type.to_type_signature}",
|
|
49
49
|
argument_name: missing_field,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphQL
|
|
3
|
+
module StaticValidation
|
|
4
|
+
module SubscriptionRootExistsAndSingleSubscriptionSelection
|
|
5
|
+
def on_operation_definition(node, parent)
|
|
6
|
+
if node.operation_type == "subscription"
|
|
7
|
+
if context.types.subscription_root.nil?
|
|
8
|
+
add_error(GraphQL::StaticValidation::SubscriptionRootExistsError.new(
|
|
9
|
+
'Schema is not configured for subscriptions',
|
|
10
|
+
nodes: node
|
|
11
|
+
))
|
|
12
|
+
elsif node.selections.size != 1
|
|
13
|
+
add_error(GraphQL::StaticValidation::NotSingleSubscriptionError.new(
|
|
14
|
+
'A subscription operation may only have one selection',
|
|
15
|
+
nodes: node,
|
|
16
|
+
))
|
|
17
|
+
else
|
|
18
|
+
super
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
super
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -19,13 +19,17 @@ module GraphQL
|
|
|
19
19
|
:on_field,
|
|
20
20
|
]
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if node.directives.
|
|
22
|
+
VALIDATE_DIRECTIVE_LOCATION_ON_NODE = <<~RUBY
|
|
23
|
+
def %{method_name}(node, parent)
|
|
24
|
+
if !node.directives.empty?
|
|
25
25
|
validate_directive_location(node)
|
|
26
26
|
end
|
|
27
27
|
super(node, parent)
|
|
28
28
|
end
|
|
29
|
+
RUBY
|
|
30
|
+
DIRECTIVE_NODE_HOOKS.each do |method_name|
|
|
31
|
+
# Can't use `define_method {...}` here because the proc can't be isolated for use in non-main Ractors
|
|
32
|
+
module_eval(VALIDATE_DIRECTIVE_LOCATION_ON_NODE % { method_name: method_name }) # rubocop:disable Development/NoEvalCop
|
|
29
33
|
end
|
|
30
34
|
|
|
31
35
|
private
|
|
@@ -5,36 +5,27 @@ module GraphQL
|
|
|
5
5
|
def on_variable_definition(node, parent)
|
|
6
6
|
if !node.default_value.nil?
|
|
7
7
|
value = node.default_value
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
nodes: node,
|
|
12
|
-
name: node.name,
|
|
13
|
-
error_type: VariableDefaultValuesAreCorrectlyTypedError::VIOLATIONS[:INVALID_ON_NON_NULL]
|
|
14
|
-
))
|
|
8
|
+
type = context.schema.type_from_ast(node.type, context: context)
|
|
9
|
+
if type.nil?
|
|
10
|
+
# This is handled by another validator
|
|
15
11
|
else
|
|
16
|
-
|
|
17
|
-
if type.nil?
|
|
18
|
-
# This is handled by another validator
|
|
19
|
-
else
|
|
20
|
-
validation_result = context.validate_literal(value, type)
|
|
12
|
+
validation_result = context.validate_literal(value, type)
|
|
21
13
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
error_message ||= "Default value for $#{node.name} doesn't match type #{type.to_type_signature}"
|
|
30
|
-
add_error(GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTypedError.new(
|
|
31
|
-
error_message,
|
|
32
|
-
nodes: node,
|
|
33
|
-
name: node.name,
|
|
34
|
-
type: type.to_type_signature,
|
|
35
|
-
error_type: VariableDefaultValuesAreCorrectlyTypedError::VIOLATIONS[:INVALID_TYPE],
|
|
36
|
-
))
|
|
14
|
+
if !validation_result.valid?
|
|
15
|
+
problems = validation_result.problems
|
|
16
|
+
first_problem = problems && problems.first
|
|
17
|
+
if first_problem
|
|
18
|
+
error_message = first_problem["explanation"]
|
|
37
19
|
end
|
|
20
|
+
|
|
21
|
+
error_message ||= "Default value for $#{node.name} doesn't match type #{type.to_type_signature}"
|
|
22
|
+
add_error(GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTypedError.new(
|
|
23
|
+
error_message,
|
|
24
|
+
nodes: node,
|
|
25
|
+
name: node.name,
|
|
26
|
+
type: type.to_type_signature,
|
|
27
|
+
error_type: VariableDefaultValuesAreCorrectlyTypedError::VIOLATIONS[:INVALID_TYPE],
|
|
28
|
+
))
|
|
38
29
|
end
|
|
39
30
|
end
|
|
40
31
|
end
|
|
@@ -4,7 +4,7 @@ module GraphQL
|
|
|
4
4
|
module VariableNamesAreUnique
|
|
5
5
|
def on_operation_definition(node, parent)
|
|
6
6
|
var_defns = node.variables
|
|
7
|
-
if var_defns.
|
|
7
|
+
if !var_defns.empty?
|
|
8
8
|
vars_by_name = Hash.new { |h, k| h[k] = [] }
|
|
9
9
|
var_defns.each { |v| vars_by_name[v.name] << v }
|
|
10
10
|
vars_by_name.each do |name, defns|
|
|
@@ -21,7 +21,7 @@ module GraphQL
|
|
|
21
21
|
end
|
|
22
22
|
node_values = node_values.select { |value| value.is_a? GraphQL::Language::Nodes::VariableIdentifier }
|
|
23
23
|
|
|
24
|
-
if node_values.
|
|
24
|
+
if !node_values.empty?
|
|
25
25
|
argument_owner = case parent
|
|
26
26
|
when GraphQL::Language::Nodes::Field
|
|
27
27
|
context.field_definition
|
|
@@ -65,7 +65,7 @@ module GraphQL
|
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
-
arg_defn =
|
|
68
|
+
arg_defn = @types.argument(argument_owner, arg_node.name)
|
|
69
69
|
arg_defn_type = arg_defn.type
|
|
70
70
|
|
|
71
71
|
# If the argument is non-null, but it was given a default value,
|
|
@@ -4,11 +4,23 @@ module GraphQL
|
|
|
4
4
|
module VariablesAreInputTypes
|
|
5
5
|
def on_variable_definition(node, parent)
|
|
6
6
|
type_name = get_type_name(node.type)
|
|
7
|
-
type = context.
|
|
7
|
+
type = context.query.types.type(type_name)
|
|
8
8
|
|
|
9
9
|
if type.nil?
|
|
10
|
+
suggestion = if @schema.did_you_mean
|
|
11
|
+
@all_possible_input_type_names ||= begin
|
|
12
|
+
names = []
|
|
13
|
+
context.types.all_types.each { |(t)|
|
|
14
|
+
if t.kind.input?
|
|
15
|
+
names << t.graphql_name
|
|
16
|
+
end
|
|
17
|
+
}
|
|
18
|
+
names
|
|
19
|
+
end
|
|
20
|
+
context.did_you_mean_suggestion(type_name, @all_possible_input_type_names)
|
|
21
|
+
end
|
|
10
22
|
add_error(GraphQL::StaticValidation::VariablesAreInputTypesError.new(
|
|
11
|
-
"#{type_name} isn't a defined input type (on $#{node.name})",
|
|
23
|
+
"#{type_name} isn't a defined input type (on $#{node.name})#{suggestion}",
|
|
12
24
|
nodes: node,
|
|
13
25
|
name: node.name,
|
|
14
26
|
type: type_name
|
|
@@ -8,20 +8,20 @@ module GraphQL
|
|
|
8
8
|
# It provides access to the schema & fragments which validators may read from.
|
|
9
9
|
#
|
|
10
10
|
# It holds a list of errors which each validator may add to.
|
|
11
|
-
#
|
|
12
|
-
# It also provides limited access to the {TypeStack} instance,
|
|
13
|
-
# which tracks state as you climb in and out of different fields.
|
|
14
11
|
class ValidationContext
|
|
15
12
|
extend Forwardable
|
|
16
13
|
|
|
17
14
|
attr_reader :query, :errors, :visitor,
|
|
18
15
|
:on_dependency_resolve_handlers,
|
|
19
|
-
:max_errors
|
|
16
|
+
:max_errors, :types, :schema
|
|
17
|
+
|
|
20
18
|
|
|
21
|
-
def_delegators :@query, :
|
|
19
|
+
def_delegators :@query, :document, :fragments, :operations
|
|
22
20
|
|
|
23
21
|
def initialize(query, visitor_class, max_errors)
|
|
24
22
|
@query = query
|
|
23
|
+
@types = query.types # TODO update migrated callers to use this accessor
|
|
24
|
+
@schema = query.schema
|
|
25
25
|
@literal_validator = LiteralValidator.new(context: query.context)
|
|
26
26
|
@errors = []
|
|
27
27
|
@max_errors = max_errors || Float::INFINITY
|
|
@@ -29,9 +29,10 @@ module GraphQL
|
|
|
29
29
|
@visitor = visitor_class.new(document, self)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
# TODO stop using def_delegators because of Array allocations
|
|
32
33
|
def_delegators :@visitor,
|
|
33
34
|
:path, :type_definition, :field_definition, :argument_definition,
|
|
34
|
-
:parent_type_definition, :directive_definition, :
|
|
35
|
+
:parent_type_definition, :directive_definition, :dependencies
|
|
35
36
|
|
|
36
37
|
def on_dependency_resolve(&handler)
|
|
37
38
|
@on_dependency_resolve_handlers << handler
|
|
@@ -48,6 +49,21 @@ module GraphQL
|
|
|
48
49
|
def schema_directives
|
|
49
50
|
@schema_directives ||= schema.directives
|
|
50
51
|
end
|
|
52
|
+
|
|
53
|
+
def did_you_mean_suggestion(name, options)
|
|
54
|
+
if did_you_mean = schema.did_you_mean
|
|
55
|
+
suggestions = did_you_mean::SpellChecker.new(dictionary: options).correct(name)
|
|
56
|
+
case suggestions.size
|
|
57
|
+
when 0
|
|
58
|
+
""
|
|
59
|
+
when 1
|
|
60
|
+
" (Did you mean `#{suggestions.first}`?)"
|
|
61
|
+
else
|
|
62
|
+
last_sugg = suggestions.pop
|
|
63
|
+
" (Did you mean #{suggestions.map {|s| "`#{s}`"}.join(", ")} or `#{last_sugg}`?)"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
51
67
|
end
|
|
52
68
|
end
|
|
53
69
|
end
|
|
@@ -27,7 +27,10 @@ module GraphQL
|
|
|
27
27
|
# @param max_errors [Integer] Maximum number of errors before aborting validation. Any positive number will limit the number of errors. Defaults to nil for no limit.
|
|
28
28
|
# @return [Array<Hash>]
|
|
29
29
|
def validate(query, validate: true, timeout: nil, max_errors: nil)
|
|
30
|
+
errors = nil
|
|
31
|
+
query.current_trace.begin_validate(query, validate)
|
|
30
32
|
query.current_trace.validate(validate: validate, query: query) do
|
|
33
|
+
begin_t = Time.now
|
|
31
34
|
errors = if validate == false
|
|
32
35
|
[]
|
|
33
36
|
else
|
|
@@ -52,13 +55,18 @@ module GraphQL
|
|
|
52
55
|
end
|
|
53
56
|
|
|
54
57
|
{
|
|
58
|
+
remaining_timeout: timeout ? (timeout - (Time.now - begin_t)) : nil,
|
|
55
59
|
errors: errors,
|
|
56
60
|
}
|
|
57
61
|
end
|
|
58
62
|
rescue GraphQL::ExecutionError => e
|
|
63
|
+
errors = [e]
|
|
59
64
|
{
|
|
60
|
-
|
|
65
|
+
remaining_timeout: nil,
|
|
66
|
+
errors: errors,
|
|
61
67
|
}
|
|
68
|
+
ensure
|
|
69
|
+
query.current_trace.end_validate(query, validate, errors)
|
|
62
70
|
end
|
|
63
71
|
|
|
64
72
|
# Invoked when static validation times out.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require "graphql/static_validation/error"
|
|
3
3
|
require "graphql/static_validation/definition_dependencies"
|
|
4
|
-
require "graphql/static_validation/type_stack"
|
|
5
4
|
require "graphql/static_validation/validator"
|
|
6
5
|
require "graphql/static_validation/validation_context"
|
|
7
6
|
require "graphql/static_validation/validation_timeout_error"
|
|
@@ -35,7 +35,7 @@ module GraphQL
|
|
|
35
35
|
# }
|
|
36
36
|
#
|
|
37
37
|
# result = MySchema.execute(
|
|
38
|
-
# query
|
|
38
|
+
# query,
|
|
39
39
|
# context: context,
|
|
40
40
|
# variables: variables,
|
|
41
41
|
# operation_name: operation_name
|
|
@@ -81,6 +81,7 @@ module GraphQL
|
|
|
81
81
|
# end
|
|
82
82
|
# end
|
|
83
83
|
#
|
|
84
|
+
# @see GraphQL::Testing::MockActionCable for test helpers
|
|
84
85
|
class ActionCableSubscriptions < GraphQL::Subscriptions
|
|
85
86
|
SUBSCRIPTION_PREFIX = "graphql-subscription:"
|
|
86
87
|
EVENT_PREFIX = "graphql-event:"
|
|
@@ -107,7 +108,7 @@ module GraphQL
|
|
|
107
108
|
when 2
|
|
108
109
|
true
|
|
109
110
|
else
|
|
110
|
-
raise ArgumentError, "#{@serializer} must
|
|
111
|
+
raise ArgumentError, "#{@serializer} must respond to `.load` accepting one or two arguments"
|
|
111
112
|
end
|
|
112
113
|
@transmit_ns = namespace
|
|
113
114
|
super
|
|
@@ -124,7 +125,8 @@ module GraphQL
|
|
|
124
125
|
# This subscription was re-evaluated.
|
|
125
126
|
# Send it to the specific stream where this client was waiting.
|
|
126
127
|
def deliver(subscription_id, result)
|
|
127
|
-
|
|
128
|
+
has_more = !result.context.namespace(:subscriptions)[:final_update]
|
|
129
|
+
payload = { result: result.to_h, more: has_more }
|
|
128
130
|
@action_cable.server.broadcast(stream_subscription_name(subscription_id), payload)
|
|
129
131
|
end
|
|
130
132
|
|
|
@@ -165,11 +167,12 @@ module GraphQL
|
|
|
165
167
|
#
|
|
166
168
|
def setup_stream(channel, initial_event)
|
|
167
169
|
topic = initial_event.topic
|
|
168
|
-
|
|
170
|
+
event_stream = stream_event_name(initial_event)
|
|
171
|
+
channel.stream_from(event_stream, coder: @action_cable_coder) do |message|
|
|
169
172
|
events_by_fingerprint = @events[topic]
|
|
170
173
|
object = nil
|
|
171
174
|
events_by_fingerprint.each do |_fingerprint, events|
|
|
172
|
-
if events.
|
|
175
|
+
if !events.empty? && events.first == initial_event
|
|
173
176
|
# The fingerprint has told us that this response should be shared by all subscribers,
|
|
174
177
|
# so just run it once, then deliver the result to every subscriber
|
|
175
178
|
first_event = events.first
|
|
@@ -9,7 +9,7 @@ module GraphQL
|
|
|
9
9
|
# Assign the result to `context.namespace(:subscriptions)[:subscription_broadcastable]`
|
|
10
10
|
# @api private
|
|
11
11
|
# @see Subscriptions#broadcastable? for a public API
|
|
12
|
-
class BroadcastAnalyzer < GraphQL::Analysis::
|
|
12
|
+
class BroadcastAnalyzer < GraphQL::Analysis::Analyzer
|
|
13
13
|
def initialize(subject)
|
|
14
14
|
super
|
|
15
15
|
@default_broadcastable = subject.schema.subscriptions.default_broadcastable
|
|
@@ -28,9 +28,8 @@ module GraphQL
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
current_field = visitor.field_definition
|
|
31
|
-
apply_broadcastable(current_field)
|
|
32
|
-
|
|
33
31
|
current_type = visitor.parent_type_definition
|
|
32
|
+
apply_broadcastable(current_type, current_field)
|
|
34
33
|
if current_type.kind.interface?
|
|
35
34
|
pt = @query.possible_types(current_type)
|
|
36
35
|
pt.each do |object_type|
|
|
@@ -38,7 +37,7 @@ module GraphQL
|
|
|
38
37
|
# Inherited fields would be exactly the same object;
|
|
39
38
|
# only check fields that are overrides of the inherited one
|
|
40
39
|
if ot_field && ot_field != current_field
|
|
41
|
-
apply_broadcastable(ot_field)
|
|
40
|
+
apply_broadcastable(object_type, ot_field)
|
|
42
41
|
end
|
|
43
42
|
end
|
|
44
43
|
end
|
|
@@ -55,10 +54,16 @@ module GraphQL
|
|
|
55
54
|
private
|
|
56
55
|
|
|
57
56
|
# Modify `@subscription_broadcastable` based on `field_defn`'s configuration (and/or the default value)
|
|
58
|
-
def apply_broadcastable(field_defn)
|
|
57
|
+
def apply_broadcastable(owner_type, field_defn)
|
|
59
58
|
current_field_broadcastable = field_defn.introspection? || field_defn.broadcastable?
|
|
59
|
+
|
|
60
|
+
if current_field_broadcastable.nil? && owner_type.respond_to?(:default_broadcastable?)
|
|
61
|
+
current_field_broadcastable = owner_type.default_broadcastable?
|
|
62
|
+
end
|
|
63
|
+
|
|
60
64
|
case current_field_broadcastable
|
|
61
65
|
when nil
|
|
66
|
+
query.logger.debug { "`broadcastable: nil` for field: #{field_defn.path}" }
|
|
62
67
|
# If the value wasn't set, mix in the default value:
|
|
63
68
|
# - If the default is false and the current value is true, make it false
|
|
64
69
|
# - If the default is true and the current value is true, it stays true
|
|
@@ -66,6 +71,7 @@ module GraphQL
|
|
|
66
71
|
# - If the default is true and the current value is false, keep it false
|
|
67
72
|
@subscription_broadcastable = @subscription_broadcastable && @default_broadcastable
|
|
68
73
|
when false
|
|
74
|
+
query.logger.debug { "`broadcastable: false` for field: #{field_defn.path}" }
|
|
69
75
|
# One non-broadcastable field is enough to make the whole subscription non-broadcastable
|
|
70
76
|
@subscription_broadcastable = false
|
|
71
77
|
when true
|