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
|
@@ -8,220 +8,310 @@ module GraphQL
|
|
|
8
8
|
# fragments) either correspond to distinct response names or can be merged
|
|
9
9
|
# without ambiguity.
|
|
10
10
|
#
|
|
11
|
-
#
|
|
11
|
+
# Optimized algorithm based on:
|
|
12
|
+
# https://tech.new-work.se/graphql-overlapping-fields-can-be-merged-fast-ea6e92e0a01
|
|
13
|
+
#
|
|
14
|
+
# Instead of comparing fields, fields-vs-fragments, and fragments-vs-fragments
|
|
15
|
+
# separately (which leads to exponential recursion through nested fragments),
|
|
16
|
+
# we flatten all fragment spreads into a single field map and compare within it.
|
|
12
17
|
NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
|
|
13
18
|
|
|
14
|
-
Field
|
|
15
|
-
|
|
19
|
+
class Field
|
|
20
|
+
attr_reader :node, :definition, :owner_type, :parents
|
|
21
|
+
|
|
22
|
+
def initialize(node, definition, owner_type, parents)
|
|
23
|
+
@node = node
|
|
24
|
+
@definition = definition
|
|
25
|
+
@owner_type = owner_type
|
|
26
|
+
@parents = parents
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def return_type
|
|
30
|
+
@return_type ||= @definition&.type
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def unwrapped_return_type
|
|
34
|
+
@unwrapped_return_type ||= return_type&.unwrap
|
|
35
|
+
end
|
|
36
|
+
end
|
|
16
37
|
|
|
17
38
|
def initialize(*)
|
|
18
39
|
super
|
|
19
|
-
@visited_fragments = {}
|
|
20
|
-
@compared_fragments = {}
|
|
21
40
|
@conflict_count = 0
|
|
41
|
+
@max_errors = context.max_errors
|
|
42
|
+
@fragments = context.fragments
|
|
43
|
+
# Track which sub-selection node pairs have been compared to prevent
|
|
44
|
+
# infinite recursion with cyclic fragments
|
|
45
|
+
@compared_sub_selections = {}.compare_by_identity
|
|
46
|
+
# Cache mutually_exclusive? results for type pairs
|
|
47
|
+
@mutually_exclusive_cache = {}.compare_by_identity
|
|
48
|
+
# Cache collect_fields results for sub-selection comparison
|
|
49
|
+
@sub_fields_cache = {}.compare_by_identity
|
|
22
50
|
end
|
|
23
51
|
|
|
24
52
|
def on_operation_definition(node, _parent)
|
|
25
|
-
|
|
53
|
+
@conflicts = nil
|
|
54
|
+
conflicts_within_selection_set(node, type_definition)
|
|
55
|
+
@conflicts&.each_value { |error_type| error_type.each_value { |error| add_error(error) } }
|
|
26
56
|
super
|
|
27
57
|
end
|
|
28
58
|
|
|
29
59
|
def on_field(node, _parent)
|
|
30
|
-
|
|
60
|
+
if !node.selections.empty? && selections_may_conflict?(node.selections)
|
|
61
|
+
@conflicts = nil
|
|
62
|
+
conflicts_within_selection_set(node, type_definition)
|
|
63
|
+
@conflicts&.each_value { |error_type| error_type.each_value { |error| add_error(error) } }
|
|
64
|
+
end
|
|
31
65
|
super
|
|
32
66
|
end
|
|
33
67
|
|
|
34
68
|
private
|
|
35
69
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
70
|
+
# Quick check: can the direct children of this selection set possibly conflict?
|
|
71
|
+
# If all direct selections are Fields with unique names and no aliases,
|
|
72
|
+
# and there are no fragments, then no response key can have >1 field,
|
|
73
|
+
# so there are no merge conflicts to check at this level.
|
|
74
|
+
def selections_may_conflict?(selections)
|
|
75
|
+
i = 0
|
|
76
|
+
len = selections.size
|
|
77
|
+
while i < len
|
|
78
|
+
sel = selections[i]
|
|
79
|
+
# Fragment spread or inline fragment — needs full check
|
|
80
|
+
return true unless sel.is_a?(GraphQL::Language::Nodes::Field)
|
|
81
|
+
|
|
82
|
+
# Aliased field — could create duplicate response key
|
|
83
|
+
return true if sel.alias
|
|
84
|
+
|
|
85
|
+
i += 1
|
|
45
86
|
end
|
|
46
|
-
end
|
|
47
87
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
88
|
+
# All are unaliased fields — check for duplicate names
|
|
89
|
+
# For small sets, O(n²) is cheaper than hash allocation
|
|
90
|
+
if len <= 8
|
|
91
|
+
i = 0
|
|
92
|
+
while i < len
|
|
93
|
+
j = i + 1
|
|
94
|
+
name_i = selections[i].name
|
|
95
|
+
while j < len
|
|
96
|
+
return true if selections[j].name == name_i
|
|
97
|
+
j += 1
|
|
98
|
+
end
|
|
99
|
+
i += 1
|
|
100
|
+
end
|
|
51
101
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
102
|
+
false
|
|
103
|
+
else
|
|
104
|
+
true # Assume potential conflicts for larger sets
|
|
105
|
+
end
|
|
56
106
|
end
|
|
57
107
|
|
|
58
|
-
def
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
# (A) Find find all conflicts "within" the fields of this selection set.
|
|
64
|
-
find_conflicts_within(fields)
|
|
65
|
-
|
|
66
|
-
fragment_spreads.each_with_index do |fragment_spread, i|
|
|
67
|
-
are_mutually_exclusive = mutually_exclusive?(
|
|
68
|
-
fragment_spread.parents,
|
|
69
|
-
[parent_type]
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
# (B) Then find conflicts between these fields and those represented by
|
|
73
|
-
# each spread fragment name found.
|
|
74
|
-
find_conflicts_between_fields_and_fragment(
|
|
75
|
-
fragment_spread,
|
|
76
|
-
fields,
|
|
77
|
-
mutually_exclusive: are_mutually_exclusive,
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
# (C) Then compare this fragment with all other fragments found in this
|
|
81
|
-
# selection set to collect conflicts between fragments spread together.
|
|
82
|
-
# This compares each item in the list of fragment names to every other
|
|
83
|
-
# item in that same list (except for itself).
|
|
84
|
-
fragment_spreads[i + 1..-1].each do |fragment_spread2|
|
|
85
|
-
are_mutually_exclusive = mutually_exclusive?(
|
|
86
|
-
fragment_spread.parents,
|
|
87
|
-
fragment_spread2.parents
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
find_conflicts_between_fragments(
|
|
91
|
-
fragment_spread,
|
|
92
|
-
fragment_spread2,
|
|
93
|
-
mutually_exclusive: are_mutually_exclusive,
|
|
94
|
-
)
|
|
108
|
+
def conflicts
|
|
109
|
+
@conflicts ||= Hash.new do |h, error_type|
|
|
110
|
+
h[error_type] = Hash.new do |h2, field_name|
|
|
111
|
+
h2[field_name] = GraphQL::StaticValidation::FieldsWillMergeError.new(kind: error_type, field_name: field_name)
|
|
95
112
|
end
|
|
96
113
|
end
|
|
97
114
|
end
|
|
98
115
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return if
|
|
103
|
-
|
|
104
|
-
cache_key = compared_fragments_key(
|
|
105
|
-
fragment_name1,
|
|
106
|
-
fragment_name2,
|
|
107
|
-
mutually_exclusive,
|
|
108
|
-
)
|
|
109
|
-
if @compared_fragments.key?(cache_key)
|
|
110
|
-
return
|
|
111
|
-
else
|
|
112
|
-
@compared_fragments[cache_key] = true
|
|
113
|
-
end
|
|
116
|
+
# Core algorithm: collect ALL fields (expanding fragments inline) into a flat
|
|
117
|
+
# map keyed by response key, then compare within each group.
|
|
118
|
+
def conflicts_within_selection_set(node, parent_type)
|
|
119
|
+
return if parent_type.nil?
|
|
120
|
+
return if node.selections.empty?
|
|
114
121
|
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
# Collect all fields from this selection set, expanding fragments transitively
|
|
123
|
+
response_keys = collect_fields(node.selections, owner_type: parent_type, parents: [])
|
|
117
124
|
|
|
118
|
-
|
|
125
|
+
# Find conflicts within each response key group
|
|
126
|
+
find_conflicts_within(response_keys)
|
|
127
|
+
end
|
|
119
128
|
|
|
120
|
-
|
|
121
|
-
|
|
129
|
+
# Collect all fields from selections, expanding fragment spreads inline.
|
|
130
|
+
# Returns a Hash of { response_key => Field | [Field, ...] }
|
|
131
|
+
def collect_fields(selections, owner_type:, parents:)
|
|
132
|
+
response_keys = {}
|
|
133
|
+
collect_fields_inner(selections, owner_type: owner_type, parents: parents, response_keys: response_keys, visited_fragments: nil)
|
|
134
|
+
response_keys
|
|
135
|
+
end
|
|
122
136
|
|
|
123
|
-
|
|
137
|
+
def collect_fields_inner(selections, owner_type:, parents:, response_keys:, visited_fragments:)
|
|
138
|
+
deferred_spreads = nil
|
|
139
|
+
sel_idx = 0
|
|
140
|
+
sel_len = selections.size
|
|
124
141
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
owner_type: fragment_type1,
|
|
128
|
-
parents: [*fragment_spread1.parents, fragment_type1]
|
|
129
|
-
)
|
|
130
|
-
fragment_fields2, fragment_spreads2 = fields_and_fragments_from_selection(
|
|
131
|
-
fragment2,
|
|
132
|
-
owner_type: fragment_type1,
|
|
133
|
-
parents: [*fragment_spread2.parents, fragment_type2]
|
|
134
|
-
)
|
|
142
|
+
while sel_idx < sel_len
|
|
143
|
+
sel = selections[sel_idx]
|
|
135
144
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
145
|
+
case sel
|
|
146
|
+
when GraphQL::Language::Nodes::Field
|
|
147
|
+
definition = @types.field(owner_type, sel.name)
|
|
148
|
+
key = sel.alias || sel.name
|
|
149
|
+
field = Field.new(sel, definition, owner_type, parents)
|
|
150
|
+
existing = response_keys[key]
|
|
151
|
+
|
|
152
|
+
if existing.nil?
|
|
153
|
+
response_keys[key] = field
|
|
154
|
+
elsif existing.is_a?(Field)
|
|
155
|
+
response_keys[key] = [existing, field]
|
|
156
|
+
else
|
|
157
|
+
existing << field
|
|
158
|
+
end
|
|
159
|
+
when GraphQL::Language::Nodes::InlineFragment
|
|
160
|
+
frag_type = sel.type ? @types.type(sel.type.name) : owner_type
|
|
143
161
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
end
|
|
162
|
+
if frag_type
|
|
163
|
+
new_parents = parents.dup
|
|
164
|
+
new_parents << frag_type
|
|
165
|
+
collect_fields_inner(sel.selections, owner_type: frag_type, parents: new_parents, response_keys: response_keys, visited_fragments: visited_fragments)
|
|
166
|
+
end
|
|
167
|
+
when GraphQL::Language::Nodes::FragmentSpread
|
|
168
|
+
(deferred_spreads ||= []) << sel
|
|
169
|
+
end
|
|
153
170
|
|
|
154
|
-
|
|
155
|
-
# fragments spread in the second fragment.
|
|
156
|
-
fragment_spreads1.each do |fragment_spread|
|
|
157
|
-
find_conflicts_between_fragments(
|
|
158
|
-
fragment_spread2,
|
|
159
|
-
fragment_spread,
|
|
160
|
-
mutually_exclusive: mutually_exclusive,
|
|
161
|
-
)
|
|
171
|
+
sel_idx += 1
|
|
162
172
|
end
|
|
163
|
-
end
|
|
164
173
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
174
|
+
if deferred_spreads
|
|
175
|
+
visited_fragments ||= {}
|
|
176
|
+
sel_idx = 0
|
|
177
|
+
sel_len = deferred_spreads.size
|
|
169
178
|
|
|
170
|
-
|
|
171
|
-
|
|
179
|
+
while sel_idx < sel_len
|
|
180
|
+
sel = deferred_spreads[sel_idx]
|
|
181
|
+
sel_idx += 1
|
|
182
|
+
next if visited_fragments.key?(sel.name)
|
|
172
183
|
|
|
173
|
-
|
|
174
|
-
|
|
184
|
+
visited_fragments[sel.name] = true
|
|
185
|
+
frag = @fragments[sel.name]
|
|
186
|
+
next unless frag
|
|
175
187
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
# (D) First find any conflicts between the provided collection of fields
|
|
179
|
-
# and the collection of fields represented by the given fragment.
|
|
180
|
-
find_conflicts_between(
|
|
181
|
-
fields,
|
|
182
|
-
fragment_fields,
|
|
183
|
-
mutually_exclusive: mutually_exclusive,
|
|
184
|
-
)
|
|
188
|
+
frag_type = @types.type(frag.type.name)
|
|
189
|
+
next unless frag_type
|
|
185
190
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
fragment_spread,
|
|
191
|
-
fields,
|
|
192
|
-
mutually_exclusive: mutually_exclusive,
|
|
193
|
-
)
|
|
191
|
+
new_parents = parents.dup
|
|
192
|
+
new_parents << frag_type
|
|
193
|
+
collect_fields_inner(frag.selections, owner_type: frag_type, parents: new_parents, response_keys: response_keys, visited_fragments: visited_fragments)
|
|
194
|
+
end
|
|
194
195
|
end
|
|
195
196
|
end
|
|
196
197
|
|
|
197
198
|
def find_conflicts_within(response_keys)
|
|
198
199
|
response_keys.each do |key, fields|
|
|
199
|
-
next
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
200
|
+
next unless fields.is_a?(Array)
|
|
201
|
+
|
|
202
|
+
# Optimization: group fields by signature (name + definition + arguments).
|
|
203
|
+
# Fields with the same signature can only conflict on sub-selections,
|
|
204
|
+
# so we only need to compare one pair within each group.
|
|
205
|
+
if fields.size > 4
|
|
206
|
+
f0 = fields[0]
|
|
207
|
+
all_same = true
|
|
208
|
+
i = 1
|
|
209
|
+
while i < fields.size
|
|
210
|
+
unless fields_same_signature?(f0, fields[i])
|
|
211
|
+
all_same = false
|
|
212
|
+
break
|
|
213
|
+
end
|
|
214
|
+
i += 1
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
if all_same
|
|
218
|
+
# All fields share a signature, so they can only conflict on
|
|
219
|
+
# sub-selections. Deduplicate by AST node identity — fields from
|
|
220
|
+
# the same node always have identical sub-selections.
|
|
221
|
+
unique_nodes = fields.uniq { |f| f.node.object_id }
|
|
222
|
+
i = 0
|
|
223
|
+
while i < unique_nodes.size
|
|
224
|
+
j = i + 1
|
|
225
|
+
while j < unique_nodes.size
|
|
226
|
+
if unique_nodes[i].node.selections.size > 0 || unique_nodes[j].node.selections.size > 0
|
|
227
|
+
find_conflict(key, unique_nodes[i], unique_nodes[j])
|
|
228
|
+
end
|
|
229
|
+
j += 1
|
|
230
|
+
end
|
|
231
|
+
i += 1
|
|
232
|
+
end
|
|
233
|
+
else
|
|
234
|
+
groups = fields.group_by { |f| field_signature(f) }
|
|
235
|
+
unique_groups = groups.values
|
|
236
|
+
|
|
237
|
+
# Compare representatives across different groups
|
|
238
|
+
gi = 0
|
|
239
|
+
while gi < unique_groups.size
|
|
240
|
+
gj = gi + 1
|
|
241
|
+
while gj < unique_groups.size
|
|
242
|
+
find_conflict(key, unique_groups[gi][0], unique_groups[gj][0])
|
|
243
|
+
gj += 1
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Within same group, deduplicate by AST node and compare all
|
|
247
|
+
# pairs for sub-selection conflicts
|
|
248
|
+
group = unique_groups[gi]
|
|
249
|
+
if group.size >= 2
|
|
250
|
+
unique_in_group = group.uniq { |f| f.node.object_id }
|
|
251
|
+
ui = 0
|
|
252
|
+
while ui < unique_in_group.size
|
|
253
|
+
uj = ui + 1
|
|
254
|
+
while uj < unique_in_group.size
|
|
255
|
+
if unique_in_group[ui].node.selections.size > 0 || unique_in_group[uj].node.selections.size > 0
|
|
256
|
+
find_conflict(key, unique_in_group[ui], unique_in_group[uj])
|
|
257
|
+
end
|
|
258
|
+
uj += 1
|
|
259
|
+
end
|
|
260
|
+
ui += 1
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
gi += 1
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
else
|
|
268
|
+
# Small number of fields — original O(n²) is fine
|
|
269
|
+
i = 0
|
|
270
|
+
while i < fields.size
|
|
271
|
+
j = i + 1
|
|
272
|
+
while j < fields.size
|
|
273
|
+
find_conflict(key, fields[i], fields[j])
|
|
274
|
+
j += 1
|
|
275
|
+
end
|
|
276
|
+
i += 1
|
|
207
277
|
end
|
|
208
|
-
i += 1
|
|
209
278
|
end
|
|
210
279
|
end
|
|
211
280
|
end
|
|
212
281
|
|
|
282
|
+
def fields_same_signature?(f1, f2)
|
|
283
|
+
n1 = f1.node
|
|
284
|
+
n2 = f2.node
|
|
285
|
+
|
|
286
|
+
f1.definition.equal?(f2.definition) &&
|
|
287
|
+
n1.name == n2.name &&
|
|
288
|
+
same_arguments?(n1, n2)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def field_signature(field)
|
|
292
|
+
node = field.node
|
|
293
|
+
defn = field.definition
|
|
294
|
+
args = node.arguments
|
|
295
|
+
|
|
296
|
+
if args.empty?
|
|
297
|
+
[node.name, defn.object_id]
|
|
298
|
+
else
|
|
299
|
+
[node.name, defn.object_id, args.map { |a| [a.name, serialize_arg(a.value)] }]
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
213
303
|
def find_conflict(response_key, field1, field2, mutually_exclusive: false)
|
|
214
|
-
return if @conflict_count >=
|
|
304
|
+
return if @conflict_count >= @max_errors
|
|
305
|
+
return if field1.definition.nil? || field2.definition.nil?
|
|
215
306
|
|
|
216
307
|
node1 = field1.node
|
|
217
308
|
node2 = field2.node
|
|
218
309
|
|
|
219
|
-
are_mutually_exclusive = mutually_exclusive ||
|
|
220
|
-
mutually_exclusive?(field1.parents, field2.parents)
|
|
310
|
+
are_mutually_exclusive = mutually_exclusive || mutually_exclusive?(field1.parents, field2.parents)
|
|
221
311
|
|
|
222
312
|
if !are_mutually_exclusive
|
|
223
313
|
if node1.name != node2.name
|
|
224
|
-
conflict =
|
|
314
|
+
conflict = conflicts[:field][response_key]
|
|
225
315
|
|
|
226
316
|
conflict.add_conflict(node1, node1.name)
|
|
227
317
|
conflict.add_conflict(node2, node2.name)
|
|
@@ -230,7 +320,7 @@ module GraphQL
|
|
|
230
320
|
end
|
|
231
321
|
|
|
232
322
|
if !same_arguments?(node1, node2)
|
|
233
|
-
conflict =
|
|
323
|
+
conflict = conflicts[:argument][response_key]
|
|
234
324
|
|
|
235
325
|
conflict.add_conflict(node1, GraphQL::Language.serialize(serialize_field_args(node1)))
|
|
236
326
|
conflict.add_conflict(node2, GraphQL::Language.serialize(serialize_field_args(node2)))
|
|
@@ -239,6 +329,54 @@ module GraphQL
|
|
|
239
329
|
end
|
|
240
330
|
end
|
|
241
331
|
|
|
332
|
+
if !conflicts[:field].key?(response_key) &&
|
|
333
|
+
!field1.definition.equal?(field2.definition) &&
|
|
334
|
+
(t1 = field1.return_type) &&
|
|
335
|
+
(t2 = field2.return_type) &&
|
|
336
|
+
return_types_conflict?(t1, t2)
|
|
337
|
+
|
|
338
|
+
return_error = nil
|
|
339
|
+
message_override = nil
|
|
340
|
+
|
|
341
|
+
case @schema.allow_legacy_invalid_return_type_conflicts
|
|
342
|
+
when false
|
|
343
|
+
return_error = true
|
|
344
|
+
when true
|
|
345
|
+
legacy_handling = @schema.legacy_invalid_return_type_conflicts(@context.query, t1, t2, node1, node2)
|
|
346
|
+
|
|
347
|
+
case legacy_handling
|
|
348
|
+
when nil
|
|
349
|
+
return_error = false
|
|
350
|
+
when :return_validation_error
|
|
351
|
+
return_error = true
|
|
352
|
+
when String
|
|
353
|
+
return_error = true
|
|
354
|
+
message_override = legacy_handling
|
|
355
|
+
else
|
|
356
|
+
raise GraphQL::Error, "#{@schema}.legacy_invalid_scalar_conflicts returned unexpected value: #{legacy_handling.inspect}. Expected `nil`, String, or `:return_validation_error`."
|
|
357
|
+
end
|
|
358
|
+
else
|
|
359
|
+
return_error = false
|
|
360
|
+
@context.query.logger.warn <<~WARN
|
|
361
|
+
GraphQL-Ruby encountered mismatched types in this query: `#{t1.to_type_signature}` (at #{node1.line}:#{node1.col}) vs. `#{t2.to_type_signature}` (at #{node2.line}:#{node2.col}).
|
|
362
|
+
This will return an error in future GraphQL-Ruby versions, as per the GraphQL specification
|
|
363
|
+
Learn about migrating here: https://graphql-ruby.org/api-doc/#{GraphQL::VERSION}/GraphQL/Schema.html#allow_legacy_invalid_return_type_conflicts-class_method
|
|
364
|
+
WARN
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
if return_error
|
|
368
|
+
conflict = conflicts[:return_type][response_key]
|
|
369
|
+
|
|
370
|
+
if message_override
|
|
371
|
+
conflict.message = message_override
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
conflict.add_conflict(node1, "`#{t1.to_type_signature}`")
|
|
375
|
+
conflict.add_conflict(node2, "`#{t2.to_type_signature}`")
|
|
376
|
+
@conflict_count += 1
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
242
380
|
find_conflicts_between_sub_selection_sets(
|
|
243
381
|
field1,
|
|
244
382
|
field2,
|
|
@@ -246,115 +384,98 @@ module GraphQL
|
|
|
246
384
|
)
|
|
247
385
|
end
|
|
248
386
|
|
|
387
|
+
def return_types_conflict?(type1, type2)
|
|
388
|
+
if type1.list?
|
|
389
|
+
if type2.list?
|
|
390
|
+
return_types_conflict?(type1.of_type, type2.of_type)
|
|
391
|
+
else
|
|
392
|
+
true
|
|
393
|
+
end
|
|
394
|
+
elsif type2.list?
|
|
395
|
+
true
|
|
396
|
+
elsif type1.non_null?
|
|
397
|
+
if type2.non_null?
|
|
398
|
+
return_types_conflict?(type1.of_type, type2.of_type)
|
|
399
|
+
else
|
|
400
|
+
true
|
|
401
|
+
end
|
|
402
|
+
elsif type2.non_null?
|
|
403
|
+
true
|
|
404
|
+
elsif type1.kind.leaf? && type2.kind.leaf?
|
|
405
|
+
type1 != type2
|
|
406
|
+
else
|
|
407
|
+
false
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# When two fields with the same response key both have sub-selections,
|
|
412
|
+
# we need to check those sub-selections against each other.
|
|
249
413
|
def find_conflicts_between_sub_selection_sets(field1, field2, mutually_exclusive:)
|
|
250
414
|
return if field1.definition.nil? ||
|
|
251
415
|
field2.definition.nil? ||
|
|
252
416
|
(field1.node.selections.empty? && field2.node.selections.empty?)
|
|
253
417
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
parents1 = [return_type1]
|
|
257
|
-
parents2 = [return_type2]
|
|
258
|
-
|
|
259
|
-
fields, fragment_spreads = fields_and_fragments_from_selection(
|
|
260
|
-
field1.node,
|
|
261
|
-
owner_type: return_type1,
|
|
262
|
-
parents: parents1
|
|
263
|
-
)
|
|
418
|
+
node1 = field1.node
|
|
419
|
+
node2 = field2.node
|
|
264
420
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
owner_type: return_type2,
|
|
268
|
-
parents: parents2
|
|
269
|
-
)
|
|
421
|
+
# Prevent infinite recursion from cyclic fragments
|
|
422
|
+
return if node1.equal?(node2)
|
|
270
423
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
fields,
|
|
280
|
-
mutually_exclusive: mutually_exclusive,
|
|
281
|
-
)
|
|
424
|
+
inner = @compared_sub_selections[node1]
|
|
425
|
+
if inner
|
|
426
|
+
return if inner.key?(node2)
|
|
427
|
+
inner[node2] = true
|
|
428
|
+
else
|
|
429
|
+
inner = {}.compare_by_identity
|
|
430
|
+
inner[node2] = true
|
|
431
|
+
@compared_sub_selections[node1] = inner
|
|
282
432
|
end
|
|
283
433
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
fragment_spreads.each do |fragment_spread|
|
|
287
|
-
find_conflicts_between_fields_and_fragment(
|
|
288
|
-
fragment_spread,
|
|
289
|
-
fields2,
|
|
290
|
-
mutually_exclusive: mutually_exclusive,
|
|
291
|
-
)
|
|
292
|
-
end
|
|
434
|
+
return_type1 = field1.unwrapped_return_type
|
|
435
|
+
return_type2 = field2.unwrapped_return_type
|
|
293
436
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
# names to each item in the second set of names.
|
|
297
|
-
fragment_spreads.each do |frag1|
|
|
298
|
-
fragment_spreads2.each do |frag2|
|
|
299
|
-
find_conflicts_between_fragments(
|
|
300
|
-
frag1,
|
|
301
|
-
frag2,
|
|
302
|
-
mutually_exclusive: mutually_exclusive
|
|
303
|
-
)
|
|
304
|
-
end
|
|
305
|
-
end
|
|
306
|
-
end
|
|
437
|
+
response_keys1 = cached_sub_fields(node1, return_type1)
|
|
438
|
+
response_keys2 = cached_sub_fields(node2, return_type2)
|
|
307
439
|
|
|
308
|
-
|
|
309
|
-
response_keys.each do |key, fields|
|
|
310
|
-
fields2 = response_keys2[key]
|
|
311
|
-
if fields2
|
|
312
|
-
fields.each do |field|
|
|
313
|
-
fields2.each do |field2|
|
|
314
|
-
find_conflict(
|
|
315
|
-
key,
|
|
316
|
-
field,
|
|
317
|
-
field2,
|
|
318
|
-
mutually_exclusive: mutually_exclusive,
|
|
319
|
-
)
|
|
320
|
-
end
|
|
321
|
-
end
|
|
322
|
-
end
|
|
323
|
-
end
|
|
440
|
+
find_conflicts_between(response_keys1, response_keys2, mutually_exclusive: mutually_exclusive)
|
|
324
441
|
end
|
|
325
442
|
|
|
326
|
-
|
|
443
|
+
def cached_sub_fields(node, return_type)
|
|
444
|
+
inner = @sub_fields_cache[node]
|
|
327
445
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
NO_SELECTIONS
|
|
446
|
+
if inner && inner.key?(return_type)
|
|
447
|
+
inner[return_type]
|
|
331
448
|
else
|
|
332
|
-
parents
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
[
|
|
449
|
+
result = collect_fields(node.selections, owner_type: return_type, parents: [return_type])
|
|
450
|
+
inner ||= {}.compare_by_identity
|
|
451
|
+
inner[return_type] = result
|
|
452
|
+
@sub_fields_cache[node] = inner
|
|
453
|
+
result
|
|
336
454
|
end
|
|
337
455
|
end
|
|
338
456
|
|
|
339
|
-
def
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
457
|
+
def find_conflicts_between(response_keys, response_keys2, mutually_exclusive:)
|
|
458
|
+
response_keys.each do |key, fields|
|
|
459
|
+
fields2 = response_keys2[key]
|
|
460
|
+
next unless fields2
|
|
461
|
+
|
|
462
|
+
fields_arr = fields.is_a?(Field) ? [fields] : fields
|
|
463
|
+
fields2_arr = fields2.is_a?(Field) ? [fields2] : fields2
|
|
464
|
+
|
|
465
|
+
fields_arr.each do |field|
|
|
466
|
+
fields2_arr.each do |field2|
|
|
467
|
+
find_conflict(
|
|
468
|
+
key,
|
|
469
|
+
field,
|
|
470
|
+
field2,
|
|
471
|
+
mutually_exclusive: mutually_exclusive,
|
|
472
|
+
)
|
|
473
|
+
end
|
|
350
474
|
end
|
|
351
475
|
end
|
|
352
|
-
|
|
353
|
-
[fields, fragment_spreads]
|
|
354
476
|
end
|
|
355
477
|
|
|
356
478
|
def same_arguments?(field1, field2)
|
|
357
|
-
# Check for incompatible / non-identical arguments on this node:
|
|
358
479
|
arguments1 = field1.arguments
|
|
359
480
|
arguments2 = field2.arguments
|
|
360
481
|
|
|
@@ -387,39 +508,47 @@ module GraphQL
|
|
|
387
508
|
serialized_args
|
|
388
509
|
end
|
|
389
510
|
|
|
390
|
-
def compared_fragments_key(frag1, frag2, exclusive)
|
|
391
|
-
# Cache key to not compare two fragments more than once.
|
|
392
|
-
# The key includes both fragment names sorted (this way we
|
|
393
|
-
# avoid computing "A vs B" and "B vs A"). It also includes
|
|
394
|
-
# "exclusive" since the result may change depending on the parent_type
|
|
395
|
-
"#{[frag1, frag2].sort.join('-')}-#{exclusive}"
|
|
396
|
-
end
|
|
397
|
-
|
|
398
511
|
# Given two list of parents, find out if they are mutually exclusive
|
|
399
|
-
# In this context, `parents` represends the "self scope" of the field,
|
|
400
|
-
# what types may be found at this point in the query.
|
|
401
512
|
def mutually_exclusive?(parents1, parents2)
|
|
402
513
|
if parents1.empty? || parents2.empty?
|
|
403
514
|
false
|
|
404
515
|
elsif parents1.length == parents2.length
|
|
405
|
-
|
|
516
|
+
i = 0
|
|
517
|
+
len = parents1.length
|
|
518
|
+
|
|
519
|
+
while i < len
|
|
406
520
|
type1 = parents1[i - 1]
|
|
407
521
|
type2 = parents2[i - 1]
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
522
|
+
unless type1.equal?(type2)
|
|
523
|
+
inner = @mutually_exclusive_cache[type1]
|
|
524
|
+
if inner
|
|
525
|
+
cached = inner[type2]
|
|
526
|
+
if cached.nil?
|
|
527
|
+
cached = types_mutually_exclusive?(type1, type2)
|
|
528
|
+
inner[type2] = cached
|
|
529
|
+
end
|
|
530
|
+
else
|
|
531
|
+
cached = types_mutually_exclusive?(type1, type2)
|
|
532
|
+
inner = {}.compare_by_identity
|
|
533
|
+
inner[type2] = cached
|
|
534
|
+
@mutually_exclusive_cache[type1] = inner
|
|
535
|
+
end
|
|
536
|
+
return true if cached
|
|
417
537
|
end
|
|
538
|
+
i += 1
|
|
418
539
|
end
|
|
540
|
+
|
|
541
|
+
false
|
|
419
542
|
else
|
|
420
543
|
true
|
|
421
544
|
end
|
|
422
545
|
end
|
|
546
|
+
|
|
547
|
+
def types_mutually_exclusive?(type1, type2)
|
|
548
|
+
possible_right_types = @types.possible_types(type1)
|
|
549
|
+
possible_left_types = @types.possible_types(type2)
|
|
550
|
+
(possible_right_types & possible_left_types).empty?
|
|
551
|
+
end
|
|
423
552
|
end
|
|
424
553
|
end
|
|
425
554
|
end
|