graphql 1.8.7 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/generators/graphql/install_generator.rb +2 -1
- data/lib/generators/graphql/scalar_generator.rb +20 -0
- data/lib/generators/graphql/templates/base_scalar.erb +4 -0
- data/lib/generators/graphql/templates/scalar.erb +13 -0
- data/lib/graphql/analysis/ast/analyzer.rb +62 -0
- data/lib/graphql/analysis/ast/field_usage.rb +28 -0
- data/lib/graphql/analysis/ast/max_query_complexity.rb +23 -0
- data/lib/graphql/analysis/ast/max_query_depth.rb +18 -0
- data/lib/graphql/analysis/ast/query_complexity.rb +114 -0
- data/lib/graphql/analysis/ast/query_depth.rb +66 -0
- data/lib/graphql/analysis/ast/visitor.rb +255 -0
- data/lib/graphql/analysis/ast.rb +82 -0
- data/lib/graphql/analysis.rb +1 -0
- data/lib/graphql/argument.rb +5 -0
- data/lib/graphql/authorization.rb +1 -0
- data/lib/graphql/backwards_compatibility.rb +1 -1
- data/lib/graphql/base_type.rb +1 -1
- data/lib/graphql/compatibility/execution_specification/specification_schema.rb +1 -2
- data/lib/graphql/compatibility/schema_parser_specification.rb +2 -6
- data/lib/graphql/dig.rb +19 -0
- data/lib/graphql/directive/include_directive.rb +1 -7
- data/lib/graphql/directive/skip_directive.rb +1 -8
- data/lib/graphql/directive.rb +13 -1
- data/lib/graphql/enum_type.rb +8 -0
- data/lib/graphql/execution/execute.rb +36 -17
- data/lib/graphql/execution/instrumentation.rb +2 -0
- data/lib/graphql/execution/interpreter/execution_errors.rb +29 -0
- data/lib/graphql/execution/interpreter/hash_response.rb +46 -0
- data/lib/graphql/execution/interpreter/resolve.rb +58 -0
- data/lib/graphql/execution/interpreter/runtime.rb +597 -0
- data/lib/graphql/execution/interpreter.rb +99 -0
- data/lib/graphql/execution/lazy.rb +8 -1
- data/lib/graphql/execution/lookahead.rb +351 -0
- data/lib/graphql/execution/multiplex.rb +37 -29
- data/lib/graphql/execution.rb +2 -0
- data/lib/graphql/execution_error.rb +1 -1
- data/lib/graphql/field.rb +1 -7
- data/lib/graphql/integer_encoding_error.rb +12 -0
- data/lib/graphql/internal_representation/rewrite.rb +127 -142
- data/lib/graphql/introspection/dynamic_fields.rb +8 -2
- data/lib/graphql/introspection/entry_points.rb +11 -6
- data/lib/graphql/introspection/enum_value_type.rb +4 -0
- data/lib/graphql/introspection/schema_type.rb +7 -2
- data/lib/graphql/introspection/type_type.rb +9 -5
- data/lib/graphql/invalid_null_error.rb +1 -1
- data/lib/graphql/language/block_string.rb +37 -0
- data/lib/graphql/language/document_from_schema_definition.rb +10 -7
- data/lib/graphql/language/lexer.rb +55 -36
- data/lib/graphql/language/lexer.rl +8 -3
- data/lib/graphql/language/nodes.rb +440 -362
- data/lib/graphql/language/parser.rb +56 -56
- data/lib/graphql/language/parser.y +12 -12
- data/lib/graphql/language/printer.rb +2 -2
- data/lib/graphql/language/visitor.rb +158 -15
- data/lib/graphql/language.rb +0 -1
- data/lib/graphql/literal_validation_error.rb +6 -0
- data/lib/graphql/query/arguments.rb +3 -2
- data/lib/graphql/query/arguments_cache.rb +1 -1
- data/lib/graphql/query/context.rb +14 -5
- data/lib/graphql/query/executor.rb +1 -1
- data/lib/graphql/query/result.rb +1 -1
- data/lib/graphql/query/validation_pipeline.rb +35 -11
- data/lib/graphql/query/variable_validation_error.rb +10 -1
- data/lib/graphql/query.rb +16 -2
- data/lib/graphql/relay/base_connection.rb +2 -0
- data/lib/graphql/relay/connection_instrumentation.rb +3 -1
- data/lib/graphql/relay/connection_resolve.rb +1 -1
- data/lib/graphql/relay/node.rb +2 -28
- data/lib/graphql/relay/relation_connection.rb +1 -1
- data/lib/graphql/schema/argument.rb +13 -5
- data/lib/graphql/schema/base_64_encoder.rb +4 -4
- data/lib/graphql/schema/build_from_definition.rb +2 -4
- data/lib/graphql/schema/default_type_error.rb +1 -1
- data/lib/graphql/schema/directive/feature.rb +66 -0
- data/lib/graphql/schema/directive/include.rb +25 -0
- data/lib/graphql/schema/directive/skip.rb +25 -0
- data/lib/graphql/schema/directive/transform.rb +48 -0
- data/lib/graphql/schema/directive.rb +103 -0
- data/lib/graphql/schema/enum_value.rb +3 -2
- data/lib/graphql/schema/field/connection_extension.rb +50 -0
- data/lib/graphql/schema/field/scope_extension.rb +18 -0
- data/lib/graphql/schema/field.rb +273 -64
- data/lib/graphql/schema/field_extension.rb +69 -0
- data/lib/graphql/schema/input_object.rb +16 -8
- data/lib/graphql/schema/interface.rb +1 -0
- data/lib/graphql/schema/loader.rb +22 -16
- data/lib/graphql/schema/member/base_dsl_methods.rb +8 -2
- data/lib/graphql/schema/member/build_type.rb +33 -1
- data/lib/graphql/schema/member/has_arguments.rb +6 -2
- data/lib/graphql/schema/member/has_fields.rb +18 -70
- data/lib/graphql/schema/member/has_path.rb +25 -0
- data/lib/graphql/schema/member/instrumentation.rb +10 -7
- data/lib/graphql/schema/member.rb +2 -0
- data/lib/graphql/schema/mutation.rb +6 -48
- data/lib/graphql/schema/non_null.rb +5 -1
- data/lib/graphql/schema/object.rb +18 -4
- data/lib/graphql/schema/printer.rb +1 -1
- data/lib/graphql/schema/relay_classic_mutation.rb +42 -9
- data/lib/graphql/schema/resolver/has_payload_type.rb +65 -0
- data/lib/graphql/schema/resolver.rb +45 -20
- data/lib/graphql/schema/subscription.rb +97 -0
- data/lib/graphql/schema/traversal.rb +11 -7
- data/lib/graphql/schema.rb +186 -38
- data/lib/graphql/static_validation/all_rules.rb +3 -2
- data/lib/graphql/static_validation/base_visitor.rb +199 -0
- data/lib/graphql/static_validation/default_visitor.rb +15 -0
- data/lib/graphql/static_validation/definition_dependencies.rb +62 -68
- data/lib/graphql/static_validation/{message.rb → error.rb} +11 -11
- data/lib/graphql/static_validation/interpreter_visitor.rb +14 -0
- data/lib/graphql/static_validation/literal_validator.rb +54 -11
- data/lib/graphql/static_validation/no_validate_visitor.rb +10 -0
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +87 -16
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +31 -0
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +11 -11
- data/lib/graphql/static_validation/rules/argument_names_are_unique_error.rb +30 -0
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +52 -8
- data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +35 -0
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +12 -15
- data/lib/graphql/static_validation/rules/directives_are_defined_error.rb +29 -0
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +19 -14
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations_error.rb +31 -0
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +17 -19
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type_error.rb +32 -0
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +30 -14
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections_error.rb +31 -0
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +356 -29
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +32 -0
- data/lib/graphql/static_validation/rules/fragment_names_are_unique.rb +20 -13
- data/lib/graphql/static_validation/rules/fragment_names_are_unique_error.rb +29 -0
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +37 -29
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible_error.rb +35 -0
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +25 -17
- data/lib/graphql/static_validation/rules/fragment_types_exist_error.rb +29 -0
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +12 -10
- data/lib/graphql/static_validation/rules/fragments_are_finite_error.rb +29 -0
- data/lib/graphql/static_validation/rules/fragments_are_named.rb +7 -11
- data/lib/graphql/static_validation/rules/fragments_are_named_error.rb +26 -0
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +16 -16
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types_error.rb +30 -0
- data/lib/graphql/static_validation/rules/fragments_are_used.rb +21 -14
- data/lib/graphql/static_validation/rules/fragments_are_used_error.rb +29 -0
- data/lib/graphql/static_validation/rules/mutation_root_exists.rb +10 -14
- data/lib/graphql/static_validation/rules/mutation_root_exists_error.rb +26 -0
- data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +30 -30
- data/lib/graphql/static_validation/rules/no_definitions_are_present_error.rb +25 -0
- data/lib/graphql/static_validation/rules/operation_names_are_valid.rb +25 -17
- data/lib/graphql/static_validation/rules/operation_names_are_valid_error.rb +28 -0
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +17 -18
- data/lib/graphql/static_validation/rules/required_arguments_are_present_error.rb +35 -0
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +47 -0
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present_error.rb +35 -0
- data/lib/graphql/static_validation/rules/subscription_root_exists.rb +10 -14
- data/lib/graphql/static_validation/rules/subscription_root_exists_error.rb +26 -0
- data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +28 -17
- data/lib/graphql/static_validation/rules/unique_directives_per_location_error.rb +29 -0
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +35 -27
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed_error.rb +39 -0
- data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +15 -14
- data/lib/graphql/static_validation/rules/variable_names_are_unique_error.rb +29 -0
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +41 -30
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed_error.rb +38 -0
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +18 -14
- data/lib/graphql/static_validation/rules/variables_are_input_types_error.rb +32 -0
- data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +73 -65
- data/lib/graphql/static_validation/rules/variables_are_used_and_defined_error.rb +37 -0
- data/lib/graphql/static_validation/validation_context.rb +8 -51
- data/lib/graphql/static_validation/validator.rb +23 -15
- data/lib/graphql/static_validation.rb +5 -3
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +21 -2
- data/lib/graphql/subscriptions/event.rb +28 -5
- data/lib/graphql/subscriptions/subscription_root.rb +66 -0
- data/lib/graphql/subscriptions.rb +16 -2
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +0 -1
- data/lib/graphql/tracing/appsignal_tracing.rb +1 -1
- data/lib/graphql/tracing/data_dog_tracing.rb +1 -1
- data/lib/graphql/tracing/new_relic_tracing.rb +3 -3
- data/lib/graphql/tracing/platform_tracing.rb +17 -1
- data/lib/graphql/tracing/prometheus_tracing.rb +1 -1
- data/lib/graphql/tracing/scout_tracing.rb +1 -1
- data/lib/graphql/tracing/skylight_tracing.rb +3 -3
- data/lib/graphql/tracing.rb +8 -8
- data/lib/graphql/types/float.rb +1 -1
- data/lib/graphql/types/int.rb +11 -2
- data/lib/graphql/types/iso_8601_date_time.rb +15 -1
- data/lib/graphql/types/relay/base_connection.rb +15 -1
- data/lib/graphql/types/relay/node.rb +0 -1
- data/lib/graphql/types/relay/node_field.rb +43 -0
- data/lib/graphql/types/relay/nodes_field.rb +45 -0
- data/lib/graphql/types/relay.rb +2 -0
- data/lib/graphql/unauthorized_error.rb +4 -0
- data/lib/graphql/unauthorized_field_error.rb +23 -0
- data/lib/graphql/upgrader/member.rb +5 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +6 -1
- data/readme.md +7 -7
- data/spec/dummy/Gemfile +1 -1
- data/spec/dummy/Gemfile.lock +157 -0
- data/spec/dummy/app/channels/graphql_channel.rb +22 -11
- data/spec/dummy/config/locales/en.yml +1 -1
- data/spec/dummy/log/test.log +199 -0
- data/spec/dummy/test/test_helper.rb +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/4w/4wzXRZrAkwKdgYaSE0pid5eB-fer8vSfSku_NPg4rMA.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/7I/7IHVBiJT06QSpgLpLoJIxboQ0B-D_tMTxsvoezBTV3Q.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/8w/8wY_SKagj8wHuwGNAAf6JnQ8joMbC6cEYpHrTAI8Urc.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/AK/AKzz1u6bGb4auXcrObA_g5LL-oV0ejNGa448AgAi_WQ.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/ET/ETW4uxvaYpruL8y6_ZptUH82ZowMaHIqvg5WexBFdEM.cache +3 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/F1/F1TWpjjyA56k9Z90n5B3xRn7DUdGjX73QCkYC6k07JQ.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/F8/F8MUNRzORGFgr329fNM0xLaoWCXdv3BIalT7dsvLfjs.cache +2 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/KB/KB07ZaKNC5uXJ7TjLi-WqnY6g7dq8wWp_8N3HNjBNxg.cache +2 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Ms/MsKSimH_UCB-H1tLvDABDHuvGciuoW6kVqQWDrXU5FQ.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Mt/Mtci-Kim50aPOmeClD4AIicKn1d1WJ0n454IjSd94sk.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/QH/QHt3Tc1Y6M66Oo_pDuMyWrQNs4Pp3SMeZR5K1wJj2Ts.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/XU/XU4k1OXnfMils5SrirorPvDSyDSqiOWLZNtmAH1HH8k.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/ZI/ZIof7mZxWWCnraIFOCuV6a8QRWzKJXJnx2Xd7C0ZyX0.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/cG/cGc_puuPS5pZKgUcy1Y_i1L6jl5UtsiIrMH59rTzR6c.cache +3 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/df/dfro_B6bx3KP1Go-7jEOqqZ2j4hVRseXIc3es9PKQno.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/jO/jO1DfbqnG0mTULsjJJANc3fefrG2zt7DIMmcptMT628.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/pE/pE7gO6pQ-z187Swb4hT554wmqsq-cNzgPWLrCz-LQQQ.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/r9/r9iU1l58a6rxkZSW5RSC52_tD-_UQuHxoMVnkfJ7Mhs.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/xi/xitPPFfPIyDMpaznV0sBBcw8eSCV8PJcLLWin78sCgE.cache +0 -0
- data/spec/dummy/tmp/screenshots/failures_test_it_handles_subscriptions.png +0 -0
- data/spec/graphql/analysis/analyze_query_spec.rb +1 -1
- data/spec/graphql/analysis/ast/field_usage_spec.rb +51 -0
- data/spec/graphql/analysis/ast/max_query_complexity_spec.rb +120 -0
- data/spec/graphql/analysis/ast/max_query_depth_spec.rb +114 -0
- data/spec/graphql/analysis/ast/query_complexity_spec.rb +299 -0
- data/spec/graphql/analysis/ast/query_depth_spec.rb +108 -0
- data/spec/graphql/analysis/ast_spec.rb +269 -0
- data/spec/graphql/authorization_spec.rb +120 -23
- data/spec/graphql/base_type_spec.rb +6 -4
- data/spec/graphql/enum_type_spec.rb +6 -1
- data/spec/graphql/execution/execute_spec.rb +9 -9
- data/spec/graphql/execution/instrumentation_spec.rb +19 -0
- data/spec/graphql/execution/interpreter_spec.rb +485 -0
- data/spec/graphql/execution/lazy_spec.rb +67 -1
- data/spec/graphql/execution/lookahead_spec.rb +363 -0
- data/spec/graphql/execution/multiplex_spec.rb +31 -3
- data/spec/graphql/execution/typecast_spec.rb +20 -20
- data/spec/graphql/execution_error_spec.rb +110 -96
- data/spec/graphql/field_spec.rb +1 -1
- data/spec/graphql/input_object_type_spec.rb +13 -352
- data/spec/graphql/int_type_spec.rb +19 -0
- data/spec/graphql/interface_type_spec.rb +4 -4
- data/spec/graphql/internal_representation/rewrite_spec.rb +2 -0
- data/spec/graphql/introspection/input_value_type_spec.rb +1 -1
- data/spec/graphql/introspection/type_type_spec.rb +1 -2
- data/spec/graphql/language/document_from_schema_definition_spec.rb +2 -2
- data/spec/graphql/language/lexer_spec.rb +72 -3
- data/spec/graphql/language/nodes_spec.rb +20 -0
- data/spec/graphql/language/printer_spec.rb +18 -6
- data/spec/graphql/language/visitor_spec.rb +320 -14
- data/spec/graphql/non_null_type_spec.rb +1 -1
- data/spec/graphql/object_type_spec.rb +32 -27
- data/spec/graphql/query/arguments_spec.rb +21 -0
- data/spec/graphql/query/context_spec.rb +28 -0
- data/spec/graphql/query/executor_spec.rb +40 -36
- data/spec/graphql/query_spec.rb +12 -6
- data/spec/graphql/schema/argument_spec.rb +35 -1
- data/spec/graphql/schema/build_from_definition_spec.rb +144 -29
- data/spec/graphql/schema/catchall_middleware_spec.rb +16 -15
- data/spec/graphql/schema/directive/feature_spec.rb +81 -0
- data/spec/graphql/schema/directive/transform_spec.rb +39 -0
- data/spec/graphql/schema/enum_spec.rb +12 -3
- data/spec/graphql/schema/enum_value_spec.rb +11 -0
- data/spec/graphql/schema/field_extension_spec.rb +115 -0
- data/spec/graphql/schema/field_spec.rb +47 -7
- data/spec/graphql/schema/input_object_spec.rb +95 -0
- data/spec/graphql/schema/instrumentation_spec.rb +3 -0
- data/spec/graphql/schema/interface_spec.rb +8 -2
- data/spec/graphql/schema/introspection_system_spec.rb +9 -1
- data/spec/graphql/schema/loader_spec.rb +5 -0
- data/spec/graphql/schema/member/accepts_definition_spec.rb +4 -0
- data/spec/graphql/schema/member/build_type_spec.rb +46 -0
- data/spec/graphql/schema/member/scoped_spec.rb +19 -3
- data/spec/graphql/schema/mutation_spec.rb +5 -3
- data/spec/graphql/schema/object_spec.rb +9 -1
- data/spec/graphql/schema/printer_spec.rb +255 -93
- data/spec/graphql/schema/relay_classic_mutation_spec.rb +133 -0
- data/spec/graphql/schema/resolver_spec.rb +173 -9
- data/spec/graphql/schema/scalar_spec.rb +6 -0
- data/spec/graphql/schema/subscription_spec.rb +416 -0
- data/spec/graphql/schema/traversal_spec.rb +10 -10
- data/spec/graphql/schema/type_expression_spec.rb +2 -2
- data/spec/graphql/schema/union_spec.rb +7 -0
- data/spec/graphql/schema/validation_spec.rb +1 -1
- data/spec/graphql/schema/warden_spec.rb +145 -88
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +213 -73
- data/spec/graphql/static_validation/rules/argument_names_are_unique_spec.rb +2 -2
- data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +72 -29
- data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +10 -5
- data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +10 -5
- data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +131 -5
- data/spec/graphql/static_validation/rules/fragment_names_are_unique_spec.rb +2 -1
- data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +6 -3
- data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/fragments_are_named_spec.rb +2 -1
- data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +6 -3
- data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +22 -2
- data/spec/graphql/static_validation/rules/mutation_root_exists_spec.rb +2 -1
- data/spec/graphql/static_validation/rules/operation_names_are_valid_spec.rb +6 -3
- data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +13 -4
- data/spec/graphql/static_validation/rules/required_input_object_attributes_are_present_spec.rb +58 -0
- data/spec/graphql/static_validation/rules/subscription_root_exists_spec.rb +2 -1
- data/spec/graphql/static_validation/rules/unique_directives_per_location_spec.rb +14 -7
- data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +14 -7
- data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +8 -4
- data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +8 -4
- data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +23 -3
- data/spec/graphql/static_validation/type_stack_spec.rb +10 -19
- data/spec/graphql/static_validation/validator_spec.rb +50 -2
- data/spec/graphql/subscriptions_spec.rb +27 -16
- data/spec/graphql/tracing/new_relic_tracing_spec.rb +16 -0
- data/spec/graphql/tracing/platform_tracing_spec.rb +59 -37
- data/spec/graphql/tracing/prometheus_tracing_spec.rb +3 -0
- data/spec/graphql/tracing/skylight_tracing_spec.rb +16 -0
- data/spec/graphql/types/iso_8601_date_time_spec.rb +29 -2
- data/spec/graphql/union_type_spec.rb +2 -2
- data/spec/graphql/upgrader/member_spec.rb +67 -0
- data/spec/{graphql → integration/mongoid/graphql}/relay/mongo_relation_connection_spec.rb +11 -22
- data/spec/integration/mongoid/spec_helper.rb +2 -0
- data/spec/{support → integration/mongoid}/star_trek/data.rb +0 -0
- data/spec/{support → integration/mongoid}/star_trek/schema.rb +56 -34
- data/spec/{support/star_wars → integration/rails}/data.rb +1 -0
- data/spec/{support → integration/rails/generators}/base_generator_test.rb +0 -0
- data/spec/{generators → integration/rails/generators}/graphql/enum_generator_spec.rb +0 -0
- data/spec/{generators → integration/rails/generators}/graphql/install_generator_spec.rb +1 -1
- data/spec/{generators → integration/rails/generators}/graphql/interface_generator_spec.rb +0 -0
- data/spec/{generators → integration/rails/generators}/graphql/loader_generator_spec.rb +0 -0
- data/spec/{generators → integration/rails/generators}/graphql/mutation_generator_spec.rb +0 -0
- data/spec/{generators → integration/rails/generators}/graphql/object_generator_spec.rb +0 -0
- data/spec/integration/rails/generators/graphql/scalar_generator_spec.rb +28 -0
- data/spec/{generators → integration/rails/generators}/graphql/union_generator_spec.rb +0 -0
- data/spec/integration/rails/graphql/input_object_type_spec.rb +364 -0
- data/spec/{graphql → integration/rails/graphql}/query/variables_spec.rb +7 -7
- data/spec/{graphql → integration/rails/graphql}/relay/array_connection_spec.rb +9 -9
- data/spec/{graphql → integration/rails/graphql}/relay/base_connection_spec.rb +11 -3
- data/spec/{graphql → integration/rails/graphql}/relay/connection_instrumentation_spec.rb +19 -22
- data/spec/{graphql → integration/rails/graphql}/relay/connection_resolve_spec.rb +16 -0
- data/spec/{graphql → integration/rails/graphql}/relay/connection_type_spec.rb +0 -0
- data/spec/{graphql → integration/rails/graphql}/relay/edge_spec.rb +0 -0
- data/spec/{graphql → integration/rails/graphql}/relay/mutation_spec.rb +48 -0
- data/spec/{graphql → integration/rails/graphql}/relay/node_spec.rb +0 -0
- data/spec/{graphql → integration/rails/graphql}/relay/page_info_spec.rb +22 -22
- data/spec/{graphql → integration/rails/graphql}/relay/range_add_spec.rb +4 -4
- data/spec/{graphql → integration/rails/graphql}/relay/relation_connection_spec.rb +56 -27
- data/spec/{graphql → integration/rails/graphql}/schema_spec.rb +15 -11
- data/spec/{graphql → integration/rails/graphql}/tracing/active_support_notifications_tracing_spec.rb +16 -9
- data/spec/integration/rails/spec_helper.rb +25 -0
- data/spec/integration/tmp/app/graphql/types/family_type.rb +9 -0
- data/spec/spec_helper.rb +23 -39
- data/spec/support/dummy/data.rb +20 -17
- data/spec/support/dummy/schema.rb +315 -305
- data/spec/support/error_bubbling_helpers.rb +23 -0
- data/spec/support/jazz.rb +213 -46
- data/spec/support/lazy_helpers.rb +69 -27
- data/spec/support/new_relic.rb +3 -0
- data/spec/support/skylight.rb +3 -0
- data/spec/support/star_wars/schema.rb +131 -81
- data/spec/support/static_validation_helpers.rb +9 -5
- metadata +418 -261
- data/lib/graphql/language/comments.rb +0 -45
- data/lib/graphql/static_validation/arguments_validator.rb +0 -50
- data/spec/graphql/schema/member/has_fields_spec.rb +0 -129
- data/spec/rails_dependency_sanity_spec.rb +0 -14
@@ -0,0 +1,597 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Execution
|
5
|
+
class Interpreter
|
6
|
+
# I think it would be even better if we could somehow make
|
7
|
+
# `continue_field` not recursive. "Trampolining" it somehow.
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class Runtime
|
11
|
+
# @return [GraphQL::Query]
|
12
|
+
attr_reader :query
|
13
|
+
|
14
|
+
# @return [Class]
|
15
|
+
attr_reader :schema
|
16
|
+
|
17
|
+
# @return [GraphQL::Query::Context]
|
18
|
+
attr_reader :context
|
19
|
+
|
20
|
+
def initialize(query:, response:)
|
21
|
+
@query = query
|
22
|
+
@schema = query.schema
|
23
|
+
@context = query.context
|
24
|
+
@interpreter_context = @context.namespace(:interpreter)
|
25
|
+
@response = response
|
26
|
+
@dead_paths = {}
|
27
|
+
@types_at_paths = {}
|
28
|
+
# A cache of { Class => { String => Schema::Field } }
|
29
|
+
# Which assumes that MyObject.get_field("myField") will return the same field
|
30
|
+
# during the lifetime of a query
|
31
|
+
@fields_cache = Hash.new { |h, k| h[k] = {} }
|
32
|
+
end
|
33
|
+
|
34
|
+
def final_value
|
35
|
+
@response.final_value
|
36
|
+
end
|
37
|
+
|
38
|
+
def inspect
|
39
|
+
"#<#{self.class.name} response=#{@response.inspect}>"
|
40
|
+
end
|
41
|
+
|
42
|
+
# This _begins_ the execution. Some deferred work
|
43
|
+
# might be stored up in lazies.
|
44
|
+
# @return [void]
|
45
|
+
def run_eager
|
46
|
+
root_operation = query.selected_operation
|
47
|
+
root_op_type = root_operation.operation_type || "query"
|
48
|
+
legacy_root_type = schema.root_type_for_operation(root_op_type)
|
49
|
+
root_type = legacy_root_type.metadata[:type_class] || raise("Invariant: type must be class-based: #{legacy_root_type}")
|
50
|
+
object_proxy = root_type.authorized_new(query.root_value, context)
|
51
|
+
object_proxy = schema.sync_lazy(object_proxy)
|
52
|
+
path = []
|
53
|
+
evaluate_selections(path, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def gather_selections(owner_object, owner_type, selections, selections_by_name)
|
58
|
+
selections.each do |node|
|
59
|
+
# Skip gathering this if the directive says so
|
60
|
+
if !directives_include?(node, owner_object, owner_type)
|
61
|
+
next
|
62
|
+
end
|
63
|
+
|
64
|
+
case node
|
65
|
+
when GraphQL::Language::Nodes::Field
|
66
|
+
response_key = node.alias || node.name
|
67
|
+
selections = selections_by_name[response_key]
|
68
|
+
# if there was already a selection of this field,
|
69
|
+
# use an array to hold all selections,
|
70
|
+
# otherise, use the single node to represent the selection
|
71
|
+
if selections
|
72
|
+
# This field was already selected at least once,
|
73
|
+
# add this node to the list of selections
|
74
|
+
s = Array(selections)
|
75
|
+
s << node
|
76
|
+
selections_by_name[response_key] = s
|
77
|
+
else
|
78
|
+
# No selection was found for this field yet
|
79
|
+
selections_by_name[response_key] = node
|
80
|
+
end
|
81
|
+
when GraphQL::Language::Nodes::InlineFragment
|
82
|
+
if node.type
|
83
|
+
type_defn = schema.types[node.type.name]
|
84
|
+
type_defn = type_defn.metadata[:type_class]
|
85
|
+
# Faster than .map{}.include?()
|
86
|
+
query.warden.possible_types(type_defn).each do |t|
|
87
|
+
if t.metadata[:type_class] == owner_type
|
88
|
+
gather_selections(owner_object, owner_type, node.selections, selections_by_name)
|
89
|
+
break
|
90
|
+
end
|
91
|
+
end
|
92
|
+
else
|
93
|
+
# it's an untyped fragment, definitely continue
|
94
|
+
gather_selections(owner_object, owner_type, node.selections, selections_by_name)
|
95
|
+
end
|
96
|
+
when GraphQL::Language::Nodes::FragmentSpread
|
97
|
+
fragment_def = query.fragments[node.name]
|
98
|
+
type_defn = schema.types[fragment_def.type.name]
|
99
|
+
type_defn = type_defn.metadata[:type_class]
|
100
|
+
schema.possible_types(type_defn).each do |t|
|
101
|
+
if t.metadata[:type_class] == owner_type
|
102
|
+
gather_selections(owner_object, owner_type, fragment_def.selections, selections_by_name)
|
103
|
+
break
|
104
|
+
end
|
105
|
+
end
|
106
|
+
else
|
107
|
+
raise "Invariant: unexpected selection class: #{node.class}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def evaluate_selections(path, owner_object, owner_type, selections, root_operation_type: nil)
|
113
|
+
selections_by_name = {}
|
114
|
+
gather_selections(owner_object, owner_type, selections, selections_by_name)
|
115
|
+
selections_by_name.each do |result_name, field_ast_nodes_or_ast_node|
|
116
|
+
# As a performance optimization, the hash key will be a `Node` if
|
117
|
+
# there's only one selection of the field. But if there are multiple
|
118
|
+
# selections of the field, it will be an Array of nodes
|
119
|
+
if field_ast_nodes_or_ast_node.is_a?(Array)
|
120
|
+
field_ast_nodes = field_ast_nodes_or_ast_node
|
121
|
+
ast_node = field_ast_nodes.first
|
122
|
+
else
|
123
|
+
field_ast_nodes = nil
|
124
|
+
ast_node = field_ast_nodes_or_ast_node
|
125
|
+
end
|
126
|
+
field_name = ast_node.name
|
127
|
+
field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name)
|
128
|
+
is_introspection = false
|
129
|
+
if field_defn.nil?
|
130
|
+
field_defn = if owner_type == schema.query.metadata[:type_class] && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
|
131
|
+
is_introspection = true
|
132
|
+
entry_point_field.metadata[:type_class]
|
133
|
+
elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
|
134
|
+
is_introspection = true
|
135
|
+
dynamic_field.metadata[:type_class]
|
136
|
+
else
|
137
|
+
raise "Invariant: no field for #{owner_type}.#{field_name}"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
return_type = resolve_if_late_bound_type(field_defn.type)
|
142
|
+
|
143
|
+
next_path = path.dup
|
144
|
+
next_path << result_name
|
145
|
+
next_path.freeze
|
146
|
+
|
147
|
+
# This seems janky, but we need to know
|
148
|
+
# the field's return type at this path in order
|
149
|
+
# to propagate `null`
|
150
|
+
set_type_at_path(next_path, return_type)
|
151
|
+
# Set this before calling `run_with_directives`, so that the directive can have the latest path
|
152
|
+
@interpreter_context[:current_path] = next_path
|
153
|
+
@interpreter_context[:current_field] = field_defn
|
154
|
+
|
155
|
+
object = owner_object
|
156
|
+
|
157
|
+
if is_introspection
|
158
|
+
object = field_defn.owner.authorized_new(object, context)
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
kwarg_arguments = arguments(object, field_defn, ast_node)
|
163
|
+
# It might turn out that making arguments for every field is slow.
|
164
|
+
# If we have to cache them, we'll need a more subtle approach here.
|
165
|
+
field_defn.extras.each do |extra|
|
166
|
+
case extra
|
167
|
+
when :ast_node
|
168
|
+
kwarg_arguments[:ast_node] = ast_node
|
169
|
+
when :execution_errors
|
170
|
+
kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
|
171
|
+
when :path
|
172
|
+
kwarg_arguments[:path] = next_path
|
173
|
+
when :lookahead
|
174
|
+
if !field_ast_nodes
|
175
|
+
field_ast_nodes = [ast_node]
|
176
|
+
end
|
177
|
+
kwarg_arguments[:lookahead] = Execution::Lookahead.new(
|
178
|
+
query: query,
|
179
|
+
ast_nodes: field_ast_nodes,
|
180
|
+
field: field_defn,
|
181
|
+
)
|
182
|
+
else
|
183
|
+
kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Optimize for the case that field is selected only once
|
188
|
+
if field_ast_nodes.nil? || field_ast_nodes.size == 1
|
189
|
+
next_selections = ast_node.selections
|
190
|
+
else
|
191
|
+
next_selections = []
|
192
|
+
field_ast_nodes.each { |f| next_selections.concat(f.selections) }
|
193
|
+
end
|
194
|
+
|
195
|
+
field_result = resolve_with_directives(object, ast_node) do
|
196
|
+
# Actually call the field resolver and capture the result
|
197
|
+
app_result = query.trace("execute_field", {field: field_defn, path: next_path}) do
|
198
|
+
field_defn.resolve(object, kwarg_arguments, context)
|
199
|
+
end
|
200
|
+
after_lazy(app_result, field: field_defn, path: next_path) do |inner_result|
|
201
|
+
continue_value = continue_value(next_path, inner_result, field_defn, return_type.non_null?, ast_node)
|
202
|
+
if HALT != continue_value
|
203
|
+
continue_field(next_path, continue_value, field_defn, return_type, ast_node, next_selections, false)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# If this field is a root mutation field, immediately resolve
|
209
|
+
# all of its child fields before moving on to the next root mutation field.
|
210
|
+
# (Subselections of this mutation will still be resolved level-by-level.)
|
211
|
+
if root_operation_type == "mutation"
|
212
|
+
Interpreter::Resolve.resolve_all([field_result])
|
213
|
+
else
|
214
|
+
field_result
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
HALT = Object.new
|
220
|
+
def continue_value(path, value, field, is_non_null, ast_node)
|
221
|
+
if value.nil?
|
222
|
+
if is_non_null
|
223
|
+
err = GraphQL::InvalidNullError.new(field.owner, field, value)
|
224
|
+
write_invalid_null_in_response(path, err)
|
225
|
+
else
|
226
|
+
write_in_response(path, nil)
|
227
|
+
end
|
228
|
+
HALT
|
229
|
+
elsif value.is_a?(GraphQL::ExecutionError)
|
230
|
+
value.path ||= path
|
231
|
+
value.ast_node ||= ast_node
|
232
|
+
write_execution_errors_in_response(path, [value])
|
233
|
+
HALT
|
234
|
+
elsif value.is_a?(Array) && value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
|
235
|
+
value.each do |v|
|
236
|
+
v.path ||= path
|
237
|
+
v.ast_node ||= ast_node
|
238
|
+
end
|
239
|
+
write_execution_errors_in_response(path, value)
|
240
|
+
HALT
|
241
|
+
elsif value.is_a?(GraphQL::UnauthorizedError)
|
242
|
+
# this hook might raise & crash, or it might return
|
243
|
+
# a replacement value
|
244
|
+
next_value = begin
|
245
|
+
schema.unauthorized_object(value)
|
246
|
+
rescue GraphQL::ExecutionError => err
|
247
|
+
err
|
248
|
+
end
|
249
|
+
|
250
|
+
continue_value(path, next_value, field, is_non_null, ast_node)
|
251
|
+
elsif GraphQL::Execution::Execute::SKIP == value
|
252
|
+
HALT
|
253
|
+
else
|
254
|
+
value
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# The resolver for `field` returned `value`. Continue to execute the query,
|
259
|
+
# treating `value` as `type` (probably the return type of the field).
|
260
|
+
#
|
261
|
+
# Use `next_selections` to resolve object fields, if there are any.
|
262
|
+
#
|
263
|
+
# Location information from `path` and `ast_node`.
|
264
|
+
#
|
265
|
+
# @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
|
266
|
+
def continue_field(path, value, field, type, ast_node, next_selections, is_non_null)
|
267
|
+
case type.kind.name
|
268
|
+
when "SCALAR", "ENUM"
|
269
|
+
r = type.coerce_result(value, context)
|
270
|
+
write_in_response(path, r)
|
271
|
+
r
|
272
|
+
when "UNION", "INTERFACE"
|
273
|
+
resolved_type_or_lazy = query.resolve_type(type, value)
|
274
|
+
after_lazy(resolved_type_or_lazy, path: path, field: field) do |resolved_type|
|
275
|
+
possible_types = query.possible_types(type)
|
276
|
+
|
277
|
+
if !possible_types.include?(resolved_type)
|
278
|
+
parent_type = field.owner
|
279
|
+
type_error = GraphQL::UnresolvedTypeError.new(value, field, parent_type, resolved_type, possible_types)
|
280
|
+
schema.type_error(type_error, context)
|
281
|
+
write_in_response(path, nil)
|
282
|
+
nil
|
283
|
+
else
|
284
|
+
resolved_type = resolved_type.metadata[:type_class]
|
285
|
+
continue_field(path, value, field, resolved_type, ast_node, next_selections, is_non_null)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
when "OBJECT"
|
289
|
+
object_proxy = begin
|
290
|
+
type.authorized_new(value, context)
|
291
|
+
rescue GraphQL::ExecutionError => err
|
292
|
+
err
|
293
|
+
end
|
294
|
+
after_lazy(object_proxy, path: path, field: field) do |inner_object|
|
295
|
+
continue_value = continue_value(path, inner_object, field, is_non_null, ast_node)
|
296
|
+
if HALT != continue_value
|
297
|
+
response_hash = {}
|
298
|
+
write_in_response(path, response_hash)
|
299
|
+
evaluate_selections(path, continue_value, type, next_selections)
|
300
|
+
response_hash
|
301
|
+
end
|
302
|
+
end
|
303
|
+
when "LIST"
|
304
|
+
response_list = []
|
305
|
+
write_in_response(path, response_list)
|
306
|
+
inner_type = type.of_type
|
307
|
+
idx = 0
|
308
|
+
value.each do |inner_value|
|
309
|
+
next_path = path.dup
|
310
|
+
next_path << idx
|
311
|
+
next_path.freeze
|
312
|
+
idx += 1
|
313
|
+
set_type_at_path(next_path, inner_type)
|
314
|
+
# This will update `response_list` with the lazy
|
315
|
+
after_lazy(inner_value, path: next_path, field: field) do |inner_inner_value|
|
316
|
+
# reset `is_non_null` here and below, because the inner type will have its own nullability constraint
|
317
|
+
continue_value = continue_value(next_path, inner_inner_value, field, false, ast_node)
|
318
|
+
if HALT != continue_value
|
319
|
+
continue_field(next_path, continue_value, field, inner_type, ast_node, next_selections, false)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
response_list
|
324
|
+
when "NON_NULL"
|
325
|
+
inner_type = type.of_type
|
326
|
+
# For fields like `__schema: __Schema!`
|
327
|
+
inner_type = resolve_if_late_bound_type(inner_type)
|
328
|
+
# Don't `set_type_at_path` because we want the static type,
|
329
|
+
# we're going to use that to determine whether a `nil` should be propagated or not.
|
330
|
+
continue_field(path, value, field, inner_type, ast_node, next_selections, true)
|
331
|
+
else
|
332
|
+
raise "Invariant: Unhandled type kind #{type.kind} (#{type})"
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
def resolve_with_directives(object, ast_node)
|
337
|
+
run_directive(object, ast_node, 0) { yield }
|
338
|
+
end
|
339
|
+
|
340
|
+
def run_directive(object, ast_node, idx)
|
341
|
+
dir_node = ast_node.directives[idx]
|
342
|
+
if !dir_node
|
343
|
+
yield
|
344
|
+
else
|
345
|
+
dir_defn = schema.directives.fetch(dir_node.name)
|
346
|
+
if !dir_defn.is_a?(Class)
|
347
|
+
dir_defn = dir_defn.metadata[:type_class] || raise("Only class-based directives are supported (not `@#{dir_node.name}`)")
|
348
|
+
end
|
349
|
+
dir_args = arguments(nil, dir_defn, dir_node)
|
350
|
+
dir_defn.resolve(object, dir_args, context) do
|
351
|
+
run_directive(object, ast_node, idx + 1) { yield }
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
# Check {Schema::Directive.include?} for each directive that's present
|
357
|
+
def directives_include?(node, graphql_object, parent_type)
|
358
|
+
node.directives.each do |dir_node|
|
359
|
+
dir_defn = schema.directives.fetch(dir_node.name).metadata[:type_class] || raise("Only class-based directives are supported (not #{dir_node.name.inspect})")
|
360
|
+
args = arguments(graphql_object, dir_defn, dir_node)
|
361
|
+
if !dir_defn.include?(graphql_object, args, context)
|
362
|
+
return false
|
363
|
+
end
|
364
|
+
end
|
365
|
+
true
|
366
|
+
end
|
367
|
+
|
368
|
+
def resolve_if_late_bound_type(type)
|
369
|
+
if type.is_a?(GraphQL::Schema::LateBoundType)
|
370
|
+
query.warden.get_type(type.name).metadata[:type_class]
|
371
|
+
else
|
372
|
+
type
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
# @param obj [Object] Some user-returned value that may want to be batched
|
377
|
+
# @param path [Array<String>]
|
378
|
+
# @param field [GraphQL::Schema::Field]
|
379
|
+
# @param eager [Boolean] Set to `true` for mutation root fields only
|
380
|
+
# @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
|
381
|
+
def after_lazy(obj, field:, path:, eager: false)
|
382
|
+
@interpreter_context[:current_path] = path
|
383
|
+
@interpreter_context[:current_field] = field
|
384
|
+
if schema.lazy?(obj)
|
385
|
+
lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
|
386
|
+
@interpreter_context[:current_path] = path
|
387
|
+
@interpreter_context[:current_field] = field
|
388
|
+
# Wrap the execution of _this_ method with tracing,
|
389
|
+
# but don't wrap the continuation below
|
390
|
+
inner_obj = query.trace("execute_field_lazy", {field: field, path: path}) do
|
391
|
+
begin
|
392
|
+
schema.sync_lazy(obj)
|
393
|
+
rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
|
394
|
+
yield(err)
|
395
|
+
end
|
396
|
+
end
|
397
|
+
after_lazy(inner_obj, field: field, path: path, eager: eager) do |really_inner_obj|
|
398
|
+
yield(really_inner_obj)
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
if eager
|
403
|
+
lazy.value
|
404
|
+
else
|
405
|
+
write_in_response(path, lazy)
|
406
|
+
lazy
|
407
|
+
end
|
408
|
+
else
|
409
|
+
yield(obj)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
def each_argument_pair(ast_args_or_hash)
|
414
|
+
case ast_args_or_hash
|
415
|
+
when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive
|
416
|
+
ast_args_or_hash.arguments.each do |arg|
|
417
|
+
yield(arg.name, arg.value)
|
418
|
+
end
|
419
|
+
when Hash
|
420
|
+
ast_args_or_hash.each do |key, value|
|
421
|
+
normalized_name = GraphQL::Schema::Member::BuildType.camelize(key.to_s)
|
422
|
+
yield(normalized_name, value)
|
423
|
+
end
|
424
|
+
else
|
425
|
+
raise "Invariant, unexpected #{ast_args_or_hash.inspect}"
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
def arguments(graphql_object, arg_owner, ast_node_or_hash)
|
430
|
+
kwarg_arguments = {}
|
431
|
+
arg_defns = arg_owner.arguments
|
432
|
+
each_argument_pair(ast_node_or_hash) do |arg_name, arg_value|
|
433
|
+
arg_defn = arg_defns[arg_name]
|
434
|
+
# Need to distinguish between client-provided `nil`
|
435
|
+
# and nothing-at-all
|
436
|
+
is_present, value = arg_to_value(graphql_object, arg_defn.type, arg_value)
|
437
|
+
if is_present
|
438
|
+
# This doesn't apply to directives, which are legacy
|
439
|
+
# Can remove this when Skip and Include use classes or something.
|
440
|
+
if graphql_object
|
441
|
+
value = arg_defn.prepare_value(graphql_object, value)
|
442
|
+
end
|
443
|
+
kwarg_arguments[arg_defn.keyword] = value
|
444
|
+
end
|
445
|
+
end
|
446
|
+
arg_defns.each do |name, arg_defn|
|
447
|
+
if arg_defn.default_value? && !kwarg_arguments.key?(arg_defn.keyword)
|
448
|
+
_is_present, value = arg_to_value(graphql_object, arg_defn.type, arg_defn.default_value)
|
449
|
+
kwarg_arguments[arg_defn.keyword] = value
|
450
|
+
end
|
451
|
+
end
|
452
|
+
kwarg_arguments
|
453
|
+
end
|
454
|
+
|
455
|
+
# Get a Ruby-ready value from a client query.
|
456
|
+
# @param graphql_object [Object] The owner of the field whose argument this is
|
457
|
+
# @param arg_type [Class, GraphQL::Schema::NonNull, GraphQL::Schema::List]
|
458
|
+
# @param ast_value [GraphQL::Language::Nodes::VariableIdentifier, String, Integer, Float, Boolean]
|
459
|
+
# @return [Array(is_present, value)]
|
460
|
+
def arg_to_value(graphql_object, arg_type, ast_value)
|
461
|
+
if ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
|
462
|
+
# If it's not here, it will get added later
|
463
|
+
if query.variables.key?(ast_value.name)
|
464
|
+
return true, query.variables[ast_value.name]
|
465
|
+
else
|
466
|
+
return false, nil
|
467
|
+
end
|
468
|
+
elsif ast_value.is_a?(GraphQL::Language::Nodes::NullValue)
|
469
|
+
return true, nil
|
470
|
+
elsif arg_type.is_a?(GraphQL::Schema::NonNull)
|
471
|
+
arg_to_value(graphql_object, arg_type.of_type, ast_value)
|
472
|
+
elsif arg_type.is_a?(GraphQL::Schema::List)
|
473
|
+
# Treat a single value like a list
|
474
|
+
arg_value = Array(ast_value)
|
475
|
+
list = []
|
476
|
+
arg_value.map do |inner_v|
|
477
|
+
_present, value = arg_to_value(graphql_object, arg_type.of_type, inner_v)
|
478
|
+
list << value
|
479
|
+
end
|
480
|
+
return true, list
|
481
|
+
elsif arg_type.is_a?(Class) && arg_type < GraphQL::Schema::InputObject
|
482
|
+
# For these, `prepare` is applied during `#initialize`.
|
483
|
+
# Pass `nil` so it will be skipped in `#arguments`.
|
484
|
+
# What a mess.
|
485
|
+
args = arguments(nil, arg_type, ast_value)
|
486
|
+
# We're not tracking defaults_used, but for our purposes
|
487
|
+
# we compare the value to the default value.
|
488
|
+
return true, arg_type.new(ruby_kwargs: args, context: context, defaults_used: nil)
|
489
|
+
else
|
490
|
+
flat_value = flatten_ast_value(ast_value)
|
491
|
+
return true, arg_type.coerce_input(flat_value, context)
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
def flatten_ast_value(v)
|
496
|
+
case v
|
497
|
+
when GraphQL::Language::Nodes::Enum
|
498
|
+
v.name
|
499
|
+
when GraphQL::Language::Nodes::InputObject
|
500
|
+
h = {}
|
501
|
+
v.arguments.each do |arg|
|
502
|
+
h[arg.name] = flatten_ast_value(arg.value)
|
503
|
+
end
|
504
|
+
h
|
505
|
+
when Array
|
506
|
+
v.map { |v2| flatten_ast_value(v2) }
|
507
|
+
when GraphQL::Language::Nodes::VariableIdentifier
|
508
|
+
flatten_ast_value(query.variables[v.name])
|
509
|
+
else
|
510
|
+
v
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
def write_invalid_null_in_response(path, invalid_null_error)
|
515
|
+
if !dead_path?(path)
|
516
|
+
schema.type_error(invalid_null_error, context)
|
517
|
+
write_in_response(path, nil)
|
518
|
+
add_dead_path(path)
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
def write_execution_errors_in_response(path, errors)
|
523
|
+
if !dead_path?(path)
|
524
|
+
errors.each do |v|
|
525
|
+
context.errors << v
|
526
|
+
end
|
527
|
+
write_in_response(path, nil)
|
528
|
+
add_dead_path(path)
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
def write_in_response(path, value)
|
533
|
+
if dead_path?(path)
|
534
|
+
return
|
535
|
+
else
|
536
|
+
if value.nil? && path.any? && type_at(path).non_null?
|
537
|
+
# This nil is invalid, try writing it at the previous spot
|
538
|
+
propagate_path = path[0..-2]
|
539
|
+
write_in_response(propagate_path, value)
|
540
|
+
add_dead_path(propagate_path)
|
541
|
+
else
|
542
|
+
@response.write(path, value)
|
543
|
+
end
|
544
|
+
end
|
545
|
+
end
|
546
|
+
|
547
|
+
# To propagate nulls, we have to know what the field type was
|
548
|
+
# at previous parts of the response.
|
549
|
+
# This hash matches the response
|
550
|
+
def type_at(path)
|
551
|
+
t = @types_at_paths
|
552
|
+
path.each do |part|
|
553
|
+
t = t[part] || (raise("Invariant: #{part.inspect} not found in #{t}"))
|
554
|
+
end
|
555
|
+
t = t[:__type]
|
556
|
+
t
|
557
|
+
end
|
558
|
+
|
559
|
+
def set_type_at_path(path, type)
|
560
|
+
types = @types_at_paths
|
561
|
+
path.each do |part|
|
562
|
+
types = types[part] ||= {}
|
563
|
+
end
|
564
|
+
# Use this magic key so that the hash contains:
|
565
|
+
# - string keys for nested fields
|
566
|
+
# - :__type for the object type of a selection
|
567
|
+
types[:__type] ||= type
|
568
|
+
nil
|
569
|
+
end
|
570
|
+
|
571
|
+
# Mark `path` as having been permanently nulled out.
|
572
|
+
# No values will be added beyond that path.
|
573
|
+
def add_dead_path(path)
|
574
|
+
dead = @dead_paths
|
575
|
+
path.each do |part|
|
576
|
+
dead = dead[part] ||= {}
|
577
|
+
end
|
578
|
+
dead[:__dead] = true
|
579
|
+
end
|
580
|
+
|
581
|
+
def dead_path?(path)
|
582
|
+
res = @dead_paths
|
583
|
+
path.each do |part|
|
584
|
+
if res
|
585
|
+
if res[:__dead]
|
586
|
+
break
|
587
|
+
else
|
588
|
+
res = res[part]
|
589
|
+
end
|
590
|
+
end
|
591
|
+
end
|
592
|
+
res && res[:__dead]
|
593
|
+
end
|
594
|
+
end
|
595
|
+
end
|
596
|
+
end
|
597
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "graphql/execution/interpreter/execution_errors"
|
3
|
+
require "graphql/execution/interpreter/hash_response"
|
4
|
+
require "graphql/execution/interpreter/runtime"
|
5
|
+
require "graphql/execution/interpreter/resolve"
|
6
|
+
|
7
|
+
module GraphQL
|
8
|
+
module Execution
|
9
|
+
class Interpreter
|
10
|
+
def initialize
|
11
|
+
end
|
12
|
+
|
13
|
+
# Support `Executor` :S
|
14
|
+
def execute(_operation, _root_type, query)
|
15
|
+
runtime = evaluate(query)
|
16
|
+
sync_lazies(query: query)
|
17
|
+
runtime.final_value
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.use(schema_defn)
|
21
|
+
schema_defn.target.interpreter = true
|
22
|
+
# Reach through the legacy objects for the actual class defn
|
23
|
+
schema_class = schema_defn.target.class
|
24
|
+
# This is not good, since both of these are holding state now,
|
25
|
+
# we have to update both :(
|
26
|
+
[schema_class, schema_defn].each do |schema_config|
|
27
|
+
schema_config.query_execution_strategy(GraphQL::Execution::Interpreter)
|
28
|
+
schema_config.mutation_execution_strategy(GraphQL::Execution::Interpreter)
|
29
|
+
schema_config.subscription_execution_strategy(GraphQL::Execution::Interpreter)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.begin_multiplex(multiplex)
|
34
|
+
# Since this is basically the batching context,
|
35
|
+
# share it for a whole multiplex
|
36
|
+
multiplex.context[:interpreter_instance] ||= self.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.begin_query(query, multiplex)
|
40
|
+
# The batching context is shared by the multiplex,
|
41
|
+
# so fetch it out and use that instance.
|
42
|
+
interpreter =
|
43
|
+
query.context.namespace(:interpreter)[:interpreter_instance] =
|
44
|
+
multiplex.context[:interpreter_instance]
|
45
|
+
interpreter.evaluate(query)
|
46
|
+
query
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.finish_multiplex(_results, multiplex)
|
50
|
+
interpreter = multiplex.context[:interpreter_instance]
|
51
|
+
interpreter.sync_lazies(multiplex: multiplex)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.finish_query(query, _multiplex)
|
55
|
+
{
|
56
|
+
"data" => query.context.namespace(:interpreter)[:runtime].final_value
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
# Run the eager part of `query`
|
61
|
+
# @return {Interpreter::Runtime}
|
62
|
+
def evaluate(query)
|
63
|
+
# Although queries in a multiplex _share_ an Interpreter instance,
|
64
|
+
# they also have another item of state, which is private to that query
|
65
|
+
# in particular, assign it here:
|
66
|
+
runtime = Runtime.new(
|
67
|
+
query: query,
|
68
|
+
response: HashResponse.new,
|
69
|
+
)
|
70
|
+
query.context.namespace(:interpreter)[:runtime] = runtime
|
71
|
+
|
72
|
+
query.trace("execute_query", {query: query}) do
|
73
|
+
runtime.run_eager
|
74
|
+
end
|
75
|
+
|
76
|
+
runtime
|
77
|
+
end
|
78
|
+
|
79
|
+
# Run the lazy part of `query` or `multiplex`.
|
80
|
+
# @return [void]
|
81
|
+
def sync_lazies(query: nil, multiplex: nil)
|
82
|
+
tracer = query || multiplex
|
83
|
+
if query.nil? && multiplex.queries.length == 1
|
84
|
+
query = multiplex.queries[0]
|
85
|
+
end
|
86
|
+
queries = multiplex ? multiplex.queries : [query]
|
87
|
+
final_values = queries.map do |query|
|
88
|
+
runtime = query.context.namespace(:interpreter)[:runtime]
|
89
|
+
# it might not be present if the query has an error
|
90
|
+
runtime ? runtime.final_value : nil
|
91
|
+
end
|
92
|
+
final_values.compact!
|
93
|
+
tracer.trace("execute_query_lazy", {multiplex: multiplex, query: query}) do
|
94
|
+
Interpreter::Resolve.resolve_all(final_values)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|