pax_global_header00006660000000000000000000000064136260135100014507gustar00rootroot0000000000000052 comment=dbe43ccca73573c5ff09c2dcc9b418805cc8fa01 ruby-graphql-1.9.19/000077500000000000000000000000001362601351000142255ustar00rootroot00000000000000ruby-graphql-1.9.19/.yardopts000066400000000000000000000001621362601351000160720ustar00rootroot00000000000000--no-private --markup=markdown --readme=readme.md --title='GraphQL Ruby API Documentation' 'lib/**/*.rb' - '*.md' ruby-graphql-1.9.19/MIT-LICENSE000066400000000000000000000020361362601351000156620ustar00rootroot00000000000000Copyright 2015 Robert Mosolgo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ruby-graphql-1.9.19/graphql.gemspec000066400000000000000000000621421362601351000172350ustar00rootroot00000000000000######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: graphql 1.9.19 ruby lib Gem::Specification.new do |s| s.name = "graphql".freeze s.version = "1.9.19" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "bug_tracker_uri" => "https://github.com/rmosolgo/graphql-ruby/issues", "changelog_uri" => "https://github.com/rmosolgo/graphql-ruby/blob/master/CHANGELOG.md", "homepage_uri" => "https://graphql-ruby.org", "mailing_list_uri" => "https://tinyletter.com/graphql-ruby", "source_code_uri" => "https://github.com/rmosolgo/graphql-ruby" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Robert Mosolgo".freeze] s.date = "2020-01-28" s.description = "A plain-Ruby implementation of GraphQL.".freeze s.email = ["rdmosolgo@gmail.com".freeze] s.files = [".yardopts".freeze, "MIT-LICENSE".freeze, "lib/generators/graphql/core.rb".freeze, "lib/generators/graphql/enum_generator.rb".freeze, "lib/generators/graphql/install_generator.rb".freeze, "lib/generators/graphql/interface_generator.rb".freeze, "lib/generators/graphql/loader_generator.rb".freeze, "lib/generators/graphql/mutation_generator.rb".freeze, "lib/generators/graphql/object_generator.rb".freeze, "lib/generators/graphql/scalar_generator.rb".freeze, "lib/generators/graphql/templates/base_argument.erb".freeze, "lib/generators/graphql/templates/base_enum.erb".freeze, "lib/generators/graphql/templates/base_field.erb".freeze, "lib/generators/graphql/templates/base_input_object.erb".freeze, "lib/generators/graphql/templates/base_interface.erb".freeze, "lib/generators/graphql/templates/base_mutation.erb".freeze, "lib/generators/graphql/templates/base_object.erb".freeze, "lib/generators/graphql/templates/base_scalar.erb".freeze, "lib/generators/graphql/templates/base_union.erb".freeze, "lib/generators/graphql/templates/enum.erb".freeze, "lib/generators/graphql/templates/graphql_controller.erb".freeze, "lib/generators/graphql/templates/interface.erb".freeze, "lib/generators/graphql/templates/loader.erb".freeze, "lib/generators/graphql/templates/mutation.erb".freeze, "lib/generators/graphql/templates/mutation_type.erb".freeze, "lib/generators/graphql/templates/object.erb".freeze, "lib/generators/graphql/templates/query_type.erb".freeze, "lib/generators/graphql/templates/scalar.erb".freeze, "lib/generators/graphql/templates/schema.erb".freeze, "lib/generators/graphql/templates/union.erb".freeze, "lib/generators/graphql/type_generator.rb".freeze, "lib/generators/graphql/union_generator.rb".freeze, "lib/graphql.rb".freeze, "lib/graphql/analysis.rb".freeze, "lib/graphql/analysis/analyze_query.rb".freeze, "lib/graphql/analysis/ast.rb".freeze, "lib/graphql/analysis/ast/analyzer.rb".freeze, "lib/graphql/analysis/ast/field_usage.rb".freeze, "lib/graphql/analysis/ast/max_query_complexity.rb".freeze, "lib/graphql/analysis/ast/max_query_depth.rb".freeze, "lib/graphql/analysis/ast/query_complexity.rb".freeze, "lib/graphql/analysis/ast/query_depth.rb".freeze, "lib/graphql/analysis/ast/visitor.rb".freeze, "lib/graphql/analysis/field_usage.rb".freeze, "lib/graphql/analysis/max_query_complexity.rb".freeze, "lib/graphql/analysis/max_query_depth.rb".freeze, "lib/graphql/analysis/query_complexity.rb".freeze, "lib/graphql/analysis/query_depth.rb".freeze, "lib/graphql/analysis/reducer_state.rb".freeze, "lib/graphql/analysis_error.rb".freeze, "lib/graphql/argument.rb".freeze, "lib/graphql/authorization.rb".freeze, "lib/graphql/backtrace.rb".freeze, "lib/graphql/backtrace/inspect_result.rb".freeze, "lib/graphql/backtrace/table.rb".freeze, "lib/graphql/backtrace/traced_error.rb".freeze, "lib/graphql/backtrace/tracer.rb".freeze, "lib/graphql/backwards_compatibility.rb".freeze, "lib/graphql/base_type.rb".freeze, "lib/graphql/boolean_type.rb".freeze, "lib/graphql/coercion_error.rb".freeze, "lib/graphql/compatibility.rb".freeze, "lib/graphql/compatibility/execution_specification.rb".freeze, "lib/graphql/compatibility/execution_specification/counter_schema.rb".freeze, "lib/graphql/compatibility/execution_specification/specification_schema.rb".freeze, "lib/graphql/compatibility/lazy_execution_specification.rb".freeze, "lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb".freeze, "lib/graphql/compatibility/query_parser_specification.rb".freeze, "lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb".freeze, "lib/graphql/compatibility/query_parser_specification/query_assertions.rb".freeze, "lib/graphql/compatibility/schema_parser_specification.rb".freeze, "lib/graphql/define.rb".freeze, "lib/graphql/define/assign_argument.rb".freeze, "lib/graphql/define/assign_connection.rb".freeze, "lib/graphql/define/assign_enum_value.rb".freeze, "lib/graphql/define/assign_global_id_field.rb".freeze, "lib/graphql/define/assign_mutation_function.rb".freeze, "lib/graphql/define/assign_object_field.rb".freeze, "lib/graphql/define/defined_object_proxy.rb".freeze, "lib/graphql/define/instance_definable.rb".freeze, "lib/graphql/define/no_definition_error.rb".freeze, "lib/graphql/define/non_null_with_bang.rb".freeze, "lib/graphql/define/type_definer.rb".freeze, "lib/graphql/deprecated_dsl.rb".freeze, "lib/graphql/dig.rb".freeze, "lib/graphql/directive.rb".freeze, "lib/graphql/directive/deprecated_directive.rb".freeze, "lib/graphql/directive/include_directive.rb".freeze, "lib/graphql/directive/skip_directive.rb".freeze, "lib/graphql/enum_type.rb".freeze, "lib/graphql/execution.rb".freeze, "lib/graphql/execution/directive_checks.rb".freeze, "lib/graphql/execution/errors.rb".freeze, "lib/graphql/execution/execute.rb".freeze, "lib/graphql/execution/flatten.rb".freeze, "lib/graphql/execution/instrumentation.rb".freeze, "lib/graphql/execution/interpreter.rb".freeze, "lib/graphql/execution/interpreter/execution_errors.rb".freeze, "lib/graphql/execution/interpreter/hash_response.rb".freeze, "lib/graphql/execution/interpreter/resolve.rb".freeze, "lib/graphql/execution/interpreter/runtime.rb".freeze, "lib/graphql/execution/lazy.rb".freeze, "lib/graphql/execution/lazy/lazy_method_map.rb".freeze, "lib/graphql/execution/lazy/resolve.rb".freeze, "lib/graphql/execution/lookahead.rb".freeze, "lib/graphql/execution/multiplex.rb".freeze, "lib/graphql/execution/typecast.rb".freeze, "lib/graphql/execution_error.rb".freeze, "lib/graphql/field.rb".freeze, "lib/graphql/field/resolve.rb".freeze, "lib/graphql/filter.rb".freeze, "lib/graphql/float_type.rb".freeze, "lib/graphql/function.rb".freeze, "lib/graphql/id_type.rb".freeze, "lib/graphql/input_object_type.rb".freeze, "lib/graphql/int_type.rb".freeze, "lib/graphql/integer_encoding_error.rb".freeze, "lib/graphql/interface_type.rb".freeze, "lib/graphql/internal_representation.rb".freeze, "lib/graphql/internal_representation/document.rb".freeze, "lib/graphql/internal_representation/node.rb".freeze, "lib/graphql/internal_representation/print.rb".freeze, "lib/graphql/internal_representation/rewrite.rb".freeze, "lib/graphql/internal_representation/scope.rb".freeze, "lib/graphql/internal_representation/visit.rb".freeze, "lib/graphql/introspection.rb".freeze, "lib/graphql/introspection/base_object.rb".freeze, "lib/graphql/introspection/directive_location_enum.rb".freeze, "lib/graphql/introspection/directive_type.rb".freeze, "lib/graphql/introspection/dynamic_fields.rb".freeze, "lib/graphql/introspection/entry_points.rb".freeze, "lib/graphql/introspection/enum_value_type.rb".freeze, "lib/graphql/introspection/field_type.rb".freeze, "lib/graphql/introspection/input_value_type.rb".freeze, "lib/graphql/introspection/introspection_query.rb".freeze, "lib/graphql/introspection/schema_type.rb".freeze, "lib/graphql/introspection/type_kind_enum.rb".freeze, "lib/graphql/introspection/type_type.rb".freeze, "lib/graphql/invalid_name_error.rb".freeze, "lib/graphql/invalid_null_error.rb".freeze, "lib/graphql/language.rb".freeze, "lib/graphql/language/block_string.rb".freeze, "lib/graphql/language/definition_slice.rb".freeze, "lib/graphql/language/document_from_schema_definition.rb".freeze, "lib/graphql/language/generation.rb".freeze, "lib/graphql/language/lexer.rb".freeze, "lib/graphql/language/lexer.rl".freeze, "lib/graphql/language/nodes.rb".freeze, "lib/graphql/language/parser.rb".freeze, "lib/graphql/language/parser.y".freeze, "lib/graphql/language/printer.rb".freeze, "lib/graphql/language/token.rb".freeze, "lib/graphql/language/visitor.rb".freeze, "lib/graphql/list_type.rb".freeze, "lib/graphql/literal_validation_error.rb".freeze, "lib/graphql/load_application_object_failed_error.rb".freeze, "lib/graphql/name_validator.rb".freeze, "lib/graphql/non_null_type.rb".freeze, "lib/graphql/object_type.rb".freeze, "lib/graphql/parse_error.rb".freeze, "lib/graphql/query.rb".freeze, "lib/graphql/query/arguments.rb".freeze, "lib/graphql/query/arguments_cache.rb".freeze, "lib/graphql/query/context.rb".freeze, "lib/graphql/query/executor.rb".freeze, "lib/graphql/query/input_validation_result.rb".freeze, "lib/graphql/query/literal_input.rb".freeze, "lib/graphql/query/null_context.rb".freeze, "lib/graphql/query/result.rb".freeze, "lib/graphql/query/serial_execution.rb".freeze, "lib/graphql/query/serial_execution/field_resolution.rb".freeze, "lib/graphql/query/serial_execution/operation_resolution.rb".freeze, "lib/graphql/query/serial_execution/selection_resolution.rb".freeze, "lib/graphql/query/serial_execution/value_resolution.rb".freeze, "lib/graphql/query/validation_pipeline.rb".freeze, "lib/graphql/query/variable_validation_error.rb".freeze, "lib/graphql/query/variables.rb".freeze, "lib/graphql/railtie.rb".freeze, "lib/graphql/rake_task.rb".freeze, "lib/graphql/rake_task/validate.rb".freeze, "lib/graphql/relay.rb".freeze, "lib/graphql/relay/array_connection.rb".freeze, "lib/graphql/relay/base_connection.rb".freeze, "lib/graphql/relay/connection_instrumentation.rb".freeze, "lib/graphql/relay/connection_resolve.rb".freeze, "lib/graphql/relay/connection_type.rb".freeze, "lib/graphql/relay/edge.rb".freeze, "lib/graphql/relay/edge_type.rb".freeze, "lib/graphql/relay/edges_instrumentation.rb".freeze, "lib/graphql/relay/global_id_resolve.rb".freeze, "lib/graphql/relay/mongo_relation_connection.rb".freeze, "lib/graphql/relay/mutation.rb".freeze, "lib/graphql/relay/mutation/instrumentation.rb".freeze, "lib/graphql/relay/mutation/resolve.rb".freeze, "lib/graphql/relay/mutation/result.rb".freeze, "lib/graphql/relay/node.rb".freeze, "lib/graphql/relay/page_info.rb".freeze, "lib/graphql/relay/range_add.rb".freeze, "lib/graphql/relay/relation_connection.rb".freeze, "lib/graphql/relay/type_extensions.rb".freeze, "lib/graphql/runtime_type_error.rb".freeze, "lib/graphql/scalar_type.rb".freeze, "lib/graphql/schema.rb".freeze, "lib/graphql/schema/argument.rb".freeze, "lib/graphql/schema/base_64_bp.rb".freeze, "lib/graphql/schema/base_64_encoder.rb".freeze, "lib/graphql/schema/build_from_definition.rb".freeze, "lib/graphql/schema/build_from_definition/resolve_map.rb".freeze, "lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb".freeze, "lib/graphql/schema/built_in_types.rb".freeze, "lib/graphql/schema/catchall_middleware.rb".freeze, "lib/graphql/schema/default_parse_error.rb".freeze, "lib/graphql/schema/default_type_error.rb".freeze, "lib/graphql/schema/directive.rb".freeze, "lib/graphql/schema/directive/feature.rb".freeze, "lib/graphql/schema/directive/include.rb".freeze, "lib/graphql/schema/directive/skip.rb".freeze, "lib/graphql/schema/directive/transform.rb".freeze, "lib/graphql/schema/enum.rb".freeze, "lib/graphql/schema/enum_value.rb".freeze, "lib/graphql/schema/field.rb".freeze, "lib/graphql/schema/field/connection_extension.rb".freeze, "lib/graphql/schema/field/scope_extension.rb".freeze, "lib/graphql/schema/field_extension.rb".freeze, "lib/graphql/schema/find_inherited_value.rb".freeze, "lib/graphql/schema/finder.rb".freeze, "lib/graphql/schema/input_object.rb".freeze, "lib/graphql/schema/interface.rb".freeze, "lib/graphql/schema/introspection_system.rb".freeze, "lib/graphql/schema/invalid_type_error.rb".freeze, "lib/graphql/schema/late_bound_type.rb".freeze, "lib/graphql/schema/list.rb".freeze, "lib/graphql/schema/loader.rb".freeze, "lib/graphql/schema/member.rb".freeze, "lib/graphql/schema/member/accepts_definition.rb".freeze, "lib/graphql/schema/member/base_dsl_methods.rb".freeze, "lib/graphql/schema/member/build_type.rb".freeze, "lib/graphql/schema/member/cached_graphql_definition.rb".freeze, "lib/graphql/schema/member/graphql_type_names.rb".freeze, "lib/graphql/schema/member/has_arguments.rb".freeze, "lib/graphql/schema/member/has_fields.rb".freeze, "lib/graphql/schema/member/has_path.rb".freeze, "lib/graphql/schema/member/instrumentation.rb".freeze, "lib/graphql/schema/member/relay_shortcuts.rb".freeze, "lib/graphql/schema/member/scoped.rb".freeze, "lib/graphql/schema/member/type_system_helpers.rb".freeze, "lib/graphql/schema/middleware_chain.rb".freeze, "lib/graphql/schema/mutation.rb".freeze, "lib/graphql/schema/non_null.rb".freeze, "lib/graphql/schema/null_mask.rb".freeze, "lib/graphql/schema/object.rb".freeze, "lib/graphql/schema/possible_types.rb".freeze, "lib/graphql/schema/printer.rb".freeze, "lib/graphql/schema/relay_classic_mutation.rb".freeze, "lib/graphql/schema/rescue_middleware.rb".freeze, "lib/graphql/schema/resolver.rb".freeze, "lib/graphql/schema/resolver/has_payload_type.rb".freeze, "lib/graphql/schema/scalar.rb".freeze, "lib/graphql/schema/subscription.rb".freeze, "lib/graphql/schema/timeout.rb".freeze, "lib/graphql/schema/timeout_middleware.rb".freeze, "lib/graphql/schema/traversal.rb".freeze, "lib/graphql/schema/type_expression.rb".freeze, "lib/graphql/schema/type_membership.rb".freeze, "lib/graphql/schema/union.rb".freeze, "lib/graphql/schema/unique_within_type.rb".freeze, "lib/graphql/schema/validation.rb".freeze, "lib/graphql/schema/warden.rb".freeze, "lib/graphql/schema/wrapper.rb".freeze, "lib/graphql/static_validation.rb".freeze, "lib/graphql/static_validation/all_rules.rb".freeze, "lib/graphql/static_validation/base_visitor.rb".freeze, "lib/graphql/static_validation/default_visitor.rb".freeze, "lib/graphql/static_validation/definition_dependencies.rb".freeze, "lib/graphql/static_validation/error.rb".freeze, "lib/graphql/static_validation/interpreter_visitor.rb".freeze, "lib/graphql/static_validation/literal_validator.rb".freeze, "lib/graphql/static_validation/no_validate_visitor.rb".freeze, "lib/graphql/static_validation/rules/argument_literals_are_compatible.rb".freeze, "lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb".freeze, "lib/graphql/static_validation/rules/argument_names_are_unique.rb".freeze, "lib/graphql/static_validation/rules/argument_names_are_unique_error.rb".freeze, "lib/graphql/static_validation/rules/arguments_are_defined.rb".freeze, "lib/graphql/static_validation/rules/arguments_are_defined_error.rb".freeze, "lib/graphql/static_validation/rules/directives_are_defined.rb".freeze, "lib/graphql/static_validation/rules/directives_are_defined_error.rb".freeze, "lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb".freeze, "lib/graphql/static_validation/rules/directives_are_in_valid_locations_error.rb".freeze, "lib/graphql/static_validation/rules/fields_are_defined_on_type.rb".freeze, "lib/graphql/static_validation/rules/fields_are_defined_on_type_error.rb".freeze, "lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb".freeze, "lib/graphql/static_validation/rules/fields_have_appropriate_selections_error.rb".freeze, "lib/graphql/static_validation/rules/fields_will_merge.rb".freeze, "lib/graphql/static_validation/rules/fields_will_merge_error.rb".freeze, "lib/graphql/static_validation/rules/fragment_names_are_unique.rb".freeze, "lib/graphql/static_validation/rules/fragment_names_are_unique_error.rb".freeze, "lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb".freeze, "lib/graphql/static_validation/rules/fragment_spreads_are_possible_error.rb".freeze, "lib/graphql/static_validation/rules/fragment_types_exist.rb".freeze, "lib/graphql/static_validation/rules/fragment_types_exist_error.rb".freeze, "lib/graphql/static_validation/rules/fragments_are_finite.rb".freeze, "lib/graphql/static_validation/rules/fragments_are_finite_error.rb".freeze, "lib/graphql/static_validation/rules/fragments_are_named.rb".freeze, "lib/graphql/static_validation/rules/fragments_are_named_error.rb".freeze, "lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb".freeze, "lib/graphql/static_validation/rules/fragments_are_on_composite_types_error.rb".freeze, "lib/graphql/static_validation/rules/fragments_are_used.rb".freeze, "lib/graphql/static_validation/rules/fragments_are_used_error.rb".freeze, "lib/graphql/static_validation/rules/mutation_root_exists.rb".freeze, "lib/graphql/static_validation/rules/mutation_root_exists_error.rb".freeze, "lib/graphql/static_validation/rules/no_definitions_are_present.rb".freeze, "lib/graphql/static_validation/rules/no_definitions_are_present_error.rb".freeze, "lib/graphql/static_validation/rules/operation_names_are_valid.rb".freeze, "lib/graphql/static_validation/rules/operation_names_are_valid_error.rb".freeze, "lib/graphql/static_validation/rules/required_arguments_are_present.rb".freeze, "lib/graphql/static_validation/rules/required_arguments_are_present_error.rb".freeze, "lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb".freeze, "lib/graphql/static_validation/rules/required_input_object_attributes_are_present_error.rb".freeze, "lib/graphql/static_validation/rules/subscription_root_exists.rb".freeze, "lib/graphql/static_validation/rules/subscription_root_exists_error.rb".freeze, "lib/graphql/static_validation/rules/unique_directives_per_location.rb".freeze, "lib/graphql/static_validation/rules/unique_directives_per_location_error.rb".freeze, "lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb".freeze, "lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed_error.rb".freeze, "lib/graphql/static_validation/rules/variable_names_are_unique.rb".freeze, "lib/graphql/static_validation/rules/variable_names_are_unique_error.rb".freeze, "lib/graphql/static_validation/rules/variable_usages_are_allowed.rb".freeze, "lib/graphql/static_validation/rules/variable_usages_are_allowed_error.rb".freeze, "lib/graphql/static_validation/rules/variables_are_input_types.rb".freeze, "lib/graphql/static_validation/rules/variables_are_input_types_error.rb".freeze, "lib/graphql/static_validation/rules/variables_are_used_and_defined.rb".freeze, "lib/graphql/static_validation/rules/variables_are_used_and_defined_error.rb".freeze, "lib/graphql/static_validation/type_stack.rb".freeze, "lib/graphql/static_validation/validation_context.rb".freeze, "lib/graphql/static_validation/validator.rb".freeze, "lib/graphql/string_encoding_error.rb".freeze, "lib/graphql/string_type.rb".freeze, "lib/graphql/subscriptions.rb".freeze, "lib/graphql/subscriptions/action_cable_subscriptions.rb".freeze, "lib/graphql/subscriptions/event.rb".freeze, "lib/graphql/subscriptions/instrumentation.rb".freeze, "lib/graphql/subscriptions/serialize.rb".freeze, "lib/graphql/subscriptions/subscription_root.rb".freeze, "lib/graphql/tracing.rb".freeze, "lib/graphql/tracing/active_support_notifications_tracing.rb".freeze, "lib/graphql/tracing/appsignal_tracing.rb".freeze, "lib/graphql/tracing/data_dog_tracing.rb".freeze, "lib/graphql/tracing/new_relic_tracing.rb".freeze, "lib/graphql/tracing/platform_tracing.rb".freeze, "lib/graphql/tracing/prometheus_tracing.rb".freeze, "lib/graphql/tracing/prometheus_tracing/graphql_collector.rb".freeze, "lib/graphql/tracing/scout_tracing.rb".freeze, "lib/graphql/tracing/skylight_tracing.rb".freeze, "lib/graphql/type_kinds.rb".freeze, "lib/graphql/types.rb".freeze, "lib/graphql/types/big_int.rb".freeze, "lib/graphql/types/boolean.rb".freeze, "lib/graphql/types/float.rb".freeze, "lib/graphql/types/id.rb".freeze, "lib/graphql/types/int.rb".freeze, "lib/graphql/types/iso_8601_date.rb".freeze, "lib/graphql/types/iso_8601_date_time.rb".freeze, "lib/graphql/types/json.rb".freeze, "lib/graphql/types/relay.rb".freeze, "lib/graphql/types/relay/base_connection.rb".freeze, "lib/graphql/types/relay/base_edge.rb".freeze, "lib/graphql/types/relay/base_field.rb".freeze, "lib/graphql/types/relay/base_interface.rb".freeze, "lib/graphql/types/relay/base_object.rb".freeze, "lib/graphql/types/relay/node.rb".freeze, "lib/graphql/types/relay/node_field.rb".freeze, "lib/graphql/types/relay/nodes_field.rb".freeze, "lib/graphql/types/relay/page_info.rb".freeze, "lib/graphql/types/string.rb".freeze, "lib/graphql/unauthorized_error.rb".freeze, "lib/graphql/unauthorized_field_error.rb".freeze, "lib/graphql/union_type.rb".freeze, "lib/graphql/unresolved_type_error.rb".freeze, "lib/graphql/upgrader/member.rb".freeze, "lib/graphql/upgrader/schema.rb".freeze, "lib/graphql/version.rb".freeze, "readme.md".freeze] s.homepage = "https://github.com/rmosolgo/graphql-ruby".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.2.0".freeze) s.rubygems_version = "2.7.6.2".freeze s.summary = "A GraphQL language and runtime for Ruby".freeze if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, ["~> 0.4"]) s.add_development_dependency(%q.freeze, ["~> 1.0"]) s.add_development_dependency(%q.freeze, ["~> 2.12"]) s.add_development_dependency(%q.freeze, ["~> 2.4"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, ["~> 3.0.0"]) s.add_development_dependency(%q.freeze, ["~> 1.5.0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, ["~> 5.9.0"]) s.add_development_dependency(%q.freeze, ["~> 1.1"]) s.add_development_dependency(%q.freeze, ["~> 1.0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, ["~> 1.4"]) s.add_development_dependency(%q.freeze, ["~> 12"]) s.add_development_dependency(%q.freeze, ["= 0.68"]) s.add_development_dependency(%q.freeze, [">= 0"]) else s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, ["~> 0.4"]) s.add_dependency(%q.freeze, ["~> 1.0"]) s.add_dependency(%q.freeze, ["~> 2.12"]) s.add_dependency(%q.freeze, ["~> 2.4"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, ["~> 3.0.0"]) s.add_dependency(%q.freeze, ["~> 1.5.0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, ["~> 5.9.0"]) s.add_dependency(%q.freeze, ["~> 1.1"]) s.add_dependency(%q.freeze, ["~> 1.0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, ["~> 1.4"]) s.add_dependency(%q.freeze, ["~> 12"]) s.add_dependency(%q.freeze, ["= 0.68"]) s.add_dependency(%q.freeze, [">= 0"]) end else s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, ["~> 0.4"]) s.add_dependency(%q.freeze, ["~> 1.0"]) s.add_dependency(%q.freeze, ["~> 2.12"]) s.add_dependency(%q.freeze, ["~> 2.4"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, ["~> 3.0.0"]) s.add_dependency(%q.freeze, ["~> 1.5.0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, ["~> 5.9.0"]) s.add_dependency(%q.freeze, ["~> 1.1"]) s.add_dependency(%q.freeze, ["~> 1.0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, ["~> 1.4"]) s.add_dependency(%q.freeze, ["~> 12"]) s.add_dependency(%q.freeze, ["= 0.68"]) s.add_dependency(%q.freeze, [">= 0"]) end end ruby-graphql-1.9.19/lib/000077500000000000000000000000001362601351000147735ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/generators/000077500000000000000000000000001362601351000171445ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/generators/graphql/000077500000000000000000000000001362601351000206025ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/generators/graphql/core.rb000066400000000000000000000030601362601351000220560ustar00rootroot00000000000000# frozen_string_literal: true require 'rails/generators/base' module Graphql module Generators module Core def self.included(base) base.send( :class_option, :directory, type: :string, default: "app/graphql", desc: "Directory where generated files should be saved" ) end def insert_root_type(type, name) log :add_root_type, type sentinel = /< GraphQL::Schema\s*\n/m in_root do inject_into_file schema_file_path, " #{type}(Types::#{name})\n", after: sentinel, verbose: false, force: false end end def create_mutation_root_type create_dir("#{options[:directory]}/mutations") template("base_mutation.erb", "#{options[:directory]}/mutations/base_mutation.rb", { skip: true }) template("mutation_type.erb", "#{options[:directory]}/types/mutation_type.rb", { skip: true }) insert_root_type('mutation', 'MutationType') end def schema_file_path "#{options[:directory]}/#{schema_name.underscore}.rb" end def create_dir(dir) empty_directory(dir) if !options[:skip_keeps] create_file("#{dir}/.keep") end end private def schema_name @schema_name ||= begin if options[:schema] options[:schema] else require File.expand_path("config/application", destination_root) "#{Rails.application.class.parent_name}Schema" end end end end end end ruby-graphql-1.9.19/lib/generators/graphql/enum_generator.rb000066400000000000000000000017011362601351000241400ustar00rootroot00000000000000# frozen_string_literal: true require 'generators/graphql/type_generator' module Graphql module Generators # Generate an enum type by name, with the given values. # To add a `value:` option, add another value after a `:`. # # ``` # rails g graphql:enum ProgrammingLanguage RUBY PYTHON PERL PERL6:"PERL" # ``` class EnumGenerator < TypeGeneratorBase desc "Create a GraphQL::EnumType with the given name and values" source_root File.expand_path('../templates', __FILE__) argument :values, type: :array, default: [], banner: "value{:ruby_value} value{:ruby_value} ...", desc: "Values for this enum (if present, ruby_value will be inserted verbatim)" def create_type_file template "enum.erb", "#{options[:directory]}/types/#{type_file_name}.rb" end private def prepared_values values.map { |v| v.split(":", 2) } end end end end ruby-graphql-1.9.19/lib/generators/graphql/install_generator.rb000066400000000000000000000110521362601351000246420ustar00rootroot00000000000000# frozen_string_literal: true require 'rails/generators/base' require_relative 'core' module Graphql module Generators # Add GraphQL to a Rails app with `rails g graphql:install`. # # Setup a folder structure for GraphQL: # # ``` # - app/ # - graphql/ # - resolvers/ # - types/ # - base_argument.rb # - base_field.rb # - base_enum.rb # - base_input_object.rb # - base_interface.rb # - base_object.rb # - base_scalar.rb # - base_union.rb # - query_type.rb # - loaders/ # - mutations/ # - base_mutation.rb # - {app_name}_schema.rb # ``` # # (Add `.gitkeep`s by default, support `--skip-keeps`) # # Add a controller for serving GraphQL queries: # # ``` # app/controllers/graphql_controller.rb # ``` # # Add a route for that controller: # # ```ruby # # config/routes.rb # post "/graphql", to: "graphql#execute" # ``` # # Accept a `--relay` option which adds # The root `node(id: ID!)` field. # # Accept a `--batch` option which adds `GraphQL::Batch` setup. # # Use `--no-graphiql` to skip `graphiql-rails` installation. # # TODO: also add base classes class InstallGenerator < Rails::Generators::Base include Core desc "Install GraphQL folder structure and boilerplate code" source_root File.expand_path('../templates', __FILE__) class_option :schema, type: :string, default: nil, desc: "Name for the schema constant (default: {app_name}Schema)" class_option :skip_keeps, type: :boolean, default: false, desc: "Skip .keep files for source control" class_option :skip_graphiql, type: :boolean, default: false, desc: "Skip graphiql-rails installation" class_option :skip_mutation_root_type, type: :boolean, default: false, desc: "Skip creation of the mutation root type" class_option :relay, type: :boolean, default: false, desc: "Include GraphQL::Relay installation" class_option :batch, type: :boolean, default: false, desc: "Include GraphQL::Batch installation" # These two options are taken from Rails' own generators' class_option :api, type: :boolean, desc: "Preconfigure smaller stack for API only apps" def create_folder_structure create_dir("#{options[:directory]}/types") template("schema.erb", schema_file_path) ["base_object", "base_argument", "base_field", "base_enum", "base_input_object", "base_interface", "base_scalar", "base_union"].each do |base_type| template("#{base_type}.erb", "#{options[:directory]}/types/#{base_type}.rb") end # Note: You can't have a schema without the query type, otherwise introspection breaks template("query_type.erb", "#{options[:directory]}/types/query_type.rb") insert_root_type('query', 'QueryType') create_mutation_root_type unless options.skip_mutation_root_type? template("graphql_controller.erb", "app/controllers/graphql_controller.rb") route('post "/graphql", to: "graphql#execute"') if options[:batch] gem("graphql-batch") create_dir("#{options[:directory]}/loaders") end if options.api? say("Skipped graphiql, as this rails project is API only") say(" You may wish to use GraphiQL.app for development: https://github.com/skevy/graphiql-app") elsif !options[:skip_graphiql] gem("graphiql-rails", group: :development) # This is a little cheat just to get cleaner shell output: log :route, 'graphiql-rails' shell.mute do # Rails 5.2 has better support for `route`? if Rails::VERSION::STRING > "5.2" route <<-RUBY if Rails.env.development? mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql" end RUBY else route <<-RUBY if Rails.env.development? mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql" end RUBY end end end if gemfile_modified? say "Gemfile has been modified, make sure you `bundle install`" end end private def gemfile_modified? @gemfile_modified end def gem(*args) @gemfile_modified = true super(*args) end end end end ruby-graphql-1.9.19/lib/generators/graphql/interface_generator.rb000066400000000000000000000014341362601351000251370ustar00rootroot00000000000000# frozen_string_literal: true require 'generators/graphql/type_generator' module Graphql module Generators # Generate an interface type by name, # with the specified fields. # # ``` # rails g graphql:interface NamedEntityType name:String! # ``` class InterfaceGenerator < TypeGeneratorBase desc "Create a GraphQL::InterfaceType with the given name and fields" source_root File.expand_path('../templates', __FILE__) argument :fields, type: :array, default: [], banner: "name:type name:type ...", desc: "Fields for this interface (type may be expressed as Ruby or GraphQL)" def create_type_file template "interface.erb", "#{options[:directory]}/types/#{type_file_name}.rb" end end end end ruby-graphql-1.9.19/lib/generators/graphql/loader_generator.rb000066400000000000000000000010621362601351000244420ustar00rootroot00000000000000# frozen_string_literal: true require "rails/generators/named_base" require_relative "core" module Graphql module Generators # @example Generate a `GraphQL::Batch` loader by name. # rails g graphql:loader RecordLoader class LoaderGenerator < Rails::Generators::NamedBase include Core desc "Create a GraphQL::Batch::Loader by name" source_root File.expand_path('../templates', __FILE__) def create_loader_file template "loader.erb", "#{options[:directory]}/loaders/#{file_path}.rb" end end end end ruby-graphql-1.9.19/lib/generators/graphql/mutation_generator.rb000066400000000000000000000033141362601351000250360ustar00rootroot00000000000000# frozen_string_literal: true require 'rails/generators/named_base' require_relative 'core' module Graphql module Generators # TODO: What other options should be supported? # # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name # rails g graphql:mutation CreatePostMutation class MutationGenerator < Rails::Generators::Base include Core desc "Create a Relay Classic mutation by name" source_root File.expand_path('../templates', __FILE__) argument :name, type: :string def initialize(args, *options) #:nodoc: # Unfreeze name in case it's given as a frozen string args[0] = args[0].dup if args[0].is_a?(String) && args[0].frozen? super assign_names!(name) end attr_reader :file_name, :mutation_name, :field_name def create_mutation_file unless @behavior == :revoke create_mutation_root_type else log :gsub, "#{options[:directory]}/types/mutation_type.rb" end template "mutation.erb", "#{options[:directory]}/mutations/#{file_name}.rb" sentinel = /class .*MutationType\s*<\s*[^\s]+?\n/m in_root do gsub_file "#{options[:directory]}/types/mutation_type.rb", / \# TODO\: Add Mutations as fields\s*\n/m, "" inject_into_file "#{options[:directory]}/types/mutation_type.rb", " field :#{field_name}, mutation: Mutations::#{mutation_name}\n", after: sentinel, verbose: false, force: false end end private def assign_names!(name) @field_name = name.camelize.underscore @mutation_name = name.camelize(:upper) @file_name = name.camelize.underscore end end end end ruby-graphql-1.9.19/lib/generators/graphql/object_generator.rb000066400000000000000000000016611362601351000244470ustar00rootroot00000000000000# frozen_string_literal: true require 'generators/graphql/type_generator' module Graphql module Generators # Generate an object type by name, # with the specified fields. # # ``` # rails g graphql:object PostType name:String! # ``` # # Add the Node interface with `--node`. class ObjectGenerator < TypeGeneratorBase desc "Create a GraphQL::ObjectType with the given name and fields" source_root File.expand_path('../templates', __FILE__) argument :fields, type: :array, default: [], banner: "name:type name:type ...", desc: "Fields for this object (type may be expressed as Ruby or GraphQL)" class_option :node, type: :boolean, default: false, desc: "Include the Relay Node interface" def create_type_file template "object.erb", "#{options[:directory]}/types/#{type_file_name}.rb" end end end end ruby-graphql-1.9.19/lib/generators/graphql/scalar_generator.rb000066400000000000000000000010121362601351000244340ustar00rootroot00000000000000# frozen_string_literal: true require 'generators/graphql/type_generator' module Graphql module Generators # Generate a scalar type by given name. # # ``` # rails g graphql:scalar Date # ``` class ScalarGenerator < TypeGeneratorBase desc "Create a GraphQL::ScalarType with the given name" source_root File.expand_path('../templates', __FILE__) def create_type_file template "scalar.erb", "#{options[:directory]}/types/#{type_file_name}.rb" end end end end ruby-graphql-1.9.19/lib/generators/graphql/templates/000077500000000000000000000000001362601351000226005ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/generators/graphql/templates/base_argument.erb000066400000000000000000000001101362601351000260760ustar00rootroot00000000000000module Types class BaseArgument < GraphQL::Schema::Argument end end ruby-graphql-1.9.19/lib/generators/graphql/templates/base_enum.erb000066400000000000000000000001001362601351000252170ustar00rootroot00000000000000module Types class BaseEnum < GraphQL::Schema::Enum end end ruby-graphql-1.9.19/lib/generators/graphql/templates/base_field.erb000066400000000000000000000001511362601351000253440ustar00rootroot00000000000000module Types class BaseField < GraphQL::Schema::Field argument_class Types::BaseArgument end end ruby-graphql-1.9.19/lib/generators/graphql/templates/base_input_object.erb000066400000000000000000000001651362601351000267530ustar00rootroot00000000000000module Types class BaseInputObject < GraphQL::Schema::InputObject argument_class Types::BaseArgument end end ruby-graphql-1.9.19/lib/generators/graphql/templates/base_interface.erb000066400000000000000000000001671362601351000262300ustar00rootroot00000000000000module Types module BaseInterface include GraphQL::Schema::Interface field_class Types::BaseField end end ruby-graphql-1.9.19/lib/generators/graphql/templates/base_mutation.erb000066400000000000000000000003611362601351000261240ustar00rootroot00000000000000module Mutations class BaseMutation < GraphQL::Schema::RelayClassicMutation argument_class Types::BaseArgument field_class Types::BaseField input_object_class Types::BaseInputObject object_class Types::BaseObject end end ruby-graphql-1.9.19/lib/generators/graphql/templates/base_object.erb000066400000000000000000000001451362601351000255320ustar00rootroot00000000000000module Types class BaseObject < GraphQL::Schema::Object field_class Types::BaseField end end ruby-graphql-1.9.19/lib/generators/graphql/templates/base_scalar.erb000066400000000000000000000001041362601351000255240ustar00rootroot00000000000000module Types class BaseScalar < GraphQL::Schema::Scalar end end ruby-graphql-1.9.19/lib/generators/graphql/templates/base_union.erb000066400000000000000000000001021362601351000254050ustar00rootroot00000000000000module Types class BaseUnion < GraphQL::Schema::Union end end ruby-graphql-1.9.19/lib/generators/graphql/templates/enum.erb000066400000000000000000000003061362601351000242350ustar00rootroot00000000000000module Types class <%= type_ruby_name.split('::')[-1] %> < Types::BaseEnum <% prepared_values.each do |v| %> value "<%= v[0] %>"<%= v.length > 1 ? ", value: #{v[1]}" : "" %> <% end %> end end ruby-graphql-1.9.19/lib/generators/graphql/templates/graphql_controller.erb000066400000000000000000000026001362601351000271710ustar00rootroot00000000000000class GraphqlController < ApplicationController # If accessing from outside this domain, nullify the session # This allows for outside API access while preventing CSRF attacks, # but you'll have to authenticate your user separately # protect_from_forgery with: :null_session def execute variables = ensure_hash(params[:variables]) query = params[:query] operation_name = params[:operationName] context = { # Query context goes here, for example: # current_user: current_user, } result = <%= schema_name %>.execute(query, variables: variables, context: context, operation_name: operation_name) render json: result rescue => e raise e unless Rails.env.development? handle_error_in_development e end private # Handle form data, JSON body, or a blank value def ensure_hash(ambiguous_param) case ambiguous_param when String if ambiguous_param.present? ensure_hash(JSON.parse(ambiguous_param)) else {} end when Hash, ActionController::Parameters ambiguous_param when nil {} else raise ArgumentError, "Unexpected parameter: #{ambiguous_param}" end end def handle_error_in_development(e) logger.error e.message logger.error e.backtrace.join("\n") render json: { error: { message: e.message, backtrace: e.backtrace }, data: {} }, status: 500 end end ruby-graphql-1.9.19/lib/generators/graphql/templates/interface.erb000066400000000000000000000002501362601351000252270ustar00rootroot00000000000000module Types module <%= type_ruby_name.split('::')[-1] %> include Types::BaseInterface <% normalized_fields.each do |f| %> <%= f.to_ruby %> <% end %> end end ruby-graphql-1.9.19/lib/generators/graphql/templates/loader.erb000066400000000000000000000006661362601351000245500ustar00rootroot00000000000000module Loaders class <%= class_name %> < GraphQL::Batch::Loader # Define `initialize` to store grouping arguments, eg # # Loaders::<%= class_name %>.for(group).load(value) # # def initialize() # end # `keys` contains each key from `.load(key)`. # Find the corresponding values, then # call `fulfill(key, value)` or `fulfill(key, nil)` # for each key. def perform(keys) end end end ruby-graphql-1.9.19/lib/generators/graphql/templates/mutation.erb000066400000000000000000000004771362601351000251420ustar00rootroot00000000000000module Mutations class <%= mutation_name %> < BaseMutation # TODO: define return fields # field :post, Types::PostType, null: false # TODO: define arguments # argument :name, String, required: true # TODO: define resolve method # def resolve(name:) # { post: ... } # end end end ruby-graphql-1.9.19/lib/generators/graphql/templates/mutation_type.erb000066400000000000000000000003561362601351000261770ustar00rootroot00000000000000module Types class MutationType < Types::BaseObject # TODO: remove me field :test_field, String, null: false, description: "An example field added by the generator" def test_field "Hello World" end end end ruby-graphql-1.9.19/lib/generators/graphql/templates/object.erb000066400000000000000000000003461362601351000245430ustar00rootroot00000000000000module Types class <%= type_ruby_name.split('::')[-1] %> < Types::BaseObject <% if options.node %> implements GraphQL::Relay::Node.interface <% end %><% normalized_fields.each do |f| %> <%= f.to_ruby %> <% end %> end end ruby-graphql-1.9.19/lib/generators/graphql/templates/query_type.erb000066400000000000000000000006401362601351000255000ustar00rootroot00000000000000module Types class QueryType < Types::BaseObject # Add root-level fields here. # They will be entry points for queries on your schema. # TODO: remove me field :test_field, String, null: false, description: "An example field added by the generator" def test_field "Hello World!" end <% if options[:relay] %> field :node, field: GraphQL::Relay::Node.field <% end %> end end ruby-graphql-1.9.19/lib/generators/graphql/templates/scalar.erb000066400000000000000000000006171362601351000245430ustar00rootroot00000000000000module Types class <%= type_ruby_name.split('::')[-1] %> < Types::BaseScalar def self.coerce_input(input_value, context) # Override this to prepare a client-provided GraphQL value for your Ruby code input_value end def self.coerce_result(ruby_value, context) # Override this to serialize a Ruby value for the GraphQL response ruby_value.to_s end end end ruby-graphql-1.9.19/lib/generators/graphql/templates/schema.erb000066400000000000000000000017711362601351000245400ustar00rootroot00000000000000class <%= schema_name %> < GraphQL::Schema <% if options[:relay] %> # Relay Object Identification: # Return a string UUID for `object` def self.id_from_object(object, type_definition, query_ctx) # Here's a simple implementation which: # - joins the type name & object.id # - encodes it with base64: # GraphQL::Schema::UniqueWithinType.encode(type_definition.name, object.id) end # Given a string UUID, find the object def self.object_from_id(id, query_ctx) # For example, to decode the UUIDs generated above: # type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id) # # Then, based on `type_name` and `id` # find an object in your application # ... end # Object Resolution def self.resolve_type(type, obj, ctx) # TODO: Implement this function # to return the correct type for `obj` raise(GraphQL::RequiredImplementationMissingError) end <% end %><% if options[:batch] %> # GraphQL::Batch setup: use GraphQL::Batch <% end %>end ruby-graphql-1.9.19/lib/generators/graphql/templates/union.erb000066400000000000000000000002761362601351000244270ustar00rootroot00000000000000module Types class <%= type_ruby_name.split('::')[-1] %> < Types::BaseUnion <% if possible_types.any? %> possible_types [<%= normalized_possible_types.join(", ") %>] <% end %> end end ruby-graphql-1.9.19/lib/generators/graphql/type_generator.rb000066400000000000000000000070141362601351000241600ustar00rootroot00000000000000# frozen_string_literal: true require 'rails/generators/base' require 'graphql' require 'active_support' require 'active_support/core_ext/string/inflections' require_relative 'core' module Graphql module Generators class TypeGeneratorBase < Rails::Generators::Base include Core argument :type_name, type: :string, required: true, banner: "TypeName", desc: "Name of this object type (expressed as Ruby or GraphQL)" # Take a type expression in any combination of GraphQL or Ruby styles # and return it in a specified output style # TODO: nullability / list with `mode: :graphql` doesn't work # @param type_expresson [String] # @param mode [Symbol] # @param null [Boolean] # @return [(String, Boolean)] The type expression, followed by `null:` value def self.normalize_type_expression(type_expression, mode:, null: true) if type_expression.start_with?("!") normalize_type_expression(type_expression[1..-1], mode: mode, null: false) elsif type_expression.end_with?("!") normalize_type_expression(type_expression[0..-2], mode: mode, null: false) elsif type_expression.start_with?("[") && type_expression.end_with?("]") name, is_null = normalize_type_expression(type_expression[1..-2], mode: mode, null: null) ["[#{name}]", is_null] elsif type_expression.end_with?("Type") normalize_type_expression(type_expression[0..-5], mode: mode, null: null) elsif type_expression.start_with?("Types::") normalize_type_expression(type_expression[7..-1], mode: mode, null: null) elsif type_expression.start_with?("types.") normalize_type_expression(type_expression[6..-1], mode: mode, null: null) else case mode when :ruby case type_expression when "Int" ["Integer", null] when "Integer", "Float", "Boolean", "String", "ID" [type_expression, null] else ["Types::#{type_expression.camelize}Type", null] end when :graphql [type_expression.camelize, null] else raise "Unexpected normalize mode: #{mode}" end end end private # @return [String] The user-provided type name, normalized to Ruby code def type_ruby_name @type_ruby_name ||= self.class.normalize_type_expression(type_name, mode: :ruby)[0] end # @return [String] The user-provided type name, as a GraphQL name def type_graphql_name @type_graphql_name ||= self.class.normalize_type_expression(type_name, mode: :graphql)[0] end # @return [String] The user-provided type name, as a file name (without extension) def type_file_name @type_file_name ||= "#{type_graphql_name}Type".underscore end # @return [Array] User-provided fields, in `(name, Ruby type name)` pairs def normalized_fields @normalized_fields ||= fields.map { |f| name, raw_type = f.split(":", 2) type_expr, null = self.class.normalize_type_expression(raw_type, mode: :ruby) NormalizedField.new(name, type_expr, null) } end class NormalizedField def initialize(name, type_expr, null) @name = name @type_expr = type_expr @null = null end def to_ruby "field :#{@name}, #{@type_expr}, null: #{@null}" end end end end end ruby-graphql-1.9.19/lib/generators/graphql/union_generator.rb000066400000000000000000000016531362601351000243320ustar00rootroot00000000000000# frozen_string_literal: true require 'generators/graphql/type_generator' module Graphql module Generators # Generate a union type by name # with the specified member types. # # ``` # rails g graphql:union SearchResultType ImageType AudioType # ``` class UnionGenerator < TypeGeneratorBase desc "Create a GraphQL::UnionType with the given name and possible types" source_root File.expand_path('../templates', __FILE__) argument :possible_types, type: :array, default: [], banner: "type type ...", desc: "Possible types for this union (expressed as Ruby or GraphQL)" def create_type_file template "union.erb", "#{options[:directory]}/types/#{type_file_name}.rb" end private def normalized_possible_types possible_types.map { |t| self.class.normalize_type_expression(t, mode: :ruby)[0] } end end end end ruby-graphql-1.9.19/lib/graphql.rb000066400000000000000000000071021362601351000167560ustar00rootroot00000000000000# frozen_string_literal: true require "delegate" require "json" require "set" require "singleton" require "forwardable" require_relative "./graphql/railtie" if defined? Rails::Railtie module GraphQL # forwards-compat for argument handling module Ruby2Keywords if RUBY_VERSION < "2.7" def ruby2_keywords(*) end end end class Error < StandardError end class RequiredImplementationMissingError < Error end # Turn a query string or schema definition into an AST # @param graphql_string [String] a GraphQL query string or schema definition # @return [GraphQL::Language::Nodes::Document] def self.parse(graphql_string, tracer: GraphQL::Tracing::NullTracer) parse_with_racc(graphql_string, tracer: tracer) end # Read the contents of `filename` and parse them as GraphQL # @param filename [String] Path to a `.graphql` file containing IDL or query # @return [GraphQL::Language::Nodes::Document] def self.parse_file(filename) content = File.read(filename) parse_with_racc(content, filename: filename) end def self.parse_with_racc(string, filename: nil, tracer: GraphQL::Tracing::NullTracer) GraphQL::Language::Parser.parse(string, filename: filename, tracer: tracer) end # @return [Array] def self.scan(graphql_string) scan_with_ragel(graphql_string) end def self.scan_with_ragel(graphql_string) GraphQL::Language::Lexer.tokenize(graphql_string) end # Support Ruby 2.2 by implementing `-"str"`. If we drop 2.2 support, we can remove this backport. module StringDedupBackport refine String do def -@ if frozen? self else self.dup.freeze end end end end end # Order matters for these: require "graphql/execution_error" require "graphql/define" require "graphql/base_type" require "graphql/object_type" require "graphql/enum_type" require "graphql/input_object_type" require "graphql/interface_type" require "graphql/list_type" require "graphql/non_null_type" require "graphql/union_type" require "graphql/argument" require "graphql/field" require "graphql/type_kinds" require "graphql/backwards_compatibility" require "graphql/scalar_type" require "graphql/name_validator" require "graphql/language" require "graphql/analysis" require "graphql/tracing" require "graphql/execution" require "graphql/dig" require "graphql/schema" require "graphql/directive" require "graphql/execution" require "graphql/types" require "graphql/relay" require "graphql/boolean_type" require "graphql/float_type" require "graphql/id_type" require "graphql/int_type" require "graphql/string_type" require "graphql/schema/built_in_types" require "graphql/schema/loader" require "graphql/schema/printer" require "graphql/introspection" require "graphql/analysis_error" require "graphql/coercion_error" require "graphql/literal_validation_error" require "graphql/runtime_type_error" require "graphql/invalid_null_error" require "graphql/invalid_name_error" require "graphql/unresolved_type_error" require "graphql/integer_encoding_error" require "graphql/string_encoding_error" require "graphql/query" require "graphql/internal_representation" require "graphql/static_validation" require "graphql/version" require "graphql/compatibility" require "graphql/function" require "graphql/filter" require "graphql/subscriptions" require "graphql/parse_error" require "graphql/backtrace" require "graphql/deprecated_dsl" require "graphql/authorization" require "graphql/unauthorized_error" require "graphql/unauthorized_field_error" require "graphql/load_application_object_failed_error" ruby-graphql-1.9.19/lib/graphql/000077500000000000000000000000001362601351000164315ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/analysis.rb000066400000000000000000000005441362601351000206040ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/analysis/ast" require "graphql/analysis/max_query_complexity" require "graphql/analysis/max_query_depth" require "graphql/analysis/query_complexity" require "graphql/analysis/query_depth" require "graphql/analysis/reducer_state" require "graphql/analysis/analyze_query" require "graphql/analysis/field_usage" ruby-graphql-1.9.19/lib/graphql/analysis/000077500000000000000000000000001362601351000202545ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/analysis/analyze_query.rb000066400000000000000000000060321362601351000234720ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis module_function # @return [void] def analyze_multiplex(multiplex, analyzers) multiplex.trace("analyze_multiplex", { multiplex: multiplex }) do reducer_states = analyzers.map { |r| ReducerState.new(r, multiplex) } query_results = multiplex.queries.map do |query| if query.valid? analyze_query(query, query.analyzers, multiplex_states: reducer_states) else [] end end multiplex_results = reducer_states.map(&:finalize_reducer) multiplex_errors = analysis_errors(multiplex_results) multiplex.queries.each_with_index do |query, idx| query.analysis_errors = multiplex_errors + analysis_errors(query_results[idx]) end end nil end # Visit `query`'s internal representation, calling `analyzers` along the way. # # - First, query analyzers are filtered down by calling `.analyze?(query)`, if they respond to that method # - Then, query analyzers are initialized by calling `.initial_value(query)`, if they respond to that method. # - Then, they receive `.call(memo, visit_type, irep_node)`, where visit type is `:enter` or `:leave`. # - Last, they receive `.final_value(memo)`, if they respond to that method. # # It returns an array of final `memo` values in the order that `analyzers` were passed in. # # @param query [GraphQL::Query] # @param analyzers [Array<#call>] Objects that respond to `#call(memo, visit_type, irep_node)` # @return [Array] Results from those analyzers def analyze_query(query, analyzers, multiplex_states: []) query.trace("analyze_query", { query: query }) do analyzers_to_run = analyzers.select do |analyzer| if analyzer.respond_to?(:analyze?) analyzer.analyze?(query) else true end end reducer_states = analyzers_to_run.map { |r| ReducerState.new(r, query) } + multiplex_states irep = query.internal_representation irep.operation_definitions.each do |name, op_node| reduce_node(op_node, reducer_states) end reducer_states.map(&:finalize_reducer) end end private module_function # Enter the node, visit its children, then leave the node. def reduce_node(irep_node, reducer_states) visit_analyzers(:enter, irep_node, reducer_states) irep_node.typed_children.each do |type_defn, children| children.each do |name, child_irep_node| reduce_node(child_irep_node, reducer_states) end end visit_analyzers(:leave, irep_node, reducer_states) end def visit_analyzers(visit_type, irep_node, reducer_states) reducer_states.each do |reducer_state| next_memo = reducer_state.call(visit_type, irep_node) reducer_state.memo = next_memo end end def analysis_errors(results) results.flatten.select { |r| r.is_a?(GraphQL::AnalysisError) } end end end ruby-graphql-1.9.19/lib/graphql/analysis/ast.rb000066400000000000000000000052301362601351000213700ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/analysis/ast/visitor" require "graphql/analysis/ast/analyzer" require "graphql/analysis/ast/field_usage" require "graphql/analysis/ast/query_complexity" require "graphql/analysis/ast/max_query_complexity" require "graphql/analysis/ast/query_depth" require "graphql/analysis/ast/max_query_depth" module GraphQL module Analysis module AST module_function def use(schema_defn) schema = schema_defn.target schema.analysis_engine = GraphQL::Analysis::AST end # Analyze a multiplex, and all queries within. # Multiplex analyzers are ran for all queries, keeping state. # Query analyzers are ran per query, without carrying state between queries. # # @param multiplex [GraphQL::Execution::Multiplex] # @param analyzers [Array] # @return [Array] Results from multiplex analyzers def analyze_multiplex(multiplex, analyzers) multiplex_analyzers = analyzers.map { |analyzer| analyzer.new(multiplex) } multiplex.trace("analyze_multiplex", { multiplex: multiplex }) do query_results = multiplex.queries.map do |query| if query.valid? analyze_query( query, query.analyzers, multiplex_analyzers: multiplex_analyzers ) else [] end end multiplex_results = multiplex_analyzers.map(&:result) multiplex_errors = analysis_errors(multiplex_results) multiplex.queries.each_with_index do |query, idx| query.analysis_errors = multiplex_errors + analysis_errors(query_results[idx]) end multiplex_results end end # @param query [GraphQL::Query] # @param analyzers [Array] # @return [Array] Results from those analyzers def analyze_query(query, analyzers, multiplex_analyzers: []) query.trace("analyze_query", { query: query }) do query_analyzers = analyzers .map { |analyzer| analyzer.new(query) } .select { |analyzer| analyzer.analyze? } analyzers_to_run = query_analyzers + multiplex_analyzers return [] unless analyzers_to_run.any? visitor = GraphQL::Analysis::AST::Visitor.new( query: query, analyzers: analyzers_to_run ) visitor.visit query_analyzers.map(&:result) end end def analysis_errors(results) results.flatten.select { |r| r.is_a?(GraphQL::AnalysisError) } end end end end ruby-graphql-1.9.19/lib/graphql/analysis/ast/000077500000000000000000000000001362601351000210435ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/analysis/ast/analyzer.rb000066400000000000000000000053211362601351000232160ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis module AST # Query analyzer for query ASTs. Query analyzers respond to visitor style methods # but are prefixed by `enter` and `leave`. # # When an analyzer is initialized with a Multiplex, you can always get the current query from # `visitor.query` in the visit methods. # # @param [GraphQL::Query, GraphQL::Execution::Multiplex] The query or multiplex to analyze class Analyzer def initialize(subject) @subject = subject if subject.is_a?(GraphQL::Query) @query = subject @multiplex = nil else @multiplex = subject @query = nil end end # Analyzer hook to decide at analysis time whether a query should # be analyzed or not. # @return [Boolean] If the query should be analyzed or not def analyze? true end # The result for this analyzer. Returning {GraphQL::AnalysisError} results # in a query error. # @return [Any] The analyzer result def result raise GraphQL::RequiredImplementationMissingError end class << self private def build_visitor_hooks(member_name) class_eval(<<-EOS, __FILE__, __LINE__ + 1) def on_enter_#{member_name}(node, parent, visitor) end def on_leave_#{member_name}(node, parent, visitor) end EOS end end build_visitor_hooks :argument build_visitor_hooks :directive build_visitor_hooks :document build_visitor_hooks :enum build_visitor_hooks :field build_visitor_hooks :fragment_spread build_visitor_hooks :inline_fragment build_visitor_hooks :input_object build_visitor_hooks :list_type build_visitor_hooks :non_null_type build_visitor_hooks :null_value build_visitor_hooks :operation_definition build_visitor_hooks :type_name build_visitor_hooks :variable_definition build_visitor_hooks :variable_identifier build_visitor_hooks :abstract_node protected # @return [GraphQL::Query, GraphQL::Execution::Multiplex] Whatever this analyzer is analyzing attr_reader :subject # @return [GraphQL::Query, nil] `nil` if this analyzer is visiting a multiplex # (When this is `nil`, use `visitor.query` inside visit methods to get the current query) attr_reader :query # @return [GraphQL::Execution::Multiplex, nil] `nil` if this analyzer is visiting a query attr_reader :multiplex end end end end ruby-graphql-1.9.19/lib/graphql/analysis/ast/field_usage.rb000066400000000000000000000013411362601351000236360ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis module AST class FieldUsage < Analyzer def initialize(query) super @used_fields = Set.new @used_deprecated_fields = Set.new end def on_leave_field(node, parent, visitor) field_defn = visitor.field_definition field = "#{visitor.parent_type_definition.name}.#{field_defn.name}" @used_fields << field @used_deprecated_fields << field if field_defn.deprecation_reason end def result { used_fields: @used_fields.to_a, used_deprecated_fields: @used_deprecated_fields.to_a } end end end end end ruby-graphql-1.9.19/lib/graphql/analysis/ast/max_query_complexity.rb000066400000000000000000000012731362601351000256620ustar00rootroot00000000000000# frozen_string_literal: true require_relative "./query_complexity" module GraphQL module Analysis module AST # Used under the hood to implement complexity validation, # see {Schema#max_complexity} and {Query#max_complexity} class MaxQueryComplexity < QueryComplexity def result return if subject.max_complexity.nil? total_complexity = max_possible_complexity if total_complexity > subject.max_complexity GraphQL::AnalysisError.new("Query has complexity of #{total_complexity}, which exceeds max complexity of #{subject.max_complexity}") else nil end end end end end end ruby-graphql-1.9.19/lib/graphql/analysis/ast/max_query_depth.rb000066400000000000000000000010561362601351000245700ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis module AST class MaxQueryDepth < QueryDepth def result configured_max_depth = if query query.max_depth else multiplex.schema.max_depth end if configured_max_depth && @max_depth > configured_max_depth GraphQL::AnalysisError.new("Query has depth of #{@max_depth}, which exceeds max depth of #{configured_max_depth}") else nil end end end end end end ruby-graphql-1.9.19/lib/graphql/analysis/ast/query_complexity.rb000066400000000000000000000105061362601351000250140ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis # Calculate the complexity of a query, using {Field#complexity} values. module AST class QueryComplexity < Analyzer # State for the query complexity calculation: # - `complexities_on_type` holds complexity scores for each type in an IRep node def initialize(query) super @complexities_on_type = [ConcreteTypeComplexity.new] end # Overide this method to use the complexity result def result max_possible_complexity end def on_enter_field(node, parent, visitor) # We don't want to visit fragment definitions, # we'll visit them when we hit the spreads instead return if visitor.visiting_fragment_definition? return if visitor.skipping? if visitor.type_definition.kind.abstract? @complexities_on_type.push(AbstractTypeComplexity.new) else @complexities_on_type.push(ConcreteTypeComplexity.new) end end def on_leave_field(node, parent, visitor) # We don't want to visit fragment definitions, # we'll visit them when we hit the spreads instead return if visitor.visiting_fragment_definition? return if visitor.skipping? type_complexities = @complexities_on_type.pop child_complexity = type_complexities.max_possible_complexity own_complexity = get_complexity(node, visitor.field_definition, child_complexity, visitor) if @complexities_on_type.last.is_a?(AbstractTypeComplexity) key = selection_key(visitor.response_path, visitor.query) parent_type = visitor.parent_type_definition visitor.query.possible_types(parent_type).each do |type| @complexities_on_type.last.merge(type, key, own_complexity) end else @complexities_on_type.last.merge(own_complexity) end end # @return [Integer] def max_possible_complexity @complexities_on_type.last.max_possible_complexity end private def selection_key(response_path, query) # We add the query object id to support multiplex queries # even if they have the same response path, they should # always be added. "#{response_path.join(".")}-#{query.object_id}" end # Get a complexity value for a field, # by getting the number or calling its proc def get_complexity(ast_node, field_defn, child_complexity, visitor) # Return if we've visited this response path before (not counting duplicates) defined_complexity = field_defn.complexity arguments = visitor.arguments_for(ast_node, field_defn) case defined_complexity when Proc defined_complexity.call(visitor.query.context, arguments, child_complexity) when Numeric defined_complexity + (child_complexity || 0) else raise("Invalid complexity: #{defined_complexity.inspect} on #{field_defn.name}") end end # Selections on an object may apply differently depending on what is _actually_ returned by the resolve function. # Find the maximum possible complexity among those combinations. class AbstractTypeComplexity def initialize @types = Hash.new { |h, k| h[k] = {} } end # Return the max possible complexity for types in this selection def max_possible_complexity max = 0 @types.each_value do |fields| complexity = fields.each_value.inject(:+) max = complexity if complexity > max end max end # Store the complexity for the branch on `type_defn`. # Later we will see if this is the max complexity among branches. def merge(type_defn, key, complexity) @types[type_defn][key] = complexity end end class ConcreteTypeComplexity attr_reader :max_possible_complexity def initialize @max_possible_complexity = 0 end def merge(complexity) @max_possible_complexity += complexity end end end end end end ruby-graphql-1.9.19/lib/graphql/analysis/ast/query_depth.rb000066400000000000000000000026211362601351000237220ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis # A query reducer for measuring the depth of a given query. # # See https://graphql-ruby.org/queries/ast_analysis.html for more examples. # # @example Logging the depth of a query # class LogQueryDepth < GraphQL::Analysis::QueryDepth # def result # log("GraphQL query depth: #{@max_depth}") # end # end # # # In your Schema file: # # class MySchema < GraphQL::Schema # use GraphQL::Analysis::AST # query_analyzer LogQueryDepth # end # # # When you run the query, the depth will get logged: # # Schema.execute(query_str) # # GraphQL query depth: 8 # module AST class QueryDepth < Analyzer def initialize(query) @max_depth = 0 @current_depth = 0 super end def on_enter_field(node, parent, visitor) return if visitor.skipping? || visitor.visiting_fragment_definition? @current_depth += 1 end def on_leave_field(node, parent, visitor) return if visitor.skipping? || visitor.visiting_fragment_definition? if @max_depth < @current_depth @max_depth = @current_depth end @current_depth -= 1 end def result @max_depth end end end end end ruby-graphql-1.9.19/lib/graphql/analysis/ast/visitor.rb000066400000000000000000000206011362601351000230660ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis module AST # Depth first traversal through a query AST, calling AST analyzers # along the way. # # The visitor is a special case of GraphQL::Language::Visitor, visiting # only the selected operation, providing helpers for common use cases such # as skipped fields and visiting fragment spreads. # # @see {GraphQL::Analysis::AST::Analyzer} AST Analyzers for queries class Visitor < GraphQL::Language::Visitor def initialize(query:, analyzers:) @analyzers = analyzers @path = [] @object_types = [] @directives = [] @field_definitions = [] @argument_definitions = [] @directive_definitions = [] @query = query @schema = query.schema @response_path = [] @skip_stack = [false] super(query.selected_operation) end # @return [GraphQL::Query] the query being visited attr_reader :query # @return [Array] Types whose scope we've entered attr_reader :object_types def visit return unless @document super end # Visit Helpers # @return [GraphQL::Query::Arguments] Arguments for this node, merging default values, literal values and query variables # @see {GraphQL::Query#arguments_for} def arguments_for(ast_node, field_definition) @query.arguments_for(ast_node, field_definition) end # @return [Boolean] If the visitor is currently inside a fragment definition def visiting_fragment_definition? @in_fragment_def end # @return [Boolean] If the current node should be skipped because of a skip or include directive def skipping? @skipping end # @return [Array] The path to the response key for the current field def response_path @response_path.dup end # Visitor Hooks def on_operation_definition(node, parent) object_type = @schema.root_type_for_operation(node.operation_type) @object_types.push(object_type) @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}") call_analyzers(:on_enter_operation_definition, node, parent) super call_analyzers(:on_leave_operation_definition, node, parent) @object_types.pop @path.pop end def on_fragment_definition(node, parent) on_fragment_with_type(node) do @path.push("fragment #{node.name}") @in_fragment_def = false call_analyzers(:on_enter_fragment_definition, node, parent) super @in_fragment_def = false call_analyzers(:on_leave_fragment_definition, node, parent) end end def on_inline_fragment(node, parent) on_fragment_with_type(node) do @path.push("...#{node.type ? " on #{node.type.name}" : ""}") call_analyzers(:on_enter_inline_fragment, node, parent) super call_analyzers(:on_leave_inline_fragment, node, parent) end end def on_field(node, parent) @response_path.push(node.alias || node.name) parent_type = @object_types.last field_definition = @schema.get_field(parent_type, node.name) @field_definitions.push(field_definition) if !field_definition.nil? next_object_type = field_definition.type.unwrap @object_types.push(next_object_type) else @object_types.push(nil) end @path.push(node.alias || node.name) @skipping = @skip_stack.last || skip?(node) @skip_stack << @skipping call_analyzers(:on_enter_field, node, parent) super @skipping = @skip_stack.pop call_analyzers(:on_leave_field, node, parent) @response_path.pop @field_definitions.pop @object_types.pop @path.pop end def on_directive(node, parent) directive_defn = @schema.directives[node.name] @directive_definitions.push(directive_defn) call_analyzers(:on_enter_directive, node, parent) super call_analyzers(:on_leave_directive, node, parent) @directive_definitions.pop end def on_argument(node, parent) argument_defn = if (arg = @argument_definitions.last) arg_type = arg.type.unwrap if arg_type.kind.input_object? arg_type.input_fields[node.name] else nil end elsif (directive_defn = @directive_definitions.last) directive_defn.arguments[node.name] elsif (field_defn = @field_definitions.last) field_defn.arguments[node.name] else nil end @argument_definitions.push(argument_defn) @path.push(node.name) call_analyzers(:on_enter_argument, node, parent) super call_analyzers(:on_leave_argument, node, parent) @argument_definitions.pop @path.pop end def on_fragment_spread(node, parent) @path.push("... #{node.name}") call_analyzers(:on_enter_fragment_spread, node, parent) enter_fragment_spread_inline(node) super leave_fragment_spread_inline(node) call_analyzers(:on_leave_fragment_spread, node, parent) @path.pop end def on_abstract_node(node, parent) call_analyzers(:on_enter_abstract_node, node, parent) super call_analyzers(:on_leave_abstract_node, node, parent) end # @return [GraphQL::BaseType] The current object type def type_definition @object_types.last end # @return [GraphQL::BaseType] The type which the current type came from def parent_type_definition @object_types[-2] end # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one def field_definition @field_definitions.last end # @return [GraphQL::Field, nil] The GraphQL field which returned the object that the current field belongs to def previous_field_definition @field_definitions[-2] end # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one def directive_definition @directive_definitions.last end # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one def argument_definition @argument_definitions.last end # @return [GraphQL::Argument, nil] The previous GraphQL argument def previous_argument_definition @argument_definitions[-2] end private # Visit a fragment spread inline instead of visiting the definition # by itself. def enter_fragment_spread_inline(fragment_spread) fragment_def = query.fragments[fragment_spread.name] object_type = if fragment_def.type query.schema.types.fetch(fragment_def.type.name, nil) else object_types.last end object_types << object_type fragment_def.selections.each do |selection| visit_node(selection, fragment_def) end end # Visit a fragment spread inline instead of visiting the definition # by itself. def leave_fragment_spread_inline(_fragment_spread) object_types.pop end def skip?(ast_node) dir = ast_node.directives dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, query) end def call_analyzers(method, node, parent) @analyzers.each do |analyzer| analyzer.public_send(method, node, parent, self) end end def on_fragment_with_type(node) object_type = if node.type @schema.types.fetch(node.type.name, nil) else @object_types.last end @object_types.push(object_type) yield(node) @object_types.pop @path.pop end end end end end ruby-graphql-1.9.19/lib/graphql/analysis/field_usage.rb000066400000000000000000000027231362601351000230540ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis # A query reducer for tracking both field usage and deprecated field usage. # # @example Logging field usage and deprecated field usage # Schema.query_analyzers << GraphQL::Analysis::FieldUsage.new { |query, used_fields, used_deprecated_fields| # puts "Used GraphQL fields: #{used_fields.join(', ')}" # puts "Used deprecated GraphQL fields: #{used_deprecated_fields.join(', ')}" # } # Schema.execute(query_str) # # Used GraphQL fields: Cheese.id, Cheese.fatContent, Query.cheese # # Used deprecated GraphQL fields: Cheese.fatContent # class FieldUsage def initialize(&block) @field_usage_handler = block end def initial_value(query) { query: query, used_fields: Set.new, used_deprecated_fields: Set.new } end def call(memo, visit_type, irep_node) if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field) && visit_type == :leave field = "#{irep_node.owner_type.name}.#{irep_node.definition.name}" memo[:used_fields] << field if irep_node.definition.deprecation_reason memo[:used_deprecated_fields] << field end end memo end def final_value(memo) @field_usage_handler.call(memo[:query], memo[:used_fields].to_a, memo[:used_deprecated_fields].to_a) end end end end ruby-graphql-1.9.19/lib/graphql/analysis/max_query_complexity.rb000066400000000000000000000016571362601351000251010ustar00rootroot00000000000000# frozen_string_literal: true require_relative "./query_complexity" module GraphQL module Analysis # Used under the hood to implement complexity validation, # see {Schema#max_complexity} and {Query#max_complexity} # # @example Assert max complexity of 10 # # DON'T actually do this, graphql-ruby # # Does this for you based on your `max_complexity` setting # MySchema.query_analyzers << GraphQL::Analysis::MaxQueryComplexity.new(10) # class MaxQueryComplexity < GraphQL::Analysis::QueryComplexity def initialize(max_complexity) disallow_excessive_complexity = ->(query, complexity) { if complexity > max_complexity GraphQL::AnalysisError.new("Query has complexity of #{complexity}, which exceeds max complexity of #{max_complexity}") else nil end } super(&disallow_excessive_complexity) end end end end ruby-graphql-1.9.19/lib/graphql/analysis/max_query_depth.rb000066400000000000000000000015201362601351000237750ustar00rootroot00000000000000# frozen_string_literal: true require_relative "./query_depth" module GraphQL module Analysis # Used under the hood to implement depth validation, # see {Schema#max_depth} and {Query#max_depth} # # @example Assert max depth of 10 # # DON'T actually do this, graphql-ruby # # Does this for you based on your `max_depth` setting # MySchema.query_analyzers << GraphQL::Analysis::MaxQueryDepth.new(10) # class MaxQueryDepth < GraphQL::Analysis::QueryDepth def initialize(max_depth) disallow_excessive_depth = ->(query, depth) { if depth > max_depth GraphQL::AnalysisError.new("Query has depth of #{depth}, which exceeds max depth of #{max_depth}") else nil end } super(&disallow_excessive_depth) end end end end ruby-graphql-1.9.19/lib/graphql/analysis/query_complexity.rb000066400000000000000000000063561362601351000242350ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis # Calculate the complexity of a query, using {Field#complexity} values. # # @example Log the complexity of incoming queries # MySchema.query_analyzers << GraphQL::Analysis::QueryComplexity.new do |query, complexity| # Rails.logger.info("Complexity: #{complexity}") # end # class QueryComplexity # @yield [query, complexity] Called for each query analyzed by the schema, before executing it # @yieldparam query [GraphQL::Query] The query that was analyzed # @yieldparam complexity [Numeric] The complexity for this query def initialize(&block) @complexity_handler = block end # State for the query complexity calcuation: # - `target` is passed to handler # - `complexities_on_type` holds complexity scores for each type in an IRep node def initial_value(target) { target: target, complexities_on_type: [TypeComplexity.new], } end # Implement the query analyzer API def call(memo, visit_type, irep_node) if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field) if visit_type == :enter memo[:complexities_on_type].push(TypeComplexity.new) else type_complexities = memo[:complexities_on_type].pop child_complexity = type_complexities.max_possible_complexity own_complexity = get_complexity(irep_node, child_complexity) memo[:complexities_on_type].last.merge(irep_node.owner_type, own_complexity) end end memo end # Send the query and complexity to the block # @return [Object, GraphQL::AnalysisError] Whatever the handler returns def final_value(reduced_value) total_complexity = reduced_value[:complexities_on_type].last.max_possible_complexity @complexity_handler.call(reduced_value[:target], total_complexity) end private # Get a complexity value for a field, # by getting the number or calling its proc def get_complexity(irep_node, child_complexity) field_defn = irep_node.definition defined_complexity = field_defn.complexity case defined_complexity when Proc defined_complexity.call(irep_node.query.context, irep_node.arguments, child_complexity) when Numeric defined_complexity + (child_complexity || 0) else raise("Invalid complexity: #{defined_complexity.inspect} on #{field_defn.name}") end end # Selections on an object may apply differently depending on what is _actually_ returned by the resolve function. # Find the maximum possible complexity among those combinations. class TypeComplexity def initialize @types = Hash.new(0) end # Return the max possible complexity for types in this selection def max_possible_complexity @types.each_value.max || 0 end # Store the complexity for the branch on `type_defn`. # Later we will see if this is the max complexity among branches. def merge(type_defn, complexity) @types[type_defn] += complexity end end end end end ruby-graphql-1.9.19/lib/graphql/analysis/query_depth.rb000066400000000000000000000021561362601351000231360ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis # A query reducer for measuring the depth of a given query. # # @example Logging the depth of a query # Schema.query_analyzers << GraphQL::Analysis::QueryDepth.new { |query, depth| puts "GraphQL query depth: #{depth}" } # Schema.execute(query_str) # # GraphQL query depth: 8 # class QueryDepth def initialize(&block) @depth_handler = block end def initial_value(query) { max_depth: 0, current_depth: 0, query: query, } end def call(memo, visit_type, irep_node) if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field) if visit_type == :enter memo[:current_depth] += 1 else if memo[:max_depth] < memo[:current_depth] memo[:max_depth] = memo[:current_depth] end memo[:current_depth] -= 1 end end memo end def final_value(memo) @depth_handler.call(memo[:query], memo[:max_depth]) end end end end ruby-graphql-1.9.19/lib/graphql/analysis/reducer_state.rb000066400000000000000000000024001362601351000234260ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis class ReducerState attr_reader :reducer attr_accessor :memo, :errors def initialize(reducer, query) @reducer = reducer @memo = initialize_reducer(reducer, query) @errors = [] end def call(visit_type, irep_node) @memo = @reducer.call(@memo, visit_type, irep_node) rescue AnalysisError => err @errors << err end # Respond with any errors, if found. Otherwise, if the reducer accepts # `final_value`, send it the last memo value. # Otherwise, use the last value from the traversal. # @return [Any] final memo value def finalize_reducer if @errors.any? @errors elsif reducer.respond_to?(:final_value) reducer.final_value(@memo) else @memo end end private # If the reducer has an `initial_value` method, call it and store # the result as `memo`. Otherwise, use `nil` as memo. # @return [Any] initial memo value def initialize_reducer(reducer, query) if reducer.respond_to?(:initial_value) reducer.initial_value(query) else nil end end end end end ruby-graphql-1.9.19/lib/graphql/analysis_error.rb000066400000000000000000000001471362601351000220140ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class AnalysisError < GraphQL::ExecutionError end end ruby-graphql-1.9.19/lib/graphql/argument.rb000066400000000000000000000114761362601351000206110ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Used for defined arguments ({Field}, {InputObjectType}) # # {#name} must be a String. # # @example defining an argument for a field # GraphQL::Field.define do # # ... # argument :favoriteFood, types.String, "Favorite thing to eat", default_value: "pizza" # end # # @example defining an argument for an {InputObjectType} # GraphQL::InputObjectType.define do # argument :newName, !types.String # end # # @example defining an argument with a `prepare` function # GraphQL::Field.define do # argument :userId, types.ID, prepare: ->(userId) do # User.find_by(id: userId) # end # end # # @example returning an {ExecutionError} from a `prepare` function # GraphQL::Field.define do # argument :date do # type !types.String # prepare ->(date) do # return GraphQL::ExecutionError.new("Invalid date format") unless DateValidator.valid?(date) # Time.zone.parse(date) # end # end # end class Argument include GraphQL::Define::InstanceDefinable accepts_definitions :name, :type, :description, :default_value, :as, :prepare, :method_access attr_reader :default_value attr_accessor :description, :name, :as attr_accessor :ast_node attr_accessor :method_access alias :graphql_name :name ensure_defined(:name, :description, :default_value, :type=, :type, :as, :expose_as, :prepare, :method_access) # @api private module DefaultPrepare def self.call(value, ctx); value; end end def initialize @prepare_proc = DefaultPrepare end def initialize_copy(other) @expose_as = nil end def default_value? !!@has_default_value end def method_access? # Treat unset as true -- only `false` should override @method_access != false end def default_value=(new_default_value) if new_default_value == NO_DEFAULT_VALUE @has_default_value = false @default_value = nil else @has_default_value = true @default_value = GraphQL::Argument.deep_stringify(new_default_value) end end # @!attribute name # @return [String] The name of this argument on its {GraphQL::Field} or {GraphQL::InputObjectType} # @param new_input_type [GraphQL::BaseType, Proc] Assign a new input type for this argument (if it's a proc, it will be called after schema initialization) def type=(new_input_type) @clean_type = nil @dirty_type = new_input_type end # @return [GraphQL::BaseType] the input type for this argument def type @clean_type ||= GraphQL::BaseType.resolve_related_type(@dirty_type) end # @return [String] The name of this argument inside `resolve` functions def expose_as @expose_as ||= (@as || @name).to_s end # Backport this to support legacy-style directives def keyword @keyword ||= GraphQL::Schema::Member::BuildType.underscore(expose_as).to_sym end # @param value [Object] The incoming value from variables or query string literal # @param ctx [GraphQL::Query::Context] # @return [Object] The prepared `value` for this argument or `value` itself if no `prepare` function exists. def prepare(value, ctx) @prepare_proc.call(value, ctx) end # Assign a `prepare` function to prepare this argument's value before `resolve` functions are called. # @param prepare_proc [#] def prepare=(prepare_proc) @prepare_proc = BackwardsCompatibility.wrap_arity(prepare_proc, from: 1, to: 2, name: "Argument#prepare(value, ctx)") end NO_DEFAULT_VALUE = Object.new # @api private def self.from_dsl(name, type_or_argument = nil, description = nil, default_value: NO_DEFAULT_VALUE, as: nil, prepare: DefaultPrepare, **kwargs, &block) name_s = name.to_s # Move some positional args into keywords if they're present description && kwargs[:description] ||= description kwargs[:name] ||= name_s kwargs[:default_value] ||= default_value kwargs[:as] ||= as unless prepare == DefaultPrepare kwargs[:prepare] ||= prepare end if !type_or_argument.nil? && !type_or_argument.is_a?(GraphQL::Argument) # Maybe a string, proc or BaseType kwargs[:type] = type_or_argument end if type_or_argument.is_a?(GraphQL::Argument) type_or_argument.redefine(**kwargs, &block) else GraphQL::Argument.define(**kwargs, &block) end end # @api private def self.deep_stringify(val) case val when Array val.map { |v| deep_stringify(v) } when Hash new_val = {} val.each do |k, v| new_val[k.to_s] = deep_stringify(v) end new_val else val end end end end ruby-graphql-1.9.19/lib/graphql/authorization.rb000066400000000000000000000052321362601351000216600ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Authorization class InaccessibleFieldsError < GraphQL::AnalysisError # @return [Array] Fields that failed `.accessible?` checks attr_reader :fields # @return [GraphQL::Query::Context] The current query's context attr_reader :context # @return [Array] The visited nodes that failed `.accessible?` checks # @see {#fields} for the Field definitions attr_reader :irep_nodes def initialize(fields:, irep_nodes:, context:) @fields = fields @irep_nodes = irep_nodes @context = context super("Some fields in this query are not accessible: #{fields.map(&:graphql_name).join(", ")}") end end # @deprecated authorization at query runtime is generally a better idea. module Analyzer module_function def initial_value(query) { schema: query.schema, context: query.context, inaccessible_nodes: [], } end def call(memo, visit_type, irep_node) if visit_type == :enter field = irep_node.definition if field schema = memo[:schema] ctx = memo[:context] next_field_accessible = schema.accessible?(field, ctx) if !next_field_accessible memo[:inaccessible_nodes] << irep_node else arg_accessible = true irep_node.arguments.argument_values.each do |name, arg_value| arg_accessible = schema.accessible?(arg_value.definition, ctx) if !arg_accessible memo[:inaccessible_nodes] << irep_node break end end if arg_accessible return_type = field.type.unwrap next_type_accessible = schema.accessible?(return_type, ctx) if !next_type_accessible memo[:inaccessible_nodes] << irep_node end end end end end memo end def final_value(memo) nodes = memo[:inaccessible_nodes] if nodes.any? fields = nodes.map do |node| field_inst = node.definition # Get the "source of truth" for this field field_inst.metadata[:type_class] || field_inst end context = memo[:context] err = InaccessibleFieldsError.new(fields: fields, irep_nodes: nodes, context: context) context.schema.inaccessible_fields(err) else nil end end end end end ruby-graphql-1.9.19/lib/graphql/backtrace.rb000066400000000000000000000025201362601351000206740ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/backtrace/inspect_result" require "graphql/backtrace/table" require "graphql/backtrace/traced_error" require "graphql/backtrace/tracer" module GraphQL # Wrap unhandled errors with {TracedError}. # # {TracedError} provides a GraphQL backtrace with arguments and return values. # The underlying error is available as {TracedError#cause}. # # WARNING: {.enable} is not threadsafe because {GraphQL::Tracing.install} is not threadsafe. # # @example toggling backtrace annotation # # to enable: # GraphQL::Backtrace.enable # # later, to disable: # GraphQL::Backtrace.disable # class Backtrace include Enumerable extend Forwardable def_delegators :to_a, :each, :[] def self.enable warn("GraphQL::Backtrace.enable is deprecated, add `use GraphQL::Backtrace` to your schema definition instead.") GraphQL::Tracing.install(Backtrace::Tracer) nil end def self.disable GraphQL::Tracing.uninstall(Backtrace::Tracer) nil end def self.use(schema_defn) schema_defn.tracer(self::Tracer) end def initialize(context, value: nil) @table = Table.new(context, value: value) end def inspect @table.to_table end alias :to_s :inspect def to_a @table.to_backtrace end end end ruby-graphql-1.9.19/lib/graphql/backtrace/000077500000000000000000000000001362601351000203505ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/backtrace/inspect_result.rb000066400000000000000000000021411362601351000237360ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../backtrace.rb module GraphQL class Backtrace module InspectResult module_function def inspect_result(obj) case obj when Hash "{" + obj.map do |key, val| "#{key}: #{inspect_truncated(val)}" end.join(", ") + "}" when Array "[" + obj.map { |v| inspect_truncated(v) }.join(", ") + "]" when Query::Context::SharedMethods if obj.invalid_null? "nil" else inspect_truncated(obj.value) end else inspect_truncated(obj) end end def inspect_truncated(obj) case obj when Hash "{...}" when Array "[...]" when Query::Context::SharedMethods if obj.invalid_null? "nil" else inspect_truncated(obj.value) end when GraphQL::Execution::Lazy "(unresolved)" else "#{obj.inspect}" end end end end end ruby-graphql-1.9.19/lib/graphql/backtrace/table.rb000066400000000000000000000066271362601351000217770ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../backtrace.rb module GraphQL class Backtrace # A class for turning a context into a human-readable table or array class Table MIN_COL_WIDTH = 4 MAX_COL_WIDTH = 100 HEADERS = [ "Loc", "Field", "Object", "Arguments", "Result", ] def initialize(context, value:) @context = context @override_value = value end # @return [String] A table layout of backtrace with metadata def to_table @to_table ||= render_table(rows) end # @return [Array] An array of position + field name entries def to_backtrace @to_backtrace ||= begin backtrace = rows.map { |r| "#{r[0]}: #{r[1]}" } # skip the header entry backtrace.shift backtrace end end private def rows @rows ||= build_rows(@context, rows: [HEADERS], top: true) end # @return [String] def render_table(rows) max = Array.new(HEADERS.length, MIN_COL_WIDTH) rows.each do |row| row.each_with_index do |col, idx| col_len = col.length max_len = max[idx] if col_len > max_len if col_len > MAX_COL_WIDTH max[idx] = MAX_COL_WIDTH else max[idx] = col_len end end end end table = "".dup last_col_idx = max.length - 1 rows.each do |row| table << row.map.each_with_index do |col, idx| max_len = max[idx] if idx < last_col_idx col = col.ljust(max_len) end if col.length > max_len col = col[0, max_len - 3] + "..." end col end.join(" | ") table << "\n" end table end # @return [Array] 5 items for a backtrace table (not `key`) def build_rows(context_entry, rows:, top: false) case context_entry when GraphQL::Query::Context::FieldResolutionContext ctx = context_entry field_name = "#{ctx.irep_node.owner_type.name}.#{ctx.field.name}" position = "#{ctx.ast_node.line}:#{ctx.ast_node.col}" field_alias = ctx.ast_node.alias rows << [ "#{position}", "#{field_name}#{field_alias ? " as #{field_alias}" : ""}", "#{ctx.object.inspect}", ctx.irep_node.arguments.to_h.inspect, Backtrace::InspectResult.inspect_result(top && @override_value ? @override_value : ctx.value), ] build_rows(ctx.parent, rows: rows) when GraphQL::Query::Context query = context_entry.query op = query.selected_operation if op op_type = op.operation_type position = "#{op.line}:#{op.col}" else op_type = "query" position = "?:?" end op_name = query.selected_operation_name rows << [ "#{position}", "#{op_type}#{op_name ? " #{op_name}" : ""}", "#{query.root_value.inspect}", query.variables.to_h.inspect, Backtrace::InspectResult.inspect_result(query.context.value), ] else raise "Unexpected get_rows subject #{context_entry.inspect}" end end end end end ruby-graphql-1.9.19/lib/graphql/backtrace/traced_error.rb000066400000000000000000000033611362601351000233530ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../backtrace.rb module GraphQL class Backtrace # When {Backtrace} is enabled, raised errors are wrapped with {TracedError}. class TracedError < GraphQL::Error # @return [Array] Printable backtrace of GraphQL error context attr_reader :graphql_backtrace # @return [GraphQL::Query::Context] The context at the field where the error was raised attr_reader :context MESSAGE_TEMPLATE = <<-MESSAGE Unhandled error during GraphQL execution: %{cause_message} %{cause_backtrace} %{cause_backtrace_more} Use #cause to access the original exception (including #cause.backtrace). GraphQL Backtrace: %{graphql_table} MESSAGE # This many lines of the original Ruby backtrace # are included in the message CAUSE_BACKTRACE_PREVIEW_LENGTH = 10 def initialize(err, current_ctx) @context = current_ctx backtrace = Backtrace.new(current_ctx, value: err) @graphql_backtrace = backtrace.to_a cause_backtrace_preview = err.backtrace.first(CAUSE_BACKTRACE_PREVIEW_LENGTH).join("\n ") cause_backtrace_remainder_length = err.backtrace.length - CAUSE_BACKTRACE_PREVIEW_LENGTH cause_backtrace_more = if cause_backtrace_remainder_length < 0 "" elsif cause_backtrace_remainder_length == 1 "... and 1 more line\n" else "... and #{cause_backtrace_remainder_length} more lines\n" end message = MESSAGE_TEMPLATE % { cause_message: err.message, cause_backtrace: cause_backtrace_preview, cause_backtrace_more: cause_backtrace_more, graphql_table: backtrace.inspect, } super(message) end end end end ruby-graphql-1.9.19/lib/graphql/backtrace/tracer.rb000066400000000000000000000027541362601351000221650ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Backtrace module Tracer module_function # Implement the {GraphQL::Tracing} API. def trace(key, metadata) push_data = case key when "lex", "parse" # No context here, don't have a query yet nil when "execute_multiplex", "analyze_multiplex" metadata[:multiplex].queries when "validate", "analyze_query", "execute_query", "execute_query_lazy" metadata[:query] || metadata[:queries] when "execute_field", "execute_field_lazy" metadata[:context] else # Custom key, no backtrace data for this nil end if push_data Thread.current[:last_graphql_backtrace_context] = push_data end if key == "execute_multiplex" begin yield rescue StandardError => err # This is an unhandled error from execution, # Re-raise it with a GraphQL trace. potential_context = Thread.current[:last_graphql_backtrace_context] if potential_context.is_a?(GraphQL::Query::Context) || potential_context.is_a?(GraphQL::Query::Context::FieldResolutionContext) raise TracedError.new(err, potential_context) else raise end ensure Thread.current[:last_graphql_backtrace_context] = nil end else yield end end end end end ruby-graphql-1.9.19/lib/graphql/backwards_compatibility.rb000066400000000000000000000036371362601351000236610ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Helpers for migrating in a backwards-compatible way # @api private module BackwardsCompatibility module_function # Given a callable whose API used to take `from` arguments, # check its arity, and if needed, apply a wrapper so that # it can be called with `to` arguments. # If a wrapper is applied, warn the application with `name`. # # If `last`, then use the last arguments to call the function. def wrap_arity(callable, from:, to:, name:, last: false) arity = get_arity(callable) if arity == to || arity < 0 # It already matches, return it as is callable elsif arity == from # It has the old arity, so wrap it with an arity converter message ="#{name} with #{from} arguments is deprecated, it now accepts #{to} arguments, see:" backtrace = caller(0, 20) # Find the first line in the trace that isn't library internals: user_line = backtrace.find {|l| l !~ /lib\/graphql/ } warn(message + "\n" + user_line + "\n") wrapper = last ? LastArgumentsWrapper : FirstArgumentsWrapper wrapper.new(callable, from) else raise "Can't wrap #{callable} (arity: #{arity}) to have arity #{to}" end end def get_arity(callable) case callable when Method, Proc callable.arity else callable.method(:call).arity end end class FirstArgumentsWrapper def initialize(callable, old_arity) @callable = callable @old_arity = old_arity end def call(*args) backwards_compat_args = args.first(@old_arity) @callable.call(*backwards_compat_args) end end class LastArgumentsWrapper < FirstArgumentsWrapper def call(*args) backwards_compat_args = args.last(@old_arity) @callable.call(*backwards_compat_args) end end end end ruby-graphql-1.9.19/lib/graphql/base_type.rb000066400000000000000000000137771362601351000207500ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/relay/type_extensions" module GraphQL # The parent for all type classes. class BaseType include GraphQL::Define::NonNullWithBang include GraphQL::Define::InstanceDefinable include GraphQL::Relay::TypeExtensions accepts_definitions :name, :description, :introspection, :default_scalar, :default_relay, { connection: GraphQL::Define::AssignConnection, global_id_field: GraphQL::Define::AssignGlobalIdField, } ensure_defined(:graphql_name, :name, :description, :introspection?, :default_scalar?) attr_accessor :ast_node def initialize @introspection = false @default_scalar = false @default_relay = false end def initialize_copy(other) super # Reset these derived defaults @connection_type = nil @edge_type = nil end # @return [String] the name of this type, must be unique within a Schema attr_reader :name # Future-compatible alias # @see {GraphQL::SchemaMember} alias :graphql_name :name # Future-compatible alias # @see {GraphQL::SchemaMember} alias :graphql_definition :itself def name=(name) GraphQL::NameValidator.validate!(name) @name = name end # @return [String, nil] a description for this type attr_accessor :description # @return [Boolean] Is this type a predefined introspection type? def introspection? @introspection end # @return [Boolean] Is this type a built-in scalar type? (eg, `String`, `Int`) def default_scalar? @default_scalar end # @return [Boolean] Is this type a built-in Relay type? (`Node`, `PageInfo`) def default_relay? @default_relay end # @api private attr_writer :introspection, :default_scalar, :default_relay # @param other [GraphQL::BaseType] compare to this object # @return [Boolean] are these types equivalent? (incl. non-null, list) # @see {ModifiesAnotherType#==} for override on List & NonNull types def ==(other) other.is_a?(GraphQL::BaseType) && self.name == other.name end # If this type is modifying an underlying type, # return the underlying type. (Otherwise, return `self`.) def unwrap self end # @return [GraphQL::NonNullType] a non-null version of this type def to_non_null_type GraphQL::NonNullType.new(of_type: self) end # @return [GraphQL::ListType] a list version of this type def to_list_type GraphQL::ListType.new(of_type: self) end module ModifiesAnotherType def unwrap self.of_type.unwrap end def ==(other) other.is_a?(ModifiesAnotherType) && other.of_type == of_type end end # Find out which possible type to use for `value`. # Returns self if there are no possible types (ie, not Union or Interface) def resolve_type(value, ctx) self end # Print the human-readable name of this type using the query-string naming pattern def to_s name end alias :inspect :to_s alias :to_type_signature :to_s def valid_isolated_input?(value) valid_input?(value, GraphQL::Query::NullContext) end def validate_isolated_input(value) validate_input(value, GraphQL::Query::NullContext) end def coerce_isolated_input(value) coerce_input(value, GraphQL::Query::NullContext) end def coerce_isolated_result(value) coerce_result(value, GraphQL::Query::NullContext) end def valid_input?(value, ctx = nil) if ctx.nil? warn_deprecated_coerce("valid_isolated_input?") ctx = GraphQL::Query::NullContext end validate_input(value, ctx).valid? end def validate_input(value, ctx = nil) if ctx.nil? warn_deprecated_coerce("validate_isolated_input") ctx = GraphQL::Query::NullContext end if value.nil? GraphQL::Query::InputValidationResult.new else validate_non_null_input(value, ctx) end end def coerce_input(value, ctx = nil) if value.nil? nil else if ctx.nil? warn_deprecated_coerce("coerce_isolated_input") ctx = GraphQL::Query::NullContext end coerce_non_null_input(value, ctx) end end def coerce_result(value, ctx) raise GraphQL::RequiredImplementationMissingError end # Types with fields may override this # @param name [String] field name to lookup for this type # @return [GraphQL::Field, nil] def get_field(name) nil end # During schema definition, types can be defined inside procs or as strings. # This function converts it to a type instance # @return [GraphQL::BaseType] def self.resolve_related_type(type_arg) case type_arg when Proc # lazy-eval it, then try again resolve_related_type(type_arg.call) when String # Get a constant by this name resolve_related_type(Object.const_get(type_arg)) else if type_arg.respond_to?(:graphql_definition) type_arg.graphql_definition else type_arg end end end # Return a GraphQL string for the type definition # @param schema [GraphQL::Schema] # @param printer [GraphQL::Schema::Printer] # @see {GraphQL::Schema::Printer#initialize for additional options} # @return [String] type definition def to_definition(schema, printer: nil, **args) printer ||= GraphQL::Schema::Printer.new(schema, **args) printer.print_type(self) end # Returns true if this is a non-nullable type. A nullable list of non-nullables is considered nullable. def non_null? false end # Returns true if this is a list type. A non-nullable list is considered a list. def list? false end private def warn_deprecated_coerce(alt_method_name) warn("Coercing without a context is deprecated; use `#{alt_method_name}` if you don't want context-awareness") end end end ruby-graphql-1.9.19/lib/graphql/boolean_type.rb000066400000000000000000000001411362601351000214320ustar00rootroot00000000000000# frozen_string_literal: true GraphQL::BOOLEAN_TYPE = GraphQL::Types::Boolean.graphql_definition ruby-graphql-1.9.19/lib/graphql/coercion_error.rb000066400000000000000000000005301362601351000217660ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class CoercionError < GraphQL::Error # @return [Hash] Optional custom data for error objects which will be added # under the `extensions` key. attr_accessor :extensions def initialize(message, extensions: nil) @extensions = extensions super(message) end end end ruby-graphql-1.9.19/lib/graphql/compatibility.rb000066400000000000000000000004121362601351000216240ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/compatibility/execution_specification" require "graphql/compatibility/lazy_execution_specification" require "graphql/compatibility/query_parser_specification" require "graphql/compatibility/schema_parser_specification" ruby-graphql-1.9.19/lib/graphql/compatibility/000077500000000000000000000000001362601351000213025ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/compatibility/execution_specification.rb000066400000000000000000000336741362601351000265470ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/compatibility/execution_specification/counter_schema" require "graphql/compatibility/execution_specification/specification_schema" module GraphQL module Compatibility # Test an execution strategy. This spec is not meant as a development aid. # Rather, when the strategy _works_, run it here to see if it has any differences # from the built-in strategy. # # - Custom scalar input / output # - Null propagation # - Query-level masking # - Directive support # - Typecasting # - Error handling (raise / return GraphQL::ExecutionError) # - Provides Irep & AST node to resolve fn # - Skipping fields # # Some things are explicitly _not_ tested here, because they're handled # by other parts of the system: # # - Schema definition (including types and fields) # - Parsing & parse errors # - AST -> IRep transformation (eg, fragment merging) # - Query validation and analysis # - Relay features # module ExecutionSpecification # Make a minitest suite for this execution strategy, making sure it # fulfills all the requirements of this library. # @param execution_strategy [<#new, #execute>] An execution strategy class # @return [Class] A test suite for this execution strategy def self.build_suite(execution_strategy) Class.new(Minitest::Test) do class << self attr_accessor :counter_schema, :specification_schema end self.specification_schema = SpecificationSchema.build(execution_strategy) self.counter_schema = CounterSchema.build(execution_strategy) def execute_query(query_string, **kwargs) kwargs[:root_value] = SpecificationSchema::DATA self.class.specification_schema.execute(query_string, **kwargs) end def test_it_fetches_data query_string = %| query getData($nodeId: ID = "1001") { flh: node(id: $nodeId) { __typename ... on Person { name @include(if: true) skippedName: name @skip(if: true) birthdate age(on: 1477660133) } ... on NamedEntity { ne_tn: __typename ne_n: name } ... on Organization { org_n: name } } } | res = execute_query(query_string) assert_equal nil, res["errors"], "It doesn't have an errors key" flh = res["data"]["flh"] assert_equal "Fannie Lou Hamer", flh["name"], "It returns values" assert_equal Time.new(1917, 10, 6).to_i, flh["birthdate"], "It returns custom scalars" assert_equal 99, flh["age"], "It runs resolve functions" assert_equal "Person", flh["__typename"], "It serves __typename" assert_equal "Person", flh["ne_tn"], "It serves __typename on interfaces" assert_equal "Fannie Lou Hamer", flh["ne_n"], "It serves interface fields" assert_equal false, flh.key?("skippedName"), "It obeys @skip" assert_equal false, flh.key?("org_n"), "It doesn't apply other type fields" end def test_it_iterates_over_each query_string = %| query getData($nodeId: ID = "1002") { node(id: $nodeId) { ... on Person { organizations { name } } } } | res = execute_query(query_string) assert_equal ["SNCC"], res["data"]["node"]["organizations"].map { |o| o["name"] } end def test_it_skips_skipped_fields query_str = <<-GRAPHQL { o3001: organization(id: "3001") { name } o2001: organization(id: "2001") { name } } GRAPHQL res = execute_query(query_str) assert_equal ["o2001"], res["data"].keys assert_equal false, res.key?("errors") end def test_it_propagates_nulls_to_field query_string = %| query getOrg($id: ID = "2001"){ failure: node(id: $id) { ... on Organization { name leader { name } } } success: node(id: $id) { ... on Organization { name } } } | res = execute_query(query_string) failure = res["data"]["failure"] success = res["data"]["success"] assert_equal nil, failure, "It propagates nulls to the next nullable field" assert_equal({"name" => "SNCC"}, success, "It serves the same object if no invalid null is encountered") assert_equal 1, res["errors"].length , "It returns an error for the invalid null" end def test_it_propages_nulls_to_operation query_string = %| { foundOrg: organization(id: "2001") { name } organization(id: "2999") { name } } | res = execute_query(query_string) assert_equal nil, res["data"] assert_equal 1, res["errors"].length end def test_it_exposes_raised_and_returned_user_execution_errors query_string = %| { organization(id: "2001") { name returnedError raisedError } organizations { returnedError raisedError } } | res = execute_query(query_string) assert_equal "SNCC", res["data"]["organization"]["name"], "It runs the rest of the query" expected_errors = [ { "message"=>"This error was returned", "locations"=>[{"line"=>5, "column"=>19}], "path"=>["organization", "returnedError"] }, { "message"=>"This error was raised", "locations"=>[{"line"=>6, "column"=>19}], "path"=>["organization", "raisedError"] }, { "message"=>"This error was raised", "locations"=>[{"line"=>10, "column"=>19}], "path"=>["organizations", 0, "raisedError"] }, { "message"=>"This error was raised", "locations"=>[{"line"=>10, "column"=>19}], "path"=>["organizations", 1, "raisedError"] }, { "message"=>"This error was returned", "locations"=>[{"line"=>9, "column"=>19}], "path"=>["organizations", 0, "returnedError"] }, { "message"=>"This error was returned", "locations"=>[{"line"=>9, "column"=>19}], "path"=>["organizations", 1, "returnedError"] }, ] expected_errors.each do |expected_err| assert_includes res["errors"], expected_err end end def test_it_applies_masking no_org = ->(member, ctx) { member.name == "Organization" } query_string = %| { node(id: "2001") { __typename } }| err = assert_raises(GraphQL::UnresolvedTypeError) { execute_query(query_string, except: no_org) } query_string = %| { organization(id: "2001") { name } }| res = execute_query(query_string, except: no_org) assert_equal nil, res["data"] assert_equal 1, res["errors"].length assert_equal "SNCC", err.value.name assert_equal GraphQL::Relay::Node.interface, err.field.type assert_equal 1, err.possible_types.length assert_equal "Organization", err.resolved_type.name assert_equal "Query", err.parent_type.name query_string = %| { __type(name: "Organization") { name } }| res = execute_query(query_string, except: no_org) assert_equal nil, res["data"]["__type"] assert_equal nil, res["errors"] end def test_it_provides_nodes_to_resolve query_string = %| { organization(id: "2001") { name nodePresence } }| res = execute_query(query_string) assert_equal "SNCC", res["data"]["organization"]["name"] assert_equal [true, true, false], res["data"]["organization"]["nodePresence"] end def test_it_runs_the_introspection_query execute_query(GraphQL::Introspection::INTROSPECTION_QUERY) end def test_it_propagates_deeply_nested_nulls query_string = %| { node(id: "1001") { ... on Person { name first_organization { leader { name } } } } } | res = execute_query(query_string) assert_equal nil, res["data"]["node"] assert_equal 1, res["errors"].length end def test_it_doesnt_add_errors_for_invalid_nulls_from_execution_errors query_string = %| query getOrg($id: ID = "2001"){ failure: node(id: $id) { ... on Organization { name leader { name } } } } | res = execute_query(query_string, context: {return_error: true}) error_messages = res["errors"].map { |e| e["message"] } assert_equal ["Error on Nullable"], error_messages end def test_it_only_resolves_fields_once_on_typed_fragments res = self.class.counter_schema.execute(" { counter { count } ... on HasCounter { counter { count } } } ") expected_data = { "counter" => { "count" => 1 } } assert_equal expected_data, res["data"] assert_equal 1, self.class.counter_schema.metadata[:count] # Deep typed children are correctly distinguished: res = self.class.counter_schema.execute(" { counter { ... on Counter { counter { count } } ... on AltCounter { counter { count, t: __typename } } } } ") expected_data = { "counter" => { "counter" => { "count" => 2 } } } assert_equal expected_data, res["data"] end def test_it_runs_middleware log = [] query_string = %| { node(id: "2001") { __typename } }| execute_query(query_string, context: {middleware_log: log}) assert_equal ["node", "__typename"], log end def test_it_uses_type_error_hooks_for_invalid_nulls log = [] query_string = %| { node(id: "1001") { ... on Person { name first_organization { leader { name } } } } }| res = execute_query(query_string, context: { type_errors: log }) assert_equal nil, res["data"]["node"] assert_equal [nil], log end def test_it_uses_type_error_hooks_for_failed_type_resolution log = [] query_string = %| { node(id: "2003") { __typename } }| assert_raises(GraphQL::UnresolvedTypeError) { execute_query(query_string, context: { type_errors: log }) } assert_equal [SpecificationSchema::BOGUS_NODE], log end def test_it_treats_failed_type_resolution_like_nil log = [] ctx = { type_errors: log, gobble: true } query_string = %| { node(id: "2003") { __typename } }| res = execute_query(query_string, context: ctx) assert_equal nil, res["data"]["node"] assert_equal false, res.key?("errors") assert_equal [SpecificationSchema::BOGUS_NODE], log query_string_2 = %| { requiredNode(id: "2003") { __typename } }| res = execute_query(query_string_2, context: ctx) assert_equal nil, res["data"] assert_equal false, res.key?("errors") assert_equal [SpecificationSchema::BOGUS_NODE, SpecificationSchema::BOGUS_NODE], log end def test_it_skips_connections query_type = GraphQL::ObjectType.define do name "Query" connection :skipped, types[query_type], resolve: ->(o,a,c) { c.skip } end schema = GraphQL::Schema.define(query: query_type) res = schema.execute("{ skipped { __typename } }") assert_equal({"data" => nil}, res) end end end end end end ruby-graphql-1.9.19/lib/graphql/compatibility/execution_specification/000077500000000000000000000000001362601351000262055ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/compatibility/execution_specification/counter_schema.rb000066400000000000000000000034241362601351000315340ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Compatibility module ExecutionSpecification module CounterSchema def self.build(execution_strategy) counter_type = nil schema = nil has_count_interface = GraphQL::InterfaceType.define do name "HasCount" field :count, types.Int field :counter, ->{ has_count_interface } end counter_type = GraphQL::ObjectType.define do name "Counter" interfaces [has_count_interface] field :count, types.Int, resolve: ->(o,a,c) { schema.metadata[:count] += 1 } field :counter, has_count_interface, resolve: ->(o,a,c) { :counter } end alt_counter_type = GraphQL::ObjectType.define do name "AltCounter" interfaces [has_count_interface] field :count, types.Int, resolve: ->(o,a,c) { schema.metadata[:count] += 1 } field :counter, has_count_interface, resolve: ->(o,a,c) { :counter } end has_counter_interface = GraphQL::InterfaceType.define do name "HasCounter" field :counter, has_count_interface end query_type = GraphQL::ObjectType.define do name "Query" interfaces [has_counter_interface] field :counter, has_count_interface, resolve: ->(o,a,c) { :counter } end schema = GraphQL::Schema.define( query: query_type, resolve_type: ->(t, o, c) { o == :counter ? counter_type : nil }, orphan_types: [alt_counter_type, counter_type], query_execution_strategy: execution_strategy, ) schema.metadata[:count] = 0 schema end end end end end ruby-graphql-1.9.19/lib/graphql/compatibility/execution_specification/specification_schema.rb000066400000000000000000000144551362601351000327030ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Compatibility module ExecutionSpecification module SpecificationSchema BOGUS_NODE = OpenStruct.new({ bogus: true }) DATA = { "1001" => OpenStruct.new({ name: "Fannie Lou Hamer", birthdate: Time.new(1917, 10, 6), organization_ids: [], }), "1002" => OpenStruct.new({ name: "John Lewis", birthdate: Time.new(1940, 2, 21), organization_ids: ["2001"], }), "1003" => OpenStruct.new({ name: "Diane Nash", birthdate: Time.new(1938, 5, 15), organization_ids: ["2001", "2002"], }), "1004" => OpenStruct.new({ name: "Ralph Abernathy", birthdate: Time.new(1926, 3, 11), organization_ids: ["2002"], }), "2001" => OpenStruct.new({ name: "SNCC", leader_id: nil, # fail on purpose }), "2002" => OpenStruct.new({ name: "SCLC", leader_id: "1004", }), "2003" => BOGUS_NODE, } # A list object must implement #each class CustomCollection def initialize(storage) @storage = storage end def each @storage.each { |i| yield(i) } end end module TestMiddleware def self.call(parent_type, parent_object, field_definition, field_args, query_context, &next_middleware) query_context[:middleware_log] && query_context[:middleware_log] << field_definition.name next_middleware.call end end def self.build(execution_strategy) organization_type = nil timestamp_type = GraphQL::ScalarType.define do name "Timestamp" coerce_input ->(value, _ctx) { Time.at(value.to_i) } coerce_result ->(value, _ctx) { value.to_i } end named_entity_interface_type = GraphQL::InterfaceType.define do name "NamedEntity" field :name, !types.String end person_type = GraphQL::ObjectType.define do name "Person" interfaces [named_entity_interface_type] field :name, !types.String field :birthdate, timestamp_type field :age, types.Int do argument :on, !timestamp_type resolve ->(obj, args, ctx) { if obj.birthdate.nil? nil else age_on = args[:on] age_years = age_on.year - obj.birthdate.year this_year_birthday = Time.new(age_on.year, obj.birthdate.month, obj.birthdate.day) if this_year_birthday > age_on age_years -= 1 end end age_years } end field :organizations, types[organization_type] do resolve ->(obj, args, ctx) { CustomCollection.new(obj.organization_ids.map { |id| DATA[id] }) } end field :first_organization, !organization_type do resolve ->(obj, args, ctx) { DATA[obj.organization_ids.first] } end end organization_type = GraphQL::ObjectType.define do name "Organization" interfaces [named_entity_interface_type] field :name, !types.String field :leader, !person_type do resolve ->(obj, args, ctx) { DATA[obj.leader_id] || (ctx[:return_error] ? ExecutionError.new("Error on Nullable") : nil) } end field :returnedError, types.String do resolve ->(o, a, c) { GraphQL::ExecutionError.new("This error was returned") } end field :raisedError, types.String do resolve ->(o, a, c) { raise GraphQL::ExecutionError.new("This error was raised") } end field :nodePresence, !types[!types.Boolean] do resolve ->(o, a, ctx) { [ ctx.irep_node.is_a?(GraphQL::InternalRepresentation::Node), ctx.ast_node.is_a?(GraphQL::Language::Nodes::AbstractNode), false, # just testing ] } end end node_union_type = GraphQL::UnionType.define do name "Node" possible_types [person_type, organization_type] end query_type = GraphQL::ObjectType.define do name "Query" field :node, node_union_type do argument :id, !types.ID resolve ->(obj, args, ctx) { obj[args[:id]] } end field :requiredNode, node_union_type.to_non_null_type do argument :id, !types.ID resolve ->(obj, args, ctx) { obj[args[:id]] } end field :organization, !organization_type do argument :id, !types.ID resolve ->(obj, args, ctx) { if args[:id].start_with?("2") obj[args[:id]] else # test context.skip ctx.skip end } end field :organizations, types[organization_type] do resolve ->(obj, args, ctx) { [obj["2001"], obj["2002"]] } end end GraphQL::Schema.define do query_execution_strategy execution_strategy query query_type resolve_type ->(type, obj, ctx) { if obj.respond_to?(:birthdate) person_type elsif obj.respond_to?(:leader_id) organization_type else nil end } type_error ->(err, ctx) { ctx[:type_errors] && (ctx[:type_errors] << err.value) ctx[:gobble] || GraphQL::Schema::DefaultTypeError.call(err, ctx) } middleware(TestMiddleware) end end end end end end ruby-graphql-1.9.19/lib/graphql/compatibility/lazy_execution_specification.rb000066400000000000000000000143161362601351000275760ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/compatibility/lazy_execution_specification/lazy_schema" module GraphQL module Compatibility module LazyExecutionSpecification # @param execution_strategy [<#new, #execute>] An execution strategy class # @return [Class] A test suite for this execution strategy def self.build_suite(execution_strategy) Class.new(Minitest::Test) do class << self attr_accessor :lazy_schema end self.lazy_schema = LazySchema.build(execution_strategy) def test_it_resolves_lazy_values pushes = [] query_str = %| { p1: push(value: 1) { value } p2: push(value: 2) { push(value: 3) { value push(value: 21) { value } } } p3: push(value: 4) { push(value: 5) { value push(value: 22) { value } } } } | res = self.class.lazy_schema.execute(query_str, context: {pushes: pushes}) expected_data = { "p1"=>{"value"=>1}, "p2"=>{"push"=>{"value"=>3, "push"=>{"value"=>21}}}, "p3"=>{"push"=>{"value"=>5, "push"=>{"value"=>22}}}, } assert_equal expected_data, res["data"] expected_pushes = [ [1,2,4], # first level [3,5], # second level [21, 22], ] assert_equal expected_pushes, pushes end def test_it_maintains_path query_str = %| { push(value: 2) { push(value: 3) { fail1: push(value: 14) { value } fail2: push(value: 14) { value } } } } | res = self.class.lazy_schema.execute(query_str, context: {pushes: []}) assert_equal nil, res["data"] # The first fail causes the second field to never resolve assert_equal 1, res["errors"].length assert_equal ["push", "push", "fail1", "value"], res["errors"][0]["path"] end def test_it_resolves_mutation_values_eagerly pushes = [] query_str = %| mutation { p1: push(value: 1) { value } p2: push(value: 2) { push(value: 3) { value } } p3: push(value: 4) { p5: push(value: 5) { value } p6: push(value: 6) { value } } } | res = self.class.lazy_schema.execute(query_str, context: {pushes: pushes}) expected_data = { "p1"=>{"value"=>1}, "p2"=>{"push"=>{"value"=>3}}, "p3"=>{"p5"=>{"value"=>5},"p6"=>{"value"=>6}}, } assert_equal expected_data, res["data"] expected_pushes = [ [1], # first operation [2], [3], # second operation [4], [5, 6], # third operation ] assert_equal expected_pushes, pushes end def test_it_resolves_lazy_connections pushes = [] query_str = %| { pushes(values: [1,2,3]) { edges { node { value push(value: 4) { value } } } } } | res = self.class.lazy_schema.execute(query_str, context: {pushes: pushes}) expected_edges = [ {"node"=>{"value"=>1, "push"=>{"value"=>4}}}, {"node"=>{"value"=>2, "push"=>{"value"=>4}}}, {"node"=>{"value"=>3, "push"=>{"value"=>4}}}, ] assert_equal expected_edges, res["data"]["pushes"]["edges"] assert_equal [[1, 2, 3], [4, 4, 4]], pushes end def test_it_calls_lazy_resolve_instrumentation query_str = %| { p1: push(value: 1) { value } p2: push(value: 2) { push(value: 3) { value } } pushes(values: [1,2,3]) { edges { node { value push(value: 4) { value } } } } } | log = [] self.class.lazy_schema.execute(query_str, context: {lazy_instrumentation: log, pushes: []}) expected_log = [ "PUSH", "Query.push: 1", "Query.push: 2", "Query.pushes: [1, 2, 3]", "PUSH", "LazyPush.push: 3", "LazyPushEdge.node: 1", "LazyPushEdge.node: 2", "LazyPushEdge.node: 3", "PUSH", "LazyPush.push: 4", "LazyPush.push: 4", "LazyPush.push: 4", ] assert_equal expected_log, log end def test_it_skips_ctx_skip query_string = <<-GRAPHQL { p0: push(value: 15) { value } p1: push(value: 1) { value } p2: push(value: 2) { value p3: push(value: 15) { value } } } GRAPHQL pushes = [] res = self.class.lazy_schema.execute(query_string, context: {pushes: pushes}) assert_equal [[1,2]], pushes assert_equal({"data"=>{"p1"=>{"value"=>1}, "p2"=>{"value"=>2}}}, res) end end end end end end ruby-graphql-1.9.19/lib/graphql/compatibility/lazy_execution_specification/000077500000000000000000000000001362601351000272445ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb000066400000000000000000000063421362601351000320750ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Compatibility module LazyExecutionSpecification module LazySchema class LazyPush attr_reader :value def initialize(ctx, value) if value == 13 @value = nil elsif value == 14 @value = GraphQL::ExecutionError.new("oops!") elsif value == 15 @skipped = true @value = ctx.skip else @value = value end @context = ctx pushes = @context[:lazy_pushes] ||= [] if !@skipped pushes << @value end end def push if @skipped @value else if @context[:lazy_pushes].include?(@value) @context[:lazy_instrumentation] && @context[:lazy_instrumentation] << "PUSH" @context[:pushes] << @context[:lazy_pushes] @context[:lazy_pushes] = [] end # Something that _behaves_ like this object, but isn't registered lazy OpenStruct.new(value: @value) end end end class LazyPushCollection def initialize(ctx, values) @ctx = ctx @values = values end def push @values.map { |v| LazyPush.new(@ctx, v) } end def value @values end end module LazyInstrumentation def self.instrument(type, field) prev_lazy_resolve = field.lazy_resolve_proc field.redefine { lazy_resolve ->(o, a, c) { result = prev_lazy_resolve.call(o, a, c) c[:lazy_instrumentation] && c[:lazy_instrumentation].push("#{type.name}.#{field.name}: #{o.value}") result } } end end def self.build(execution_strategy) lazy_push_type = GraphQL::ObjectType.define do name "LazyPush" field :value, !types.Int field :push, !lazy_push_type do argument :value, types.Int resolve ->(o, a, c) { LazyPush.new(c, a[:value]) } end end query_type = GraphQL::ObjectType.define do name "Query" field :push, !lazy_push_type do argument :value, types.Int resolve ->(o, a, c) { LazyPush.new(c, a[:value]) } end connection :pushes, lazy_push_type.connection_type do argument :values, types[types.Int], method_access: false resolve ->(o, a, c) { LazyPushCollection.new(c, a[:values]) } end end GraphQL::Schema.define do query(query_type) mutation(query_type) query_execution_strategy(execution_strategy) mutation_execution_strategy(execution_strategy) lazy_resolve(LazyPush, :push) lazy_resolve(LazyPushCollection, :push) instrument(:field, LazyInstrumentation) end end end end end end ruby-graphql-1.9.19/lib/graphql/compatibility/query_parser_specification.rb000066400000000000000000000224131362601351000272520ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/compatibility/query_parser_specification/query_assertions" require "graphql/compatibility/query_parser_specification/parse_error_specification" module GraphQL module Compatibility # This asserts that a given parse function turns a string into # the proper tree of {{GraphQL::Language::Nodes}}. module QueryParserSpecification # @yieldparam query_string [String] A query string to parse # @yieldreturn [GraphQL::Language::Nodes::Document] # @return [Class] A test suite for this parse function def self.build_suite(&block) Class.new(Minitest::Test) do include QueryAssertions include ParseErrorSpecification @@parse_fn = block def parse(query_string) @@parse_fn.call(query_string) end def test_it_parses_queries document = parse(QUERY_STRING) query = document.definitions.first assert_valid_query(query) assert_valid_fragment(document.definitions.last) assert_valid_variable(query.variables.first) field = query.selections.first assert_valid_field(field) assert_valid_variable_argument(field.arguments.first) assert_valid_literal_argument(field.arguments.last) assert_valid_directive(field.directives.first) fragment_spread = query.selections[1].selections.last assert_valid_fragment_spread(fragment_spread) assert_valid_typed_inline_fragment(query.selections[2]) assert_valid_typeless_inline_fragment(query.selections[3]) end def test_it_parses_unnamed_queries document = parse("{ name, age, height }") operation = document.definitions.first assert_equal 1, document.definitions.length assert_equal "query", operation.operation_type assert_equal nil, operation.name assert_equal 3, operation.selections.length end def test_it_parses_the_introspection_query parse(GraphQL::Introspection::INTROSPECTION_QUERY) end def test_it_parses_inputs query_string = %| { field( int: 3, float: 4.7e-24, bool: false, string: "☀︎🏆 \\b \\f \\n \\r \\t \\" \u00b6 \\u00b6 / \\/", enum: ENUM_NAME, array: [7, 8, 9] object: {a: [1,2,3], b: {c: "4"}} unicode_bom: "\xef\xbb\xbfquery" keywordEnum: on nullValue: null nullValueInObject: {a: null, b: "b"} nullValueInArray: ["a", null, "b"] blockString: """ Hello, World """ ) } | document = parse(query_string) inputs = document.definitions.first.selections.first.arguments assert_equal 3, inputs[0].value, "Integers" assert_equal 0.47e-23, inputs[1].value, "Floats" assert_equal false, inputs[2].value, "Booleans" assert_equal %|☀︎🏆 \b \f \n \r \t " ¶ ¶ / /|, inputs[3].value, "Strings" assert_instance_of GraphQL::Language::Nodes::Enum, inputs[4].value assert_equal "ENUM_NAME", inputs[4].value.name, "Enums" assert_equal [7,8,9], inputs[5].value, "Lists" obj = inputs[6].value assert_equal "a", obj.arguments[0].name assert_equal [1,2,3], obj.arguments[0].value assert_equal "b", obj.arguments[1].name assert_equal "c", obj.arguments[1].value.arguments[0].name assert_equal "4", obj.arguments[1].value.arguments[0].value assert_equal %|\xef\xbb\xbfquery|, inputs[7].value, "Unicode BOM" assert_equal "on", inputs[8].value.name, "Enum value 'on'" assert_instance_of GraphQL::Language::Nodes::NullValue, inputs[9].value args = inputs[10].value.arguments assert_instance_of GraphQL::Language::Nodes::NullValue, args.find{ |arg| arg.name == 'a' }.value assert_equal 'b', args.find{ |arg| arg.name == 'b' }.value values = inputs[11].value assert_equal 'a', values[0] assert_instance_of GraphQL::Language::Nodes::NullValue, values[1] assert_equal 'b', values[2] block_str_value = inputs[12].value assert_equal "Hello,\n World", block_str_value end def test_it_doesnt_parse_nonsense_variables query_string_1 = "query Vars($var1) { cheese(id: $var1) { flavor } }" query_string_2 = "query Vars2($var1: Int = $var1) { cheese(id: $var1) { flavor } }" err_1 = assert_raises(GraphQL::ParseError) do parse(query_string_1) end assert_equal [1,17], [err_1.line, err_1.col] err_2 = assert_raises(GraphQL::ParseError) do parse(query_string_2) end assert_equal [1,26], [err_2.line, err_2.col] end def test_enum_value_definitions_have_a_position document = parse(""" enum Enum { VALUE } """) assert_equal [3, 17], document.definitions[0].values[0].position end def test_field_definitions_have_a_position document = parse(""" type A { field: String } """) assert_equal [3, 17], document.definitions[0].fields[0].position end def test_input_value_definitions_have_a_position document = parse(""" input A { field: String } """) assert_equal [3, 17], document.definitions[0].fields[0].position end def test_parses_when_there_are_no_interfaces schema = " type A { a: String } " document = parse(schema) assert_equal [], document.definitions[0].interfaces.map(&:name) end def test_parses_implements_with_leading_ampersand schema = " type A implements & B { a: String } " document = parse(schema) assert_equal ["B"], document.definitions[0].interfaces.map(&:name) assert_equal [2, 35], document.definitions[0].interfaces[0].position end def test_parses_implements_with_leading_ampersand_and_multiple_interfaces schema = " type A implements & B & C { a: String } " document = parse(schema) assert_equal ["B", "C"], document.definitions[0].interfaces.map(&:name) assert_equal [2, 35], document.definitions[0].interfaces[0].position assert_equal [2, 39], document.definitions[0].interfaces[1].position end def test_parses_implements_without_leading_ampersand schema = " type A implements B { a: String } " document = parse(schema) assert_equal ["B"], document.definitions[0].interfaces.map(&:name) assert_equal [2, 33], document.definitions[0].interfaces[0].position end def test_parses_implements_without_leading_ampersand_and_multiple_interfaces schema = " type A implements B & C { a: String } " document = parse(schema) assert_equal ["B", "C"], document.definitions[0].interfaces.map(&:name) assert_equal [2, 33], document.definitions[0].interfaces[0].position assert_equal [2, 37], document.definitions[0].interfaces[1].position end def test_supports_old_syntax_for_parsing_multiple_interfaces schema = " type A implements B, C { a: String } " document = parse(schema) assert_equal ["B", "C"], document.definitions[0].interfaces.map(&:name) assert_equal [2, 33], document.definitions[0].interfaces[0].position assert_equal [2, 36], document.definitions[0].interfaces[1].position end end end QUERY_STRING = %| query getStuff($someVar: Int = 1, $anotherVar: [String!] ) @skip(if: false) { myField: someField(someArg: $someVar, ok: 1.4) @skip(if: $anotherVar) @thing(or: "Whatever") anotherField(someArg: [1,2,3]) { nestedField ... moreNestedFields @skip(if: true) } ... on OtherType @include(unless: false){ field(arg: [{key: "value", anotherKey: 0.9, anotherAnotherKey: WHATEVER}]) anotherField } ... { id } } fragment moreNestedFields on NestedType @or(something: "ok") { anotherNestedField @enum(directive: true) } | end end end ruby-graphql-1.9.19/lib/graphql/compatibility/query_parser_specification/000077500000000000000000000000001362601351000267235ustar00rootroot00000000000000parse_error_specification.rb000066400000000000000000000053551362601351000344240ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/compatibility/query_parser_specification# frozen_string_literal: true module GraphQL module Compatibility module QueryParserSpecification module ParseErrorSpecification def assert_raises_parse_error(query_string) assert_raises(GraphQL::ParseError) { parse(query_string) } end def test_it_includes_line_and_column err = assert_raises_parse_error(" query getCoupons { allCoupons: {data{id}} } ") assert_includes(err.message, '{') assert_equal(3, err.line) assert_equal(27, err.col) end def test_it_rejects_unterminated_strings assert_raises_parse_error('{ " }') assert_raises_parse_error(%|{ "\n" }|) end def test_it_rejects_unexpected_ends assert_raises_parse_error("query { stuff { thing }") end def assert_rejects_character(char) err = assert_raises_parse_error("{ field#{char} }") expected_char = char.inspect.gsub('"', '').downcase msg_downcase = err.message.downcase # Case-insensitive for UTF-8 printing assert_includes(msg_downcase, expected_char, "The message includes the invalid character") end def test_it_rejects_invalid_characters assert_rejects_character(";") assert_rejects_character("\a") assert_rejects_character("\xef") assert_rejects_character("\v") assert_rejects_character("\f") assert_rejects_character("\xa0") end def test_it_rejects_bad_unicode assert_raises_parse_error(%|{ field(arg:"\\x") }|) assert_raises_parse_error(%|{ field(arg:"\\u1") }|) assert_raises_parse_error(%|{ field(arg:"\\u0XX1") }|) assert_raises_parse_error(%|{ field(arg:"\\uXXXX") }|) assert_raises_parse_error(%|{ field(arg:"\\uFXXX") }|) assert_raises_parse_error(%|{ field(arg:"\\uXXXF") }|) end def test_it_rejects_empty_inline_fragments assert_raises_parse_error(" query { viewer { login { ... on String { } } } } ") end def assert_empty_document(query_string) doc = parse(query_string) assert_equal 0, doc.definitions.length end def test_it_parses_blank_queries assert_empty_document("") assert_empty_document(" ") assert_empty_document("\t \t") end def test_it_restricts_on assert_raises_parse_error("{ ...on }") assert_raises_parse_error("fragment on on Type { field }") end end end end end ruby-graphql-1.9.19/lib/graphql/compatibility/query_parser_specification/query_assertions.rb000066400000000000000000000057171362601351000327010ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Compatibility module QueryParserSpecification module QueryAssertions def assert_valid_query(query) assert query.is_a?(GraphQL::Language::Nodes::OperationDefinition) assert_equal "getStuff", query.name assert_equal "query", query.operation_type assert_equal 2, query.variables.length assert_equal 4, query.selections.length assert_equal 1, query.directives.length assert_equal [2, 13], [query.line, query.col] end def assert_valid_fragment(fragment_def) assert fragment_def.is_a?(GraphQL::Language::Nodes::FragmentDefinition) assert_equal "moreNestedFields", fragment_def.name assert_equal 1, fragment_def.selections.length assert_equal "NestedType", fragment_def.type.name assert_equal 1, fragment_def.directives.length assert_equal [20, 13], fragment_def.position end def assert_valid_variable(variable) assert_equal "someVar", variable.name assert_equal "Int", variable.type.name assert_equal 1, variable.default_value assert_equal [2, 28], variable.position end def assert_valid_field(field) assert_equal "someField", field.name assert_equal "myField", field.alias assert_equal 2, field.directives.length assert_equal 2, field.arguments.length assert_equal 0, field.selections.length assert_equal [3, 15], field.position end def assert_valid_literal_argument(argument) assert_equal "ok", argument.name assert_equal 1.4, argument.value end def assert_valid_variable_argument(argument) assert_equal "someArg", argument.name assert_equal "someVar", argument.value.name end def assert_valid_fragment_spread(fragment_spread) assert_equal "moreNestedFields", fragment_spread.name assert_equal 1, fragment_spread.directives.length assert_equal [7, 17], fragment_spread.position end def assert_valid_directive(directive) assert_equal "skip", directive.name assert_equal "if", directive.arguments.first.name assert_equal 1, directive.arguments.length assert_equal [3, 62], directive.position end def assert_valid_typed_inline_fragment(inline_fragment) assert_equal "OtherType", inline_fragment.type.name assert_equal 2, inline_fragment.selections.length assert_equal 1, inline_fragment.directives.length assert_equal [10, 15], inline_fragment.position end def assert_valid_typeless_inline_fragment(inline_fragment) assert_equal nil, inline_fragment.type assert_equal 1, inline_fragment.selections.length assert_equal 0, inline_fragment.directives.length end end end end end ruby-graphql-1.9.19/lib/graphql/compatibility/schema_parser_specification.rb000066400000000000000000000713601362601351000273520ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Compatibility # This asserts that a given parse function turns a string into # the proper tree of {{GraphQL::Language::Nodes}}. module SchemaParserSpecification # @yieldparam query_string [String] A query string to parse # @yieldreturn [GraphQL::Language::Nodes::Document] # @return [Class] A test suite for this parse function def self.build_suite(&block) Class.new(Minitest::Test) do @@parse_fn = block def parse(query_string) @@parse_fn.call(query_string) end def test_it_parses_object_types document = parse(' # This is what # somebody said about something type Comment implements Node @deprecated(reason: "No longer supported") { id: ID! } ') type = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeDefinition, type.class assert_equal 'Comment', type.name assert_equal "This is what\nsomebody said about something", type.description assert_equal ['Node'], type.interfaces.map(&:name) assert_equal ['id'], type.fields.map(&:name) assert_equal [], type.fields[0].arguments assert_equal 'ID', type.fields[0].type.of_type.name assert_equal 1, type.directives.length deprecated_directive = type.directives[0] assert_equal 'deprecated', deprecated_directive.name assert_equal 'reason', deprecated_directive.arguments[0].name assert_equal 'No longer supported', deprecated_directive.arguments[0].value end def test_it_parses_scalars document = parse('scalar DateTime') type = document.definitions.first assert_equal GraphQL::Language::Nodes::ScalarTypeDefinition, type.class assert_equal 'DateTime', type.name end def test_it_parses_enum_types document = parse(' enum DogCommand { # Good dog SIT DOWN @deprecated(reason: "No longer supported") HEEL } ') type = document.definitions.first assert_equal GraphQL::Language::Nodes::EnumTypeDefinition, type.class assert_equal 'DogCommand', type.name assert_equal 3, type.values.length assert_equal 'SIT', type.values[0].name assert_equal [], type.values[0].directives assert_equal "Good dog", type.values[0].description assert_equal 'DOWN', type.values[1].name assert_equal 1, type.values[1].directives.length deprecated_directive = type.values[1].directives[0] assert_equal 'deprecated', deprecated_directive.name assert_equal 'reason', deprecated_directive.arguments[0].name assert_equal 'No longer supported', deprecated_directive.arguments[0].value assert_equal 'HEEL', type.values[2].name assert_equal [], type.values[2].directives end def test_it_parses_union_types document = parse( "union BagOfThings = \n" \ "A |\n" \ "B |\n" \ "C" ) union = document.definitions.first assert_equal GraphQL::Language::Nodes::UnionTypeDefinition, union.class assert_equal 'BagOfThings', union.name assert_equal 3, union.types.length assert_equal [1, 1], union.position assert_equal GraphQL::Language::Nodes::TypeName, union.types[0].class assert_equal 'A', union.types[0].name assert_equal [2, 1], union.types[0].position assert_equal GraphQL::Language::Nodes::TypeName, union.types[1].class assert_equal 'B', union.types[1].name assert_equal [3, 1], union.types[1].position assert_equal GraphQL::Language::Nodes::TypeName, union.types[2].class assert_equal 'C', union.types[2].name assert_equal [4, 1], union.types[2].position end def test_it_parses_input_types document = parse(' input EmptyMutationInput { clientMutationId: String } ') type = document.definitions.first assert_equal GraphQL::Language::Nodes::InputObjectTypeDefinition, type.class assert_equal 'EmptyMutationInput', type.name assert_equal ['clientMutationId'], type.fields.map(&:name) assert_equal 'String', type.fields[0].type.name assert_equal nil, type.fields[0].default_value end def test_it_parses_directives document = parse(' directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT ') type = document.definitions.first assert_equal GraphQL::Language::Nodes::DirectiveDefinition, type.class assert_equal 'include', type.name assert_equal 1, type.arguments.length assert_equal 'if', type.arguments[0].name assert_equal 'Boolean', type.arguments[0].type.of_type.name assert_equal 3, type.locations.length assert_instance_of GraphQL::Language::Nodes::DirectiveLocation, type.locations[0] assert_equal 'FIELD', type.locations[0].name assert_equal [3, 20], type.locations[0].position assert_instance_of GraphQL::Language::Nodes::DirectiveLocation, type.locations[1] assert_equal 'FRAGMENT_SPREAD', type.locations[1].name assert_equal [4, 19], type.locations[1].position assert_instance_of GraphQL::Language::Nodes::DirectiveLocation, type.locations[2] assert_equal 'INLINE_FRAGMENT', type.locations[2].name assert_equal [5, 19], type.locations[2].position end def test_it_parses_field_arguments document = parse(' type Mutation { post( id: ID! @deprecated(reason: "Not used"), # This is what goes in the post data: String ): Post } ') field = document.definitions.first.fields.first assert_equal ['id', 'data'], field.arguments.map(&:name) id_arg = field.arguments[0] deprecated_directive = id_arg.directives[0] assert_equal 'deprecated', deprecated_directive.name assert_equal 'reason', deprecated_directive.arguments[0].name assert_equal 'Not used', deprecated_directive.arguments[0].value data_arg = field.arguments[1] assert_equal "data", data_arg.name assert_equal "This is what goes in the post", data_arg.description end def test_it_parses_schema_definition document = parse(' schema { query: QueryRoot mutation: MutationRoot subscription: SubscriptionRoot } ') schema = document.definitions.first assert_equal 'QueryRoot', schema.query assert_equal 'MutationRoot', schema.mutation assert_equal 'SubscriptionRoot', schema.subscription end def test_it_parses_schema_extensions document = parse(' extend schema { query: QueryRoot mutation: MutationRoot subscription: SubscriptionRoot } ') schema_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::SchemaExtension, schema_extension.class assert_equal [2, 15], schema_extension.position assert_equal 'QueryRoot', schema_extension.query assert_equal 'MutationRoot', schema_extension.mutation assert_equal 'SubscriptionRoot', schema_extension.subscription end def test_it_parses_schema_extensions_with_directives document = parse(' extend schema @something { query: QueryRoot } ') schema_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::SchemaExtension, schema_extension.class assert_equal 1, schema_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, schema_extension.directives.first.class assert_equal 'something', schema_extension.directives.first.name assert_equal 'QueryRoot', schema_extension.query assert_equal nil, schema_extension.mutation assert_equal nil, schema_extension.subscription end def test_it_parses_schema_extensions_with_only_directives document = parse(' extend schema @something ') schema_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::SchemaExtension, schema_extension.class assert_equal 1, schema_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, schema_extension.directives.first.class assert_equal 'something', schema_extension.directives.first.name assert_equal nil, schema_extension.query assert_equal nil, schema_extension.mutation assert_equal nil, schema_extension.subscription end def test_it_parses_scalar_extensions document = parse(' extend scalar Date @something @somethingElse ') scalar_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ScalarTypeExtension, scalar_extension.class assert_equal 'Date', scalar_extension.name assert_equal [2, 15], scalar_extension.position assert_equal 2, scalar_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, scalar_extension.directives.first.class assert_equal 'something', scalar_extension.directives.first.name assert_equal GraphQL::Language::Nodes::Directive, scalar_extension.directives.last.class assert_equal 'somethingElse', scalar_extension.directives.last.name end def test_it_parses_object_type_extensions_with_field_definitions document = parse(' extend type User { login: String! } ') object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeExtension, object_type_extension.class assert_equal 'User', object_type_extension.name assert_equal [2, 15], object_type_extension.position assert_equal 1, object_type_extension.fields.length assert_equal GraphQL::Language::Nodes::FieldDefinition, object_type_extension.fields.first.class end def test_it_parses_object_type_extensions_with_field_definitions_and_directives document = parse(' extend type User @deprecated { login: String! } ') object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeExtension, object_type_extension.class assert_equal 'User', object_type_extension.name assert_equal [2, 15], object_type_extension.position assert_equal 1, object_type_extension.fields.length assert_equal GraphQL::Language::Nodes::FieldDefinition, object_type_extension.fields.first.class assert_equal 1, object_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, object_type_extension.directives.first.class end def test_it_parses_object_type_extensions_with_field_definitions_and_implements document = parse(' extend type User implements Node { login: String! } ') object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeExtension, object_type_extension.class assert_equal 'User', object_type_extension.name assert_equal [2, 15], object_type_extension.position assert_equal 1, object_type_extension.fields.length assert_equal GraphQL::Language::Nodes::FieldDefinition, object_type_extension.fields.first.class assert_equal 1, object_type_extension.interfaces.length assert_equal GraphQL::Language::Nodes::TypeName, object_type_extension.interfaces.first.class end def test_it_parses_object_type_extensions_with_only_directives document = parse(' extend type User @deprecated ') object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeExtension, object_type_extension.class assert_equal 'User', object_type_extension.name assert_equal [2, 15], object_type_extension.position assert_equal 1, object_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, object_type_extension.directives.first.class assert_equal 'deprecated', object_type_extension.directives.first.name end def test_it_parses_object_type_extensions_with_implements_and_directives document = parse(' extend type User implements Node @deprecated ') object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeExtension, object_type_extension.class assert_equal 'User', object_type_extension.name assert_equal [2, 15], object_type_extension.position assert_equal 1, object_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, object_type_extension.directives.first.class assert_equal 'deprecated', object_type_extension.directives.first.name assert_equal 1, object_type_extension.interfaces.length assert_equal GraphQL::Language::Nodes::TypeName, object_type_extension.interfaces.first.class assert_equal 'Node', object_type_extension.interfaces.first.name end def test_it_parses_object_type_extensions_with_only_implements document = parse(' extend type User implements Node ') object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeExtension, object_type_extension.class assert_equal 'User', object_type_extension.name assert_equal [2, 15], object_type_extension.position assert_equal 1, object_type_extension.interfaces.length assert_equal GraphQL::Language::Nodes::TypeName, object_type_extension.interfaces.first.class assert_equal 'Node', object_type_extension.interfaces.first.name end def test_it_parses_interface_type_extensions_with_directives_and_fields document = parse(' extend interface Node @directive { field: String } ') interface_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::InterfaceTypeExtension, interface_type_extension.class assert_equal 'Node', interface_type_extension.name assert_equal [2, 15], interface_type_extension.position assert_equal 1, interface_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, interface_type_extension.directives.first.class assert_equal 'directive', interface_type_extension.directives.first.name assert_equal 1, interface_type_extension.fields.length assert_equal GraphQL::Language::Nodes::FieldDefinition, interface_type_extension.fields.first.class assert_equal 'field', interface_type_extension.fields.first.name end def test_it_parses_interface_type_extensions_with_fields document = parse(' extend interface Node { field: String } ') interface_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::InterfaceTypeExtension, interface_type_extension.class assert_equal 'Node', interface_type_extension.name assert_equal [2, 15], interface_type_extension.position assert_equal 0, interface_type_extension.directives.length assert_equal 1, interface_type_extension.fields.length assert_equal GraphQL::Language::Nodes::FieldDefinition, interface_type_extension.fields.first.class assert_equal 'field', interface_type_extension.fields.first.name end def test_it_parses_interface_type_extensions_with_directives document = parse(' extend interface Node @directive ') interface_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::InterfaceTypeExtension, interface_type_extension.class assert_equal 'Node', interface_type_extension.name assert_equal [2, 15], interface_type_extension.position assert_equal 1, interface_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, interface_type_extension.directives.first.class assert_equal 'directive', interface_type_extension.directives.first.name end def test_it_parses_union_type_extension_with_union_members document = parse(' extend union BagOfThings = A | B ') union_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::UnionTypeExtension, union_type_extension.class assert_equal 'BagOfThings', union_type_extension.name assert_equal [2, 15], union_type_extension.position assert_equal 0, union_type_extension.directives.length assert_equal 2, union_type_extension.types.length assert_equal GraphQL::Language::Nodes::TypeName, union_type_extension.types.first.class assert_equal 'A', union_type_extension.types.first.name end def test_it_parses_union_type_extension_with_directives_and_union_members document = parse(' extend union BagOfThings @directive = A | B ') union_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::UnionTypeExtension, union_type_extension.class assert_equal 'BagOfThings', union_type_extension.name assert_equal [2, 15], union_type_extension.position assert_equal 1, union_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, union_type_extension.directives.first.class assert_equal 'directive', union_type_extension.directives.first.name assert_equal 2, union_type_extension.types.length assert_equal GraphQL::Language::Nodes::TypeName, union_type_extension.types.first.class assert_equal 'A', union_type_extension.types.first.name end def test_it_parses_union_type_extension_with_directives document = parse(' extend union BagOfThings @directive ') union_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::UnionTypeExtension, union_type_extension.class assert_equal 'BagOfThings', union_type_extension.name assert_equal [2, 15], union_type_extension.position assert_equal 1, union_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, union_type_extension.directives.first.class assert_equal 'directive', union_type_extension.directives.first.name assert_equal 0, union_type_extension.types.length end def test_it_parses_enum_type_extension_with_values document = parse(' extend enum Status { DRAFT PUBLISHED } ') enum_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::EnumTypeExtension, enum_type_extension.class assert_equal 'Status', enum_type_extension.name assert_equal [2, 15], enum_type_extension.position assert_equal 0, enum_type_extension.directives.length assert_equal 2, enum_type_extension.values.length assert_equal GraphQL::Language::Nodes::EnumValueDefinition, enum_type_extension.values.first.class assert_equal 'DRAFT', enum_type_extension.values.first.name end def test_it_parses_enum_type_extension_with_directives_and_values document = parse(' extend enum Status @directive { DRAFT PUBLISHED } ') enum_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::EnumTypeExtension, enum_type_extension.class assert_equal 'Status', enum_type_extension.name assert_equal [2, 15], enum_type_extension.position assert_equal 1, enum_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, enum_type_extension.directives.first.class assert_equal 'directive', enum_type_extension.directives.first.name assert_equal 2, enum_type_extension.values.length assert_equal GraphQL::Language::Nodes::EnumValueDefinition, enum_type_extension.values.first.class assert_equal 'DRAFT', enum_type_extension.values.first.name end def test_it_parses_enum_type_extension_with_directives document = parse(' extend enum Status @directive ') enum_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::EnumTypeExtension, enum_type_extension.class assert_equal 'Status', enum_type_extension.name assert_equal [2, 15], enum_type_extension.position assert_equal 1, enum_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, enum_type_extension.directives.first.class assert_equal 'directive', enum_type_extension.directives.first.name assert_equal 0, enum_type_extension.values.length end def test_it_parses_input_object_type_extension_with_fields document = parse(' extend input UserInput { login: String! } ') input_object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::InputObjectTypeExtension, input_object_type_extension.class assert_equal 'UserInput', input_object_type_extension.name assert_equal [2, 15], input_object_type_extension.position assert_equal 1, input_object_type_extension.fields.length assert_equal GraphQL::Language::Nodes::InputValueDefinition, input_object_type_extension.fields.first.class assert_equal 'login', input_object_type_extension.fields.first.name assert_equal 0, input_object_type_extension.directives.length end def test_it_parses_input_object_type_extension_with_directives_and_fields document = parse(' extend input UserInput @deprecated { login: String! } ') input_object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::InputObjectTypeExtension, input_object_type_extension.class assert_equal 'UserInput', input_object_type_extension.name assert_equal [2, 15], input_object_type_extension.position assert_equal 1, input_object_type_extension.fields.length assert_equal GraphQL::Language::Nodes::InputValueDefinition, input_object_type_extension.fields.first.class assert_equal 'login', input_object_type_extension.fields.first.name assert_equal 1, input_object_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, input_object_type_extension.directives.first.class assert_equal 'deprecated', input_object_type_extension.directives.first.name end def test_it_parses_input_object_type_extension_with_directives document = parse(' extend input UserInput @deprecated ') input_object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::InputObjectTypeExtension, input_object_type_extension.class assert_equal 'UserInput', input_object_type_extension.name assert_equal [2, 15], input_object_type_extension.position assert_equal 0, input_object_type_extension.fields.length assert_equal 1, input_object_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, input_object_type_extension.directives.first.class assert_equal 'deprecated', input_object_type_extension.directives.first.name end def test_it_parses_whole_definition_with_descriptions document = parse(SCHEMA_DEFINITION_STRING) assert_equal 6, document.definitions.size schema_definition, directive_definition, enum_type_definition, object_type_definition, input_object_type_definition, interface_type_definition = document.definitions assert_equal GraphQL::Language::Nodes::SchemaDefinition, schema_definition.class assert_equal GraphQL::Language::Nodes::DirectiveDefinition, directive_definition.class assert_equal 'This is a directive', directive_definition.description assert_equal GraphQL::Language::Nodes::EnumTypeDefinition, enum_type_definition.class assert_equal "Multiline comment\n\nWith an enum", enum_type_definition.description assert_nil enum_type_definition.values[0].description assert_equal 'Not a creative color', enum_type_definition.values[1].description assert_equal GraphQL::Language::Nodes::ObjectTypeDefinition, object_type_definition.class assert_equal 'Comment without preceding space', object_type_definition.description assert_equal 'And a field to boot', object_type_definition.fields[0].description assert_equal GraphQL::Language::Nodes::InputObjectTypeDefinition, input_object_type_definition.class assert_equal 'Comment for input object types', input_object_type_definition.description assert_equal 'Color of the car', input_object_type_definition.fields[0].description assert_equal GraphQL::Language::Nodes::InterfaceTypeDefinition, interface_type_definition.class assert_equal 'Comment for interface definitions', interface_type_definition.description assert_equal 'Amount of wheels', interface_type_definition.fields[0].description brand_field = interface_type_definition.fields[1] assert_equal 1, brand_field.arguments.length assert_equal 'argument', brand_field.arguments[0].name assert_instance_of GraphQL::Language::Nodes::NullValue, brand_field.arguments[0].default_value end end end SCHEMA_DEFINITION_STRING = %| # Schema at beginning of file schema { query: Hello } # Comment between two definitions are omitted # This is a directive directive @foo( # It has an argument arg: Int ) on FIELD # Multiline comment # # With an enum enum Color { RED # Not a creative color GREEN BLUE } #Comment without preceding space type Hello { # And a field to boot str: String } # Comment for input object types input Car { # Color of the car color: String! } # Comment for interface definitions interface Vehicle { # Amount of wheels wheels: Int! brand(argument: String = null): String! } # Comment at the end of schema | end end end ruby-graphql-1.9.19/lib/graphql/define.rb000066400000000000000000000023771362601351000202210ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/define/assign_argument" require "graphql/define/assign_connection" require "graphql/define/assign_enum_value" require "graphql/define/assign_global_id_field" require "graphql/define/assign_mutation_function" require "graphql/define/assign_object_field" require "graphql/define/defined_object_proxy" require "graphql/define/instance_definable" require "graphql/define/no_definition_error" require "graphql/define/non_null_with_bang" require "graphql/define/type_definer" module GraphQL module Define # A helper for definitions that store their value in `#metadata`. # # @example Storing application classes with GraphQL types # # Make a custom definition # GraphQL::ObjectType.accepts_definitions(resolves_to_class_names: GraphQL::Define.assign_metadata_key(:resolves_to_class_names)) # # # After definition, read the key from metadata # PostType.metadata[:resolves_to_class_names] # => [...] # # @param key [Object] the key to assign in metadata # @return [#call(defn, value)] an assignment for `.accepts_definitions` which writes `key` to `#metadata` def self.assign_metadata_key(key) GraphQL::Define::InstanceDefinable::AssignMetadataKey.new(key) end end end ruby-graphql-1.9.19/lib/graphql/define/000077500000000000000000000000001362601351000176635ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/define/assign_argument.rb000066400000000000000000000005321362601351000233760ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define # Turn argument configs into a {GraphQL::Argument}. module AssignArgument def self.call(target, *args, **kwargs, &block) argument = GraphQL::Argument.from_dsl(*args, **kwargs, &block) target.arguments[argument.name] = argument end end end end ruby-graphql-1.9.19/lib/graphql/define/assign_connection.rb000066400000000000000000000007741362601351000237230ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define module AssignConnection def self.call(type_defn, *field_args, max_page_size: nil, **field_kwargs, &field_block) underlying_field = GraphQL::Define::AssignObjectField.call(type_defn, *field_args, **field_kwargs, &field_block) underlying_field.connection_max_page_size = max_page_size underlying_field.connection = true type_defn.fields[underlying_field.name] = underlying_field end end end end ruby-graphql-1.9.19/lib/graphql/define/assign_enum_value.rb000066400000000000000000000011031362601351000237070ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define # Turn enum value configs into a {GraphQL::EnumType::EnumValue} and register it with the {GraphQL::EnumType} module AssignEnumValue def self.call(enum_type, name, desc = nil, deprecation_reason: nil, value: name, &block) enum_value = GraphQL::EnumType::EnumValue.define( name: name.to_s, description: desc, deprecation_reason: deprecation_reason, value: value, &block ) enum_type.add_value(enum_value) end end end end ruby-graphql-1.9.19/lib/graphql/define/assign_global_id_field.rb000066400000000000000000000005541362601351000246370ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define module AssignGlobalIdField def self.call(type_defn, field_name) resolve = GraphQL::Relay::GlobalIdResolve.new(type: type_defn) GraphQL::Define::AssignObjectField.call(type_defn, field_name, type: GraphQL::ID_TYPE.to_non_null_type, resolve: resolve) end end end end ruby-graphql-1.9.19/lib/graphql/define/assign_mutation_function.rb000066400000000000000000000021071362601351000253210ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define module AssignMutationFunction def self.call(target, function) # TODO: get all this logic somewhere easier to test if !function.type.is_a?(GraphQL::ObjectType) raise "Mutation functions must return object types (not #{function.type.unwrap})" end target.return_type = function.type.redefine { name(target.name + "Payload") field :clientMutationId, types.String, "A unique identifier for the client performing the mutation.", property: :client_mutation_id } target.arguments = function.arguments target.description = function.description target.resolve = ->(o, a, c) { res = function.call(o, a, c) ResultProxy.new(res, a[:clientMutationId]) } end class ResultProxy < SimpleDelegator attr_reader :client_mutation_id def initialize(target, client_mutation_id) @client_mutation_id = client_mutation_id super(target) end end end end end ruby-graphql-1.9.19/lib/graphql/define/assign_object_field.rb000066400000000000000000000025061362601351000241700ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define # Turn field configs into a {GraphQL::Field} and attach it to a {GraphQL::ObjectType} or {GraphQL::InterfaceType} module AssignObjectField def self.call(owner_type, name, type_or_field = nil, desc = nil, function: nil, field: nil, relay_mutation_function: nil, **kwargs, &block) name_s = name.to_s # Move some positional args into keywords if they're present desc && kwargs[:description] ||= desc name && kwargs[:name] ||= name_s if !type_or_field.nil? && !type_or_field.is_a?(GraphQL::Field) # Maybe a string, proc or BaseType kwargs[:type] = type_or_field end base_field = if type_or_field.is_a?(GraphQL::Field) type_or_field.redefine(name: name_s) elsif function func_field = GraphQL::Function.build_field(function) func_field.name = name_s func_field elsif field.is_a?(GraphQL::Field) field.redefine(name: name_s) else nil end obj_field = if base_field base_field.redefine(**kwargs, &block) else GraphQL::Field.define(**kwargs, &block) end # Attach the field to the type owner_type.fields[name_s] = obj_field end end end end ruby-graphql-1.9.19/lib/graphql/define/defined_object_proxy.rb000066400000000000000000000032301362601351000243730ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define # This object delegates most methods to a dictionary of functions, {@dictionary}. # {@target} is passed to the specified function, along with any arguments and block. # This allows a method-based DSL without adding methods to the defined class. class DefinedObjectProxy extend GraphQL::Ruby2Keywords # The object which will be defined by definition functions attr_reader :target def initialize(target) @target = target @dictionary = target.class.dictionary end # Provides shorthand access to GraphQL's built-in types def types GraphQL::Define::TypeDefiner.instance end # Allow `plugin` to perform complex initialization on the definition. # Calls `plugin.use(defn, **kwargs)`. # @param plugin [<#use(defn, **kwargs)>] A plugin object # @param kwargs [Hash] Any options for the plugin def use(plugin, **kwargs) # https://bugs.ruby-lang.org/issues/10708 if kwargs == {} plugin.use(self) else plugin.use(self, **kwargs) end end # Lookup a function from the dictionary and call it if it's found. ruby2_keywords def method_missing(name, *args, &block) definition = @dictionary[name] if definition definition.call(@target, *args, &block) else msg = "#{@target.class.name} can't define '#{name}'" raise NoDefinitionError, msg, caller end end def respond_to_missing?(name, include_private = false) @dictionary[name] || super end end end end ruby-graphql-1.9.19/lib/graphql/define/instance_definable.rb000066400000000000000000000237771362601351000240250ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define # This module provides the `.define { ... }` API for # {GraphQL::BaseType}, {GraphQL::Field} and others. # # Calling `.accepts_definitions(...)` creates: # # - a keyword to the `.define` method # - a helper method in the `.define { ... }` block # # The `.define { ... }` block will be called lazily. To be sure it has been # called, use the private method `#ensure_defined`. That will call the # definition block if it hasn't been called already. # # The goals are: # # - Minimal overhead in consuming classes # - Independence between consuming classes # - Extendable by third-party libraries without monkey-patching or other nastiness # # @example Make a class definable # class Car # include GraphQL::Define::InstanceDefinable # attr_accessor :make, :model, :doors # accepts_definitions( # # These attrs will be defined with plain setters, `{attr}=` # :make, :model, # # This attr has a custom definition which applies the config to the target # doors: ->(car, doors_count) { doors_count.times { car.doors << Door.new } } # ) # ensure_defined(:make, :model, :doors) # # def initialize # @doors = [] # end # end # # class Door; end; # # # Create an instance with `.define`: # subaru_baja = Car.define do # make "Subaru" # model "Baja" # doors 4 # end # # # The custom proc was applied: # subaru_baja.doors #=> [, , , ] # # @example Extending the definition of a class # # Add some definitions: # Car.accepts_definitions(all_wheel_drive: GraphQL::Define.assign_metadata_key(:all_wheel_drive)) # # # Use it in a definition # subaru_baja = Car.define do # # ... # all_wheel_drive true # end # # # Access it from metadata # subaru_baja.metadata[:all_wheel_drive] # => true # # @example Extending the definition of a class via a plugin # # A plugin is any object that responds to `.use(definition)` # module SubaruCar # extend self # # def use(defn) # # `defn` has the same methods as within `.define { ... }` block # defn.make "Subaru" # defn.doors 4 # end # end # # # Use the plugin within a `.define { ... }` block # subaru_baja = Car.define do # use SubaruCar # model 'Baja' # end # # subaru_baja.make # => "Subaru" # subaru_baja.doors # => [, , , ] # # @example Making a copy with an extended definition # # Create an instance with `.define`: # subaru_baja = Car.define do # make "Subaru" # model "Baja" # doors 4 # end # # # Then extend it with `#redefine` # two_door_baja = subaru_baja.redefine do # doors 2 # end module InstanceDefinable def self.included(base) base.extend(ClassMethods) base.ensure_defined(:metadata) end # `metadata` can store arbitrary key-values with an object. # # @return [Hash] Hash for user-defined storage def metadata @metadata ||= {} end # Mutate this instance using functions from its {.definition}s. # Keywords or helpers in the block correspond to keys given to `accepts_definitions`. # # Note that the block is not called right away -- instead, it's deferred until # one of the defined fields is needed. # @return [void] def define(**kwargs, &block) # make sure the previous definition_proc was executed: ensure_defined stash_dependent_methods @pending_definition = Definition.new(kwargs, block) nil end # Shallow-copy this object, then apply new definitions to the copy. # @see {#define} for arguments # @return [InstanceDefinable] A new instance, with any extended definitions def redefine(**kwargs, &block) ensure_defined new_inst = self.dup new_inst.define(**kwargs, &block) new_inst end def initialize_copy(other) super @metadata = other.metadata.dup end private # Run the definition block if it hasn't been run yet. # This can only be run once: the block is deleted after it's used. # You have to call this before using any value which could # come from the definition block. # @return [void] def ensure_defined if @pending_definition defn = @pending_definition @pending_definition = nil revive_dependent_methods begin defn_proxy = DefinedObjectProxy.new(self) # Apply definition from `define(...)` kwargs defn.define_keywords.each do |keyword, value| # Don't splat string hashes, which blows up on Rubies before 2.7 if value.is_a?(Hash) && value.each_key.all? { |k| k.is_a?(Symbol) } defn_proxy.public_send(keyword, **value) else defn_proxy.public_send(keyword, value) end end # and/or apply definition from `define { ... }` block if defn.define_proc defn_proxy.instance_eval(&defn.define_proc) end rescue StandardError # The definition block failed to run, so make this object pending again: stash_dependent_methods @pending_definition = defn raise end end nil end # Take the pending methods and put them back on this object's singleton class. # This reverts the process done by {#stash_dependent_methods} # @return [void] def revive_dependent_methods pending_methods = @pending_methods self.singleton_class.class_eval { pending_methods.each do |method| undef_method(method.name) if method_defined?(method.name) define_method(method.name, method) end } @pending_methods = nil end # Find the method names which were declared as definition-dependent, # then grab the method definitions off of this object's class # and store them for later. # # Then make a dummy method for each of those method names which: # # - Triggers the pending definition, if there is one # - Calls the same method again. # # It's assumed that {#ensure_defined} will put the original method definitions # back in place with {#revive_dependent_methods}. # @return [void] def stash_dependent_methods method_names = self.class.ensure_defined_method_names @pending_methods = method_names.map { |n| self.class.instance_method(n) } self.singleton_class.class_eval do method_names.each do |method_name| undef_method(method_name) if method_defined?(method_name) define_method(method_name) { |*args, &block| ensure_defined self.send(method_name, *args, &block) } end end end class Definition attr_reader :define_keywords, :define_proc def initialize(define_keywords, define_proc) @define_keywords = define_keywords @define_proc = define_proc end end module ClassMethods # Create a new instance # and prepare a definition using its {.definitions}. # @param kwargs [Hash] Key-value pairs corresponding to defininitions from `accepts_definitions` # @param block [Proc] Block which calls helper methods from `accepts_definitions` def define(**kwargs, &block) instance = self.new instance.define(**kwargs, &block) instance end # Attach definitions to this class. # Each symbol in `accepts` will be assigned with `{key}=`. # The last entry in accepts may be a hash of name-proc pairs for custom definitions. def accepts_definitions(*accepts) new_assignments = if accepts.last.is_a?(Hash) accepts.pop.dup else {} end accepts.each do |key| new_assignments[key] = AssignAttribute.new(key) end @own_dictionary = own_dictionary.merge(new_assignments) end def ensure_defined(*method_names) @ensure_defined_method_names ||= [] @ensure_defined_method_names.concat(method_names) nil end def ensure_defined_method_names own_method_names = @ensure_defined_method_names || [] if superclass.respond_to?(:ensure_defined_method_names) superclass.ensure_defined_method_names + own_method_names else own_method_names end end # @return [Hash] combined definitions for self and ancestors def dictionary if superclass.respond_to?(:dictionary) own_dictionary.merge(superclass.dictionary) else own_dictionary end end # @return [Hash] definitions for this class only def own_dictionary @own_dictionary ||= {} end end class AssignMetadataKey def initialize(key) @key = key end def call(defn, value = true) defn.metadata[@key] = value end end class AssignAttribute extend GraphQL::Ruby2Keywords def initialize(attr_name) @attr_assign_method = :"#{attr_name}=" end # Even though we're just using the first value here, # We have to add a splat here to use `ruby2_keywords`, # so that it will accept a `[{}]` input from the caller. ruby2_keywords def call(defn, *value) defn.public_send(@attr_assign_method, value.first) end end end end end ruby-graphql-1.9.19/lib/graphql/define/no_definition_error.rb000066400000000000000000000001741362601351000242470ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define class NoDefinitionError < GraphQL::Error end end end ruby-graphql-1.9.19/lib/graphql/define/non_null_with_bang.rb000066400000000000000000000006071362601351000240610ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define # Wrap the object in NonNullType in response to `!` # @example required Int type # !GraphQL::INT_TYPE # module NonNullWithBang # Make the type non-null # @return [GraphQL::NonNullType] a non-null type which wraps the original type def ! to_non_null_type end end end end ruby-graphql-1.9.19/lib/graphql/define/type_definer.rb000066400000000000000000000017501362601351000226700ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define # Some conveniences for definining return & argument types. # # Passed into initialization blocks, eg {ObjectType#initialize}, {Field#initialize} class TypeDefiner include Singleton # rubocop:disable Naming/MethodName def Int; GraphQL::INT_TYPE; end def String; GraphQL::STRING_TYPE; end def Float; GraphQL::FLOAT_TYPE; end def Boolean; GraphQL::BOOLEAN_TYPE; end def ID; GraphQL::ID_TYPE; end # rubocop:enable Naming/MethodName # Make a {ListType} which wraps the input type # # @example making a list type # list_of_strings = types[types.String] # list_of_strings.inspect # # => "[String]" # # @param type [Type] A type to be wrapped in a ListType # @return [GraphQL::ListType] A ListType wrapping `type` def [](type) type.to_list_type end end end end ruby-graphql-1.9.19/lib/graphql/deprecated_dsl.rb000066400000000000000000000020351362601351000217200ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # There are two ways to apply the deprecated `!` DSL to class-style schema definitions: # # 1. Scoped by file (CRuby only), add to the top of the file: # # using GraphQL::DeprecatedDSL # # (This is a "refinement", there are also other ways to scope it.) # # 2. Global application, add before schema definition: # # GraphQL::DeprecatedDSL.activate # module DeprecatedDSL TYPE_CLASSES = [ GraphQL::Schema::Scalar, GraphQL::Schema::Enum, GraphQL::Schema::InputObject, GraphQL::Schema::Union, GraphQL::Schema::Interface, GraphQL::Schema::Object, ] def self.activate TYPE_CLASSES.each { |c| c.extend(Methods) } GraphQL::Schema::List.include(Methods) GraphQL::Schema::NonNull.include(Methods) end module Methods def ! to_non_null_type end end TYPE_CLASSES.each do |type_class| refine type_class.singleton_class do include Methods end end end end ruby-graphql-1.9.19/lib/graphql/dig.rb000066400000000000000000000011151362601351000175170ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Dig # implemented using the old activesupport #dig instead of the ruby built-in # so we can use some of the magic in Schema::InputObject and Query::Arguments # to handle stringified/symbolized keys. # # @param args [Array<[String, Symbol>] Retrieves the value object corresponding to the each key objects repeatedly # @return [Object] def dig(own_key, *rest_keys) val = self[own_key] if val.nil? || rest_keys.empty? val else val.dig(*rest_keys) end end end end ruby-graphql-1.9.19/lib/graphql/directive.rb000066400000000000000000000100131362601351000207270ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Directives are server-defined hooks for modifying execution. # # Two directives are included out-of-the-box: # - `@skip(if: ...)` Skips the tagged field if the value of `if` is true # - `@include(if: ...)` Includes the tagged field _only_ if `if` is true # class Directive include GraphQL::Define::InstanceDefinable accepts_definitions :locations, :name, :description, :arguments, :default_directive, argument: GraphQL::Define::AssignArgument attr_accessor :locations, :arguments, :name, :description, :arguments_class attr_accessor :ast_node # @api private attr_writer :default_directive ensure_defined(:locations, :arguments, :graphql_name, :name, :description, :default_directive?) # Future-compatible alias # @see {GraphQL::SchemaMember} alias :graphql_name :name # Future-compatible alias # @see {GraphQL::SchemaMember} alias :graphql_definition :itself LOCATIONS = [ QUERY = :QUERY, MUTATION = :MUTATION, SUBSCRIPTION = :SUBSCRIPTION, FIELD = :FIELD, FRAGMENT_DEFINITION = :FRAGMENT_DEFINITION, FRAGMENT_SPREAD = :FRAGMENT_SPREAD, INLINE_FRAGMENT = :INLINE_FRAGMENT, SCHEMA = :SCHEMA, SCALAR = :SCALAR, OBJECT = :OBJECT, FIELD_DEFINITION = :FIELD_DEFINITION, ARGUMENT_DEFINITION = :ARGUMENT_DEFINITION, INTERFACE = :INTERFACE, UNION = :UNION, ENUM = :ENUM, ENUM_VALUE = :ENUM_VALUE, INPUT_OBJECT = :INPUT_OBJECT, INPUT_FIELD_DEFINITION = :INPUT_FIELD_DEFINITION, ] DEFAULT_DEPRECATION_REASON = 'No longer supported' LOCATION_DESCRIPTIONS = { QUERY: 'Location adjacent to a query operation.', MUTATION: 'Location adjacent to a mutation operation.', SUBSCRIPTION: 'Location adjacent to a subscription operation.', FIELD: 'Location adjacent to a field.', FRAGMENT_DEFINITION: 'Location adjacent to a fragment definition.', FRAGMENT_SPREAD: 'Location adjacent to a fragment spread.', INLINE_FRAGMENT: 'Location adjacent to an inline fragment.', SCHEMA: 'Location adjacent to a schema definition.', SCALAR: 'Location adjacent to a scalar definition.', OBJECT: 'Location adjacent to an object type definition.', FIELD_DEFINITION: 'Location adjacent to a field definition.', ARGUMENT_DEFINITION: 'Location adjacent to an argument definition.', INTERFACE: 'Location adjacent to an interface definition.', UNION: 'Location adjacent to a union definition.', ENUM: 'Location adjacent to an enum definition.', ENUM_VALUE: 'Location adjacent to an enum value definition.', INPUT_OBJECT: 'Location adjacent to an input object type definition.', INPUT_FIELD_DEFINITION: 'Location adjacent to an input object field definition.', } def initialize @arguments = {} @default_directive = false end def to_s "" end def on_field? locations.include?(FIELD) end def on_fragment? locations.include?(FRAGMENT_SPREAD) && locations.include?(INLINE_FRAGMENT) end def on_operation? locations.include?(QUERY) && locations.include?(MUTATION) && locations.include?(SUBSCRIPTION) end # @return [Boolean] Is this directive supplied by default? (eg `@skip`) def default_directive? @default_directive end def inspect "#" end end end require "graphql/directive/include_directive" require "graphql/directive/skip_directive" require "graphql/directive/deprecated_directive" ruby-graphql-1.9.19/lib/graphql/directive/000077500000000000000000000000001362601351000204075ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/directive/deprecated_directive.rb000066400000000000000000000012361362601351000250740ustar00rootroot00000000000000# frozen_string_literal: true GraphQL::Directive::DeprecatedDirective = GraphQL::Directive.define do name "deprecated" description "Marks an element of a GraphQL schema as no longer supported." locations([GraphQL::Directive::FIELD_DEFINITION, GraphQL::Directive::ENUM_VALUE]) reason_description = "Explains why this element was deprecated, usually also including a "\ "suggestion for how to access supported similar data. Formatted "\ "in [Markdown](https://daringfireball.net/projects/markdown/)." argument :reason, GraphQL::STRING_TYPE, reason_description, default_value: GraphQL::Directive::DEFAULT_DEPRECATION_REASON default_directive true end ruby-graphql-1.9.19/lib/graphql/directive/include_directive.rb000066400000000000000000000001741362601351000244170ustar00rootroot00000000000000# frozen_string_literal: true GraphQL::Directive::IncludeDirective = GraphQL::Schema::Directive::Include.graphql_definition ruby-graphql-1.9.19/lib/graphql/directive/skip_directive.rb000066400000000000000000000001661362601351000237430ustar00rootroot00000000000000# frozen_string_literal: true GraphQL::Directive::SkipDirective = GraphQL::Schema::Directive::Skip.graphql_definition ruby-graphql-1.9.19/lib/graphql/enum_type.rb000066400000000000000000000133161362601351000207670ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Represents a collection of related values. # By convention, enum names are `SCREAMING_CASE_NAMES`, # but other identifiers are supported too. # # You can use as return types _or_ as inputs. # # By default, enums are passed to `resolve` functions as # the strings that identify them, but you can provide a # custom Ruby value with the `value:` keyword. # # @example An enum of programming languages # LanguageEnum = GraphQL::EnumType.define do # name "Language" # description "Programming language for Web projects" # value("PYTHON", "A dynamic, function-oriented language") # value("RUBY", "A very dynamic language aimed at programmer happiness") # value("JAVASCRIPT", "Accidental lingua franca of the web") # end # # @example Using an enum as a return type # field :favoriteLanguage, LanguageEnum, "This person's favorite coding language" # # ... # # In a query: # Schema.execute("{ coder(id: 1) { favoriteLanguage } }") # # { "data" => { "coder" => { "favoriteLanguage" => "RUBY" } } } # # @example Defining an enum input # field :coders, types[CoderType] do # argument :knowing, types[LanguageEnum] # resolve ->(obj, args, ctx) { # Coder.where(language: args[:knowing]) # } # end # # @example Using an enum as input # { # # find coders who know Python and Ruby # coders(knowing: [PYTHON, RUBY]) { # name # hourlyRate # } # } # # @example Enum whose values are different in Ruby-land # GraphQL::EnumType.define do # # ... # # use the `value:` keyword: # value("RUBY", "Lisp? Smalltalk?", value: :rb) # end # # # Now, resolve functions will receive `:rb` instead of `"RUBY"` # field :favoriteLanguage, LanguageEnum # resolve ->(obj, args, ctx) { # args[:favoriteLanguage] # => :rb # } # # @example Enum whose values are different in ActiveRecord-land # class Language < ActiveRecord::Base # enum language: { # rb: 0 # } # end # # # Now enum type should be defined as # GraphQL::EnumType.define do # # ... # # use the `value:` keyword: # value("RUBY", "Lisp? Smalltalk?", value: 'rb') # end # class EnumType < GraphQL::BaseType accepts_definitions :values, value: GraphQL::Define::AssignEnumValue ensure_defined(:values, :validate_non_null_input, :coerce_non_null_input, :coerce_result) attr_accessor :ast_node def initialize super @values_by_name = {} end def initialize_copy(other) super self.values = other.values.values end # @param new_values [Array] The set of values contained in this type def values=(new_values) @values_by_name = {} new_values.each { |enum_value| add_value(enum_value) } end # @param enum_value [EnumValue] A value to add to this type's set of values def add_value(enum_value) if @values_by_name.key?(enum_value.name) raise "Enum value names must be unique. Value `#{enum_value.name}` already exists on Enum `#{name}`." end @values_by_name[enum_value.name] = enum_value end # @return [Hash EnumValue>] `{name => value}` pairs contained in this type def values @values_by_name end def kind GraphQL::TypeKinds::ENUM end def coerce_result(value, ctx = nil) if ctx.nil? warn_deprecated_coerce("coerce_isolated_result") ctx = GraphQL::Query::NullContext end warden = ctx.warden all_values = warden ? warden.enum_values(self) : @values_by_name.each_value enum_value = all_values.find { |val| val.value == value } if enum_value enum_value.name else raise(UnresolvedValueError, "Can't resolve enum #{name} for #{value.inspect}") end end def to_s name end # A value within an {EnumType} # # Created with the `value` helper class EnumValue include GraphQL::Define::InstanceDefinable ATTRIBUTES = [:name, :description, :deprecation_reason, :value] accepts_definitions(*ATTRIBUTES) attr_accessor(*ATTRIBUTES) attr_accessor :ast_node ensure_defined(*ATTRIBUTES) undef name= def name=(new_name) # Validate that the name is correct GraphQL::NameValidator.validate!(new_name) @name = new_name end def graphql_name name end end class UnresolvedValueError < GraphQL::Error end private # Get the underlying value for this enum value # # @example get episode value from Enum # episode = EpisodeEnum.coerce("NEWHOPE") # episode # => 6 # # @param value_name [String] the string representation of this enum value # @return [Object] the underlying value for this enum value def coerce_non_null_input(value_name, ctx) if @values_by_name.key?(value_name) @values_by_name.fetch(value_name).value elsif match_by_value = @values_by_name.find { |k, v| v.value == value_name } # this is for matching default values, which are "inputs", but they're # the Ruby value, not the GraphQL string. match_by_value[1].value else nil end end def validate_non_null_input(value_name, ctx) result = GraphQL::Query::InputValidationResult.new allowed_values = ctx.warden.enum_values(self) matching_value = allowed_values.find { |v| v.name == value_name } if matching_value.nil? result.add_problem("Expected #{GraphQL::Language.serialize(value_name)} to be one of: #{allowed_values.map(&:name).join(', ')}") end result end end end ruby-graphql-1.9.19/lib/graphql/execution.rb000066400000000000000000000006341362601351000207640ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/execution/directive_checks" require "graphql/execution/execute" require "graphql/execution/flatten" require "graphql/execution/instrumentation" require "graphql/execution/interpreter" require "graphql/execution/lazy" require "graphql/execution/lookahead" require "graphql/execution/multiplex" require "graphql/execution/typecast" require "graphql/execution/errors" ruby-graphql-1.9.19/lib/graphql/execution/000077500000000000000000000000001362601351000204345ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/execution/directive_checks.rb000066400000000000000000000020361362601351000242600ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution # Boolean checks for how an AST node's directives should # influence its execution # @api private module DirectiveChecks SKIP = "skip" INCLUDE = "include" module_function # @return [Boolean] Should this node be included in the query? def include?(directive_ast_nodes, query) directive_ast_nodes.each do |directive_ast_node| name = directive_ast_node.name directive_defn = query.schema.directives[name] case name when SKIP args = query.arguments_for(directive_ast_node, directive_defn) if args['if'] == true return false end when INCLUDE args = query.arguments_for(directive_ast_node, directive_defn) if args['if'] == false return false end else # Undefined directive, or one we don't care about end end true end end end end ruby-graphql-1.9.19/lib/graphql/execution/errors.rb000066400000000000000000000036071362601351000223030ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution # A plugin that wraps query execution with error handling. # Supports class-based schemas and the new {Interpreter} runtime only. # # @example Handling ActiveRecord::NotFound # # class MySchema < GraphQL::Schema # use GraphQL::Execution::Errors # # rescue_from(ActiveRecord::NotFound) do |err, obj, args, ctx, field| # ErrorTracker.log("Not Found: #{err.message}") # nil # end # end # class Errors def self.use(schema) schema_class = schema.is_a?(Class) ? schema : schema.target.class schema_class.error_handler = self.new(schema_class) end def initialize(schema) @schema = schema end class NullErrorHandler def self.with_error_handling(_ctx) yield end end # Call the given block with the schema's configured error handlers. # # If the block returns a lazy value, it's not wrapped with error handling. That area will have to be wrapped itself. # # @param ctx [GraphQL::Query::Context] # @return [Object] Either the result of the given block, or some object to replace the result, in case of error handling. def with_error_handling(ctx) yield rescue StandardError => err rescues = @schema.rescues _err_class, handler = rescues.find { |err_class, handler| err.is_a?(err_class) } if handler runtime_info = ctx.namespace(:interpreter) || {} obj = runtime_info[:current_object] args = runtime_info[:current_arguments] field = runtime_info[:current_field] if obj.is_a?(GraphQL::Schema::Object) obj = obj.object end handler.call(err, obj, args, ctx, field) else raise err end end end end end ruby-graphql-1.9.19/lib/graphql/execution/execute.rb000066400000000000000000000253251362601351000224320ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution # A valid execution strategy # @api private class Execute # @api private class Skip; end # Just a singleton for implementing {Query::Context#skip} # @api private SKIP = Skip.new # @api private class PropagateNull end # @api private PROPAGATE_NULL = PropagateNull.new def execute(ast_operation, root_type, query) result = resolve_root_selection(query) lazy_resolve_root_selection(result, **{query: query}) GraphQL::Execution::Flatten.call(query.context) end def self.begin_multiplex(_multiplex) end def self.begin_query(query, _multiplex) ExecutionFunctions.resolve_root_selection(query) end def self.finish_multiplex(results, multiplex) ExecutionFunctions.lazy_resolve_root_selection(results, multiplex: multiplex) end def self.finish_query(query, _multiplex) { "data" => Execution::Flatten.call(query.context) } end # @api private module ExecutionFunctions module_function def resolve_root_selection(query) query.trace("execute_query", query: query) do operation = query.selected_operation op_type = operation.operation_type root_type = query.root_type_for_operation(op_type) if query.context[:__root_unauthorized] # This was set by member/instrumentation.rb so that we wouldn't continue. else resolve_selection( query.root_value, root_type, query.context, mutation: query.mutation? ) end end end def lazy_resolve_root_selection(result, query: nil, multiplex: nil) if query.nil? && multiplex.queries.length == 1 query = multiplex.queries[0] end tracer = (query || multiplex) tracer.trace("execute_query_lazy", {multiplex: multiplex, query: query}) do GraphQL::Execution::Lazy.resolve(result) end end def resolve_selection(object, current_type, current_ctx, mutation: false ) # Assign this _before_ resolving the children # so that when a child propagates null, the selection result is # ready for it. current_ctx.value = {} selections_on_type = current_ctx.irep_node.typed_children[current_type] selections_on_type.each do |name, child_irep_node| field_ctx = current_ctx.spawn_child( key: name, object: object, irep_node: child_irep_node, ) field_result = resolve_field( object, field_ctx ) if field_result.is_a?(Skip) next end if mutation GraphQL::Execution::Lazy.resolve(field_ctx) end # If the last subselection caused a null to propagate to _this_ selection, # then we may as well quit executing fields because they # won't be in the response if current_ctx.invalid_null? break else current_ctx.value[name] = field_ctx end end current_ctx.value end def resolve_field(object, field_ctx) query = field_ctx.query irep_node = field_ctx.irep_node parent_type = irep_node.owner_type field = field_ctx.field raw_value = begin begin arguments = query.arguments_for(irep_node, field) field_ctx.trace("execute_field", { context: field_ctx }) do field_ctx.schema.middleware.invoke([parent_type, object, field, arguments, field_ctx]) end rescue GraphQL::UnauthorizedFieldError => err err.field ||= field field_ctx.schema.unauthorized_field(err) rescue GraphQL::UnauthorizedError => err field_ctx.schema.unauthorized_object(err) end rescue GraphQL::ExecutionError => err err end if field_ctx.schema.lazy?(raw_value) field_ctx.value = Execution::Lazy.new { inner_value = field_ctx.trace("execute_field_lazy", {context: field_ctx}) { begin begin field_ctx.field.lazy_resolve(raw_value, arguments, field_ctx) rescue GraphQL::UnauthorizedError => err field_ctx.schema.unauthorized_object(err) end rescue GraphQL::ExecutionError => err err end } continue_or_wait(inner_value, field_ctx.type, field_ctx) } else continue_or_wait(raw_value, field_ctx.type, field_ctx) end end # If the returned object is lazy (unfinished), # assign the lazy object to `.value=` so we can resolve it later. # When we resolve it later, reassign it to `.value=` so that # the finished value replaces the unfinished one. # # If the returned object is finished, continue to coerce # and resolve child fields def continue_or_wait(raw_value, field_type, field_ctx) if field_ctx.schema.lazy?(raw_value) field_ctx.value = Execution::Lazy.new { inner_value = begin begin field_ctx.schema.sync_lazy(raw_value) rescue GraphQL::UnauthorizedError => err field_ctx.schema.unauthorized_object(err) end rescue GraphQL::ExecutionError => err err end field_ctx.value = continue_or_wait(inner_value, field_type, field_ctx) } else field_ctx.value = continue_resolve_field(raw_value, field_type, field_ctx) end end def continue_resolve_field(raw_value, field_type, field_ctx) if field_ctx.parent.invalid_null? return nil end query = field_ctx.query case raw_value when GraphQL::ExecutionError raw_value.ast_node ||= field_ctx.ast_node raw_value.path = field_ctx.path query.context.errors.push(raw_value) when Array if field_type.non_null? # List type errors are handled above, this is for the case of fields returning an array of errors list_errors = raw_value.each_with_index.select { |value, _| value.is_a?(GraphQL::ExecutionError) } if list_errors.any? list_errors.each do |error, index| error.ast_node = field_ctx.ast_node error.path = field_ctx.path + (field_ctx.type.list? ? [index] : []) query.context.errors.push(error) end end end end resolve_value( raw_value, field_type, field_ctx, ) end def resolve_value(value, field_type, field_ctx) field_defn = field_ctx.field if value.nil? if field_type.kind.non_null? parent_type = field_ctx.irep_node.owner_type type_error = GraphQL::InvalidNullError.new(parent_type, field_defn, value) field_ctx.schema.type_error(type_error, field_ctx) PROPAGATE_NULL else nil end elsif value.is_a?(GraphQL::ExecutionError) if field_type.kind.non_null? PROPAGATE_NULL else nil end elsif value.is_a?(Array) && value.any? && value.all? {|v| v.is_a?(GraphQL::ExecutionError)} if field_type.kind.non_null? PROPAGATE_NULL else nil end elsif value.is_a?(Skip) field_ctx.value = value else case field_type.kind when GraphQL::TypeKinds::SCALAR, GraphQL::TypeKinds::ENUM field_type.coerce_result(value, field_ctx) when GraphQL::TypeKinds::LIST inner_type = field_type.of_type i = 0 result = [] field_ctx.value = result value.each do |inner_value| inner_ctx = field_ctx.spawn_child( key: i, object: inner_value, irep_node: field_ctx.irep_node, ) inner_result = continue_or_wait( inner_value, inner_type, inner_ctx, ) return PROPAGATE_NULL if inner_result == PROPAGATE_NULL result << inner_ctx i += 1 end result when GraphQL::TypeKinds::NON_NULL inner_type = field_type.of_type resolve_value( value, inner_type, field_ctx, ) when GraphQL::TypeKinds::OBJECT resolve_selection( value, field_type, field_ctx ) when GraphQL::TypeKinds::UNION, GraphQL::TypeKinds::INTERFACE query = field_ctx.query resolved_type_or_lazy = field_type.resolve_type(value, field_ctx) query.schema.after_lazy(resolved_type_or_lazy) do |resolved_type| possible_types = query.possible_types(field_type) if !possible_types.include?(resolved_type) parent_type = field_ctx.irep_node.owner_type type_error = GraphQL::UnresolvedTypeError.new(value, field_defn, parent_type, resolved_type, possible_types) field_ctx.schema.type_error(type_error, field_ctx) PROPAGATE_NULL else resolve_value( value, resolved_type, field_ctx, ) end end else raise("Unknown type kind: #{field_type.kind}") end end end end include ExecutionFunctions # A `.call`-able suitable to be the last step in a middleware chain module FieldResolveStep # Execute the field's resolve method def self.call(_parent_type, parent_object, field_definition, field_args, context, _next = nil) field_definition.resolve(parent_object, field_args, context) end end end end end ruby-graphql-1.9.19/lib/graphql/execution/flatten.rb000066400000000000000000000015351362601351000224220ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution # Starting from a root context, # create a hash out of the context tree. # @api private module Flatten def self.call(ctx) flatten(ctx) end class << self private def flatten(obj) case obj when Hash flattened = {} obj.each do |key, val| flattened[key] = flatten(val) end flattened when Array obj.map { |v| flatten(v) } when Query::Context::SharedMethods if obj.invalid_null? nil elsif obj.skipped? && obj.value.empty? nil else flatten(obj.value) end else obj end end end end end end ruby-graphql-1.9.19/lib/graphql/execution/instrumentation.rb000066400000000000000000000066741362601351000242410ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution module Instrumentation # This function implements the instrumentation policy: # # - Instrumenters are a stack; the first `before_query` will have the last `after_query` # - If a `before_` hook returned without an error, its corresponding `after_` hook will run. # - If the `before_` hook did _not_ run, the `after_` hook will not be called. # # When errors are raised from `after_` hooks: # - Subsequent `after_` hooks _are_ called # - The first raised error is captured; later errors are ignored # - If an error was capture, it's re-raised after all hooks are finished # # Partial runs of instrumentation are possible: # - If a `before_multiplex` hook raises an error, no `before_query` hooks will run # - If a `before_query` hook raises an error, subsequent `before_query` hooks will not run (on any query) def self.apply_instrumenters(multiplex) schema = multiplex.schema queries = multiplex.queries query_instrumenters = schema.instrumenters[:query] multiplex_instrumenters = schema.instrumenters[:multiplex] # First, run multiplex instrumentation, then query instrumentation for each query call_hooks(multiplex_instrumenters, multiplex, :before_multiplex, :after_multiplex) do each_query_call_hooks(query_instrumenters, queries) do # Let them be executed yield end end end class << self private # Call the before_ hooks of each query, # Then yield if no errors. # `call_hooks` takes care of appropriate cleanup. def each_query_call_hooks(instrumenters, queries, i = 0) if i >= queries.length yield else query = queries[i] call_hooks(instrumenters, query, :before_query, :after_query) { each_query_call_hooks(instrumenters, queries, i + 1) { yield } } end end # Call each before hook, and if they all succeed, yield. # If they don't all succeed, call after_ for each one that succeeded. def call_hooks(instrumenters, object, before_hook_name, after_hook_name) begin successful = [] instrumenters.each do |instrumenter| instrumenter.public_send(before_hook_name, object) successful << instrumenter end # if any before hooks raise an exception, quit calling before hooks, # but call the after hooks on anything that succeeded but also # raise the exception that came from the before hook. rescue GraphQL::ExecutionError => err object.context.errors << err rescue => e raise call_after_hooks(successful, object, after_hook_name, e) end begin yield # Call the user code ensure ex = call_after_hooks(successful, object, after_hook_name, nil) raise ex if ex end end def call_after_hooks(instrumenters, object, after_hook_name, ex) instrumenters.reverse.each do |instrumenter| begin instrumenter.public_send(after_hook_name, object) rescue => e ex = e end end ex end end end end end ruby-graphql-1.9.19/lib/graphql/execution/interpreter.rb000066400000000000000000000065431362601351000233340ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/execution/interpreter/execution_errors" require "graphql/execution/interpreter/hash_response" require "graphql/execution/interpreter/runtime" require "graphql/execution/interpreter/resolve" module GraphQL module Execution class Interpreter def initialize end # Support `Executor` :S def execute(_operation, _root_type, query) runtime = evaluate(query) sync_lazies(query: query) runtime.final_value end def self.use(schema_defn) schema_defn.target.interpreter = true # Reach through the legacy objects for the actual class defn schema_class = schema_defn.target.class # This is not good, since both of these are holding state now, # we have to update both :( [schema_class, schema_defn].each do |schema_config| schema_config.query_execution_strategy(GraphQL::Execution::Interpreter) schema_config.mutation_execution_strategy(GraphQL::Execution::Interpreter) schema_config.subscription_execution_strategy(GraphQL::Execution::Interpreter) end end def self.begin_multiplex(multiplex) # Since this is basically the batching context, # share it for a whole multiplex multiplex.context[:interpreter_instance] ||= self.new end def self.begin_query(query, multiplex) # The batching context is shared by the multiplex, # so fetch it out and use that instance. interpreter = query.context.namespace(:interpreter)[:interpreter_instance] = multiplex.context[:interpreter_instance] interpreter.evaluate(query) query end def self.finish_multiplex(_results, multiplex) interpreter = multiplex.context[:interpreter_instance] interpreter.sync_lazies(multiplex: multiplex) end def self.finish_query(query, _multiplex) { "data" => query.context.namespace(:interpreter)[:runtime].final_value } end # Run the eager part of `query` # @return {Interpreter::Runtime} def evaluate(query) # Although queries in a multiplex _share_ an Interpreter instance, # they also have another item of state, which is private to that query # in particular, assign it here: runtime = Runtime.new( query: query, response: HashResponse.new, ) query.context.namespace(:interpreter)[:runtime] = runtime query.trace("execute_query", {query: query}) do runtime.run_eager end runtime end # Run the lazy part of `query` or `multiplex`. # @return [void] def sync_lazies(query: nil, multiplex: nil) tracer = query || multiplex if query.nil? && multiplex.queries.length == 1 query = multiplex.queries[0] end queries = multiplex ? multiplex.queries : [query] final_values = queries.map do |query| runtime = query.context.namespace(:interpreter)[:runtime] # it might not be present if the query has an error runtime ? runtime.final_value : nil end final_values.compact! tracer.trace("execute_query_lazy", {multiplex: multiplex, query: query}) do Interpreter::Resolve.resolve_all(final_values) end end end end end ruby-graphql-1.9.19/lib/graphql/execution/interpreter/000077500000000000000000000000001362601351000227775ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/execution/interpreter/execution_errors.rb000066400000000000000000000013471362601351000267300ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter class ExecutionErrors def initialize(ctx, ast_node, path) @context = ctx @ast_node = ast_node @path = path end def add(err_or_msg) err = case err_or_msg when String GraphQL::ExecutionError.new(err_or_msg) when GraphQL::ExecutionError err_or_msg else raise ArgumentError, "expected String or GraphQL::ExecutionError, not #{err_or_msg.class} (#{err_or_msg.inspect})" end err.ast_node ||= @ast_node err.path ||= @path @context.add_error(err) end end end end end ruby-graphql-1.9.19/lib/graphql/execution/interpreter/hash_response.rb000066400000000000000000000020561362601351000261700ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter # This response class handles `#write` by accumulating # values into a Hash. class HashResponse def initialize @result = {} end def final_value @result end def inspect "#<#{self.class.name} result=#{@result.inspect}>" end # Add `value` at `path`. # @return [void] def write(path, value) if path.empty? @result = value elsif (write_target = @result) i = 0 prefinal_steps = path.size - 1 # Use `while` to avoid a closure while i < prefinal_steps path_part = path[i] i += 1 write_target = write_target[path_part] end path_part = path[i] write_target[path_part] = value else # The response is completely nulled out end nil end end end end end ruby-graphql-1.9.19/lib/graphql/execution/interpreter/resolve.rb000066400000000000000000000034511362601351000250060ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter module Resolve # Continue field results in `results` until there's nothing else to continue. # @return [void] def self.resolve_all(results) while results.any? results = resolve(results) end end # After getting `results` back from an interpreter evaluation, # continue it until you get a response-ready Ruby value. # # `results` is one level of _depth_ of a query or multiplex. # # Resolve all lazy values in that depth before moving on # to the next level. # # It's assumed that the lazies will # return {Lazy} instances if there's more work to be done, # or return {Hash}/{Array} if the query should be continued. # # @param results [Array] # @return [Array] Same size, filled with finished values def self.resolve(results) next_results = [] # Work through the queue until it's empty while results.size > 0 result_value = results.shift if result_value.is_a?(Lazy) result_value = result_value.value end if result_value.is_a?(Lazy) # Since this field returned another lazy, # add it to the same queue results << result_value elsif result_value.is_a?(Hash) # This is part of the next level, add it next_results.concat(result_value.values) elsif result_value.is_a?(Array) # This is part of the next level, add it next_results.concat(result_value) end end next_results end end end end end ruby-graphql-1.9.19/lib/graphql/execution/interpreter/runtime.rb000066400000000000000000000644201362601351000250150ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter # I think it would be even better if we could somehow make # `continue_field` not recursive. "Trampolining" it somehow. # # @api private class Runtime # @return [GraphQL::Query] attr_reader :query # @return [Class] attr_reader :schema # @return [GraphQL::Query::Context] attr_reader :context def initialize(query:, response:) @query = query @schema = query.schema @context = query.context @interpreter_context = @context.namespace(:interpreter) @response = response @dead_paths = {} @types_at_paths = {} # A cache of { Class => { String => Schema::Field } } # Which assumes that MyObject.get_field("myField") will return the same field # during the lifetime of a query @fields_cache = Hash.new { |h, k| h[k] = {} } end def final_value @response.final_value end def inspect "#<#{self.class.name} response=#{@response.inspect}>" end # This _begins_ the execution. Some deferred work # might be stored up in lazies. # @return [void] def run_eager root_operation = query.selected_operation root_op_type = root_operation.operation_type || "query" legacy_root_type = schema.root_type_for_operation(root_op_type) root_type = legacy_root_type.metadata[:type_class] || raise("Invariant: type must be class-based: #{legacy_root_type}") path = [] @interpreter_context[:current_object] = query.root_value @interpreter_context[:current_path] = path object_proxy = root_type.authorized_new(query.root_value, context) object_proxy = schema.sync_lazy(object_proxy) if object_proxy.nil? # Root .authorized? returned false. write_in_response(path, nil) nil else evaluate_selections(path, context.scoped_context, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type) nil end end def gather_selections(owner_object, owner_type, selections, selections_by_name) selections.each do |node| # Skip gathering this if the directive says so if !directives_include?(node, owner_object, owner_type) next end case node when GraphQL::Language::Nodes::Field response_key = node.alias || node.name selections = selections_by_name[response_key] # if there was already a selection of this field, # use an array to hold all selections, # otherise, use the single node to represent the selection if selections # This field was already selected at least once, # add this node to the list of selections s = Array(selections) s << node selections_by_name[response_key] = s else # No selection was found for this field yet selections_by_name[response_key] = node end when GraphQL::Language::Nodes::InlineFragment if node.type type_defn = schema.types[node.type.name] type_defn = type_defn.metadata[:type_class] # Faster than .map{}.include?() query.warden.possible_types(type_defn).each do |t| if t.metadata[:type_class] == owner_type gather_selections(owner_object, owner_type, node.selections, selections_by_name) break end end else # it's an untyped fragment, definitely continue gather_selections(owner_object, owner_type, node.selections, selections_by_name) end when GraphQL::Language::Nodes::FragmentSpread fragment_def = query.fragments[node.name] type_defn = schema.types[fragment_def.type.name] type_defn = type_defn.metadata[:type_class] schema.possible_types(type_defn).each do |t| if t.metadata[:type_class] == owner_type gather_selections(owner_object, owner_type, fragment_def.selections, selections_by_name) break end end else raise "Invariant: unexpected selection class: #{node.class}" end end end def evaluate_selections(path, scoped_context, owner_object, owner_type, selections, root_operation_type: nil) @interpreter_context[:current_object] = owner_object @interpreter_context[:current_path] = path selections_by_name = {} gather_selections(owner_object, owner_type, selections, selections_by_name) selections_by_name.each do |result_name, field_ast_nodes_or_ast_node| # As a performance optimization, the hash key will be a `Node` if # there's only one selection of the field. But if there are multiple # selections of the field, it will be an Array of nodes if field_ast_nodes_or_ast_node.is_a?(Array) field_ast_nodes = field_ast_nodes_or_ast_node ast_node = field_ast_nodes.first else field_ast_nodes = nil ast_node = field_ast_nodes_or_ast_node end field_name = ast_node.name field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name) is_introspection = false if field_defn.nil? field_defn = if owner_type == schema.query.metadata[:type_class] && (entry_point_field = schema.introspection_system.entry_point(name: field_name)) is_introspection = true entry_point_field.metadata[:type_class] elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name)) is_introspection = true dynamic_field.metadata[:type_class] else raise "Invariant: no field for #{owner_type}.#{field_name}" end end return_type = resolve_if_late_bound_type(field_defn.type) next_path = path.dup next_path << result_name next_path.freeze # This seems janky, but we need to know # the field's return type at this path in order # to propagate `null` set_type_at_path(next_path, return_type) # Set this before calling `run_with_directives`, so that the directive can have the latest path @interpreter_context[:current_path] = next_path @interpreter_context[:current_field] = field_defn context.scoped_context = scoped_context object = owner_object if is_introspection object = field_defn.owner.authorized_new(object, context) end begin kwarg_arguments = arguments(object, field_defn, ast_node) rescue GraphQL::ExecutionError => e continue_value(next_path, e, field_defn, return_type.non_null?, ast_node) next end # It might turn out that making arguments for every field is slow. # If we have to cache them, we'll need a more subtle approach here. field_defn.extras.each do |extra| case extra when :ast_node kwarg_arguments[:ast_node] = ast_node when :execution_errors kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path) when :path kwarg_arguments[:path] = next_path when :lookahead if !field_ast_nodes field_ast_nodes = [ast_node] end kwarg_arguments[:lookahead] = Execution::Lookahead.new( query: query, ast_nodes: field_ast_nodes, field: field_defn, ) else kwarg_arguments[extra] = field_defn.fetch_extra(extra, context) end end @interpreter_context[:current_arguments] = kwarg_arguments # Optimize for the case that field is selected only once if field_ast_nodes.nil? || field_ast_nodes.size == 1 next_selections = ast_node.selections else next_selections = [] field_ast_nodes.each { |f| next_selections.concat(f.selections) } end field_result = resolve_with_directives(object, ast_node) do # Actually call the field resolver and capture the result app_result = begin query.with_error_handling do query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do field_defn.resolve(object, kwarg_arguments, context) end end rescue GraphQL::ExecutionError => err err end after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result| continue_value = continue_value(next_path, inner_result, field_defn, return_type.non_null?, ast_node) if HALT != continue_value continue_field(next_path, continue_value, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments) end end end # If this field is a root mutation field, immediately resolve # all of its child fields before moving on to the next root mutation field. # (Subselections of this mutation will still be resolved level-by-level.) if root_operation_type == "mutation" Interpreter::Resolve.resolve_all([field_result]) else field_result end end end HALT = Object.new def continue_value(path, value, field, is_non_null, ast_node) if value.nil? if is_non_null err = GraphQL::InvalidNullError.new(field.owner, field, value) write_invalid_null_in_response(path, err) else write_in_response(path, nil) end HALT elsif value.is_a?(GraphQL::ExecutionError) value.path ||= path value.ast_node ||= ast_node write_execution_errors_in_response(path, [value]) HALT elsif value.is_a?(Array) && value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) } value.each_with_index do |error, index| error.ast_node ||= ast_node error.path ||= path + (field.type.list? ? [index] : []) end write_execution_errors_in_response(path, value) HALT elsif value.is_a?(GraphQL::UnauthorizedError) # this hook might raise & crash, or it might return # a replacement value next_value = begin schema.unauthorized_object(value) rescue GraphQL::ExecutionError => err err end continue_value(path, next_value, field, is_non_null, ast_node) elsif GraphQL::Execution::Execute::SKIP == value HALT else value end end # The resolver for `field` returned `value`. Continue to execute the query, # treating `value` as `type` (probably the return type of the field). # # Use `next_selections` to resolve object fields, if there are any. # # Location information from `path` and `ast_node`. # # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later def continue_field(path, value, field, type, ast_node, next_selections, is_non_null, owner_object, arguments) # rubocop:disable Metrics/ParameterLists case type.kind.name when "SCALAR", "ENUM" r = type.coerce_result(value, context) write_in_response(path, r) r when "UNION", "INTERFACE" resolved_type_or_lazy = query.resolve_type(type, value) after_lazy(resolved_type_or_lazy, owner: type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |resolved_type| possible_types = query.possible_types(type) if !possible_types.include?(resolved_type) parent_type = field.owner type_error = GraphQL::UnresolvedTypeError.new(value, field, parent_type, resolved_type, possible_types) schema.type_error(type_error, context) write_in_response(path, nil) nil else resolved_type = resolved_type.metadata[:type_class] continue_field(path, value, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments) end end when "OBJECT" object_proxy = begin type.authorized_new(value, context) rescue GraphQL::ExecutionError => err err end after_lazy(object_proxy, owner: type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_object| continue_value = continue_value(path, inner_object, field, is_non_null, ast_node) if HALT != continue_value response_hash = {} write_in_response(path, response_hash) evaluate_selections(path, context.scoped_context, continue_value, type, next_selections) response_hash end end when "LIST" response_list = [] write_in_response(path, response_list) inner_type = type.of_type idx = 0 scoped_context = context.scoped_context value.each do |inner_value| next_path = path.dup next_path << idx next_path.freeze idx += 1 set_type_at_path(next_path, inner_type) # This will update `response_list` with the lazy after_lazy(inner_value, owner: inner_type, path: next_path, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value| # reset `is_non_null` here and below, because the inner type will have its own nullability constraint continue_value = continue_value(next_path, inner_inner_value, field, false, ast_node) if HALT != continue_value continue_field(next_path, continue_value, field, inner_type, ast_node, next_selections, false, owner_object, arguments) end end end response_list when "NON_NULL" inner_type = type.of_type # For fields like `__schema: __Schema!` inner_type = resolve_if_late_bound_type(inner_type) # Don't `set_type_at_path` because we want the static type, # we're going to use that to determine whether a `nil` should be propagated or not. continue_field(path, value, field, inner_type, ast_node, next_selections, true, owner_object, arguments) else raise "Invariant: Unhandled type kind #{type.kind} (#{type})" end end def resolve_with_directives(object, ast_node) run_directive(object, ast_node, 0) { yield } end def run_directive(object, ast_node, idx) dir_node = ast_node.directives[idx] if !dir_node yield else dir_defn = schema.directives.fetch(dir_node.name) if !dir_defn.is_a?(Class) dir_defn = dir_defn.metadata[:type_class] || raise("Only class-based directives are supported (not `@#{dir_node.name}`)") end dir_args = arguments(nil, dir_defn, dir_node) dir_defn.resolve(object, dir_args, context) do run_directive(object, ast_node, idx + 1) { yield } end end end # Check {Schema::Directive.include?} for each directive that's present def directives_include?(node, graphql_object, parent_type) node.directives.each do |dir_node| dir_defn = schema.directives.fetch(dir_node.name).metadata[:type_class] || raise("Only class-based directives are supported (not #{dir_node.name.inspect})") args = arguments(graphql_object, dir_defn, dir_node) if !dir_defn.include?(graphql_object, args, context) return false end end true end def resolve_if_late_bound_type(type) if type.is_a?(GraphQL::Schema::LateBoundType) query.warden.get_type(type.name).metadata[:type_class] else type end end # @param obj [Object] Some user-returned value that may want to be batched # @param path [Array] # @param field [GraphQL::Schema::Field] # @param eager [Boolean] Set to `true` for mutation root fields only # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it. def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false) @interpreter_context[:current_object] = owner_object @interpreter_context[:current_arguments] = arguments @interpreter_context[:current_path] = path @interpreter_context[:current_field] = field if schema.lazy?(lazy_obj) lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do @interpreter_context[:current_path] = path @interpreter_context[:current_field] = field @interpreter_context[:current_object] = owner_object @interpreter_context[:current_arguments] = arguments context.scoped_context = scoped_context # Wrap the execution of _this_ method with tracing, # but don't wrap the continuation below inner_obj = begin query.with_error_handling do query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do schema.sync_lazy(lazy_obj) end end rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err yield(err) end after_lazy(inner_obj, owner: owner, field: field, path: path, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager) do |really_inner_obj| yield(really_inner_obj) end end if eager lazy.value else write_in_response(path, lazy) lazy end else yield(lazy_obj) end end def each_argument_pair(ast_args_or_hash) case ast_args_or_hash when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive ast_args_or_hash.arguments.each do |arg| yield(arg.name, arg.value) end when Hash ast_args_or_hash.each do |key, value| normalized_name = GraphQL::Schema::Member::BuildType.camelize(key.to_s) yield(normalized_name, value) end else raise "Invariant, unexpected #{ast_args_or_hash.inspect}" end end def arguments(graphql_object, arg_owner, ast_node_or_hash) kwarg_arguments = {} arg_defns = arg_owner.arguments each_argument_pair(ast_node_or_hash) do |arg_name, arg_value| arg_defn = arg_defns[arg_name] # Need to distinguish between client-provided `nil` # and nothing-at-all is_present, value = arg_to_value(graphql_object, arg_defn.type, arg_value) if is_present # This doesn't apply to directives, which are legacy # Can remove this when Skip and Include use classes or something. if graphql_object value = arg_defn.prepare_value(graphql_object, value) end kwarg_arguments[arg_defn.keyword] = value end end arg_defns.each do |name, arg_defn| if arg_defn.default_value? && !kwarg_arguments.key?(arg_defn.keyword) _is_present, value = arg_to_value(graphql_object, arg_defn.type, arg_defn.default_value) kwarg_arguments[arg_defn.keyword] = value end end kwarg_arguments end # Get a Ruby-ready value from a client query. # @param graphql_object [Object] The owner of the field whose argument this is # @param arg_type [Class, GraphQL::Schema::NonNull, GraphQL::Schema::List] # @param ast_value [GraphQL::Language::Nodes::VariableIdentifier, String, Integer, Float, Boolean] # @return [Array(is_present, value)] def arg_to_value(graphql_object, arg_type, ast_value) if ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) # If it's not here, it will get added later if query.variables.key?(ast_value.name) return true, query.variables[ast_value.name] else return false, nil end elsif ast_value.is_a?(GraphQL::Language::Nodes::NullValue) return true, nil elsif arg_type.is_a?(GraphQL::Schema::NonNull) arg_to_value(graphql_object, arg_type.of_type, ast_value) elsif arg_type.is_a?(GraphQL::Schema::List) # Treat a single value like a list arg_value = Array(ast_value) list = [] arg_value.map do |inner_v| _present, value = arg_to_value(graphql_object, arg_type.of_type, inner_v) list << value end return true, list elsif arg_type.is_a?(Class) && arg_type < GraphQL::Schema::InputObject # For these, `prepare` is applied during `#initialize`. # Pass `nil` so it will be skipped in `#arguments`. # What a mess. args = arguments(nil, arg_type, ast_value) # We're not tracking defaults_used, but for our purposes # we compare the value to the default value. input_obj = query.with_error_handling do arg_type.new(ruby_kwargs: args, context: context, defaults_used: nil) end return true, input_obj else flat_value = flatten_ast_value(ast_value) return true, arg_type.coerce_input(flat_value, context) end end def flatten_ast_value(v) case v when GraphQL::Language::Nodes::Enum v.name when GraphQL::Language::Nodes::InputObject h = {} v.arguments.each do |arg| h[arg.name] = flatten_ast_value(arg.value) end h when Array v.map { |v2| flatten_ast_value(v2) } when GraphQL::Language::Nodes::VariableIdentifier flatten_ast_value(query.variables[v.name]) else v end end def write_invalid_null_in_response(path, invalid_null_error) if !dead_path?(path) schema.type_error(invalid_null_error, context) write_in_response(path, nil) add_dead_path(path) end end def write_execution_errors_in_response(path, errors) if !dead_path?(path) errors.each do |v| context.errors << v end write_in_response(path, nil) add_dead_path(path) end end def write_in_response(path, value) if dead_path?(path) return else if value.nil? && path.any? && type_at(path).non_null? # This nil is invalid, try writing it at the previous spot propagate_path = path[0..-2] write_in_response(propagate_path, value) add_dead_path(propagate_path) else @response.write(path, value) end end end # To propagate nulls, we have to know what the field type was # at previous parts of the response. # This hash matches the response def type_at(path) t = @types_at_paths path.each do |part| t = t[part] || (raise("Invariant: #{part.inspect} not found in #{t}")) end t = t[:__type] t end def set_type_at_path(path, type) types = @types_at_paths path.each do |part| types = types[part] ||= {} end # Use this magic key so that the hash contains: # - string keys for nested fields # - :__type for the object type of a selection types[:__type] ||= type nil end # Mark `path` as having been permanently nulled out. # No values will be added beyond that path. def add_dead_path(path) dead = @dead_paths path.each do |part| dead = dead[part] ||= {} end dead[:__dead] = true end def dead_path?(path) res = @dead_paths path.each do |part| if res if res[:__dead] break else res = res[part] end end end res && res[:__dead] end end end end end ruby-graphql-1.9.19/lib/graphql/execution/lazy.rb000066400000000000000000000044531362601351000217460ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/execution/lazy/lazy_method_map" require "graphql/execution/lazy/resolve" module GraphQL module Execution # This wraps a value which is available, but not yet calculated, like a promise or future. # # Calling `#value` will trigger calculation & return the "lazy" value. # # This is an itty-bitty promise-like object, with key differences: # - It has only two states, not-resolved and resolved # - It has no error-catching functionality # @api private class Lazy # Traverse `val`, lazily resolving any values along the way # @param val [Object] A data structure containing mixed plain values and `Lazy` instances # @return void def self.resolve(val) Resolve.resolve(val) end attr_reader :path, :field # Create a {Lazy} which will get its inner value by calling the block # @param path [Array] # @param field [GraphQL::Schema::Field] # @param get_value_func [Proc] a block to get the inner value (later) def initialize(path: nil, field: nil, &get_value_func) @get_value_func = get_value_func @resolved = false @path = path @field = field end # @return [Object] The wrapped value, calling the lazy block if necessary def value if !@resolved @resolved = true @value = begin v = @get_value_func.call if v.is_a?(Lazy) v = v.value end v rescue GraphQL::ExecutionError => err err end end if @value.is_a?(StandardError) raise @value else @value end end # @return [Lazy] A {Lazy} whose value depends on another {Lazy}, plus any transformations in `block` def then self.class.new { yield(value) } end # @param lazies [Array] Maybe-lazy objects # @return [Lazy] A lazy which will sync all of `lazies` def self.all(lazies) self.new { lazies.map { |l| l.is_a?(Lazy) ? l.value : l } } end # This can be used for fields which _had no_ lazy results # @api private NullResult = Lazy.new(){} NullResult.value end end end ruby-graphql-1.9.19/lib/graphql/execution/lazy/000077500000000000000000000000001362601351000214135ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/execution/lazy/lazy_method_map.rb000066400000000000000000000054551362601351000251250ustar00rootroot00000000000000# frozen_string_literal: true require 'thread' begin require 'concurrent' rescue LoadError # no problem, we'll fallback to our own map end module GraphQL module Execution class Lazy # {GraphQL::Schema} uses this to match returned values to lazy resolution methods. # Methods may be registered for classes, they apply to its subclasses also. # The result of this lookup is cached for future resolutions. # Instances of this class are thread-safe. # @api private # @see {Schema#lazy?} looks up values from this map class LazyMethodMap def initialize(use_concurrent: defined?(Concurrent::Map)) @storage = use_concurrent ? Concurrent::Map.new : ConcurrentishMap.new end def initialize_copy(other) @storage = other.storage.dup end # @param lazy_class [Class] A class which represents a lazy value (subclasses may also be used) # @param lazy_value_method [Symbol] The method to call on this class to get its value def set(lazy_class, lazy_value_method) @storage[lazy_class] = lazy_value_method end # @param value [Object] an object which may have a `lazy_value_method` registered for its class or superclasses # @return [Symbol, nil] The `lazy_value_method` for this object, or nil def get(value) @storage.compute_if_absent(value.class) { find_superclass_method(value.class) } end protected attr_reader :storage private def find_superclass_method(value_class) @storage.each_pair { |lazy_class, lazy_value_method| return lazy_value_method if value_class < lazy_class } nil end # Mock the Concurrent::Map API class ConcurrentishMap extend Forwardable # Technically this should be under the mutex too, # but I know it's only used when the lock is already acquired. def_delegators :@storage, :each_pair, :size def initialize @semaphore = Mutex.new # Access to this hash must always be managed by the mutex # since it may be modified at runtime @storage = {} end def []=(key, value) @semaphore.synchronize { @storage[key] = value } end def compute_if_absent(key) @semaphore.synchronize { @storage.fetch(key) { @storage[key] = yield } } end def initialize_copy(other) @semaphore = Mutex.new @storage = other.copy_storage end protected def copy_storage @semaphore.synchronize { @storage.dup } end end end end end end ruby-graphql-1.9.19/lib/graphql/execution/lazy/resolve.rb000066400000000000000000000050621362601351000234220ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Lazy # Helpers for dealing with data structures containing {Lazy} instances # @api private module Resolve # Mutate `value`, replacing {Lazy} instances in place with their resolved values # @return [void] # This object can be passed like an array, but it doesn't allocate an # array until it's used. # # There's one crucial difference: you have to _capture_ the result # of `#<<`. (This _works_ with arrays but isn't required, since it has a side-effect.) # @api private module NullAccumulator def self.<<(item) [item] end def self.empty? true end end def self.resolve(value) lazies = resolve_in_place(value) deep_sync(lazies) end def self.resolve_in_place(value) acc = each_lazy(NullAccumulator, value) if acc.empty? Lazy::NullResult else Lazy.new { acc.each_with_index { |ctx, idx| acc[idx] = ctx.value.value } resolve_in_place(acc) } end end # If `value` is a collection, # add any {Lazy} instances in the collection # to `acc` # @return [void] def self.each_lazy(acc, value) case value when Hash value.each do |key, field_result| acc = each_lazy(acc, field_result) end when Array value.each do |field_result| acc = each_lazy(acc, field_result) end when Query::Context::SharedMethods field_value = value.value case field_value when Lazy acc = acc << value when Enumerable # shortcut for Hash & Array acc = each_lazy(acc, field_value) end end acc end # Traverse `val`, triggering resolution for each {Lazy}. # These {Lazy}s are expected to mutate their owner data structures # during resolution! (They're created with the `.then` calls in `resolve_in_place`). # @return [void] def self.deep_sync(val) case val when Lazy deep_sync(val.value) when Array val.each { |v| deep_sync(v.value) } when Hash val.each { |k, v| deep_sync(v.value) } end end end end end end ruby-graphql-1.9.19/lib/graphql/execution/lookahead.rb000066400000000000000000000365371362601351000227260ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution # Lookahead creates a uniform interface to inspect the forthcoming selections. # # It assumes that the AST it's working with is valid. (So, it's safe to use # during execution, but if you're using it directly, be sure to validate first.) # # A field may get access to its lookahead by adding `extras: [:lookahead]` # to its configuration. # # @example looking ahead in a field # field :articles, [Types::Article], null: false, # extras: [:lookahead] # # # For example, imagine a faster database call # # may be issued when only some fields are requested. # # # # Imagine that _full_ fetch must be made to satisfy `fullContent`, # # we can look ahead to see if we need that field. If we do, # # we make the expensive database call instead of the cheap one. # def articles(lookahead:) # if lookahead.selects?(:full_content) # fetch_full_articles(object) # else # fetch_preview_articles(object) # end # end class Lookahead # @param query [GraphQL::Query] # @param ast_nodes [Array, Array] # @param field [GraphQL::Schema::Field] if `ast_nodes` are fields, this is the field definition matching those nodes # @param root_type [Class] if `ast_nodes` are operation definition, this is the root type for that operation def initialize(query:, ast_nodes:, field: nil, root_type: nil, owner_type: nil) @ast_nodes = ast_nodes.freeze @field = field @root_type = root_type @query = query @selected_type = @field ? @field.type.unwrap : root_type @owner_type = owner_type end # @return [Array] attr_reader :ast_nodes # @return [GraphQL::Schema::Field] attr_reader :field # @return [GraphQL::Schema::Object, GraphQL::Schema::Union, GraphQL::Schema::Interface] attr_reader :owner_type # @return [Hash] def arguments @arguments ||= @field && ArgumentHelpers.arguments(@query, nil, @field, ast_nodes.first) end # True if this node has a selection on `field_name`. # If `field_name` is a String, it is treated as a GraphQL-style (camelized) # field name and used verbatim. If `field_name` is a Symbol, it is # treated as a Ruby-style (underscored) name and camelized before comparing. # # If `arguments:` is provided, each provided key/value will be matched # against the arguments in the next selection. This method will return false # if any of the given `arguments:` are not present and matching in the next selection. # (But, the next selection may contain _more_ than the given arguments.) # @param field_name [String, Symbol] # @param arguments [Hash] Arguments which must match in the selection # @return [Boolean] def selects?(field_name, arguments: nil) selection(field_name, arguments: arguments).selected? end # @return [Boolean] True if this lookahead represents a field that was requested def selected? true end # Like {#selects?}, but can be used for chaining. # It returns a null object (check with {#selected?}) # @return [GraphQL::Execution::Lookahead] def selection(field_name, selected_type: @selected_type, arguments: nil) next_field_name = normalize_name(field_name) next_field_defn = FieldHelpers.get_field(@query.schema, selected_type, next_field_name) if next_field_defn next_nodes = [] @ast_nodes.each do |ast_node| ast_node.selections.each do |selection| find_selected_nodes(selection, next_field_name, next_field_defn, arguments: arguments, matches: next_nodes) end end if next_nodes.any? Lookahead.new(query: @query, ast_nodes: next_nodes, field: next_field_defn, owner_type: selected_type) else NULL_LOOKAHEAD end else NULL_LOOKAHEAD end end # Like {#selection}, but for all nodes. # It returns a list of Lookaheads for all Selections # # If `arguments:` is provided, each provided key/value will be matched # against the arguments in each selection. This method will filter the selections # if any of the given `arguments:` do not match the given selection. # # @example getting the name of a selection # def articles(lookahead:) # next_lookaheads = lookahead.selections # => [#, ...] # next_lookaheads.map(&:name) #=> [:full_content, :title] # end # # @param arguments [Hash] Arguments which must match in the selection # @return [Array] def selections(arguments: nil) subselections_by_type = {} subselections_on_type = subselections_by_type[@selected_type] = {} @ast_nodes.each do |node| find_selections(subselections_by_type, subselections_on_type, @selected_type, node.selections, arguments) end subselections = [] subselections_by_type.each do |type, ast_nodes_by_response_key| ast_nodes_by_response_key.each do |response_key, ast_nodes| field_defn = FieldHelpers.get_field(@query.schema, type, ast_nodes.first.name) lookahead = Lookahead.new(query: @query, ast_nodes: ast_nodes, field: field_defn, owner_type: type) subselections.push(lookahead) end end subselections end # The method name of the field. # It returns the method_sym of the Lookahead's field. # # @example getting the name of a selection # def articles(lookahead:) # article.selection(:full_content).name # => :full_content # # ... # end # # @return [Symbol] def name @field && @field.original_name end def inspect "#" end # This is returned for {Lookahead#selection} when a non-existent field is passed class NullLookahead < Lookahead # No inputs required here. def initialize end def selected? false end def selects?(*) false end def selection(*) NULL_LOOKAHEAD end def selections(*) [] end def inspect "#" end end # A singleton, so that misses don't come with overhead. NULL_LOOKAHEAD = NullLookahead.new private # If it's a symbol, stringify and camelize it def normalize_name(name) if name.is_a?(Symbol) Schema::Member::BuildType.camelize(name.to_s) else name end end def normalize_keyword(keyword) if keyword.is_a?(String) Schema::Member::BuildType.underscore(keyword).to_sym else keyword end end def find_selections(subselections_by_type, selections_on_type, selected_type, ast_selections, arguments) ast_selections.each do |ast_selection| case ast_selection when GraphQL::Language::Nodes::Field response_key = ast_selection.alias || ast_selection.name if selections_on_type.key?(response_key) selections_on_type[response_key] << ast_selection elsif arguments.nil? || arguments.empty? selections_on_type[response_key] = [ast_selection] else field_defn = FieldHelpers.get_field(@query.schema, selected_type, ast_selection.name) if arguments_match?(arguments, field_defn, ast_selection) selections_on_type[response_key] = [ast_selection] end end when GraphQL::Language::Nodes::InlineFragment on_type = selected_type subselections_on_type = selections_on_type if (t = ast_selection.type) # Assuming this is valid, that `t` will be found. on_type = @query.schema.types[t.name].metadata[:type_class] subselections_on_type = subselections_by_type[on_type] ||= {} end find_selections(subselections_by_type, subselections_on_type, on_type, ast_selection.selections, arguments) when GraphQL::Language::Nodes::FragmentSpread frag_defn = @query.fragments[ast_selection.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{ast_selection.name} (found: #{@query.fragments.keys})") # Again, assuming a valid AST on_type = @query.schema.types[frag_defn.type.name].metadata[:type_class] subselections_on_type = subselections_by_type[on_type] ||= {} find_selections(subselections_by_type, subselections_on_type, on_type, frag_defn.selections, arguments) else raise "Invariant: Unexpected selection type: #{ast_selection.class}" end end end # If a selection on `node` matches `field_name` (which is backed by `field_defn`) # and matches the `arguments:` constraints, then add that node to `matches` def find_selected_nodes(node, field_name, field_defn, arguments:, matches:) case node when GraphQL::Language::Nodes::Field if node.name == field_name if arguments.nil? || arguments.empty? # No constraint applied matches << node elsif arguments_match?(arguments, field_defn, node) matches << node end end when GraphQL::Language::Nodes::InlineFragment node.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches) } when GraphQL::Language::Nodes::FragmentSpread frag_defn = @query.fragments[node.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{node.name} (found: #{@query.fragments.keys})") frag_defn.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches) } else raise "Unexpected selection comparison on #{node.class.name} (#{node})" end end def arguments_match?(arguments, field_defn, field_node) query_kwargs = ArgumentHelpers.arguments(@query, nil, field_defn, field_node) arguments.all? do |arg_name, arg_value| arg_name = normalize_keyword(arg_name) # Make sure the constraint is present with a matching value query_kwargs.key?(arg_name) && query_kwargs[arg_name] == arg_value end end # TODO Dedup with interpreter module ArgumentHelpers module_function def arguments(query, graphql_object, arg_owner, ast_node) kwarg_arguments = {} arg_defns = arg_owner.arguments ast_node.arguments.each do |arg| arg_defn = arg_defns[arg.name] || raise("Invariant: missing argument definition for #{arg.name.inspect} in #{arg_defns.keys} from #{arg_owner}") # Need to distinguish between client-provided `nil` # and nothing-at-all is_present, value = arg_to_value(query, graphql_object, arg_defn.type, arg.value) if is_present # This doesn't apply to directives, which are legacy # Can remove this when Skip and Include use classes or something. if graphql_object value = arg_defn.prepare_value(graphql_object, value) end kwarg_arguments[arg_defn.keyword] = value end end arg_defns.each do |name, arg_defn| if arg_defn.default_value? && !kwarg_arguments.key?(arg_defn.keyword) kwarg_arguments[arg_defn.keyword] = arg_defn.default_value end end kwarg_arguments end # Get a Ruby-ready value from a client query. # @param graphql_object [Object] The owner of the field whose argument this is # @param arg_type [Class, GraphQL::Schema::NonNull, GraphQL::Schema::List] # @param ast_value [GraphQL::Language::Nodes::VariableIdentifier, String, Integer, Float, Boolean] # @return [Array(is_present, value)] def arg_to_value(query, graphql_object, arg_type, ast_value) if ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) # If it's not here, it will get added later if query.variables.key?(ast_value.name) return true, query.variables[ast_value.name] else return false, nil end elsif ast_value.is_a?(GraphQL::Language::Nodes::NullValue) return true, nil elsif arg_type.is_a?(GraphQL::Schema::NonNull) arg_to_value(query, graphql_object, arg_type.of_type, ast_value) elsif arg_type.is_a?(GraphQL::Schema::List) # Treat a single value like a list arg_value = Array(ast_value) list = [] arg_value.map do |inner_v| _present, value = arg_to_value(query, graphql_object, arg_type.of_type, inner_v) list << value end return true, list elsif arg_type.is_a?(Class) && arg_type < GraphQL::Schema::InputObject # For these, `prepare` is applied during `#initialize`. # Pass `nil` so it will be skipped in `#arguments`. # What a mess. args = arguments(query, nil, arg_type, ast_value) # We're not tracking defaults_used, but for our purposes # we compare the value to the default value. return true, arg_type.new(ruby_kwargs: args, context: query.context, defaults_used: nil) else flat_value = flatten_ast_value(query, ast_value) return true, arg_type.coerce_input(flat_value, query.context) end end def flatten_ast_value(query, v) case v when GraphQL::Language::Nodes::Enum v.name when GraphQL::Language::Nodes::InputObject h = {} v.arguments.each do |arg| h[arg.name] = flatten_ast_value(query, arg.value) end h when Array v.map { |v2| flatten_ast_value(query, v2) } when GraphQL::Language::Nodes::VariableIdentifier flatten_ast_value(query.variables[v.name]) else v end end end # TODO dedup with interpreter module FieldHelpers module_function def get_field(schema, owner_type, field_name) field_defn = owner_type.get_field(field_name) field_defn ||= if owner_type == schema.query.metadata[:type_class] && (entry_point_field = schema.introspection_system.entry_point(name: field_name)) entry_point_field.metadata[:type_class] elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name)) dynamic_field.metadata[:type_class] else nil end field_defn end end end end end ruby-graphql-1.9.19/lib/graphql/execution/multiplex.rb000066400000000000000000000166431362601351000230160ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution # Execute multiple queries under the same multiplex "umbrella". # They can share a batching context and reduce redundant database hits. # # The flow is: # # - Multiplex instrumentation setup # - Query instrumentation setup # - Analyze the multiplex + each query # - Begin each query # - Resolve lazy values, breadth-first across all queries # - Finish each query (eg, get errors) # - Query instrumentation teardown # - Multiplex instrumentation teardown # # If one query raises an application error, all queries will be in undefined states. # # Validation errors and {GraphQL::ExecutionError}s are handled in isolation: # one of these errors in one query will not affect the other queries. # # @see {Schema#multiplex} for public API # @api private class Multiplex # Used internally to signal that the query shouldn't be executed # @api private NO_OPERATION = {}.freeze include Tracing::Traceable attr_reader :context, :queries, :schema, :max_complexity def initialize(schema:, queries:, context:, max_complexity:) @schema = schema @queries = queries @context = context # TODO remove support for global tracers @tracers = schema.tracers + GraphQL::Tracing.tracers + (context[:tracers] || []) # Support `context: {backtrace: true}` if context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer) @tracers << GraphQL::Backtrace::Tracer end @max_complexity = max_complexity end class << self def run_all(schema, query_options, **kwargs) queries = query_options.map { |opts| GraphQL::Query.new(schema, nil, **opts) } run_queries(schema, queries, **kwargs) end # @param schema [GraphQL::Schema] # @param queries [Array] # @param context [Hash] # @param max_complexity [Integer, nil] # @return [Array] One result per query def run_queries(schema, queries, context: {}, max_complexity: schema.max_complexity) multiplex = self.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity) multiplex.trace("execute_multiplex", { multiplex: multiplex }) do if supports_multiplexing?(schema) instrument_and_analyze(multiplex) do run_as_multiplex(multiplex) end else if queries.length != 1 raise ArgumentError, "Multiplexing doesn't support custom execution strategies, run one query at a time instead" else instrument_and_analyze(multiplex) do [run_one_legacy(schema, queries.first)] end end end end end private def run_as_multiplex(multiplex) multiplex.schema.query_execution_strategy.begin_multiplex(multiplex) queries = multiplex.queries # Do as much eager evaluation of the query as possible results = queries.map do |query| begin_query(query, multiplex) end # Then, work through lazy results in a breadth-first way multiplex.schema.query_execution_strategy.finish_multiplex(results, multiplex) # Then, find all errors and assign the result to the query object results.each_with_index.map do |data_result, idx| query = queries[idx] finish_query(data_result, query, multiplex) # Get the Query::Result, not the Hash query.result end rescue Exception # Assign values here so that the query's `@executed` becomes true queries.map { |q| q.result_values ||= {} } raise end # @param query [GraphQL::Query] # @return [Hash] The initial result (may not be finished if there are lazy values) def begin_query(query, multiplex) operation = query.selected_operation if operation.nil? || !query.valid? || query.context.errors.any? NO_OPERATION else begin # These were checked to be the same in `#supports_multiplexing?` query.schema.query_execution_strategy.begin_query(query, multiplex) rescue GraphQL::ExecutionError => err query.context.errors << err NO_OPERATION end end end # @param data_result [Hash] The result for the "data" key, if any # @param query [GraphQL::Query] The query which was run # @return [Hash] final result of this query, including all values and errors def finish_query(data_result, query, multiplex) # Assign the result so that it can be accessed in instrumentation query.result_values = if data_result.equal?(NO_OPERATION) if !query.valid? || query.context.errors.any? # A bit weird, but `Query#static_errors` _includes_ `query.context.errors` { "errors" => query.static_errors.map(&:to_h) } else data_result end else # Use `context.value` which was assigned during execution result = query.schema.query_execution_strategy.finish_query(query, multiplex) if query.context.errors.any? error_result = query.context.errors.map(&:to_h) result["errors"] = error_result end result end end # use the old `query_execution_strategy` etc to run this query def run_one_legacy(schema, query) query.result_values = if !query.valid? all_errors = query.validation_errors + query.analysis_errors + query.context.errors if all_errors.any? { "errors" => all_errors.map(&:to_h) } else nil end else GraphQL::Query::Executor.new(query).result end end DEFAULT_STRATEGIES = [ GraphQL::Execution::Execute, GraphQL::Execution::Interpreter ] # @return [Boolean] True if the schema is only using one strategy, and it's one that supports multiplexing. def supports_multiplexing?(schema) schema_strategies = [schema.query_execution_strategy, schema.mutation_execution_strategy, schema.subscription_execution_strategy] schema_strategies.uniq! schema_strategies.size == 1 && DEFAULT_STRATEGIES.include?(schema_strategies.first) end # Apply multiplex & query instrumentation to `queries`. # # It yields when the queries should be executed, then runs teardown. def instrument_and_analyze(multiplex) GraphQL::Execution::Instrumentation.apply_instrumenters(multiplex) do schema = multiplex.schema multiplex_analyzers = schema.multiplex_analyzers if multiplex.max_complexity multiplex_analyzers += if schema.using_ast_analysis? [GraphQL::Analysis::AST::MaxQueryComplexity] else [GraphQL::Analysis::MaxQueryComplexity.new(multiplex.max_complexity)] end end schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers) yield end end end end end end ruby-graphql-1.9.19/lib/graphql/execution/typecast.rb000066400000000000000000000031061362601351000226150ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution # @api private module Typecast # @return [Boolean] def self.subtype?(parent_type, child_type) if parent_type == child_type # Equivalent types are subtypes true elsif child_type.is_a?(GraphQL::NonNullType) # A non-null type is a subtype of a nullable type # if its inner type is a subtype of that type if parent_type.is_a?(GraphQL::NonNullType) subtype?(parent_type.of_type, child_type.of_type) else subtype?(parent_type, child_type.of_type) end else case parent_type when GraphQL::InterfaceType # A type is a subtype of an interface # if it implements that interface case child_type when GraphQL::ObjectType child_type.interfaces.include?(parent_type) else false end when GraphQL::UnionType # A type is a subtype of that union # if the union includes that type parent_type.possible_types.include?(child_type) when GraphQL::ListType # A list type is a subtype of another list type # if its inner type is a subtype of the other inner type case child_type when GraphQL::ListType subtype?(parent_type.of_type, child_type.of_type) else false end else false end end end end end end ruby-graphql-1.9.19/lib/graphql/execution_error.rb000066400000000000000000000032171362601351000221750ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # If a field's resolve function returns a {ExecutionError}, # the error will be inserted into the response's `"errors"` key # and the field will resolve to `nil`. class ExecutionError < GraphQL::Error # @return [GraphQL::Language::Nodes::Field] the field where the error occurred attr_accessor :ast_node # @return [String] an array describing the JSON-path into the execution # response which corresponds to this error. attr_accessor :path # @return [Hash] Optional data for error objects # @deprecated Use `extensions` instead of `options`. The GraphQL spec # recommends that any custom entries in an error be under the # `extensions` key. attr_accessor :options # @return [Hash] Optional custom data for error objects which will be added # under the `extensions` key. attr_accessor :extensions def initialize(message, ast_node: nil, options: nil, extensions: nil) @ast_node = ast_node @options = options @extensions = extensions super(message) end # @return [Hash] An entry for the response's "errors" key def to_h hash = { "message" => message, } if ast_node hash["locations"] = [ { "line" => ast_node.line, "column" => ast_node.col, } ] end if path hash["path"] = path end if options hash.merge!(options) end if extensions hash["extensions"] = extensions.each_with_object({}) { |(key, value), ext| ext[key.to_s] = value } end hash end end end ruby-graphql-1.9.19/lib/graphql/field.rb000066400000000000000000000263641362601351000200540ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/field/resolve" module GraphQL # {Field}s belong to {ObjectType}s and {InterfaceType}s. # # They're usually created with the `field` helper. If you create it by hand, make sure {#name} is a String. # # A field must have a return type, but if you want to defer the return type calculation until later, # you can pass a proc for the return type. That proc will be called when the schema is defined. # # @example Lazy type resolution # # If the field's type isn't defined yet, you can pass a proc # field :city, -> { TypeForModelName.find("City") } # # For complex field definition, you can pass a block to the `field` helper, eg `field :name do ... end`. # This block is equivalent to calling `GraphQL::Field.define { ... }`. # # @example Defining a field with a block # field :city, CityType do # # field definition continues inside the block # end # # ## Resolve # # Fields have `resolve` functions to determine their values at query-time. # The default implementation is to call a method on the object based on the field name. # # @example Create a field which calls a method with the same name. # GraphQL::ObjectType.define do # field :name, types.String, "The name of this thing " # end # # You can specify a custom proc with the `resolve` helper. # # There are some shortcuts for common `resolve` implementations: # - Provide `property:` to call a method with a different name than the field name # - Provide `hash_key:` to resolve the field by doing a key lookup, eg `obj[:my_hash_key]` # # @example Create a field that calls a different method on the object # GraphQL::ObjectType.define do # # use the `property` keyword: # field :firstName, types.String, property: :first_name # end # # @example Create a field looks up with `[hash_key]` # GraphQL::ObjectType.define do # # use the `hash_key` keyword: # field :firstName, types.String, hash_key: :first_name # end # # ## Arguments # # Fields can take inputs; they're called arguments. You can define them with the `argument` helper. # # @example Create a field with an argument # field :students, types[StudentType] do # argument :grade, types.Int # resolve ->(obj, args, ctx) { # Student.where(grade: args[:grade]) # } # end # # They can have default values which will be provided to `resolve` if the query doesn't include a value. # # @example Argument with a default value # field :events, types[EventType] do # # by default, don't include past events # argument :includePast, types.Boolean, default_value: false # resolve ->(obj, args, ctx) { # args[:includePast] # => false if no value was provided in the query # # ... # } # end # # Only certain types maybe used for inputs: # # - Scalars # - Enums # - Input Objects # - Lists of those types # # Input types may also be non-null -- in that case, the query will fail # if the input is not present. # # ## Complexity # # Fields can have _complexity_ values which describe the computation cost of resolving the field. # You can provide the complexity as a constant with `complexity:` or as a proc, with the `complexity` helper. # # @example Custom complexity values # # Complexity can be a number or a proc. # # # Complexity can be defined with a keyword: # field :expensive_calculation, !types.Int, complexity: 10 # # # Or inside the block: # field :expensive_calculation_2, !types.Int do # complexity ->(ctx, args, child_complexity) { ctx[:current_user].staff? ? 0 : 10 } # end # # @example Calculating the complexity of a list field # field :items, types[ItemType] do # argument :limit, !types.Int # # Multiply the child complexity by the possible items on the list # complexity ->(ctx, args, child_complexity) { child_complexity * args[:limit] } # end # # @example Creating a field, then assigning it to a type # name_field = GraphQL::Field.define do # name("Name") # type(!types.String) # description("The name of this thing") # resolve ->(object, arguments, context) { object.name } # end # # NamedType = GraphQL::ObjectType.define do # # The second argument may be a GraphQL::Field # field :name, name_field # end # class Field include GraphQL::Define::InstanceDefinable accepts_definitions :name, :description, :deprecation_reason, :resolve, :lazy_resolve, :type, :arguments, :property, :hash_key, :complexity, :mutation, :function, :edge_class, :relay_node_field, :relay_nodes_field, :subscription_scope, :trace, :introspection, argument: GraphQL::Define::AssignArgument ensure_defined( :name, :deprecation_reason, :description, :description=, :property, :hash_key, :mutation, :arguments, :complexity, :function, :resolve, :resolve=, :lazy_resolve, :lazy_resolve=, :lazy_resolve_proc, :resolve_proc, :type, :type=, :name=, :property=, :hash_key=, :relay_node_field, :relay_nodes_field, :edges?, :edge_class, :subscription_scope, :introspection? ) # @return [Boolean] True if this is the Relay find-by-id field attr_accessor :relay_node_field # @return [Boolean] True if this is the Relay find-by-ids field attr_accessor :relay_nodes_field # @return [<#call(obj, args, ctx)>] A proc-like object which can be called to return the field's value attr_reader :resolve_proc # @return [<#call(obj, args, ctx)>] A proc-like object which can be called trigger a lazy resolution attr_reader :lazy_resolve_proc # @return [String] The name of this field on its {GraphQL::ObjectType} (or {GraphQL::InterfaceType}) attr_reader :name alias :graphql_name :name # @return [String, nil] The client-facing description of this field attr_accessor :description # @return [String, nil] The client-facing reason why this field is deprecated (if present, the field is deprecated) attr_accessor :deprecation_reason # @return [Hash GraphQL::Argument>] Map String argument names to their {GraphQL::Argument} implementations attr_accessor :arguments # @return [GraphQL::Relay::Mutation, nil] The mutation this field was derived from, if it was derived from a mutation attr_accessor :mutation # @return [Numeric, Proc] The complexity for this field (default: 1), as a constant or a proc like `->(query_ctx, args, child_complexity) { } # Numeric` attr_accessor :complexity # @return [Symbol, nil] The method to call on `obj` to return this field (overrides {#name} if present) attr_reader :property # @return [Object, nil] The key to access with `obj.[]` to resolve this field (overrides {#name} if present) attr_reader :hash_key # @return [Object, GraphQL::Function] The function used to derive this field attr_accessor :function attr_accessor :arguments_class attr_writer :connection attr_writer :introspection # @return [nil, String] Prefix for subscription names from this field attr_accessor :subscription_scope # @return [Boolean] True if this field should be traced. By default, fields are only traced if they are not a ScalarType or EnumType. attr_accessor :trace attr_accessor :ast_node # @return [Boolean] def connection? @connection end # @return [nil, Class] # @api private attr_accessor :edge_class # @return [Boolean] def edges? !!@edge_class end # @return [nil, Integer] attr_accessor :connection_max_page_size def initialize @complexity = 1 @arguments = {} @resolve_proc = build_default_resolver @lazy_resolve_proc = DefaultLazyResolve @relay_node_field = false @connection = false @connection_max_page_size = nil @edge_class = nil @trace = nil @introspection = false end def initialize_copy(other) ensure_defined super @arguments = other.arguments.dup end # @return [Boolean] Is this field a predefined introspection field? def introspection? @introspection end # Get a value for this field # @example resolving a field value # field.resolve(obj, args, ctx) # # @param object [Object] The object this field belongs to # @param arguments [Hash] Arguments declared in the query # @param context [GraphQL::Query::Context] def resolve(object, arguments, context) resolve_proc.call(object, arguments, context) end # Provide a new callable for this field's resolve function. If `nil`, # a new resolve proc will be build based on its {#name}, {#property} or {#hash_key}. # @param new_resolve_proc [<#call(obj, args, ctx)>, nil] def resolve=(new_resolve_proc) @resolve_proc = new_resolve_proc || build_default_resolver end def type=(new_return_type) @clean_type = nil @dirty_type = new_return_type end # Get the return type for this field. def type @clean_type ||= GraphQL::BaseType.resolve_related_type(@dirty_type) end def name=(new_name) old_name = @name @name = new_name if old_name != new_name && @resolve_proc.is_a?(Field::Resolve::NameResolve) # Since the NameResolve would use the old field name, # reset resolve proc when the name has changed self.resolve = nil end end # @param new_property [Symbol] A method to call to resolve this field. Overrides the existing resolve proc. def property=(new_property) @property = new_property self.resolve = nil # reset resolve proc end # @param new_hash_key [Symbol] A key to access with `#[key]` to resolve this field. Overrides the existing resolve proc. def hash_key=(new_hash_key) @hash_key = new_hash_key self.resolve = nil # reset resolve proc end def to_s "" end # If {#resolve} returned an object which should be handled lazily, # this method will be called later to force the object to return its value. # @param obj [Object] The {#resolve}-provided object, registered with {Schema#lazy_resolve} # @param args [GraphQL::Query::Arguments] Arguments to this field # @param ctx [GraphQL::Query::Context] Context for this field # @return [Object] The result of calling the registered method on `obj` def lazy_resolve(obj, args, ctx) @lazy_resolve_proc.call(obj, args, ctx) end # Assign a new resolve proc to this field. Used for {#lazy_resolve} def lazy_resolve=(new_lazy_resolve_proc) @lazy_resolve_proc = new_lazy_resolve_proc end # Prepare a lazy value for this field. It may be `then`-ed and resolved later. # @return [GraphQL::Execution::Lazy] A lazy wrapper around `obj` and its registered method name def prepare_lazy(obj, args, ctx) GraphQL::Execution::Lazy.new { lazy_resolve(obj, args, ctx) } end private def build_default_resolver GraphQL::Field::Resolve.create_proc(self) end module DefaultLazyResolve def self.call(obj, args, ctx) ctx.schema.sync_lazy(obj) end end end end ruby-graphql-1.9.19/lib/graphql/field/000077500000000000000000000000001362601351000175145ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/field/resolve.rb000066400000000000000000000030031362601351000215140ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Field # Create resolve procs ahead of time based on a {GraphQL::Field}'s `name`, `property`, and `hash_key` configuration. module Resolve module_function # @param field [GraphQL::Field] A field that needs a resolve proc # @return [Proc] A resolver for this field, based on its config def create_proc(field) if field.property MethodResolve.new(field) elsif !field.hash_key.nil? HashKeyResolve.new(field.hash_key) else NameResolve.new(field) end end # These only require `obj` as input class BuiltInResolve end # Resolve the field by `public_send`ing `@method_name` class MethodResolve < BuiltInResolve def initialize(field) @method_name = field.property.to_sym end def call(obj, args, ctx) obj.public_send(@method_name) end end # Resolve the field by looking up `@hash_key` with `#[]` class HashKeyResolve < BuiltInResolve def initialize(hash_key) @hash_key = hash_key end def call(obj, args, ctx) obj[@hash_key] end end # Call the field's name at query-time since # it might have changed class NameResolve < BuiltInResolve def initialize(field) @field = field end def call(obj, args, ctx) obj.public_send(@field.name) end end end end end ruby-graphql-1.9.19/lib/graphql/filter.rb000066400000000000000000000023031362601351000202410ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # @api private class Filter def initialize(only: nil, except: nil) @only = only @except = except end # Returns true if `member, ctx` passes this filter def call(member, ctx) (@only ? @only.call(member, ctx) : true) && (@except ? !@except.call(member, ctx) : true) end def merge(only: nil, except: nil) onlies = [self].concat(Array(only)) merged_only = MergedOnly.build(onlies) merged_except = MergedExcept.build(Array(except)) self.class.new(only: merged_only, except: merged_except) end private class MergedOnly def initialize(first, second) @first = first @second = second end def call(member, ctx) @first.call(member, ctx) && @second.call(member, ctx) end def self.build(onlies) case onlies when 0 nil when 1 onlies[0] else onlies.reduce { |memo, only| self.new(memo, only) } end end end class MergedExcept < MergedOnly def call(member, ctx) @first.call(member, ctx) || @second.call(member, ctx) end end end end ruby-graphql-1.9.19/lib/graphql/float_type.rb000066400000000000000000000001351362601351000211230ustar00rootroot00000000000000# frozen_string_literal: true GraphQL::FLOAT_TYPE = GraphQL::Types::Float.graphql_definition ruby-graphql-1.9.19/lib/graphql/function.rb000066400000000000000000000104361362601351000206070ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # A reusable container for field logic, including arguments, resolve, return type, and documentation. # # Class-level values defined with the DSL will be inherited, # so {GraphQL::Function}s can extend one another. # # It's OK to override the instance methods here in order to customize behavior of instances. # # @example A reusable GraphQL::Function attached as a field # class FindRecord < GraphQL::Function # attr_reader :type # # def initialize(model:, type:) # @model = model # @type = type # end # # argument :id, GraphQL::ID_TYPE # # def call(obj, args, ctx) # @model.find(args.id) # end # end # # QueryType = GraphQL::ObjectType.define do # name "Query" # field :post, function: FindRecord.new(model: Post, type: PostType) # field :comment, function: FindRecord.new(model: Comment, type: CommentType) # end # # @see {GraphQL::Schema::Resolver} for a replacement for `GraphQL::Function` class Function # @return [Hash GraphQL::Argument>] Arguments, keyed by name def arguments self.class.arguments end # @return [GraphQL::BaseType] Return type def type self.class.type end # @return [Object] This function's resolver def call(obj, args, ctx) raise GraphQL::RequiredImplementationMissingError end # @return [String, nil] def description self.class.description end # @return [String, nil] def deprecation_reason self.class.deprecation_reason end # @return [Integer, Proc] def complexity self.class.complexity || 1 end class << self # Define an argument for this function & its subclasses # @see {GraphQL::Field} same arguments as the `argument` definition helper # @return [void] def argument(*args, **kwargs, &block) argument = GraphQL::Argument.from_dsl(*args, **kwargs, &block) own_arguments[argument.name] = argument nil end # @return [Hash GraphQL::Argument>] Arguments for this function class, including inherited arguments def arguments if parent_function? own_arguments.merge(superclass.arguments) else own_arguments.dup end end # Provides shorthand access to GraphQL's built-in types def types GraphQL::Define::TypeDefiner.instance end # Get or set the return type for this function class & descendants # @return [GraphQL::BaseType] def type(premade_type = nil, &block) if block_given? @type = GraphQL::ObjectType.define(&block) elsif premade_type @type = premade_type elsif parent_function? @type || superclass.type else @type end end def build_field(function) GraphQL::Field.define( arguments: function.arguments, complexity: function.complexity, type: function.type, resolve: function, description: function.description, function: function, deprecation_reason: function.deprecation_reason, ) end # Class-level reader/writer which is inherited # @api private def self.inherited_value(name) self.class_eval <<-RUBY def #{name}(new_value = nil) if new_value @#{name} = new_value elsif parent_function? @#{name} || superclass.#{name} else @#{name} end end RUBY end # @!method description(new_value = nil) # Get or set this class's description inherited_value(:description) # @!method deprecation_reason(new_value = nil) # Get or set this class's deprecation_reason inherited_value(:deprecation_reason) # @!method complexity(new_value = nil) # Get or set this class's complexity inherited_value(:complexity) private # Does this function inherit from another function? def parent_function? superclass <= GraphQL::Function end # Arguments defined on this class (not superclasses) def own_arguments @own_arguments ||= {} end end end end ruby-graphql-1.9.19/lib/graphql/id_type.rb000066400000000000000000000001271362601351000204130ustar00rootroot00000000000000# frozen_string_literal: true GraphQL::ID_TYPE = GraphQL::Types::ID.graphql_definition ruby-graphql-1.9.19/lib/graphql/input_object_type.rb000066400000000000000000000114221362601351000225040ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # {InputObjectType}s are key-value inputs for fields. # # Input objects have _arguments_ which are identical to {GraphQL::Field} arguments. # They map names to types and support default values. # Their input types can be any input types, including {InputObjectType}s. # # @example An input type with name and number # PlayerInput = GraphQL::InputObjectType.define do # name("Player") # argument :name, !types.String # argument :number, !types.Int # end # # In a `resolve` function, you can access the values by making nested lookups on `args`. # # @example Accessing input values in a resolve function # resolve ->(obj, args, ctx) { # args[:player][:name] # => "Tony Gwynn" # args[:player][:number] # => 19 # args[:player].to_h # { "name" => "Tony Gwynn", "number" => 19 } # # ... # } # class InputObjectType < GraphQL::BaseType accepts_definitions( :arguments, :mutation, input_field: GraphQL::Define::AssignArgument, argument: GraphQL::Define::AssignArgument ) attr_accessor :mutation, :arguments, :arguments_class ensure_defined(:mutation, :arguments, :input_fields) alias :input_fields :arguments # @!attribute mutation # @return [GraphQL::Relay::Mutation, nil] The mutation this field was derived from, if it was derived from a mutation # @!attribute arguments # @return [Hash GraphQL::Argument>] Map String argument names to their {GraphQL::Argument} implementations def initialize super @arguments = {} end def initialize_copy(other) super @arguments = other.arguments.dup end def kind GraphQL::TypeKinds::INPUT_OBJECT end def coerce_result(value, ctx = nil) if ctx.nil? warn_deprecated_coerce("coerce_isolated_result") ctx = GraphQL::Query::NullContext end # Allow the application to provide values as :symbols, and convert them to the strings value = value.reduce({}) { |memo, (k, v)| memo[k.to_s] = v; memo } result = {} arguments.each do |input_key, input_field_defn| input_value = value[input_key] if value.key?(input_key) result[input_key] = if input_value.nil? nil else input_field_defn.type.coerce_result(input_value, ctx) end end end result end private def coerce_non_null_input(value, ctx) input_values = {} defaults_used = Set.new arguments.each do |input_key, input_field_defn| field_value = value[input_key] if value.key?(input_key) coerced_value = input_field_defn.type.coerce_input(field_value, ctx) input_values[input_key] = input_field_defn.prepare(coerced_value, ctx) elsif input_field_defn.default_value? coerced_value = input_field_defn.type.coerce_input(input_field_defn.default_value, ctx) input_values[input_key] = coerced_value defaults_used << input_key end end result = arguments_class.new(input_values, context: ctx, defaults_used: defaults_used) result.prepare end # @api private INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key, value object responding to `to_h` or `to_unsafe_h`." def validate_non_null_input(input, ctx) warden = ctx.warden result = GraphQL::Query::InputValidationResult.new if input.is_a?(Array) result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) }) return result end # We're not actually _using_ the coerced result, we're just # using these methods to make sure that the object will # behave like a hash below, when we call `each` on it. begin input.to_h rescue begin # Handle ActionController::Parameters: input.to_unsafe_h rescue # We're not sure it'll act like a hash, so reject it: result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) }) return result end end visible_arguments_map = warden.arguments(self).reduce({}) { |m, f| m[f.name] = f; m} # Items in the input that are unexpected input.each do |name, value| if visible_arguments_map[name].nil? result.add_problem("Field is not defined on #{self.name}", [name]) end end # Items in the input that are expected, but have invalid values visible_arguments_map.map do |name, field| field_result = field.type.validate_input(input[name], ctx) if !field_result.valid? result.merge_result!(name, field_result) end end result end end end ruby-graphql-1.9.19/lib/graphql/int_type.rb000066400000000000000000000001311362601351000206040ustar00rootroot00000000000000# frozen_string_literal: true GraphQL::INT_TYPE = GraphQL::Types::Int.graphql_definition ruby-graphql-1.9.19/lib/graphql/integer_encoding_error.rb000066400000000000000000000012541362601351000234740ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # This error is raised when `Types::Int` is asked to return a value outside of 32-bit integer range. # # For values outside that range, consider: # # - `ID` for database primary keys or other identifiers # - `GraphQL::Types::BigInt` for really big integer values # # @see GraphQL::Types::Int which raises this error class IntegerEncodingError < GraphQL::RuntimeTypeError # The value which couldn't be encoded attr_reader :integer_value def initialize(value) @integer_value = value super("Integer out of bounds: #{value}. \nConsider using ID or GraphQL::Types::BigInt instead.") end end end ruby-graphql-1.9.19/lib/graphql/interface_type.rb000066400000000000000000000056201362601351000217620ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # An Interface contains a collection of types which implement some of the same fields. # # Interfaces can have fields, defined with `field`, just like an object type. # # Objects which implement this field _inherit_ field definitions from the interface. # An object type can override the inherited definition by redefining that field. # # @example An interface with three fields # DeviceInterface = GraphQL::InterfaceType.define do # name("Device") # description("Hardware devices for computing") # # field :ram, types.String # field :processor, ProcessorType # field :release_year, types.Int # end # # @example Implementing an interface with an object type # Laptoptype = GraphQL::ObjectType.define do # interfaces [DeviceInterface] # end # class InterfaceType < GraphQL::BaseType accepts_definitions :fields, :orphan_types, :resolve_type, field: GraphQL::Define::AssignObjectField attr_accessor :fields, :orphan_types, :resolve_type_proc ensure_defined :fields, :orphan_types, :resolve_type_proc, :resolve_type def initialize super @fields = {} @orphan_types = [] @resolve_type_proc = nil end def initialize_copy(other) super @fields = other.fields.dup @orphan_types = other.orphan_types.dup end def kind GraphQL::TypeKinds::INTERFACE end def resolve_type(value, ctx) ctx.query.resolve_type(self, value) end def resolve_type=(resolve_type_callable) @resolve_type_proc = resolve_type_callable end # @return [GraphQL::Field] The defined field for `field_name` def get_field(field_name) fields[field_name] end # These fields don't have instrumenation applied # @see [Schema#get_fields] Get fields with instrumentation # @return [Array] All fields on this type def all_fields fields.values end # Get a possible type of this {InterfaceType} by type name # @param type_name [String] # @param ctx [GraphQL::Query::Context] The context for the current query # @return [GraphQL::ObjectType, nil] The type named `type_name` if it exists and implements this {InterfaceType}, (else `nil`) def get_possible_type(type_name, ctx) type = ctx.query.get_type(type_name) type if type && ctx.query.schema.possible_types(self).include?(type) end # Check if a type is a possible type of this {InterfaceType} # @param type [String, GraphQL::BaseType] Name of the type or a type definition # @param ctx [GraphQL::Query::Context] The context for the current query # @return [Boolean] True if the `type` exists and is a member of this {InterfaceType}, (else `nil`) def possible_type?(type, ctx) type_name = type.is_a?(String) ? type : type.graphql_name !get_possible_type(type_name, ctx).nil? end end end ruby-graphql-1.9.19/lib/graphql/internal_representation.rb000066400000000000000000000005021362601351000237110ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/internal_representation/document" require "graphql/internal_representation/node" require "graphql/internal_representation/print" require "graphql/internal_representation/rewrite" require "graphql/internal_representation/scope" require "graphql/internal_representation/visit" ruby-graphql-1.9.19/lib/graphql/internal_representation/000077500000000000000000000000001362601351000233675ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/internal_representation/document.rb000066400000000000000000000013671362601351000255410ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module InternalRepresentation class Document # @return [Hash] Operation Nodes of this query attr_reader :operation_definitions # @return [Hash] Fragment definition Nodes of this query attr_reader :fragment_definitions def initialize @operation_definitions = {} @fragment_definitions = {} end def [](key) warn "#{self.class}#[] is deprecated; use `operation_definitions[]` instead" operation_definitions[key] end def each(&block) warn "#{self.class}#each is deprecated; use `operation_definitions.each` instead" operation_definitions.each(&block) end end end end ruby-graphql-1.9.19/lib/graphql/internal_representation/node.rb000066400000000000000000000151241362601351000246440ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module InternalRepresentation class Node # @api private DEFAULT_TYPED_CHILDREN = Proc.new { |h, k| h[k] = {} } # A specialized, reusable object for leaf nodes. NO_TYPED_CHILDREN = Hash.new({}.freeze) def NO_TYPED_CHILDREN.dup; self; end; NO_TYPED_CHILDREN.freeze # @return [String] the name this node has in the response attr_reader :name # @return [GraphQL::ObjectType] attr_reader :owner_type # Each key is a {GraphQL::ObjectType} which this selection _may_ be made on. # The values for that key are selections which apply to that type. # # This value is derived from {#scoped_children} after the rewrite is finished. # @return [Hash Node>>] def typed_children @typed_children ||= begin if @scoped_children.any? new_tc = Hash.new(&DEFAULT_TYPED_CHILDREN) all_object_types = Set.new scoped_children.each_key { |t| all_object_types.merge(@query.possible_types(t)) } # Remove any scoped children which don't follow this return type # (This can happen with fragment merging where lexical scope is lost) all_object_types &= @query.possible_types(@return_type.unwrap) all_object_types.each do |t| new_tc[t] = get_typed_children(t) end new_tc else NO_TYPED_CHILDREN end end end # These children correspond closely to scopes in the AST. # Keys _may_ be abstract types. They're assumed to be read-only after rewrite is finished # because {#typed_children} is derived from them. # # Using {#scoped_children} during the rewrite step reduces the overhead of reifying # abstract types because they're only reified _after_ the rewrite. # @return [Hash Node>>] attr_reader :scoped_children # @return [Array] AST nodes which are represented by this node attr_reader :ast_nodes # @return [Array] Field definitions for this node (there should only be one!) attr_reader :definitions # @return [GraphQL::BaseType] The expected wrapped type this node must return. attr_reader :return_type # @return [InternalRepresentation::Node, nil] attr_reader :parent def initialize( name:, owner_type:, query:, return_type:, parent:, ast_nodes: [], definitions: [] ) @name = name @query = query @owner_type = owner_type @parent = parent @typed_children = nil @scoped_children = Hash.new { |h1, k1| h1[k1] = {} } @ast_nodes = ast_nodes @definitions = definitions @return_type = return_type end def initialize_copy(other_node) super # Bust some caches: @typed_children = nil @definition = nil @definition_name = nil @ast_node = nil # Shallow-copy some state: @scoped_children = other_node.scoped_children.dup @ast_nodes = other_node.ast_nodes.dup @definitions = other_node.definitions.dup end def ==(other) other.is_a?(self.class) && other.name == name && other.parent == parent && other.return_type == return_type && other.owner_type == owner_type && other.scoped_children == scoped_children && other.definitions == definitions && other.ast_nodes == ast_nodes end def definition_name definition && definition.name end def arguments @query.arguments_for(self, definition) end def definition @definition ||= begin first_def = @definitions.first first_def && @query.get_field(@owner_type, first_def.name) end end def ast_node @ast_node ||= ast_nodes.first end def inspect all_children_names = scoped_children.values.map(&:keys).flatten.uniq.join(", ") all_locations = ast_nodes.map {|n| "#{n.line}:#{n.col}" }.join(", ") "# #{@return_type} {#{all_children_names}} @ [#{all_locations}] #{object_id}>" end # Merge selections from `new_parent` into `self`. # Selections are merged in place, not copied. def deep_merge_node(new_parent, scope: nil, merge_self: true) if merge_self @ast_nodes |= new_parent.ast_nodes @definitions |= new_parent.definitions end new_sc = new_parent.scoped_children if new_sc.any? scope ||= Scope.new(@query, @return_type.unwrap) new_sc.each do |obj_type, new_fields| inner_scope = scope.enter(obj_type) inner_scope.each do |scoped_type| prev_fields = @scoped_children[scoped_type] new_fields.each do |name, new_node| prev_node = prev_fields[name] if prev_node prev_node.deep_merge_node(new_node) else prev_fields[name] = new_node end end end end end end # @return [GraphQL::Query] attr_reader :query def subscription_topic @subscription_topic ||= begin scope = if definition.subscription_scope @query.context[definition.subscription_scope] else nil end Subscriptions::Event.serialize( definition_name, @query.arguments_for(self, definition), definition, scope: scope ) end end protected attr_writer :owner_type, :parent private # Get applicable children from {#scoped_children} # @param obj_type [GraphQL::ObjectType] # @return [Hash Node>] def get_typed_children(obj_type) new_tc = {} @scoped_children.each do |scope_type, scope_nodes| if GraphQL::Execution::Typecast.subtype?(scope_type, obj_type) scope_nodes.each do |name, new_node| prev_node = new_tc[name] if prev_node prev_node.deep_merge_node(new_node) else copied_node = new_node.dup copied_node.owner_type = obj_type copied_node.parent = self new_tc[name] = copied_node end end end end new_tc end end end end ruby-graphql-1.9.19/lib/graphql/internal_representation/print.rb000066400000000000000000000027671362601351000250640ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module InternalRepresentation module Print module_function def print(schema, query_string) query = GraphQL::Query.new(schema, query_string) print_node(query.irep_selection) end def print_node(node, indent: 0) padding = " " * indent typed_children_padding = " " * (indent + 2) query_str = "".dup if !node.definition op_node = node.ast_node name = op_node.name ? " " + op_node.name : "" op_type = op_node.operation_type query_str << "#{op_type}#{name}" else if node.name == node.definition_name query_str << "#{padding}#{node.name}" else query_str << "#{padding}#{node.name}: #{node.definition_name}" end args = node.ast_nodes.map { |n| n.arguments.map(&:to_query_string).join(",") }.uniq query_str << args.map { |a| "(#{a})"}.join("|") end if node.typed_children.any? query_str << " {\n" node.typed_children.each do |type, children| query_str << "#{typed_children_padding}... on #{type.name} {\n" children.each do |name, child| query_str << print_node(child, indent: indent + 4) end query_str << "#{typed_children_padding}}\n" end query_str << "#{padding}}\n" else query_str << "\n" end query_str end end end end ruby-graphql-1.9.19/lib/graphql/internal_representation/rewrite.rb000066400000000000000000000144171362601351000254040ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module InternalRepresentation # While visiting an AST, build a normalized, flattened tree of {InternalRepresentation::Node}s. # # No unions or interfaces are present in this tree, only object types. # # Selections from the AST are attached to the object types they apply to. # # Inline fragments and fragment spreads are preserved in {InternalRepresentation::Node#ast_spreads}, # where they can be used to check for the presence of directives. This might not be sufficient # for future directives, since the selections' grouping is lost. # # The rewritten query tree serves as the basis for the `FieldsWillMerge` validation. # module Rewrite include GraphQL::Language NO_DIRECTIVES = [].freeze # @return InternalRepresentation::Document attr_reader :rewrite_document def initialize(*) super @query = context.query @rewrite_document = InternalRepresentation::Document.new # Hash Set> # A record of fragment spreads and the irep nodes that used them @rewrite_spread_parents = Hash.new { |h, k| h[k] = Set.new } # Hash Scope> @rewrite_spread_scopes = {} # Array> # The current point of the irep_tree during visitation @rewrite_nodes_stack = [] # Array @rewrite_scopes_stack = [] @rewrite_skip_nodes = Set.new # Resolve fragment spreads. # Fragment definitions got their own irep trees during visitation. # Those nodes are spliced in verbatim (not copied), but this is OK # because fragments are resolved from the "bottom up", each fragment # can be shared between its usages. context.on_dependency_resolve do |defn_ast_node, spread_ast_nodes, frag_ast_node| frag_name = frag_ast_node.name fragment_node = @rewrite_document.fragment_definitions[frag_name] if fragment_node spread_ast_nodes.each do |spread_ast_node| parent_nodes = @rewrite_spread_parents[spread_ast_node] parent_scope = @rewrite_spread_scopes[spread_ast_node] parent_nodes.each do |parent_node| parent_node.deep_merge_node(fragment_node, scope: parent_scope, merge_self: false) end end end end end # @return [Hash] Roots of this query def operations warn "#{self.class}#operations is deprecated; use `document.operation_definitions` instead" @document.operation_definitions end def on_operation_definition(ast_node, parent) push_root_node(ast_node, @rewrite_document.operation_definitions) { super } end def on_fragment_definition(ast_node, parent) push_root_node(ast_node, @rewrite_document.fragment_definitions) { super } end def push_root_node(ast_node, definitions) # Either QueryType or the fragment type condition owner_type = context.type_definition defn_name = ast_node.name node = Node.new( parent: nil, name: defn_name, owner_type: owner_type, query: @query, ast_nodes: [ast_node], return_type: owner_type, ) definitions[defn_name] = node @rewrite_scopes_stack.push(Scope.new(@query, owner_type)) @rewrite_nodes_stack.push([node]) yield @rewrite_nodes_stack.pop @rewrite_scopes_stack.pop end def on_inline_fragment(node, parent) # Inline fragments provide two things to the rewritten tree: # - They _may_ narrow the scope by their type condition # - They _may_ apply their directives to their children if skip?(node) @rewrite_skip_nodes.add(node) end if @rewrite_skip_nodes.empty? @rewrite_scopes_stack.push(@rewrite_scopes_stack.last.enter(context.type_definition)) end super if @rewrite_skip_nodes.empty? @rewrite_scopes_stack.pop end if @rewrite_skip_nodes.include?(node) @rewrite_skip_nodes.delete(node) end end def on_field(ast_node, ast_parent) if skip?(ast_node) @rewrite_skip_nodes.add(ast_node) end if @rewrite_skip_nodes.empty? node_name = ast_node.alias || ast_node.name parent_nodes = @rewrite_nodes_stack.last next_nodes = [] field_defn = context.field_definition if field_defn.nil? # It's a non-existent field new_scope = nil else field_return_type = field_defn.type @rewrite_scopes_stack.last.each do |scope_type| parent_nodes.each do |parent_node| node = parent_node.scoped_children[scope_type][node_name] ||= Node.new( parent: parent_node, name: node_name, owner_type: scope_type, query: @query, return_type: field_return_type, ) node.ast_nodes << ast_node node.definitions << field_defn next_nodes << node end end new_scope = Scope.new(@query, field_return_type.unwrap) end @rewrite_nodes_stack.push(next_nodes) @rewrite_scopes_stack.push(new_scope) end super if @rewrite_skip_nodes.empty? @rewrite_nodes_stack.pop @rewrite_scopes_stack.pop end if @rewrite_skip_nodes.include?(ast_node) @rewrite_skip_nodes.delete(ast_node) end end def on_fragment_spread(ast_node, ast_parent) if @rewrite_skip_nodes.empty? && !skip?(ast_node) # Register the irep nodes that depend on this AST node: @rewrite_spread_parents[ast_node].merge(@rewrite_nodes_stack.last) @rewrite_spread_scopes[ast_node] = @rewrite_scopes_stack.last end super end def skip?(ast_node) dir = ast_node.directives dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, @query) end end end end ruby-graphql-1.9.19/lib/graphql/internal_representation/scope.rb000066400000000000000000000052241362601351000250300ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module InternalRepresentation # At a point in the AST, selections may apply to one or more types. # {Scope} represents those types which selections may apply to. # # Scopes can be defined by: # # - A single concrete or abstract type # - An array of types # - `nil` # # The AST may be scoped to an array of types when two abstractly-typed # fragments occur in inside one another. class Scope NO_TYPES = [].freeze # @param query [GraphQL::Query] # @param type_defn [GraphQL::BaseType, Array, nil] def initialize(query, type_defn) @query = query @type = type_defn @abstract_type = false @types = case type_defn when Array type_defn when GraphQL::BaseType @abstract_type = true nil when nil NO_TYPES else raise "Unexpected scope type: #{type_defn}" end end # From a starting point of `self`, create a new scope by condition `other_type_defn`. # @param other_type_defn [GraphQL::BaseType, nil] # @return [Scope] def enter(other_type_defn) case other_type_defn when nil # The type wasn't found, who cares Scope.new(@query, nil) when @type # The condition is the same as current, so reuse self self when GraphQL::UnionType, GraphQL::InterfaceType # Make a new scope of the intersection between the previous & next conditions new_types = @query.possible_types(other_type_defn) & concrete_types Scope.new(@query, new_types) when GraphQL::BaseType # If this type is valid within the current scope, # return a new scope of _exactly_ this type. # Otherwise, this type is out-of-scope so the scope is null. if concrete_types.include?(other_type_defn) Scope.new(@query, other_type_defn) else Scope.new(@query, nil) end else raise "Unexpected scope: #{other_type_defn.inspect}" end end # Call the block for each type in `self`. # This uses the simplest possible expression of `self`, # so if this scope is defined by an abstract type, it gets yielded. def each if @abstract_type yield(@type) else @types.each { |t| yield(t) } end end private def concrete_types @concrete_types ||= if @abstract_type @query.possible_types(@type) else @types end end end end end ruby-graphql-1.9.19/lib/graphql/internal_representation/visit.rb000066400000000000000000000021711362601351000250530ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module InternalRepresentation # Traverse a re-written query tree, calling handlers for each node module Visit module_function def visit_each_node(operations, handlers) return if handlers.empty? # Post-validation: make some assertions about the rewritten query tree operations.each do |op_name, op_node| # Yield each node to listeners which were attached by validators op_node.typed_children.each do |obj_type, children| children.each do |name, op_child_node| each_node(op_child_node) do |node| for h in handlers h.call(node) end end end end end end # Traverse a node in a rewritten query tree, # visiting the node itself and each of its typed children. def each_node(node) yield(node) node.typed_children.each do |obj_type, children| children.each do |name, node| each_node(node) { |n| yield(n) } end end end end end end ruby-graphql-1.9.19/lib/graphql/introspection.rb000066400000000000000000000012031362601351000216520ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection end end require "graphql/introspection/base_object" require "graphql/introspection/input_value_type" require "graphql/introspection/enum_value_type" require "graphql/introspection/type_kind_enum" require "graphql/introspection/type_type" require "graphql/introspection/field_type" require "graphql/introspection/directive_location_enum" require "graphql/introspection/directive_type" require "graphql/introspection/schema_type" require "graphql/introspection/introspection_query" require "graphql/introspection/dynamic_fields" require "graphql/introspection/entry_points" ruby-graphql-1.9.19/lib/graphql/introspection/000077500000000000000000000000001362601351000213315ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/introspection/base_object.rb000066400000000000000000000005611362601351000241200ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class BaseObject < GraphQL::Schema::Object def self.field(*args, **kwargs, &block) kwargs[:introspection] = true super(*args, **kwargs, &block) end def self.inherited(child_class) child_class.introspection(true) super end end end end ruby-graphql-1.9.19/lib/graphql/introspection/directive_location_enum.rb000066400000000000000000000010411362601351000265440ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class DirectiveLocationEnum < GraphQL::Schema::Enum graphql_name "__DirectiveLocation" description "A Directive can be adjacent to many parts of the GraphQL language, "\ "a __DirectiveLocation describes one such possible adjacencies." GraphQL::Directive::LOCATIONS.each do |location| value(location.to_s, GraphQL::Directive::LOCATION_DESCRIPTIONS[location], value: location) end introspection true end end end ruby-graphql-1.9.19/lib/graphql/introspection/directive_type.rb000066400000000000000000000024741362601351000247040ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class DirectiveType < Introspection::BaseObject graphql_name "__Directive" description "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document."\ "\n\n"\ "In some cases, you need to provide options to alter GraphQL's execution behavior "\ "in ways field arguments will not suffice, such as conditionally including or "\ "skipping a field. Directives provide this by describing additional information "\ "to the executor." field :name, String, null: false field :description, String, null: true field :locations, [GraphQL::Schema::LateBoundType.new("__DirectiveLocation")], null: false field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false field :on_operation, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_operation? field :on_fragment, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_fragment? field :on_field, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_field? def args @context.warden.arguments(@object) end end end end ruby-graphql-1.9.19/lib/graphql/introspection/dynamic_fields.rb000066400000000000000000000007521362601351000246340ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class DynamicFields < Introspection::BaseObject field :__typename, String, "The name of this type", null: false, extras: [:irep_node] # `irep_node:` will be nil for the interpreter, since there is no such thing def __typename(irep_node: nil) if context.interpreter? object.class.graphql_name else irep_node.owner_type.name end end end end end ruby-graphql-1.9.19/lib/graphql/introspection/entry_points.rb000066400000000000000000000025721362601351000244210ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class EntryPoints < Introspection::BaseObject field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system", null: true do argument :name, String, required: true end def __schema # Apply wrapping manually since this field isn't wrapped by instrumentation schema = @context.query.schema schema_type = schema.introspection_system.schema_type schema_type.metadata[:type_class].authorized_new(schema, @context) end def __type(name:) return unless context.warden.reachable_type?(name) type = context.warden.get_type(name) if type && context.interpreter? type = type.metadata[:type_class] || raise("Invariant: interpreter requires class-based type for #{name}") end # The interpreter provides this wrapping, other execution doesnt, so support both. if type && !context.interpreter? # Apply wrapping manually since this field isn't wrapped by instrumentation type_type = context.schema.introspection_system.type_type type = type_type.metadata[:type_class].authorized_new(type, context) end type end end end end ruby-graphql-1.9.19/lib/graphql/introspection/enum_value_type.rb000066400000000000000000000013311362601351000250550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class EnumValueType < Introspection::BaseObject graphql_name "__EnumValue" description "One possible value for a given Enum. Enum values are unique values, not a "\ "placeholder for a string or numeric value. However an Enum value is returned in "\ "a JSON response as a string." field :name, String, null: false field :description, String, null: true field :is_deprecated, Boolean, null: false field :deprecation_reason, String, null: true def name object.graphql_name end def is_deprecated !!@object.deprecation_reason end end end end ruby-graphql-1.9.19/lib/graphql/introspection/field_type.rb000066400000000000000000000015011362601351000237770ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class FieldType < Introspection::BaseObject graphql_name "__Field" description "Object and Interface types are described by a list of Fields, each of which has "\ "a name, potentially a list of arguments, and a return type." field :name, String, null: false field :description, String, null: true field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false field :is_deprecated, Boolean, null: false field :deprecation_reason, String, null: true def is_deprecated !!@object.deprecation_reason end def args @context.warden.arguments(@object) end end end end ruby-graphql-1.9.19/lib/graphql/introspection/input_value_type.rb000066400000000000000000000024561362601351000252610ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class InputValueType < Introspection::BaseObject graphql_name "__InputValue" description "Arguments provided to Fields or Directives and the input fields of an "\ "InputObject are represented as Input Values which describe their type and "\ "optionally a default value." field :name, String, null: false field :description, String, null: true field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false field :default_value, String, "A GraphQL-formatted string representing the default value for this input value.", null: true def default_value if @object.default_value? value = @object.default_value if value.nil? 'null' else coerced_default_value = @object.type.coerce_result(value, @context) if @object.type.unwrap.is_a?(GraphQL::EnumType) if @object.type.list? "[#{coerced_default_value.join(", ")}]" else coerced_default_value end else GraphQL::Language.serialize(coerced_default_value) end end else nil end end end end end ruby-graphql-1.9.19/lib/graphql/introspection/introspection_query.rb000066400000000000000000000027421362601351000260100ustar00rootroot00000000000000# frozen_string_literal: true # The introspection query to end all introspection queries, copied from # https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js GraphQL::Introspection::INTROSPECTION_QUERY = " query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } } } fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } } " ruby-graphql-1.9.19/lib/graphql/introspection/schema_type.rb000066400000000000000000000035711362601351000241650ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class SchemaType < Introspection::BaseObject graphql_name "__Schema" description "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all "\ "available types and directives on the server, as well as the entry points for "\ "query, mutation, and subscription operations." field :types, [GraphQL::Schema::LateBoundType.new("__Type")], "A list of all types supported by this server.", null: false field :queryType, GraphQL::Schema::LateBoundType.new("__Type"), "The type that query operations will be rooted at.", null: false field :mutationType, GraphQL::Schema::LateBoundType.new("__Type"), "If this server supports mutation, the type that mutation operations will be rooted at.", null: true field :subscriptionType, GraphQL::Schema::LateBoundType.new("__Type"), "If this server support subscription, the type that subscription operations will be rooted at.", null: true field :directives, [GraphQL::Schema::LateBoundType.new("__Directive")], "A list of all directives supported by this server.", null: false def types types = @context.warden.reachable_types.sort_by(&:graphql_name) if context.interpreter? types.map { |t| t.metadata[:type_class] || raise("Invariant: can't introspect non-class-based type: #{t}") } else types end end def query_type permitted_root_type("query") end def mutation_type permitted_root_type("mutation") end def subscription_type permitted_root_type("subscription") end def directives context.schema.directives.values end private def permitted_root_type(op_type) @context.warden.root_type_for_operation(op_type) end end end end ruby-graphql-1.9.19/lib/graphql/introspection/type_kind_enum.rb000066400000000000000000000006041362601351000246700ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class TypeKindEnum < GraphQL::Schema::Enum graphql_name "__TypeKind" description "An enum describing what kind of type a given `__Type` is." GraphQL::TypeKinds::TYPE_KINDS.each do |type_kind| value(type_kind.name, type_kind.description) end introspection true end end end ruby-graphql-1.9.19/lib/graphql/introspection/type_type.rb000066400000000000000000000060631362601351000237050ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class TypeType < Introspection::BaseObject graphql_name "__Type" description "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in "\ "GraphQL as represented by the `__TypeKind` enum.\n\n"\ "Depending on the kind of a type, certain fields describe information about that type. "\ "Scalar types provide no information beyond a name and description, while "\ "Enum types provide their values. Object and Interface types provide the fields "\ "they describe. Abstract types, Union and Interface, provide the Object types "\ "possible at runtime. List and NonNull types compose other types." field :kind, GraphQL::Schema::LateBoundType.new("__TypeKind"), null: false field :name, String, null: true field :description, String, null: true field :fields, [GraphQL::Schema::LateBoundType.new("__Field")], null: true do argument :include_deprecated, Boolean, required: false, default_value: false end field :interfaces, [GraphQL::Schema::LateBoundType.new("__Type")], null: true field :possible_types, [GraphQL::Schema::LateBoundType.new("__Type")], null: true field :enum_values, [GraphQL::Schema::LateBoundType.new("__EnumValue")], null: true do argument :include_deprecated, Boolean, required: false, default_value: false end field :input_fields, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: true field :of_type, GraphQL::Schema::LateBoundType.new("__Type"), null: true def name object.graphql_name end def kind @object.kind.name end def enum_values(include_deprecated:) if !@object.kind.enum? nil else enum_values = @context.warden.enum_values(@object.graphql_definition) if !include_deprecated enum_values = enum_values.select {|f| !f.deprecation_reason } end enum_values end end def interfaces if @object.kind == GraphQL::TypeKinds::OBJECT @context.warden.interfaces(@object.graphql_definition) else nil end end def input_fields if @object.kind.input_object? @context.warden.arguments(@object.graphql_definition) else nil end end def possible_types if @object.kind.abstract? @context.warden.possible_types(@object.graphql_definition) else nil end end def fields(include_deprecated:) if !@object.kind.fields? nil else fields = @context.warden.fields(@object.graphql_definition) if !include_deprecated fields = fields.select {|f| !f.deprecation_reason } end fields.sort_by(&:name) end end def of_type @object.kind.wraps? ? @object.of_type : nil end end end end ruby-graphql-1.9.19/lib/graphql/invalid_name_error.rb000066400000000000000000000005001362601351000226100ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class InvalidNameError < GraphQL::ExecutionError attr_reader :name, :valid_regex def initialize(name, valid_regex) @name = name @valid_regex = valid_regex super("Names must match #{@valid_regex.inspect} but '#{@name}' does not") end end end ruby-graphql-1.9.19/lib/graphql/invalid_null_error.rb000066400000000000000000000016241362601351000226520ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Raised automatically when a field's resolve function returns `nil` # for a non-null field. class InvalidNullError < GraphQL::RuntimeTypeError # @return [GraphQL::BaseType] The owner of {#field} attr_reader :parent_type # @return [GraphQL::Field] The field which failed to return a value attr_reader :field # @return [nil, GraphQL::ExecutionError] The invalid value for this field attr_reader :value def initialize(parent_type, field, value) @parent_type = parent_type @field = field @value = value super("Cannot return null for non-nullable field #{@parent_type.graphql_name}.#{@field.graphql_name}") end # @return [Hash] An entry for the response's "errors" key def to_h { "message" => message } end # @deprecated always false def parent_error? false end end end ruby-graphql-1.9.19/lib/graphql/language.rb000066400000000000000000000015771362601351000205530ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/language/block_string" require "graphql/language/printer" require "graphql/language/definition_slice" require "graphql/language/document_from_schema_definition" require "graphql/language/generation" require "graphql/language/lexer" require "graphql/language/nodes" require "graphql/language/parser" require "graphql/language/token" require "graphql/language/visitor" module GraphQL module Language # @api private def self.serialize(value) if value.is_a?(Hash) serialized_hash = value.map do |k, v| "#{k}:#{serialize v}" end.join(",") "{#{serialized_hash}}" elsif value.is_a?(Array) serialized_array = value.map do |v| serialize v end.join(",") "[#{serialized_array}]" else JSON.generate(value, quirks_mode: true) end end end end ruby-graphql-1.9.19/lib/graphql/language/000077500000000000000000000000001362601351000202145ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/language/block_string.rb000066400000000000000000000042211362601351000232200ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language module BlockString # Remove leading and trailing whitespace from a block string. # See "Block Strings" in https://github.com/facebook/graphql/blob/master/spec/Section%202%20--%20Language.md def self.trim_whitespace(str) lines = str.split("\n") common_indent = nil # find the common whitespace lines.each_with_index do |line, idx| if idx == 0 next end line_length = line.size line_indent = line[/\A */].size if line_indent < line_length && (common_indent.nil? || line_indent < common_indent) common_indent = line_indent end end # Remove the common whitespace if common_indent lines.each_with_index do |line, idx| if idx == 0 next else line[0, common_indent] = "" end end end # Remove leading & trailing blank lines while lines.size > 0 && lines[0].empty? lines.shift end while lines.size > 0 && lines[-1].empty? lines.pop end # Rebuild the string lines.join("\n") end def self.print(str, indent: '') lines = str.split("\n") block_str = "#{indent}\"\"\"\n".dup lines.each do |line| if line == '' block_str << "\n" else sublines = break_line(line, 120 - indent.length) sublines.each do |subline| block_str << "#{indent}#{subline}\n" end end end block_str << "#{indent}\"\"\"\n".dup end private def self.break_line(line, length) return [line] if line.length < length + 5 parts = line.split(Regexp.new("((?: |^).{15,#{length - 40}}(?= |$))")) return [line] if parts.length < 4 sublines = [parts.slice!(0, 3).join] parts.each_with_index do |part, i| next if i % 2 == 1 sublines << "#{part[1..-1]}#{parts[i + 1]}" end sublines end end end end ruby-graphql-1.9.19/lib/graphql/language/definition_slice.rb000066400000000000000000000015521362601351000240530ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language module DefinitionSlice extend self def slice(document, name) definitions = {} document.definitions.each { |d| definitions[d.name] = d } names = find_definition_dependencies(definitions, name) definitions = document.definitions.select { |d| names.include?(d.name) } Nodes::Document.new(definitions: definitions) end private def find_definition_dependencies(definitions, name) names = Set.new([name]) visitor = Visitor.new(definitions[name]) visitor[Nodes::FragmentSpread] << ->(node, parent) { if fragment = definitions[node.name] names.merge(find_definition_dependencies(definitions, fragment.name)) end } visitor.visit names end end end end ruby-graphql-1.9.19/lib/graphql/language/document_from_schema_definition.rb000066400000000000000000000243241362601351000271370ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language # @api private # # {GraphQL::Language::DocumentFromSchemaDefinition} is used to convert a {GraphQL::Schema} object # To a {GraphQL::Language::Document} AST node. # # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] # @param include_introspection_types [Boolean] Whether or not to include introspection types in the AST # @param include_built_in_scalars [Boolean] Whether or not to include built in scalars in the AST # @param include_built_in_directives [Boolean] Whether or not to include built in directives in the AST class DocumentFromSchemaDefinition def initialize( schema, context: nil, only: nil, except: nil, include_introspection_types: false, include_built_in_directives: false, include_built_in_scalars: false, always_include_schema: false ) @schema = schema @always_include_schema = always_include_schema @include_introspection_types = include_introspection_types @include_built_in_scalars = include_built_in_scalars @include_built_in_directives = include_built_in_directives filter = GraphQL::Filter.new(only: only, except: except) if @schema.respond_to?(:visible?) filter = filter.merge(only: @schema.method(:visible?)) end schema_context = schema.context_class.new(query: nil, object: nil, schema: schema, values: context) @warden = GraphQL::Schema::Warden.new( filter, schema: @schema, context: schema_context, ) end def document GraphQL::Language::Nodes::Document.new( definitions: build_definition_nodes ) end def build_schema_node GraphQL::Language::Nodes::SchemaDefinition.new( query: warden.root_type_for_operation("query"), mutation: warden.root_type_for_operation("mutation"), subscription: warden.root_type_for_operation("subscription"), # This only supports directives from parsing, # use a custom printer to add to this list. directives: @schema.ast_node ? @schema.ast_node.directives : [], ) end def build_object_type_node(object_type) GraphQL::Language::Nodes::ObjectTypeDefinition.new( name: object_type.name, interfaces: warden.interfaces(object_type).sort_by(&:name).map { |iface| build_type_name_node(iface) }, fields: build_field_nodes(warden.fields(object_type)), description: object_type.description, ) end def build_field_node(field) field_node = GraphQL::Language::Nodes::FieldDefinition.new( name: field.name, arguments: build_argument_nodes(warden.arguments(field)), type: build_type_name_node(field.type), description: field.description, ) if field.deprecation_reason field_node = field_node.merge_directive( name: GraphQL::Directive::DeprecatedDirective.name, arguments: [GraphQL::Language::Nodes::Argument.new(name: "reason", value: field.deprecation_reason)] ) end field_node end def build_union_type_node(union_type) GraphQL::Language::Nodes::UnionTypeDefinition.new( name: union_type.name, description: union_type.description, types: warden.possible_types(union_type).sort_by(&:name).map { |type| build_type_name_node(type) } ) end def build_interface_type_node(interface_type) GraphQL::Language::Nodes::InterfaceTypeDefinition.new( name: interface_type.name, description: interface_type.description, fields: build_field_nodes(warden.fields(interface_type)) ) end def build_enum_type_node(enum_type) GraphQL::Language::Nodes::EnumTypeDefinition.new( name: enum_type.name, values: warden.enum_values(enum_type).sort_by(&:name).map do |enum_value| build_enum_value_node(enum_value) end, description: enum_type.description, ) end def build_enum_value_node(enum_value) enum_value_node = GraphQL::Language::Nodes::EnumValueDefinition.new( name: enum_value.name, description: enum_value.description, ) if enum_value.deprecation_reason enum_value_node = enum_value_node.merge_directive( name: GraphQL::Directive::DeprecatedDirective.name, arguments: [GraphQL::Language::Nodes::Argument.new(name: "reason", value: enum_value.deprecation_reason)] ) end enum_value_node end def build_scalar_type_node(scalar_type) GraphQL::Language::Nodes::ScalarTypeDefinition.new( name: scalar_type.name, description: scalar_type.description, ) end def build_argument_node(argument) if argument.default_value? default_value = build_default_value(argument.default_value, argument.type) else default_value = nil end argument_node = GraphQL::Language::Nodes::InputValueDefinition.new( name: argument.name, description: argument.description, type: build_type_name_node(argument.type), default_value: default_value, ) argument_node end def build_input_object_node(input_object) GraphQL::Language::Nodes::InputObjectTypeDefinition.new( name: input_object.name, fields: build_argument_nodes(warden.arguments(input_object)), description: input_object.description, ) end def build_directive_node(directive) GraphQL::Language::Nodes::DirectiveDefinition.new( name: directive.name, arguments: build_argument_nodes(warden.arguments(directive)), locations: build_directive_location_nodes(directive.locations), description: directive.description, ) end def build_directive_location_nodes(locations) locations.map { |location| build_directive_location_node(location) } end def build_directive_location_node(location) GraphQL::Language::Nodes::DirectiveLocation.new( name: location.to_s ) end def build_type_name_node(type) case type when GraphQL::ListType GraphQL::Language::Nodes::ListType.new( of_type: build_type_name_node(type.of_type) ) when GraphQL::NonNullType GraphQL::Language::Nodes::NonNullType.new( of_type: build_type_name_node(type.of_type) ) else GraphQL::Language::Nodes::TypeName.new(name: type.name) end end def build_default_value(default_value, type) if default_value.nil? return GraphQL::Language::Nodes::NullValue.new(name: "null") end case type when GraphQL::ScalarType type.coerce_isolated_result(default_value) when EnumType GraphQL::Language::Nodes::Enum.new(name: type.coerce_isolated_result(default_value)) when InputObjectType GraphQL::Language::Nodes::InputObject.new( arguments: default_value.to_h.map do |arg_name, arg_value| arg_type = type.input_fields.fetch(arg_name.to_s).type GraphQL::Language::Nodes::Argument.new( name: arg_name, value: build_default_value(arg_value, arg_type) ) end ) when NonNullType build_default_value(default_value, type.of_type) when ListType default_value.to_a.map { |v| build_default_value(v, type.of_type) } else raise GraphQL::RequiredImplementationMissingError, "Unexpected default value type #{type.inspect}" end end def build_type_definition_node(type) case type when GraphQL::ObjectType build_object_type_node(type) when GraphQL::UnionType build_union_type_node(type) when GraphQL::InterfaceType build_interface_type_node(type) when GraphQL::ScalarType build_scalar_type_node(type) when GraphQL::EnumType build_enum_type_node(type) when GraphQL::InputObjectType build_input_object_node(type) else raise TypeError end end def build_argument_nodes(arguments) arguments .map { |arg| build_argument_node(arg) } .sort_by(&:name) end def build_directive_nodes(directives) if !include_built_in_directives directives = directives.reject { |directive| directive.default_directive? } end directives .map { |directive| build_directive_node(directive) } .sort_by(&:name) end def build_definition_nodes definitions = [] definitions << build_schema_node if include_schema_node? definitions += build_directive_nodes(warden.directives) definitions += build_type_definition_nodes(warden.reachable_types) definitions end def build_type_definition_nodes(types) if !include_introspection_types types = types.reject { |type| type.introspection? } end if !include_built_in_scalars types = types.reject { |type| type.default_scalar? } end types .map { |type| build_type_definition_node(type) } .sort_by(&:name) end def build_field_nodes(fields) fields .map { |field| build_field_node(field) } .sort_by(&:name) end private def include_schema_node? always_include_schema || !schema_respects_root_name_conventions?(schema) end def schema_respects_root_name_conventions?(schema) (schema.query.nil? || schema.query.name == 'Query') && (schema.mutation.nil? || schema.mutation.name == 'Mutation') && (schema.subscription.nil? || schema.subscription.name == 'Subscription') end attr_reader :schema, :warden, :always_include_schema, :include_introspection_types, :include_built_in_directives, :include_built_in_scalars end end end ruby-graphql-1.9.19/lib/graphql/language/generation.rb000066400000000000000000000016461362601351000227030ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language # Exposes {.generate}, which turns AST nodes back into query strings. module Generation extend self # Turn an AST node back into a string. # # @example Turning a document into a query # document = GraphQL.parse(query_string) # GraphQL::Language::Generation.generate(document) # # => "{ ... }" # # @param node [GraphQL::Language::Nodes::AbstractNode] an AST node to recursively stringify # @param indent [String] Whitespace to add to each printed node # @param printer [GraphQL::Language::Printer] An optional custom printer for printing AST nodes. Defaults to GraphQL::Language::Printer # @return [String] Valid GraphQL for `node` def generate(node, indent: "", printer: GraphQL::Language::Printer.new) printer.print(node, indent: indent) end end end end ruby-graphql-1.9.19/lib/graphql/language/lexer.rb000066400000000000000000001261741362601351000216730ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language module Lexer def self.tokenize(query_string) run_lexer(query_string) end # Replace any escaped unicode or whitespace with the _actual_ characters # To avoid allocating more strings, this modifies the string passed into it def self.replace_escaped_characters_in_place(raw_string) raw_string.gsub!(ESCAPES, ESCAPES_REPLACE) raw_string.gsub!(UTF_8, &UTF_8_REPLACE) nil end private class << self attr_accessor :_graphql_lexer_trans_keys private :_graphql_lexer_trans_keys, :_graphql_lexer_trans_keys= end self._graphql_lexer_trans_keys = [ 1, 0, 4, 22, 4, 43, 14, 46, 14, 46, 14, 46, 14, 46, 4, 22, 4, 4, 4, 4, 4, 22, 4, 4, 4, 4, 14, 15, 14, 15, 10, 15, 12, 12, 4, 22, 4, 43, 14, 46, 14, 46, 14, 46, 14, 46, 0, 49, 0, 0, 4, 22, 4, 4, 4, 4, 4, 4, 4, 22, 4, 4, 4, 4, 1, 1, 14, 15, 10, 29, 14, 15, 10, 29, 10, 29, 12, 12, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 4, 4, 0 , ] class << self attr_accessor :_graphql_lexer_char_class private :_graphql_lexer_char_class, :_graphql_lexer_char_class= end self._graphql_lexer_char_class = [ 0, 1, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 3, 4, 5, 6, 2, 7, 2, 8, 9, 2, 10, 0, 11, 12, 13, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 2, 2, 17, 2, 2, 18, 19, 19, 19, 19, 20, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 21, 22, 23, 2, 24, 2, 25, 26, 27, 28, 29, 30, 31, 32, 33, 19, 19, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 19, 45, 46, 19, 47, 48, 49, 0 , ] class << self attr_accessor :_graphql_lexer_index_offsets private :_graphql_lexer_index_offsets, :_graphql_lexer_index_offsets= end self._graphql_lexer_index_offsets = [ 0, 0, 19, 59, 92, 125, 158, 191, 210, 211, 212, 231, 232, 233, 235, 237, 243, 244, 263, 303, 336, 369, 402, 435, 485, 486, 505, 506, 507, 508, 527, 528, 529, 530, 532, 552, 554, 574, 594, 595, 628, 661, 694, 727, 760, 793, 826, 859, 892, 925, 958, 991, 1024, 1057, 1090, 1123, 1156, 1189, 1222, 1255, 1288, 1321, 1354, 1387, 1420, 1453, 1486, 1519, 1552, 1585, 1618, 1651, 1684, 1717, 1750, 1783, 1816, 1849, 1882, 1915, 1948, 1981, 2014, 2047, 2080, 2113, 2146, 2179, 2212, 2245, 2278, 2311, 2344, 2377, 2410, 2443, 2476, 2509, 2542, 2575, 2608, 2641, 2674, 2707, 2740, 2773, 2806, 2839, 2872, 2905, 2938, 2971, 3004, 3037, 3070, 3103, 3136, 3169, 3202, 3235, 3268, 3301, 3334, 3367, 3400, 3433, 0 , ] class << self attr_accessor :_graphql_lexer_indicies private :_graphql_lexer_indicies, :_graphql_lexer_indicies= end self._graphql_lexer_indicies = [ 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 4, 5, 5, 0, 0, 0, 5, 5, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 0, 0, 0, 6, 6, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 0, 0, 0, 7, 7, 0, 0, 0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 11, 12, 13, 14, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 11, 15, 16, 17, 17, 19, 19, 20, 20, 8, 8, 17, 17, 21, 23, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 24, 22, 25, 25, 25, 25, 25, 25, 25, 25, 22, 25, 25, 25, 25, 25, 25, 25, 25, 22, 25, 25, 25, 22, 25, 25, 25, 22, 25, 25, 25, 25, 25, 22, 25, 25, 25, 22, 25, 22, 26, 27, 27, 25, 25, 25, 27, 27, 25, 25, 25, 25, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 25, 25, 25, 28, 28, 25, 25, 25, 25, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 25, 25, 25, 29, 29, 25, 25, 25, 25, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 22, 22, 25, 25, 25, 22, 22, 25, 25, 25, 25, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 31, 32, 30, 33, 34, 35, 36, 37, 38, 39, 30, 40, 41, 30, 42, 43, 44, 45, 46, 47, 47, 48, 30, 49, 47, 47, 47, 47, 50, 51, 52, 47, 47, 53, 47, 54, 55, 56, 47, 57, 47, 58, 59, 60, 47, 47, 47, 61, 62, 63, 31, 66, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 9, 69, 70, 71, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 11, 72, 13, 73, 42, 43, 20, 20, 75, 74, 17, 17, 74, 74, 74, 74, 76, 74, 74, 74, 74, 74, 74, 74, 74, 76, 17, 17, 20, 20, 77, 77, 19, 19, 77, 77, 77, 77, 76, 77, 77, 77, 77, 77, 77, 77, 77, 76, 20, 20, 75, 74, 43, 43, 74, 74, 74, 74, 76, 74, 74, 74, 74, 74, 74, 74, 74, 76, 78, 47, 47, 8, 8, 8, 47, 47, 8, 8, 8, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 80, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 81, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 82, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 83, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 84, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 85, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 86, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 87, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 88, 47, 47, 47, 47, 47, 47, 47, 47, 89, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 90, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 91, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 92, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 93, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 94, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 95, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 96, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 97, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 98, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 99, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 100, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 101, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 102, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 103, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 104, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 105, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 106, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 107, 108, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 109, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 110, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 111, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 112, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 113, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 114, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 115, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 116, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 117, 47, 47, 47, 118, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 119, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 120, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 121, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 122, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 123, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 124, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 125, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 126, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 127, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 128, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 129, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 130, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 131, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 132, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 133, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 134, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 135, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 136, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 137, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 138, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 139, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 140, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 141, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 142, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 143, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 144, 47, 47, 47, 47, 47, 47, 145, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 146, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 147, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 148, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 149, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 150, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 151, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 152, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 153, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 154, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 155, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 156, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 157, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 158, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 159, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 160, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 161, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 162, 47, 47, 47, 47, 47, 163, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 164, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 165, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 166, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 167, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 168, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 169, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 170, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 171, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 22, 0 , ] class << self attr_accessor :_graphql_lexer_index_defaults private :_graphql_lexer_index_defaults, :_graphql_lexer_index_defaults= end self._graphql_lexer_index_defaults = [ 0, 1, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 8, 18, 8, 0, 22, 25, 25, 25, 25, 25, 30, 64, 1, 67, 68, 68, 9, 9, 9, 35, 65, 74, 77, 77, 74, 65, 8, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 25, 0 , ] class << self attr_accessor :_graphql_lexer_trans_cond_spaces private :_graphql_lexer_trans_cond_spaces, :_graphql_lexer_trans_cond_spaces= end self._graphql_lexer_trans_cond_spaces = [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0 , ] class << self attr_accessor :_graphql_lexer_cond_targs private :_graphql_lexer_cond_targs, :_graphql_lexer_cond_targs= end self._graphql_lexer_cond_targs = [ 23, 1, 23, 2, 3, 4, 5, 6, 23, 7, 8, 10, 9, 27, 11, 12, 29, 35, 23, 36, 13, 23, 17, 125, 18, 0, 19, 20, 21, 22, 23, 24, 23, 23, 25, 32, 23, 23, 23, 23, 33, 38, 34, 37, 23, 23, 23, 39, 23, 23, 40, 48, 55, 65, 83, 90, 93, 94, 98, 116, 121, 23, 23, 23, 23, 23, 26, 23, 23, 28, 23, 30, 31, 23, 23, 14, 15, 23, 16, 23, 41, 42, 43, 44, 45, 46, 47, 39, 49, 51, 50, 39, 52, 53, 54, 39, 56, 59, 57, 58, 39, 60, 61, 62, 63, 64, 39, 66, 74, 67, 68, 69, 70, 71, 72, 73, 39, 75, 77, 76, 39, 78, 79, 80, 81, 82, 39, 84, 85, 86, 87, 88, 89, 39, 91, 92, 39, 39, 95, 96, 97, 39, 99, 106, 100, 103, 101, 102, 39, 104, 105, 39, 107, 108, 109, 110, 111, 112, 113, 114, 115, 39, 117, 119, 118, 39, 120, 39, 122, 123, 124, 39, 0 , ] class << self attr_accessor :_graphql_lexer_cond_actions private :_graphql_lexer_cond_actions, :_graphql_lexer_cond_actions= end self._graphql_lexer_cond_actions = [ 1, 0, 2, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 5, 6, 0, 7, 0, 8, 0, 0, 0, 0, 0, 0, 11, 0, 12, 13, 14, 0, 15, 16, 17, 18, 0, 14, 19, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 0, 34, 4, 4, 35, 36, 0, 0, 37, 0, 38, 0, 0, 0, 0, 0, 0, 0, 39, 0, 0, 0, 40, 0, 0, 0, 41, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 45, 0, 0, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 47, 0, 0, 48, 49, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 51, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 53, 0, 0, 0, 54, 0, 55, 0, 0, 0, 56, 0 , ] class << self attr_accessor :_graphql_lexer_to_state_actions private :_graphql_lexer_to_state_actions, :_graphql_lexer_to_state_actions= end self._graphql_lexer_to_state_actions = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0 , ] class << self attr_accessor :_graphql_lexer_from_state_actions private :_graphql_lexer_from_state_actions, :_graphql_lexer_from_state_actions= end self._graphql_lexer_from_state_actions = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0 , ] class << self attr_accessor :_graphql_lexer_eof_trans private :_graphql_lexer_eof_trans, :_graphql_lexer_eof_trans= end self._graphql_lexer_eof_trans = [ 0, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 19, 9, 1, 0, 0, 0, 0, 0, 0, 0, 65, 66, 68, 69, 69, 69, 69, 69, 74, 66, 75, 78, 78, 75, 66, 9, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 0, 0 , ] class << self attr_accessor :_graphql_lexer_nfa_targs private :_graphql_lexer_nfa_targs, :_graphql_lexer_nfa_targs= end self._graphql_lexer_nfa_targs = [ 0, 0 , ] class << self attr_accessor :_graphql_lexer_nfa_offsets private :_graphql_lexer_nfa_offsets, :_graphql_lexer_nfa_offsets= end self._graphql_lexer_nfa_offsets = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 , ] class << self attr_accessor :_graphql_lexer_nfa_push_actions private :_graphql_lexer_nfa_push_actions, :_graphql_lexer_nfa_push_actions= end self._graphql_lexer_nfa_push_actions = [ 0, 0 , ] class << self attr_accessor :_graphql_lexer_nfa_pop_trans private :_graphql_lexer_nfa_pop_trans, :_graphql_lexer_nfa_pop_trans= end self._graphql_lexer_nfa_pop_trans = [ 0, 0 , ] class << self attr_accessor :graphql_lexer_start end self.graphql_lexer_start = 23; class << self attr_accessor :graphql_lexer_first_final end self.graphql_lexer_first_final = 23; class << self attr_accessor :graphql_lexer_error end self.graphql_lexer_error = 0; class << self attr_accessor :graphql_lexer_en_str end self.graphql_lexer_en_str = 125; class << self attr_accessor :graphql_lexer_en_main end self.graphql_lexer_en_main = 23; def self.run_lexer(query_string) data = query_string.unpack("c*") eof = data.length # Since `Lexer` is a module, store all lexer state # in this local variable: meta = { line: 1, col: 1, data: data, tokens: [], previous_token: nil, } p ||= 0 pe ||= data.length begin cs = graphql_lexer_start; ts = 0; te = 0; act = 0; end begin _trans = 0; _have = 0; _cont = 1; _keys = 0; _inds = 0; while ( _cont == 1 ) begin if ( cs == 0 ) _cont = 0; end _have = 0; if ( p == pe ) begin if ( p == eof ) begin if ( _graphql_lexer_eof_trans[cs] > 0 ) begin _trans = _graphql_lexer_eof_trans[cs] - 1; _have = 1; end end if ( _have == 0 ) begin end end end end if ( _have == 0 ) _cont = 0; end end end if ( _cont == 1 ) begin if ( _have == 0 ) begin case _graphql_lexer_from_state_actions[cs] when -2 then begin end when 10 then begin begin begin ts = p; end end end end _keys = (cs<<1) ; _inds = _graphql_lexer_index_offsets[cs] ; if ( ( data[p ].ord) <= 125 && ( data[p ].ord) >= 9 ) begin _ic = _graphql_lexer_char_class[( data[p ].ord) - 9]; if ( _ic <= _graphql_lexer_trans_keys[_keys+1 ]&& _ic >= _graphql_lexer_trans_keys[_keys ] ) _trans = _graphql_lexer_indicies[_inds + ( _ic - _graphql_lexer_trans_keys[_keys ]) ]; else _trans = _graphql_lexer_index_defaults[cs]; end end else begin _trans = _graphql_lexer_index_defaults[cs]; end end end end if ( _cont == 1 ) begin cs = _graphql_lexer_cond_targs[_trans]; case _graphql_lexer_cond_actions[_trans] when -2 then begin end when 14 then begin begin begin te = p+1; end end end when 8 then begin begin begin te = p+1; begin emit_string(ts, te, meta, block: false) end end end end when 28 then begin begin begin te = p+1; begin emit(:RCURLY, ts, te, meta, "}") end end end end when 26 then begin begin begin te = p+1; begin emit(:LCURLY, ts, te, meta, "{") end end end end when 18 then begin begin begin te = p+1; begin emit(:RPAREN, ts, te, meta, ")") end end end end when 17 then begin begin begin te = p+1; begin emit(:LPAREN, ts, te, meta, "(") end end end end when 25 then begin begin begin te = p+1; begin emit(:RBRACKET, ts, te, meta, "]") end end end end when 24 then begin begin begin te = p+1; begin emit(:LBRACKET, ts, te, meta, "[") end end end end when 20 then begin begin begin te = p+1; begin emit(:COLON, ts, te, meta, ":") end end end end when 2 then begin begin begin te = p+1; begin emit_string(ts, te, meta, block: false) end end end end when 34 then begin begin begin te = p+1; begin emit_string(ts, te, meta, block: true) end end end end when 15 then begin begin begin te = p+1; begin emit(:VAR_SIGN, ts, te, meta, "$") end end end end when 22 then begin begin begin te = p+1; begin emit(:DIR_SIGN, ts, te, meta, "@") end end end end when 7 then begin begin begin te = p+1; begin emit(:ELLIPSIS, ts, te, meta, "...") end end end end when 21 then begin begin begin te = p+1; begin emit(:EQUALS, ts, te, meta, "=") end end end end when 13 then begin begin begin te = p+1; begin emit(:BANG, ts, te, meta, "!") end end end end when 27 then begin begin begin te = p+1; begin emit(:PIPE, ts, te, meta, "|") end end end end when 16 then begin begin begin te = p+1; begin emit(:AMP, ts, te, meta, "&") end end end end when 12 then begin begin begin te = p+1; begin meta[:line] += 1 meta[:col] = 1 end end end end when 11 then begin begin begin te = p+1; begin emit(:UNKNOWN_CHAR, ts, te, meta) end end end end when 36 then begin begin begin te = p; p = p - 1; begin emit(:INT, ts, te, meta) end end end end when 37 then begin begin begin te = p; p = p - 1; begin emit(:FLOAT, ts, te, meta) end end end end when 32 then begin begin begin te = p; p = p - 1; begin emit_string(ts, te, meta, block: false) end end end end when 33 then begin begin begin te = p; p = p - 1; begin emit_string(ts, te, meta, block: true) end end end end when 38 then begin begin begin te = p; p = p - 1; begin emit(:IDENTIFIER, ts, te, meta) end end end end when 35 then begin begin begin te = p; p = p - 1; begin record_comment(ts, te, meta) end end end end when 29 then begin begin begin te = p; p = p - 1; begin meta[:col] += te - ts end end end end when 30 then begin begin begin te = p; p = p - 1; begin emit(:UNKNOWN_CHAR, ts, te, meta) end end end end when 5 then begin begin begin p = ((te))-1; begin emit(:INT, ts, te, meta) end end end end when 1 then begin begin begin p = ((te))-1; begin emit(:UNKNOWN_CHAR, ts, te, meta) end end end end when 3 then begin begin begin case act when -2 then begin end when 2 then begin p = ((te))-1; begin emit(:INT, ts, te, meta) end end when 3 then begin p = ((te))-1; begin emit(:FLOAT, ts, te, meta) end end when 4 then begin p = ((te))-1; begin emit(:ON, ts, te, meta, "on") end end when 5 then begin p = ((te))-1; begin emit(:FRAGMENT, ts, te, meta, "fragment") end end when 6 then begin p = ((te))-1; begin emit(:TRUE, ts, te, meta, "true") end end when 7 then begin p = ((te))-1; begin emit(:FALSE, ts, te, meta, "false") end end when 8 then begin p = ((te))-1; begin emit(:NULL, ts, te, meta, "null") end end when 9 then begin p = ((te))-1; begin emit(:QUERY, ts, te, meta, "query") end end when 10 then begin p = ((te))-1; begin emit(:MUTATION, ts, te, meta, "mutation") end end when 11 then begin p = ((te))-1; begin emit(:SUBSCRIPTION, ts, te, meta, "subscription") end end when 12 then begin p = ((te))-1; begin emit(:SCHEMA, ts, te, meta) end end when 13 then begin p = ((te))-1; begin emit(:SCALAR, ts, te, meta) end end when 14 then begin p = ((te))-1; begin emit(:TYPE, ts, te, meta) end end when 15 then begin p = ((te))-1; begin emit(:EXTEND, ts, te, meta) end end when 16 then begin p = ((te))-1; begin emit(:IMPLEMENTS, ts, te, meta) end end when 17 then begin p = ((te))-1; begin emit(:INTERFACE, ts, te, meta) end end when 18 then begin p = ((te))-1; begin emit(:UNION, ts, te, meta) end end when 19 then begin p = ((te))-1; begin emit(:ENUM, ts, te, meta) end end when 20 then begin p = ((te))-1; begin emit(:INPUT, ts, te, meta) end end when 21 then begin p = ((te))-1; begin emit(:DIRECTIVE, ts, te, meta) end end when 29 then begin p = ((te))-1; begin emit_string(ts, te, meta, block: false) end end when 30 then begin p = ((te))-1; begin emit_string(ts, te, meta, block: true) end end when 38 then begin p = ((te))-1; begin emit(:IDENTIFIER, ts, te, meta) end end end end end end when 19 then begin begin begin te = p+1; end end begin begin act = 2; end end end when 6 then begin begin begin te = p+1; end end begin begin act = 3; end end end when 49 then begin begin begin te = p+1; end end begin begin act = 4; end end end when 43 then begin begin begin te = p+1; end end begin begin act = 5; end end end when 54 then begin begin begin te = p+1; end end begin begin act = 6; end end end when 42 then begin begin begin te = p+1; end end begin begin act = 7; end end end when 48 then begin begin begin te = p+1; end end begin begin act = 8; end end end when 50 then begin begin begin te = p+1; end end begin begin act = 9; end end end when 47 then begin begin begin te = p+1; end end begin begin act = 10; end end end when 53 then begin begin begin te = p+1; end end begin begin act = 11; end end end when 52 then begin begin begin te = p+1; end end begin begin act = 12; end end end when 51 then begin begin begin te = p+1; end end begin begin act = 13; end end end when 55 then begin begin begin te = p+1; end end begin begin act = 14; end end end when 41 then begin begin begin te = p+1; end end begin begin act = 15; end end end when 44 then begin begin begin te = p+1; end end begin begin act = 16; end end end when 46 then begin begin begin te = p+1; end end begin begin act = 17; end end end when 56 then begin begin begin te = p+1; end end begin begin act = 18; end end end when 40 then begin begin begin te = p+1; end end begin begin act = 19; end end end when 45 then begin begin begin te = p+1; end end begin begin act = 20; end end end when 39 then begin begin begin te = p+1; end end begin begin act = 21; end end end when 31 then begin begin begin te = p+1; end end begin begin act = 29; end end end when 4 then begin begin begin te = p+1; end end begin begin act = 30; end end end when 23 then begin begin begin te = p+1; end end begin begin act = 38; end end end end case _graphql_lexer_to_state_actions[cs] when -2 then begin end when 9 then begin begin begin ts = 0; end end end end if ( cs == 0 ) _cont = 0; end if ( _cont == 1 ) p += 1; end end end end end end end end meta[:tokens] end def self.record_comment(ts, te, meta) token = GraphQL::Language::Token.new( :COMMENT, meta[:data][ts, te - ts].pack(PACK_DIRECTIVE).force_encoding(UTF_8_ENCODING), meta[:line], meta[:col], meta[:previous_token], ) meta[:previous_token] = token meta[:col] += te - ts end def self.emit(token_name, ts, te, meta, token_value = nil) token_value ||= meta[:data][ts, te - ts].pack(PACK_DIRECTIVE).force_encoding(UTF_8_ENCODING) meta[:tokens] << token = GraphQL::Language::Token.new( token_name, token_value, meta[:line], meta[:col], meta[:previous_token], ) meta[:previous_token] = token # Bump the column counter for the next token meta[:col] += te - ts end ESCAPES = /\\["\\\/bfnrt]/ ESCAPES_REPLACE = { '\\"' => '"', "\\\\" => "\\", "\\/" => '/', "\\b" => "\b", "\\f" => "\f", "\\n" => "\n", "\\r" => "\r", "\\t" => "\t", } UTF_8 = /\\u[\dAa-f]{4}/i UTF_8_REPLACE = ->(m) { [m[-4..-1].to_i(16)].pack('U'.freeze) } VALID_STRING = /\A(?:[^\\]|#{ESCAPES}|#{UTF_8})*\z/o PACK_DIRECTIVE = "c*" UTF_8_ENCODING = "UTF-8" def self.emit_string(ts, te, meta, block:) quotes_length = block ? 3 : 1 value = meta[:data][ts + quotes_length, te - ts - 2 * quotes_length].pack(PACK_DIRECTIVE).force_encoding(UTF_8_ENCODING) || '' line_incr = 0 if block && !value.length.zero? line_incr = value.count("\n") value = GraphQL::Language::BlockString.trim_whitespace(value) end # TODO: replace with `String#match?` when we support only Ruby 2.4+ # (It's faster: https://bugs.ruby-lang.org/issues/8110) if !value.valid_encoding? || value !~ VALID_STRING meta[:tokens] << token = GraphQL::Language::Token.new( :BAD_UNICODE_ESCAPE, value, meta[:line], meta[:col], meta[:previous_token], ) else replace_escaped_characters_in_place(value) if !value.valid_encoding? meta[:tokens] << token = GraphQL::Language::Token.new( :BAD_UNICODE_ESCAPE, value, meta[:line], meta[:col], meta[:previous_token], ) else meta[:tokens] << token = GraphQL::Language::Token.new( :STRING, value, meta[:line], meta[:col], meta[:previous_token], ) end end meta[:previous_token] = token meta[:col] += te - ts meta[:line] += line_incr end end end end ruby-graphql-1.9.19/lib/graphql/language/lexer.rl000066400000000000000000000203101362601351000216660ustar00rootroot00000000000000%%{ machine graphql_lexer; IDENTIFIER = [_A-Za-z][_0-9A-Za-z]*; NEWLINE = [\c\r\n]; BLANK = [, \t]+; COMMENT = '#' [^\n\r]*; INT = '-'? ('0'|[1-9][0-9]*); FLOAT_DECIMAL = '.'[0-9]+; FLOAT_EXP = ('e' | 'E')?('+' | '-')?[0-9]+; FLOAT = INT FLOAT_DECIMAL? FLOAT_EXP?; ON = 'on'; FRAGMENT = 'fragment'; TRUE = 'true'; FALSE = 'false'; NULL = 'null'; QUERY = 'query'; MUTATION = 'mutation'; SUBSCRIPTION = 'subscription'; SCHEMA = 'schema'; SCALAR = 'scalar'; TYPE = 'type'; EXTEND = 'extend'; IMPLEMENTS = 'implements'; INTERFACE = 'interface'; UNION = 'union'; ENUM = 'enum'; INPUT = 'input'; DIRECTIVE = 'directive'; LCURLY = '{'; RCURLY = '}'; LPAREN = '('; RPAREN = ')'; LBRACKET = '['; RBRACKET = ']'; COLON = ':'; QUOTE = '"'; BACKSLASH = '\\'; # Could limit to hex here, but “bad unicode escape” on 0XXF is probably a # more helpful error than “unknown char” UNICODE_ESCAPE = '\\u' [0-9A-Za-z]{4}; # https://graphql.github.io/graphql-spec/June2018/#sec-String-Value STRING_ESCAPE = '\\' [\\/bfnrt]; BLOCK_QUOTE = '"""'; ESCAPED_BLOCK_QUOTE = '\\"""'; BLOCK_STRING_CHAR = (ESCAPED_BLOCK_QUOTE | ^QUOTE | QUOTE{1,2} ^QUOTE); ESCAPED_QUOTE = '\\"'; STRING_CHAR = ((ESCAPED_QUOTE | ^QUOTE) - BACKSLASH) | UNICODE_ESCAPE | STRING_ESCAPE; VAR_SIGN = '$'; DIR_SIGN = '@'; ELLIPSIS = '...'; EQUALS = '='; BANG = '!'; PIPE = '|'; AMP = '&'; QUOTED_STRING = QUOTE STRING_CHAR* QUOTE; BLOCK_STRING = BLOCK_QUOTE BLOCK_STRING_CHAR* QUOTE{0,2} BLOCK_QUOTE; # catch-all for anything else. must be at the bottom for precedence. UNKNOWN_CHAR = /./; # Used with ragel -V for graphviz visualization str := |* QUOTED_STRING => { emit_string(ts, te, meta, block: false) }; *|; main := |* INT => { emit(:INT, ts, te, meta) }; FLOAT => { emit(:FLOAT, ts, te, meta) }; ON => { emit(:ON, ts, te, meta, "on") }; FRAGMENT => { emit(:FRAGMENT, ts, te, meta, "fragment") }; TRUE => { emit(:TRUE, ts, te, meta, "true") }; FALSE => { emit(:FALSE, ts, te, meta, "false") }; NULL => { emit(:NULL, ts, te, meta, "null") }; QUERY => { emit(:QUERY, ts, te, meta, "query") }; MUTATION => { emit(:MUTATION, ts, te, meta, "mutation") }; SUBSCRIPTION => { emit(:SUBSCRIPTION, ts, te, meta, "subscription") }; SCHEMA => { emit(:SCHEMA, ts, te, meta) }; SCALAR => { emit(:SCALAR, ts, te, meta) }; TYPE => { emit(:TYPE, ts, te, meta) }; EXTEND => { emit(:EXTEND, ts, te, meta) }; IMPLEMENTS => { emit(:IMPLEMENTS, ts, te, meta) }; INTERFACE => { emit(:INTERFACE, ts, te, meta) }; UNION => { emit(:UNION, ts, te, meta) }; ENUM => { emit(:ENUM, ts, te, meta) }; INPUT => { emit(:INPUT, ts, te, meta) }; DIRECTIVE => { emit(:DIRECTIVE, ts, te, meta) }; RCURLY => { emit(:RCURLY, ts, te, meta, "}") }; LCURLY => { emit(:LCURLY, ts, te, meta, "{") }; RPAREN => { emit(:RPAREN, ts, te, meta, ")") }; LPAREN => { emit(:LPAREN, ts, te, meta, "(")}; RBRACKET => { emit(:RBRACKET, ts, te, meta, "]") }; LBRACKET => { emit(:LBRACKET, ts, te, meta, "[") }; COLON => { emit(:COLON, ts, te, meta, ":") }; QUOTED_STRING => { emit_string(ts, te, meta, block: false) }; BLOCK_STRING => { emit_string(ts, te, meta, block: true) }; VAR_SIGN => { emit(:VAR_SIGN, ts, te, meta, "$") }; DIR_SIGN => { emit(:DIR_SIGN, ts, te, meta, "@") }; ELLIPSIS => { emit(:ELLIPSIS, ts, te, meta, "...") }; EQUALS => { emit(:EQUALS, ts, te, meta, "=") }; BANG => { emit(:BANG, ts, te, meta, "!") }; PIPE => { emit(:PIPE, ts, te, meta, "|") }; AMP => { emit(:AMP, ts, te, meta, "&") }; IDENTIFIER => { emit(:IDENTIFIER, ts, te, meta) }; COMMENT => { record_comment(ts, te, meta) }; NEWLINE => { meta[:line] += 1 meta[:col] = 1 }; BLANK => { meta[:col] += te - ts }; UNKNOWN_CHAR => { emit(:UNKNOWN_CHAR, ts, te, meta) }; *|; }%% # frozen_string_literal: true module GraphQL module Language module Lexer def self.tokenize(query_string) run_lexer(query_string) end # Replace any escaped unicode or whitespace with the _actual_ characters # To avoid allocating more strings, this modifies the string passed into it def self.replace_escaped_characters_in_place(raw_string) raw_string.gsub!(ESCAPES, ESCAPES_REPLACE) raw_string.gsub!(UTF_8, &UTF_8_REPLACE) nil end private %% write data; def self.run_lexer(query_string) data = query_string.unpack("c*") eof = data.length # Since `Lexer` is a module, store all lexer state # in this local variable: meta = { line: 1, col: 1, data: data, tokens: [], previous_token: nil, } p ||= 0 pe ||= data.length %% write init; %% write exec; meta[:tokens] end def self.record_comment(ts, te, meta) token = GraphQL::Language::Token.new( :COMMENT, meta[:data][ts, te - ts].pack(PACK_DIRECTIVE).force_encoding(UTF_8_ENCODING), meta[:line], meta[:col], meta[:previous_token], ) meta[:previous_token] = token meta[:col] += te - ts end def self.emit(token_name, ts, te, meta, token_value = nil) token_value ||= meta[:data][ts, te - ts].pack(PACK_DIRECTIVE).force_encoding(UTF_8_ENCODING) meta[:tokens] << token = GraphQL::Language::Token.new( token_name, token_value, meta[:line], meta[:col], meta[:previous_token], ) meta[:previous_token] = token # Bump the column counter for the next token meta[:col] += te - ts end ESCAPES = /\\["\\\/bfnrt]/ ESCAPES_REPLACE = { '\\"' => '"', "\\\\" => "\\", "\\/" => '/', "\\b" => "\b", "\\f" => "\f", "\\n" => "\n", "\\r" => "\r", "\\t" => "\t", } UTF_8 = /\\u[\dAa-f]{4}/i UTF_8_REPLACE = ->(m) { [m[-4..-1].to_i(16)].pack('U'.freeze) } VALID_STRING = /\A(?:[^\\]|#{ESCAPES}|#{UTF_8})*\z/o PACK_DIRECTIVE = "c*" UTF_8_ENCODING = "UTF-8" def self.emit_string(ts, te, meta, block:) quotes_length = block ? 3 : 1 value = meta[:data][ts + quotes_length, te - ts - 2 * quotes_length].pack(PACK_DIRECTIVE).force_encoding(UTF_8_ENCODING) || '' line_incr = 0 if block && !value.length.zero? line_incr = value.count("\n") value = GraphQL::Language::BlockString.trim_whitespace(value) end # TODO: replace with `String#match?` when we support only Ruby 2.4+ # (It's faster: https://bugs.ruby-lang.org/issues/8110) if !value.valid_encoding? || value !~ VALID_STRING meta[:tokens] << token = GraphQL::Language::Token.new( :BAD_UNICODE_ESCAPE, value, meta[:line], meta[:col], meta[:previous_token], ) else replace_escaped_characters_in_place(value) if !value.valid_encoding? meta[:tokens] << token = GraphQL::Language::Token.new( :BAD_UNICODE_ESCAPE, value, meta[:line], meta[:col], meta[:previous_token], ) else meta[:tokens] << token = GraphQL::Language::Token.new( :STRING, value, meta[:line], meta[:col], meta[:previous_token], ) end end meta[:previous_token] = token meta[:col] += te - ts meta[:line] += line_incr end end end end ruby-graphql-1.9.19/lib/graphql/language/nodes.rb000066400000000000000000000567741362601351000216740ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language module Nodes # {AbstractNode} is the base class for all nodes in a GraphQL AST. # # It provides some APIs for working with ASTs: # - `children` returns all AST nodes attached to this one. Used for tree traversal. # - `scalars` returns all scalar (Ruby) values attached to this one. Used for comparing nodes. # - `to_query_string` turns an AST node into a GraphQL string class AbstractNode module DefinitionNode # This AST node's {#line} returns the first line, which may be the description. # @return [Integer] The first line of the definition (not the description) attr_reader :definition_line def initialize(options = {}) @definition_line = options.delete(:definition_line) super(options) end end attr_reader :line, :col, :filename # Initialize a node by extracting its position, # then calling the class's `initialize_node` method. # @param options [Hash] Initial attributes for this node def initialize(options = {}) if options.key?(:position_source) position_source = options.delete(:position_source) @line = position_source.line @col = position_source.col end @filename = options.delete(:filename) initialize_node(**options) end # Value equality # @return [Boolean] True if `self` is equivalent to `other` def eql?(other) return true if equal?(other) other.is_a?(self.class) && other.scalars.eql?(self.scalars) && other.children.eql?(self.children) end NO_CHILDREN = [].freeze # @return [Array] all nodes in the tree below this one def children NO_CHILDREN end # @return [Array] Scalar values attached to this node def scalars NO_CHILDREN end # This might be unnecessary, but its easiest to add it here. def initialize_copy(other) @children = nil @scalars = nil @query_string = nil end # @return [Symbol] the method to call on {Language::Visitor} for this node def visit_method raise GraphQL::RequiredImplementationMissingError, "#{self.class.name}#visit_method shold return a symbol" end def position [line, col] end def to_query_string(printer: GraphQL::Language::Printer.new) if printer.is_a?(GraphQL::Language::Printer) @query_string ||= printer.print(self) else printer.print(self) end end # This creates a copy of `self`, with `new_options` applied. # @param new_options [Hash] # @return [AbstractNode] a shallow copy of `self` def merge(new_options) dup.merge!(new_options) end # Copy `self`, but modify the copy so that `previous_child` is replaced by `new_child` def replace_child(previous_child, new_child) # Figure out which list `previous_child` may be found in method_name = previous_child.children_method_name # Get the value from this (original) node prev_children = public_send(method_name) if prev_children.is_a?(Array) # Copy that list, and replace `previous_child` with `new_child` # in the list. new_children = prev_children.dup prev_idx = new_children.index(previous_child) new_children[prev_idx] = new_child else # Use the new value for the given attribute new_children = new_child end # Copy this node, but with the new child value copy_of_self = merge(method_name => new_children) # Return the copy: copy_of_self end # TODO DRY with `replace_child` def delete_child(previous_child) # Figure out which list `previous_child` may be found in method_name = previous_child.children_method_name # Copy that list, and delete previous_child new_children = public_send(method_name).dup new_children.delete(previous_child) # Copy this node, but with the new list of children: copy_of_self = merge(method_name => new_children) # Return the copy: copy_of_self end protected def merge!(new_options) new_options.each do |key, value| instance_variable_set(:"@#{key}", value) end self end class << self # Add a default `#visit_method` and `#children_method_name` using the class name def inherited(child_class) super name_underscored = child_class.name .split("::").last .gsub(/([a-z])([A-Z])/,'\1_\2') # insert underscores .downcase # remove caps child_class.module_eval <<-RUBY def visit_method :on_#{name_underscored} end def children_method_name :#{name_underscored}s end RUBY end private # Name accessors which return lists of nodes, # along with the kind of node they return, if possible. # - Add a reader for these children # - Add a persistent update method to add a child # - Generate a `#children` method def children_methods(children_of_type) if @children_methods raise "Can't re-call .children_methods for #{self} (already have: #{@children_methods})" else @children_methods = children_of_type end if children_of_type == false @children_methods = {} # skip else children_of_type.each do |method_name, node_type| module_eval <<-RUBY, __FILE__, __LINE__ # A reader for these children attr_reader :#{method_name} RUBY if node_type # Only generate a method if we know what kind of node to make module_eval <<-RUBY, __FILE__, __LINE__ # Singular method: create a node with these options # and return a new `self` which includes that node in this list. def merge_#{method_name.to_s.sub(/s$/, "")}(node_opts) merge(#{method_name}: #{method_name} + [#{node_type.name}.new(node_opts)]) end RUBY end end if children_of_type.size == 1 module_eval <<-RUBY, __FILE__, __LINE__ alias :children #{children_of_type.keys.first} RUBY else module_eval <<-RUBY, __FILE__, __LINE__ def children @children ||= (#{children_of_type.keys.map { |k| "@#{k}" }.join(" + ")}).freeze end RUBY end end if defined?(@scalar_methods) generate_initialize_node else raise "Can't generate_initialize_node because scalar_methods wasn't called; call it before children_methods" end end # These methods return a plain Ruby value, not another node # - Add reader methods # - Add a `#scalars` method def scalar_methods(*method_names) if @scalar_methods raise "Can't re-call .scalar_methods for #{self} (already have: #{@scalar_methods})" else @scalar_methods = method_names end if method_names == [false] @scalar_methods = [] # skip it else module_eval <<-RUBY, __FILE__, __LINE__ # add readers for each scalar attr_reader #{method_names.map { |m| ":#{m}"}.join(", ")} def scalars @scalars ||= [#{method_names.map { |k| "@#{k}" }.join(", ")}].freeze end RUBY end end def generate_initialize_node scalar_method_names = @scalar_methods # TODO: These probably should be scalar methods, but `types` returns an array [:types, :description].each do |extra_method| if method_defined?(extra_method) scalar_method_names += [extra_method] end end all_method_names = scalar_method_names + @children_methods.keys if all_method_names.include?(:alias) # Rather than complicating this special case, # let it be overridden (in field) return else arguments = scalar_method_names.map { |m| "#{m}: nil"} + @children_methods.keys.map { |m| "#{m}: []" } assignments = scalar_method_names.map { |m| "@#{m} = #{m}"} + @children_methods.keys.map { |m| "@#{m} = #{m}.freeze" } module_eval <<-RUBY, __FILE__, __LINE__ def initialize_node #{arguments.join(", ")} #{assignments.join("\n")} end RUBY end end end end # Base class for non-null type names and list type names class WrapperType < AbstractNode scalar_methods :of_type children_methods(false) end # Base class for nodes whose only value is a name (no child nodes or other scalars) class NameOnlyNode < AbstractNode scalar_methods :name children_methods(false) end # A key-value pair for a field's inputs class Argument < AbstractNode scalar_methods :name, :value children_methods(false) # @!attribute name # @return [String] the key for this argument # @!attribute value # @return [String, Float, Integer, Boolean, Array, InputObject] The value passed for this key def children @children ||= Array(value).flatten.select { |v| v.is_a?(AbstractNode) } end end class Directive < AbstractNode scalar_methods :name children_methods(arguments: GraphQL::Language::Nodes::Argument) end class DirectiveLocation < NameOnlyNode end class DirectiveDefinition < AbstractNode include DefinitionNode attr_reader :description scalar_methods :name children_methods( locations: Nodes::DirectiveLocation, arguments: Nodes::Argument, ) end # This is the AST root for normal queries # # @example Deriving a document by parsing a string # document = GraphQL.parse(query_string) # # @example Creating a string from a document # document.to_query_string # # { ... } # # @example Creating a custom string from a document # class VariableScrubber < GraphQL::Language::Printer # def print_argument(arg) # "#{arg.name}: " # end # end # # document.to_query_string(printer: VariableSrubber.new) # class Document < AbstractNode scalar_methods false children_methods(definitions: nil) # @!attribute definitions # @return [Array] top-level GraphQL units: operations or fragments def slice_definition(name) GraphQL::Language::DefinitionSlice.slice(self, name) end end # An enum value. The string is available as {#name}. class Enum < NameOnlyNode end # A null value literal. class NullValue < NameOnlyNode end # A single selection in a GraphQL query. class Field < AbstractNode NONE = [].freeze scalar_methods :name, :alias children_methods({ arguments: GraphQL::Language::Nodes::Argument, selections: GraphQL::Language::Nodes::Field, directives: GraphQL::Language::Nodes::Directive, }) # @!attribute selections # @return [Array] Selections on this object (or empty array if this is a scalar field) def initialize_node(attributes) @name = attributes[:name] @arguments = attributes[:arguments] || NONE @directives = attributes[:directives] || NONE @selections = attributes[:selections] || NONE # oops, alias is a keyword: @alias = attributes[:alias] end # Override this because default is `:fields` def children_method_name :selections end end # A reusable fragment, defined at document-level. class FragmentDefinition < AbstractNode scalar_methods :name, :type children_methods({ selections: GraphQL::Language::Nodes::Field, directives: GraphQL::Language::Nodes::Directive, }) # @!attribute name # @return [String] the identifier for this fragment, which may be applied with `...#{name}` # @!attribute type # @return [String] the type condition for this fragment (name of type which it may apply to) def initialize_node(name: nil, type: nil, directives: [], selections: []) @name = name @type = type @directives = directives @selections = selections end def children_method_name :definitions end end # Application of a named fragment in a selection class FragmentSpread < AbstractNode scalar_methods :name children_methods(directives: GraphQL::Language::Nodes::Directive) def children_method_name :selections end # @!attribute name # @return [String] The identifier of the fragment to apply, corresponds with {FragmentDefinition#name} end # An unnamed fragment, defined directly in the query with `... { }` class InlineFragment < AbstractNode scalar_methods :type children_methods({ selections: GraphQL::Language::Nodes::Field, directives: GraphQL::Language::Nodes::Directive, }) def children_method_name :selections end # @!attribute type # @return [String, nil] Name of the type this fragment applies to, or `nil` if this fragment applies to any type end # A collection of key-value inputs which may be a field argument class InputObject < AbstractNode scalar_methods(false) children_methods(arguments: GraphQL::Language::Nodes::Argument) # @!attribute arguments # @return [Array] A list of key-value pairs inside this input object # @return [Hash] Recursively turn this input object into a Ruby Hash def to_h(options={}) arguments.inject({}) do |memo, pair| v = pair.value memo[pair.name] = serialize_value_for_hash v memo end end def children_method_name :value end private def serialize_value_for_hash(value) case value when InputObject value.to_h when Array value.map do |v| serialize_value_for_hash v end when Enum value.name when NullValue nil else value end end end # A list type definition, denoted with `[...]` (used for variable type definitions) class ListType < WrapperType end # A non-null type definition, denoted with `...!` (used for variable type definitions) class NonNullType < WrapperType end # An operation-level query variable class VariableDefinition < AbstractNode scalar_methods :name, :type, :default_value children_methods false # @!attribute default_value # @return [String, Integer, Float, Boolean, Array, NullValue] A Ruby value to use if no other value is provided # @!attribute type # @return [TypeName, NonNullType, ListType] The expected type of this value # @!attribute name # @return [String] The identifier for this variable, _without_ `$` end # A query, mutation or subscription. # May be anonymous or named. # May be explicitly typed (eg `mutation { ... }`) or implicitly a query (eg `{ ... }`). class OperationDefinition < AbstractNode scalar_methods :operation_type, :name children_methods({ variables: GraphQL::Language::Nodes::VariableDefinition, selections: GraphQL::Language::Nodes::Field, directives: GraphQL::Language::Nodes::Directive, }) # @!attribute variables # @return [Array] Variable definitions for this operation # @!attribute selections # @return [Array] Root-level fields on this operation # @!attribute operation_type # @return [String, nil] The root type for this operation, or `nil` for implicit `"query"` # @!attribute name # @return [String, nil] The name for this operation, or `nil` if unnamed def children_method_name :definitions end end # A type name, used for variable definitions class TypeName < NameOnlyNode end # Usage of a variable in a query. Name does _not_ include `$`. class VariableIdentifier < NameOnlyNode end class SchemaDefinition < AbstractNode include DefinitionNode scalar_methods :query, :mutation, :subscription children_methods({ directives: GraphQL::Language::Nodes::Directive, }) def children_method_name :definitions end end class SchemaExtension < AbstractNode scalar_methods :query, :mutation, :subscription children_methods({ directives: GraphQL::Language::Nodes::Directive, }) def children_method_name :definitions end end class ScalarTypeDefinition < AbstractNode include DefinitionNode attr_reader :description scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) def children_method_name :definitions end end class ScalarTypeExtension < AbstractNode scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) def children_method_name :definitions end end class InputValueDefinition < AbstractNode include DefinitionNode attr_reader :description scalar_methods :name, :type, :default_value children_methods({ directives: GraphQL::Language::Nodes::Directive, }) def children_method_name :fields end end class FieldDefinition < AbstractNode include DefinitionNode attr_reader :description scalar_methods :name, :type children_methods({ directives: GraphQL::Language::Nodes::Directive, arguments: GraphQL::Language::Nodes::InputValueDefinition, }) def children_method_name :fields end # this is so that `children_method_name` of `InputValueDefinition` works properly # with `#replace_child` alias :fields :arguments def merge(new_options) if (f = new_options.delete(:fields)) new_options[:arguments] = f end super end end class ObjectTypeDefinition < AbstractNode include DefinitionNode attr_reader :description scalar_methods :name, :interfaces children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::FieldDefinition, }) def children_method_name :definitions end end class ObjectTypeExtension < AbstractNode scalar_methods :name, :interfaces children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::FieldDefinition, }) def children_method_name :definitions end end class InterfaceTypeDefinition < AbstractNode include DefinitionNode attr_reader :description scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::FieldDefinition, }) def children_method_name :definitions end end class InterfaceTypeExtension < AbstractNode scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::FieldDefinition, }) def children_method_name :definitions end end class UnionTypeDefinition < AbstractNode include DefinitionNode attr_reader :description, :types scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) def children_method_name :definitions end end class UnionTypeExtension < AbstractNode attr_reader :types scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) def children_method_name :definitions end end class EnumValueDefinition < AbstractNode include DefinitionNode attr_reader :description scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) def children_method_name :values end end class EnumTypeDefinition < AbstractNode include DefinitionNode attr_reader :description scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, values: GraphQL::Language::Nodes::EnumValueDefinition, }) def children_method_name :definitions end end class EnumTypeExtension < AbstractNode scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, values: GraphQL::Language::Nodes::EnumValueDefinition, }) def children_method_name :definitions end end class InputObjectTypeDefinition < AbstractNode include DefinitionNode attr_reader :description scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::InputValueDefinition, }) def children_method_name :definitions end end class InputObjectTypeExtension < AbstractNode scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::InputValueDefinition, }) def children_method_name :definitions end end end end end ruby-graphql-1.9.19/lib/graphql/language/parser.rb000066400000000000000000002003451362601351000220410ustar00rootroot00000000000000# # DO NOT MODIFY!!!! # This file is automatically generated by Racc 1.4.15 # from Racc grammer file "". # require 'racc/parser.rb' module GraphQL module Language class Parser < Racc::Parser module_eval(<<'...end parser.y/module_eval...', 'parser.y', 435) EMPTY_ARRAY = [].freeze def initialize(query_string, filename:, tracer: Tracing::NullTracer) raise GraphQL::ParseError.new("No query string was present", nil, nil, query_string) if query_string.nil? @query_string = query_string @filename = filename @tracer = tracer @reused_next_token = [nil, nil] end def parse_document @document ||= begin # Break the string into tokens @tracer.trace("lex", {query_string: @query_string}) do @tokens ||= GraphQL.scan(@query_string) end # From the tokens, build an AST @tracer.trace("parse", {query_string: @query_string}) do if @tokens.empty? make_node(:Document, definitions: [], filename: @filename) else do_parse end end end end def self.parse(query_string, filename: nil, tracer: GraphQL::Tracing::NullTracer) self.new(query_string, filename: filename, tracer: tracer).parse_document end private def next_token lexer_token = @tokens.shift if lexer_token.nil? nil else @reused_next_token[0] = lexer_token.name @reused_next_token[1] = lexer_token @reused_next_token end end def get_description(token) comments = [] loop do prev_token = token token = token.prev_token break if token.nil? break if token.name != :COMMENT break if prev_token.line != token.line + 1 comments.unshift(token.to_s.sub(/^#\s*/, "")) end return nil if comments.empty? comments.join("\n") end def on_error(parser_token_id, lexer_token, vstack) if lexer_token == "$" || lexer_token == nil raise GraphQL::ParseError.new("Unexpected end of document", nil, nil, @query_string, filename: @filename) else parser_token_name = token_to_str(parser_token_id) if parser_token_name.nil? raise GraphQL::ParseError.new("Parse Error on unknown token: {token_id: #{parser_token_id}, lexer_token: #{lexer_token}} from #{@query_string}", nil, nil, @query_string, filename: @filename) else line, col = lexer_token.line_and_column if lexer_token.name == :BAD_UNICODE_ESCAPE raise GraphQL::ParseError.new("Parse error on bad Unicode escape sequence: #{lexer_token.to_s.inspect} (#{parser_token_name}) at [#{line}, #{col}]", line, col, @query_string, filename: @filename) else raise GraphQL::ParseError.new("Parse error on #{lexer_token.to_s.inspect} (#{parser_token_name}) at [#{line}, #{col}]", line, col, @query_string, filename: @filename) end end end end def make_node(node_name, assigns) assigns.each do |key, value| if key != :position_source && value.is_a?(GraphQL::Language::Token) assigns[key] = value.to_s end end assigns[:filename] = @filename GraphQL::Language::Nodes.const_get(node_name).new(assigns) end ...end parser.y/module_eval... ##### State transition tables begin ### racc_action_table = [ -2, -166, 11, -100, 12, 13, 14, 280, 11, -100, 12, 13, 14, -100, -100, -100, 19, 178, 169, 273, 169, 284, 19, 169, 169, 222, 15, 284, 71, 35, 35, 35, 15, 71, 71, 28, -100, 35, 12, 13, 14, 28, 71, -166, 71, 71, 71, 35, -152, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 223, 12, 13, 14, 71, -166, -166, 181, 35, 299, 160, 120, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 60, 12, 13, 14, 295, 66, 263, 35, 35, -166, 38, 35, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 90, 12, 13, 14, 279, 66, 35, 278, 35, 268, 277, 35, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 219, 71, 12, 13, 14, 66, 35, 217, 272, 35, 218, 35, 35, 200, 44, 45, 46, 47, 48, 49, 50, 51, 52, 198, 199, 207, 208, 204, 205, 206, 216, 219, 87, 12, 13, 14, 35, 94, 217, 89, 93, 218, 254, 98, 200, 44, 45, 46, 47, 48, 49, 50, 51, 52, 198, 199, 207, 208, 204, 205, 206, 216, 302, 71, 12, 13, 14, 174, 12, 13, 14, 101, 218, 71, 116, 200, 44, 45, 46, 47, 48, 49, 50, 51, 52, 198, 199, 207, 208, 204, 205, 206, 216, 219, 71, 12, 13, 14, 120, 71, 217, 71, 131, 218, 94, 138, 200, 44, 45, 46, 47, 48, 49, 50, 51, 52, 198, 199, 207, 208, 204, 205, 206, 216, 302, 71, 12, 13, 14, 226, 12, 13, 14, 142, 218, 71, 71, 200, 44, 45, 46, 47, 48, 49, 50, 51, 52, 198, 199, 207, 208, 204, 205, 206, 216, 219, 71, 12, 13, 14, 71, 71, 217, 142, 71, 218, 71, 169, 200, 44, 45, 46, 47, 48, 49, 50, 51, 52, 198, 199, 207, 208, 204, 205, 206, 216, 12, 13, 14, 71, 169, 12, 13, 14, 12, 13, 14, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 219, 71, 12, 13, 14, 66, 71, 217, 71, 131, 218, 285, 131, 200, 44, 45, 46, 47, 48, 49, 50, 51, 52, 198, 199, 207, 208, 204, 205, 206, 216, 12, 13, 14, 80, 81, 94, 82, 83, 84, 85, 86, 96, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 287, 12, 13, 14, 71, 168, 176, 71, 71, 185, 186, 187, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 257, 12, 13, 14, 73, 74, 75, 188, 76, 77, 78, 79, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 291, 12, 13, 14, 71, 190, 191, 192, 193, 194, 196, 71, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 310, 12, 13, 14, 131, 131, 230, 233, 35, 35, 244, 35, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, 35, 35, 250, 131, 233, 269, 269, 290, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, 194, 71, 298, 300, 303, 306, 307, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, 130, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, 123, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, 130, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, nil, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, 130, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, 166, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, 130, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, 130, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, 130, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 200, 44, 45, 46, 47, 48, 49, 50, 51, 52, 198, 199, -158, nil, nil, nil, -158, nil, nil, nil, nil, nil, -158, nil, -158, -158 ] racc_action_check = [ 3, 244, 3, 147, 3, 3, 3, 249, 0, 145, 0, 0, 0, 179, 149, 143, 3, 140, 308, 241, 133, 308, 0, 171, 251, 171, 3, 251, 145, 244, 249, 3, 0, 313, 147, 3, 102, 0, 142, 142, 142, 0, 133, 178, 179, 149, 143, 241, 140, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 172, 172, 172, 172, 102, 185, 190, 142, 178, 276, 118, 118, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 11, 11, 11, 11, 266, 172, 227, 185, 190, 230, 1, 276, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 59, 59, 59, 59, 248, 11, 266, 247, 227, 235, 245, 230, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 168, 19, 168, 168, 168, 59, 248, 168, 240, 247, 168, 235, 245, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 218, 38, 218, 218, 218, 240, 65, 218, 40, 65, 218, 218, 67, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 306, 69, 306, 306, 306, 135, 135, 135, 135, 72, 306, 73, 86, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 303, 88, 303, 303, 303, 89, 92, 303, 95, 97, 303, 100, 103, 303, 303, 303, 303, 303, 303, 303, 303, 303, 303, 303, 303, 303, 303, 303, 303, 303, 303, 284, 104, 284, 284, 284, 177, 177, 177, 177, 105, 284, 106, 107, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 290, 108, 290, 290, 290, 109, 110, 290, 111, 112, 290, 113, 312, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 131, 131, 131, 312, 129, 101, 101, 101, 138, 138, 138, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 255, 129, 255, 255, 255, 131, 114, 255, 115, 117, 255, 255, 121, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 66, 66, 66, 37, 37, 122, 37, 37, 37, 37, 37, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 258, 258, 258, 258, 66, 126, 137, 139, 141, 144, 146, 148, 258, 258, 258, 258, 258, 258, 258, 258, 258, 258, 258, 258, 258, 258, 219, 219, 219, 219, 28, 28, 28, 150, 28, 28, 28, 28, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 261, 261, 261, 261, 152, 154, 155, 156, 157, 158, 162, 165, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 302, 302, 302, 302, 170, 173, 180, 182, 187, 188, 189, 192, 302, 302, 302, 302, 302, 302, 302, 302, 302, 302, 302, 302, 302, 302, 181, 181, 181, 193, 194, 195, 197, 231, 236, 246, 260, 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, 10, 10, 10, 265, 270, 275, 281, 289, 293, 294, nil, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 233, 233, 233, nil, nil, nil, nil, nil, nil, nil, nil, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 120, 120, 120, nil, nil, nil, nil, nil, nil, nil, nil, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 80, 80, 80, nil, nil, nil, nil, nil, nil, nil, nil, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 307, 307, 307, nil, nil, nil, nil, nil, 307, nil, nil, 307, 307, 307, 307, 307, 307, 307, 307, 307, 307, 307, 307, 307, 307, 79, 79, 79, nil, nil, nil, nil, nil, nil, nil, nil, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 186, 186, 186, nil, nil, nil, nil, nil, nil, nil, nil, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 93, 93, 93, nil, nil, nil, nil, nil, nil, nil, nil, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 183, 183, 183, nil, nil, nil, nil, nil, nil, nil, nil, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 94, 94, 94, nil, 94, nil, nil, nil, nil, nil, nil, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 78, 78, 78, nil, nil, nil, nil, nil, nil, nil, nil, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 85, 85, 85, nil, nil, nil, nil, nil, nil, nil, nil, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 96, 96, 96, nil, nil, nil, nil, nil, 96, nil, nil, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 77, 77, 77, nil, nil, nil, nil, nil, nil, nil, nil, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 15, 15, 15, nil, nil, nil, nil, nil, nil, nil, nil, nil, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 98, 98, 98, nil, nil, nil, nil, nil, 98, nil, nil, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 176, 176, 176, nil, nil, nil, nil, nil, nil, nil, nil, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 71, 71, 71, nil, nil, nil, nil, nil, nil, nil, nil, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 74, 74, 74, nil, nil, nil, nil, nil, nil, nil, nil, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 76, 76, 76, nil, nil, nil, nil, nil, nil, nil, nil, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 242, 242, 242, nil, nil, nil, nil, nil, nil, nil, nil, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 250, 250, 250, nil, nil, nil, nil, nil, nil, nil, nil, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 228, 228, 228, nil, nil, nil, nil, nil, nil, nil, nil, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 217, 217, 217, nil, nil, nil, nil, nil, nil, nil, nil, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 124, 124, 124, nil, 124, nil, nil, nil, nil, nil, nil, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 75, 75, 75, nil, nil, nil, nil, nil, nil, nil, nil, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 196, 196, 196, nil, nil, nil, nil, nil, 196, nil, nil, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 269, 269, 269, nil, nil, nil, nil, nil, nil, nil, nil, 269, 269, 269, 269, 269, 269, 269, 269, 269, 269, 269, 269, 269, 269, 130, 130, 130, nil, nil, nil, nil, nil, 130, nil, nil, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 84, 84, 84, nil, nil, nil, nil, nil, nil, nil, nil, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 116, 116, 116, nil, nil, nil, nil, nil, nil, nil, nil, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 83, 83, 83, nil, nil, nil, nil, nil, nil, nil, nil, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 82, 82, 82, nil, nil, nil, nil, nil, nil, nil, nil, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 191, 191, 191, nil, nil, nil, nil, nil, nil, nil, nil, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 298, 298, 298, nil, nil, nil, nil, nil, 298, nil, nil, 298, 298, 298, 298, 298, 298, 298, 298, 298, 298, 298, 298, 298, 298, 300, 300, 300, nil, nil, nil, nil, nil, nil, nil, nil, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 81, 81, 81, nil, nil, nil, nil, nil, nil, nil, nil, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 238, 238, 238, nil, nil, nil, nil, nil, nil, nil, nil, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 184, nil, nil, nil, 184, nil, nil, nil, nil, nil, 184, nil, 184, 184 ] racc_action_pointer = [ 6, 99, nil, 0, nil, nil, nil, nil, nil, nil, 533, 86, nil, nil, nil, 883, nil, nil, nil, 109, nil, nil, nil, nil, nil, nil, nil, nil, 422, nil, nil, nil, nil, nil, nil, nil, nil, 369, 173, nil, 173, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 112, nil, nil, nil, nil, nil, 171, 379, 169, nil, 171, nil, 958, 210, 181, 983, 1158, 1008, 858, 783, 658, 608, 1433, 1333, 1308, 1258, 808, 182, nil, 202, 230, nil, nil, 207, 708, 758, 209, 833, 241, 908, nil, 238, 328, 34, 244, 233, 255, 243, 244, 264, 268, 269, 285, 272, 274, 325, 327, 1283, 359, 65, nil, 583, 362, 381, nil, 1133, nil, 403, nil, nil, 320, 1233, 323, nil, 9, nil, 205, nil, 404, 331, 382, 15, 383, 34, 13, 415, -5, 404, 1, 417, 12, 439, nil, 431, nil, 463, 452, 465, 466, 462, nil, nil, nil, 460, nil, nil, 438, nil, nil, 139, nil, 488, 12, 60, 489, nil, nil, 933, 267, 40, 11, 490, 508, 457, 733, 1470, 65, 683, 463, 464, 494, 66, 1358, 466, 484, 485, 502, 1183, 516, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1108, 170, 431, nil, nil, nil, nil, nil, nil, nil, 92, 1083, nil, 95, 483, nil, 558, nil, 121, 483, nil, 1458, nil, 146, 16, 1033, nil, -2, 122, 484, 119, 116, -1, 1058, 13, nil, nil, nil, 350, nil, nil, 405, nil, 512, 457, nil, nil, nil, 533, 90, nil, nil, 1208, 508, nil, nil, nil, nil, 532, 69, nil, nil, nil, nil, 506, nil, nil, 263, nil, nil, nil, nil, 534, 294, nil, nil, 535, 536, nil, nil, nil, 1383, nil, 1408, nil, 483, 232, nil, nil, 201, 633, 7, nil, nil, nil, 297, 0, nil, nil ] racc_action_default = [ -147, -178, -1, -147, -3, -5, -6, -7, -8, -9, -16, -178, -13, -14, -15, -108, -110, -111, -112, -99, -117, -118, -119, -120, -121, -122, -123, -124, -178, -127, -128, -129, -130, -131, -132, -146, -148, -178, -178, -4, -18, -17, -38, -39, -40, -41, -42, -43, -44, -45, -46, -47, -48, -49, -50, -51, -52, -53, -54, -178, -12, -31, -33, -34, -35, -63, -99, -178, -109, -100, -101, -178, -178, -99, -178, -178, -178, -178, -178, -178, -178, -178, -178, -178, -178, -178, -178, 316, -99, -178, -11, -32, -99, -178, -178, -99, -178, -178, -178, -102, -63, -178, -126, -178, -178, -151, -99, -99, -99, -99, -99, -151, -99, -99, -99, -99, -178, -178, -178, -20, -178, -29, -63, -64, -178, -66, -178, -104, -23, -99, -178, -178, -106, -99, -103, -178, -114, -178, -178, -133, -137, -99, -178, -139, -178, -141, -178, -143, -178, -145, -178, -149, -99, -152, -178, -178, -178, -178, -163, -10, -19, -21, -178, -30, -36, -99, -65, -67, -78, -24, -178, -178, -178, -178, -113, -115, -178, -178, -147, -136, -178, -178, -154, -155, -156, -147, -178, -147, -147, -178, -147, -178, -147, -147, -147, -178, -178, -29, -55, -56, -57, -58, -59, -68, -69, -70, -71, -72, -73, -74, -75, -76, -77, -79, -80, -81, -82, -178, -178, -178, -98, -105, -25, -28, -107, -116, -125, -147, -178, -167, -147, -153, -156, -178, -159, -147, -140, -170, -178, -61, -147, -147, -178, -161, -147, -147, -172, -147, -147, -147, -178, -26, -37, -83, -84, -178, -86, -88, -178, -90, -178, -178, -95, -134, -168, -163, -147, -157, -138, -178, -99, -62, -142, -144, -162, -178, -147, -169, -173, -174, -164, -175, -176, -22, -178, -85, -87, -89, -91, -178, -78, -94, -96, -178, -178, -135, -171, -60, -178, -150, -178, -27, -178, -78, -79, -92, -178, -178, -26, -177, -93, -97, -99, -99, -165, -160 ] racc_goto_table = [ 10, 72, 59, 10, 91, 195, 129, 132, 133, 283, 164, 203, 68, 125, 175, 202, 102, 135, 92, 37, 259, 227, 37, 4, 301, 182, 39, 159, 235, 99, 304, 119, 271, 245, 274, 270, 118, 2, 140, 271, 171, 274, 274, 167, 153, 88, 311, 139, 97, 143, 145, 147, 149, 134, 177, 103, 175, 281, 1, 288, 161, 256, 99, 95, 231, 202, 313, 40, 183, 3, 117, 258, 141, 266, 121, 165, 241, 127, 152, 292, 221, 248, 249, 224, 179, 202, 252, 276, 144, 146, 148, 150, 151, 255, 154, 155, 156, 157, 286, 99, 124, 137, 202, 99, 67, 99, 251, 99, 236, 99, 264, 170, 294, 246, 240, 173, nil, 91, 264, 247, nil, nil, 172, 180, nil, nil, 41, 65, 264, nil, nil, 202, nil, nil, 189, 137, nil, 202, 137, 99, nil, nil, nil, nil, nil, nil, nil, 197, nil, 264, 202, nil, nil, 202, nil, nil, nil, nil, nil, 264, nil, nil, nil, nil, nil, nil, nil, nil, 201, nil, nil, nil, nil, nil, nil, 65, nil, 137, nil, nil, nil, nil, nil, nil, nil, nil, nil, 100, nil, nil, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, nil, nil, nil, nil, 238, 242, 308, 122, 126, 238, 242, 242, nil, nil, nil, 312, 201, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 158, nil, nil, nil, 162, nil, 201, nil, 126, nil, nil, nil, nil, nil, nil, 65, nil, nil, nil, nil, 297, nil, nil, 201, nil, nil, 184, 238, 242, nil, nil, nil, nil, nil, 238, 242, 242, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 201, nil, nil, nil, 65, nil, 201, nil, 225, nil, 314, 315, nil, 232, nil, 234, nil, nil, 237, 201, nil, nil, 201, 237, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 253, nil, 260, nil, nil, nil, nil, nil, nil, nil, nil, 265, nil, nil, nil, nil, 267, nil, nil, nil, nil, nil, nil, nil, nil, 275, nil, nil, nil, nil, nil, nil, nil, 282, nil, nil, nil, nil, nil, nil, nil, 289, nil, nil, 293, nil, nil, nil, nil, nil, nil, nil, 296, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 309, nil, 293 ] racc_goto_check = [ 10, 13, 15, 10, 23, 79, 19, 14, 19, 20, 22, 36, 28, 35, 55, 29, 48, 54, 27, 32, 45, 71, 32, 4, 21, 76, 4, 14, 71, 49, 21, 18, 31, 71, 78, 30, 17, 2, 70, 31, 19, 78, 78, 35, 70, 12, 21, 48, 13, 48, 48, 48, 48, 27, 54, 13, 55, 81, 1, 45, 18, 36, 49, 28, 76, 29, 20, 11, 77, 3, 13, 44, 72, 71, 13, 27, 74, 13, 72, 47, 14, 74, 74, 14, 48, 29, 22, 71, 13, 13, 13, 13, 13, 43, 13, 13, 13, 13, 36, 49, 34, 10, 29, 49, 50, 49, 19, 49, 73, 49, 80, 13, 79, 73, 33, 13, nil, 23, 80, 33, nil, nil, 15, 13, nil, nil, 16, 16, 80, nil, nil, 29, nil, nil, 13, 10, nil, 29, 10, 49, nil, nil, nil, nil, nil, nil, nil, 13, nil, 80, 29, nil, nil, 29, nil, nil, nil, nil, nil, 80, nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, nil, nil, 16, nil, 10, nil, nil, nil, nil, nil, nil, nil, nil, nil, 16, nil, nil, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, nil, nil, nil, nil, 32, 32, 19, 16, 16, 32, 32, 32, nil, nil, nil, 19, 10, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 16, nil, nil, nil, 16, nil, 10, nil, 16, nil, nil, nil, nil, nil, nil, 16, nil, nil, nil, nil, 13, nil, nil, 10, nil, nil, 16, 32, 32, nil, nil, nil, nil, nil, 32, 32, 32, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, 16, nil, 10, nil, 16, nil, 13, 13, nil, 16, nil, 16, nil, nil, 16, 10, nil, nil, 10, 16, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 16, nil, 16, nil, nil, nil, nil, nil, nil, nil, nil, 16, nil, nil, nil, nil, 16, nil, nil, nil, nil, nil, nil, nil, nil, 16, nil, nil, nil, nil, nil, nil, nil, 16, nil, nil, nil, nil, nil, nil, nil, 16, nil, nil, 16, nil, nil, nil, nil, nil, nil, nil, 16, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 16, nil, 16 ] racc_goto_pointer = [ nil, 58, 37, 69, 23, nil, nil, nil, nil, nil, 0, 57, 5, -18, -90, -9, 116, -53, -58, -90, -242, -260, -111, -55, nil, nil, nil, -47, -3, -153, -203, -208, 19, -73, 6, -81, -157, nil, nil, nil, nil, nil, nil, -125, -148, -199, nil, -182, -57, -40, 89, nil, nil, nil, -84, -121, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, -67, -157, -33, -78, -112, nil, -117, -74, -207, -153, -117, -193 ] racc_goto_default = [ nil, nil, nil, nil, nil, 5, 6, 7, 8, 9, 57, nil, nil, nil, 163, nil, 128, nil, nil, nil, nil, 213, nil, 61, 62, 63, 64, nil, 42, 58, 220, 239, 228, nil, nil, nil, 305, 209, 210, 211, 212, 214, 215, nil, nil, nil, 261, 262, 69, 70, nil, 16, 17, 18, nil, 136, 20, 21, 22, 23, 24, 25, 26, 27, 29, 30, 31, 32, 33, 34, nil, nil, nil, nil, nil, 36, nil, nil, 243, nil, 229, nil ] racc_reduce_table = [ 0, 0, :racc_error, 1, 39, :_reduce_none, 1, 40, :_reduce_2, 1, 41, :_reduce_3, 2, 41, :_reduce_4, 1, 42, :_reduce_none, 1, 42, :_reduce_none, 1, 42, :_reduce_none, 1, 43, :_reduce_none, 1, 43, :_reduce_none, 5, 46, :_reduce_10, 3, 46, :_reduce_11, 2, 46, :_reduce_12, 1, 48, :_reduce_none, 1, 48, :_reduce_none, 1, 48, :_reduce_none, 0, 49, :_reduce_16, 1, 49, :_reduce_none, 0, 50, :_reduce_18, 3, 50, :_reduce_19, 1, 55, :_reduce_20, 2, 55, :_reduce_21, 5, 56, :_reduce_22, 1, 57, :_reduce_23, 2, 57, :_reduce_24, 3, 57, :_reduce_25, 0, 58, :_reduce_26, 2, 58, :_reduce_27, 3, 52, :_reduce_28, 0, 60, :_reduce_29, 1, 60, :_reduce_30, 1, 53, :_reduce_31, 2, 53, :_reduce_32, 1, 61, :_reduce_none, 1, 61, :_reduce_none, 1, 61, :_reduce_none, 4, 62, :_reduce_36, 6, 62, :_reduce_37, 1, 54, :_reduce_none, 1, 54, :_reduce_none, 1, 67, :_reduce_none, 1, 67, :_reduce_none, 1, 67, :_reduce_none, 1, 67, :_reduce_none, 1, 67, :_reduce_none, 1, 67, :_reduce_none, 1, 67, :_reduce_none, 1, 67, :_reduce_none, 1, 67, :_reduce_none, 1, 66, :_reduce_none, 1, 66, :_reduce_none, 1, 66, :_reduce_none, 1, 66, :_reduce_none, 1, 66, :_reduce_none, 1, 66, :_reduce_none, 1, 68, :_reduce_none, 1, 68, :_reduce_none, 1, 68, :_reduce_none, 1, 68, :_reduce_none, 1, 68, :_reduce_none, 3, 69, :_reduce_60, 1, 71, :_reduce_61, 2, 71, :_reduce_62, 0, 65, :_reduce_63, 2, 65, :_reduce_64, 3, 65, :_reduce_65, 1, 72, :_reduce_66, 2, 72, :_reduce_67, 3, 73, :_reduce_68, 1, 59, :_reduce_69, 1, 59, :_reduce_70, 1, 59, :_reduce_71, 1, 59, :_reduce_72, 1, 59, :_reduce_73, 1, 59, :_reduce_none, 1, 59, :_reduce_none, 1, 59, :_reduce_none, 1, 59, :_reduce_none, 0, 74, :_reduce_none, 1, 74, :_reduce_none, 1, 74, :_reduce_none, 1, 74, :_reduce_none, 1, 75, :_reduce_82, 2, 79, :_reduce_83, 2, 77, :_reduce_84, 3, 77, :_reduce_85, 1, 81, :_reduce_86, 2, 81, :_reduce_87, 2, 80, :_reduce_88, 3, 80, :_reduce_89, 1, 82, :_reduce_90, 2, 82, :_reduce_91, 3, 83, :_reduce_92, 2, 78, :_reduce_93, 3, 78, :_reduce_94, 1, 84, :_reduce_95, 2, 84, :_reduce_96, 3, 85, :_reduce_97, 1, 76, :_reduce_98, 0, 51, :_reduce_99, 1, 51, :_reduce_none, 1, 86, :_reduce_101, 2, 86, :_reduce_102, 3, 87, :_reduce_103, 3, 63, :_reduce_104, 5, 64, :_reduce_105, 3, 64, :_reduce_106, 6, 47, :_reduce_107, 0, 88, :_reduce_108, 1, 88, :_reduce_none, 1, 44, :_reduce_none, 1, 44, :_reduce_none, 1, 44, :_reduce_none, 5, 89, :_reduce_113, 1, 92, :_reduce_none, 2, 92, :_reduce_115, 3, 93, :_reduce_116, 1, 90, :_reduce_none, 1, 90, :_reduce_none, 1, 90, :_reduce_none, 1, 90, :_reduce_none, 1, 90, :_reduce_none, 1, 90, :_reduce_none, 1, 45, :_reduce_none, 1, 45, :_reduce_none, 6, 100, :_reduce_125, 3, 100, :_reduce_126, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 4, 102, :_reduce_133, 7, 103, :_reduce_134, 8, 103, :_reduce_135, 5, 103, :_reduce_136, 4, 103, :_reduce_137, 7, 104, :_reduce_138, 4, 104, :_reduce_139, 6, 105, :_reduce_140, 4, 105, :_reduce_141, 7, 106, :_reduce_142, 4, 106, :_reduce_143, 7, 107, :_reduce_144, 4, 107, :_reduce_145, 1, 113, :_reduce_none, 0, 70, :_reduce_none, 1, 70, :_reduce_none, 4, 94, :_reduce_149, 8, 95, :_reduce_150, 0, 110, :_reduce_151, 1, 110, :_reduce_none, 3, 108, :_reduce_153, 2, 108, :_reduce_154, 2, 108, :_reduce_155, 1, 114, :_reduce_156, 3, 114, :_reduce_157, 1, 115, :_reduce_158, 2, 115, :_reduce_159, 6, 116, :_reduce_160, 1, 112, :_reduce_161, 2, 112, :_reduce_162, 0, 117, :_reduce_163, 3, 117, :_reduce_164, 6, 118, :_reduce_165, 0, 109, :_reduce_166, 1, 109, :_reduce_167, 2, 109, :_reduce_168, 7, 96, :_reduce_169, 1, 111, :_reduce_170, 3, 111, :_reduce_171, 6, 97, :_reduce_172, 7, 98, :_reduce_173, 7, 99, :_reduce_174, 7, 91, :_reduce_175, 1, 119, :_reduce_176, 3, 119, :_reduce_177 ] racc_reduce_n = 178 racc_shift_n = 316 racc_token_table = { false => 0, :error => 1, :LCURLY => 2, :RCURLY => 3, :QUERY => 4, :MUTATION => 5, :SUBSCRIPTION => 6, :LPAREN => 7, :RPAREN => 8, :VAR_SIGN => 9, :COLON => 10, :BANG => 11, :LBRACKET => 12, :RBRACKET => 13, :EQUALS => 14, :ON => 15, :SCHEMA => 16, :SCALAR => 17, :TYPE => 18, :IMPLEMENTS => 19, :INTERFACE => 20, :UNION => 21, :ENUM => 22, :INPUT => 23, :DIRECTIVE => 24, :IDENTIFIER => 25, :FRAGMENT => 26, :TRUE => 27, :FALSE => 28, :FLOAT => 29, :INT => 30, :STRING => 31, :NULL => 32, :DIR_SIGN => 33, :ELLIPSIS => 34, :EXTEND => 35, :AMP => 36, :PIPE => 37 } racc_nt_base = 38 racc_use_result_var = true Racc_arg = [ racc_action_table, racc_action_check, racc_action_default, racc_action_pointer, racc_goto_table, racc_goto_check, racc_goto_default, racc_goto_pointer, racc_nt_base, racc_reduce_table, racc_token_table, racc_shift_n, racc_reduce_n, racc_use_result_var ] Racc_token_to_s_table = [ "$end", "error", "LCURLY", "RCURLY", "QUERY", "MUTATION", "SUBSCRIPTION", "LPAREN", "RPAREN", "VAR_SIGN", "COLON", "BANG", "LBRACKET", "RBRACKET", "EQUALS", "ON", "SCHEMA", "SCALAR", "TYPE", "IMPLEMENTS", "INTERFACE", "UNION", "ENUM", "INPUT", "DIRECTIVE", "IDENTIFIER", "FRAGMENT", "TRUE", "FALSE", "FLOAT", "INT", "STRING", "NULL", "DIR_SIGN", "ELLIPSIS", "EXTEND", "AMP", "PIPE", "$start", "target", "document", "definitions_list", "definition", "executable_definition", "type_system_definition", "type_system_extension", "operation_definition", "fragment_definition", "operation_type", "operation_name_opt", "variable_definitions_opt", "directives_list_opt", "selection_set", "selection_list", "name", "variable_definitions_list", "variable_definition", "type", "default_value_opt", "literal_value", "selection_set_opt", "selection", "field", "fragment_spread", "inline_fragment", "arguments_opt", "name_without_on", "schema_keyword", "enum_name", "enum_value_definition", "description_opt", "enum_value_definitions", "arguments_list", "argument", "input_value", "null_value", "enum_value", "list_value", "object_literal_value", "variable", "object_value", "list_value_list", "object_value_list", "object_value_field", "object_literal_value_list", "object_literal_value_field", "directives_list", "directive", "fragment_name_opt", "schema_definition", "type_definition", "directive_definition", "operation_type_definition_list", "operation_type_definition", "scalar_type_definition", "object_type_definition", "interface_type_definition", "union_type_definition", "enum_type_definition", "input_object_type_definition", "schema_extension", "type_extension", "scalar_type_extension", "object_type_extension", "interface_type_extension", "union_type_extension", "enum_type_extension", "input_object_type_extension", "implements", "field_definition_list", "implements_opt", "union_members", "input_value_definition_list", "description", "interfaces_list", "legacy_interfaces_list", "input_value_definition", "arguments_definitions_opt", "field_definition", "directive_locations" ] Racc_debug_parser = false ##### State transition tables end ##### # reduce 0 omitted # reduce 1 omitted module_eval(<<'.,.,', 'parser.y', 4) def _reduce_2(val, _values, result) return make_node(:Document, definitions: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 7) def _reduce_3(val, _values, result) return [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 8) def _reduce_4(val, _values, result) val[0] << val[1] result end .,., # reduce 5 omitted # reduce 6 omitted # reduce 7 omitted # reduce 8 omitted # reduce 9 omitted module_eval(<<'.,.,', 'parser.y', 21) def _reduce_10(val, _values, result) return make_node( :OperationDefinition, { operation_type: val[0], name: val[1], variables: val[2], directives: val[3], selections: val[4], position_source: val[0], } ) result end .,., module_eval(<<'.,.,', 'parser.y', 33) def _reduce_11(val, _values, result) return make_node( :OperationDefinition, { operation_type: "query", selections: val[1], position_source: val[0], } ) result end .,., module_eval(<<'.,.,', 'parser.y', 42) def _reduce_12(val, _values, result) return make_node( :OperationDefinition, { operation_type: "query", selections: [], position_source: val[0], } ) result end .,., # reduce 13 omitted # reduce 14 omitted # reduce 15 omitted module_eval(<<'.,.,', 'parser.y', 57) def _reduce_16(val, _values, result) return nil result end .,., # reduce 17 omitted module_eval(<<'.,.,', 'parser.y', 61) def _reduce_18(val, _values, result) return EMPTY_ARRAY result end .,., module_eval(<<'.,.,', 'parser.y', 62) def _reduce_19(val, _values, result) return val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 65) def _reduce_20(val, _values, result) return [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 66) def _reduce_21(val, _values, result) val[0] << val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 70) def _reduce_22(val, _values, result) return make_node(:VariableDefinition, { name: val[1], type: val[3], default_value: val[4], position_source: val[0], }) result end .,., module_eval(<<'.,.,', 'parser.y', 79) def _reduce_23(val, _values, result) return make_node(:TypeName, name: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 80) def _reduce_24(val, _values, result) return make_node(:NonNullType, of_type: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 81) def _reduce_25(val, _values, result) return make_node(:ListType, of_type: val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 84) def _reduce_26(val, _values, result) return nil result end .,., module_eval(<<'.,.,', 'parser.y', 85) def _reduce_27(val, _values, result) return val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 88) def _reduce_28(val, _values, result) return val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 91) def _reduce_29(val, _values, result) return EMPTY_ARRAY result end .,., module_eval(<<'.,.,', 'parser.y', 92) def _reduce_30(val, _values, result) return val[0] result end .,., module_eval(<<'.,.,', 'parser.y', 95) def _reduce_31(val, _values, result) return [result] result end .,., module_eval(<<'.,.,', 'parser.y', 96) def _reduce_32(val, _values, result) val[0] << val[1] result end .,., # reduce 33 omitted # reduce 34 omitted # reduce 35 omitted module_eval(<<'.,.,', 'parser.y', 105) def _reduce_36(val, _values, result) return make_node( :Field, { name: val[0], arguments: val[1], directives: val[2], selections: val[3], position_source: val[0], } ) result end .,., module_eval(<<'.,.,', 'parser.y', 116) def _reduce_37(val, _values, result) return make_node( :Field, { alias: val[0], name: val[2], arguments: val[3], directives: val[4], selections: val[5], position_source: val[0], } ) result end .,., # reduce 38 omitted # reduce 39 omitted # reduce 40 omitted # reduce 41 omitted # reduce 42 omitted # reduce 43 omitted # reduce 44 omitted # reduce 45 omitted # reduce 46 omitted # reduce 47 omitted # reduce 48 omitted # reduce 49 omitted # reduce 50 omitted # reduce 51 omitted # reduce 52 omitted # reduce 53 omitted # reduce 54 omitted # reduce 55 omitted # reduce 56 omitted # reduce 57 omitted # reduce 58 omitted # reduce 59 omitted module_eval(<<'.,.,', 'parser.y', 159) def _reduce_60(val, _values, result) return make_node(:EnumValueDefinition, name: val[1], directives: val[2], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 162) def _reduce_61(val, _values, result) return [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 163) def _reduce_62(val, _values, result) return val[0] << val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 166) def _reduce_63(val, _values, result) return EMPTY_ARRAY result end .,., module_eval(<<'.,.,', 'parser.y', 167) def _reduce_64(val, _values, result) return EMPTY_ARRAY result end .,., module_eval(<<'.,.,', 'parser.y', 168) def _reduce_65(val, _values, result) return val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 171) def _reduce_66(val, _values, result) return [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 172) def _reduce_67(val, _values, result) val[0] << val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 175) def _reduce_68(val, _values, result) return make_node(:Argument, name: val[0], value: val[2], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 178) def _reduce_69(val, _values, result) return val[0].to_f result end .,., module_eval(<<'.,.,', 'parser.y', 179) def _reduce_70(val, _values, result) return val[0].to_i result end .,., module_eval(<<'.,.,', 'parser.y', 180) def _reduce_71(val, _values, result) return val[0].to_s result end .,., module_eval(<<'.,.,', 'parser.y', 181) def _reduce_72(val, _values, result) return true result end .,., module_eval(<<'.,.,', 'parser.y', 182) def _reduce_73(val, _values, result) return false result end .,., # reduce 74 omitted # reduce 75 omitted # reduce 76 omitted # reduce 77 omitted # reduce 78 omitted # reduce 79 omitted # reduce 80 omitted # reduce 81 omitted module_eval(<<'.,.,', 'parser.y', 193) def _reduce_82(val, _values, result) return make_node(:NullValue, name: val[0], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 194) def _reduce_83(val, _values, result) return make_node(:VariableIdentifier, name: val[1], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 197) def _reduce_84(val, _values, result) return EMPTY_ARRAY result end .,., module_eval(<<'.,.,', 'parser.y', 198) def _reduce_85(val, _values, result) return val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 201) def _reduce_86(val, _values, result) return [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 202) def _reduce_87(val, _values, result) val[0] << val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 205) def _reduce_88(val, _values, result) return make_node(:InputObject, arguments: [], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 206) def _reduce_89(val, _values, result) return make_node(:InputObject, arguments: val[1], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 209) def _reduce_90(val, _values, result) return [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 210) def _reduce_91(val, _values, result) val[0] << val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 213) def _reduce_92(val, _values, result) return make_node(:Argument, name: val[0], value: val[2], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 217) def _reduce_93(val, _values, result) return make_node(:InputObject, arguments: [], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 218) def _reduce_94(val, _values, result) return make_node(:InputObject, arguments: val[1], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 221) def _reduce_95(val, _values, result) return [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 222) def _reduce_96(val, _values, result) val[0] << val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 225) def _reduce_97(val, _values, result) return make_node(:Argument, name: val[0], value: val[2], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 227) def _reduce_98(val, _values, result) return make_node(:Enum, name: val[0], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 230) def _reduce_99(val, _values, result) return EMPTY_ARRAY result end .,., # reduce 100 omitted module_eval(<<'.,.,', 'parser.y', 234) def _reduce_101(val, _values, result) return [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 235) def _reduce_102(val, _values, result) val[0] << val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 237) def _reduce_103(val, _values, result) return make_node(:Directive, name: val[1], arguments: val[2], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 240) def _reduce_104(val, _values, result) return make_node(:FragmentSpread, name: val[1], directives: val[2], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 244) def _reduce_105(val, _values, result) return make_node(:InlineFragment, { type: val[2], directives: val[3], selections: val[4], position_source: val[0] }) result end .,., module_eval(<<'.,.,', 'parser.y', 252) def _reduce_106(val, _values, result) return make_node(:InlineFragment, { type: nil, directives: val[1], selections: val[2], position_source: val[0] }) result end .,., module_eval(<<'.,.,', 'parser.y', 262) def _reduce_107(val, _values, result) return make_node(:FragmentDefinition, { name: val[1], type: val[3], directives: val[4], selections: val[5], position_source: val[0], } ) result end .,., module_eval(<<'.,.,', 'parser.y', 273) def _reduce_108(val, _values, result) return nil result end .,., # reduce 109 omitted # reduce 110 omitted # reduce 111 omitted # reduce 112 omitted module_eval(<<'.,.,', 'parser.y', 282) def _reduce_113(val, _values, result) return make_node(:SchemaDefinition, position_source: val[0], definition_line: val[0].line, directives: val[1], **val[3]) result end .,., # reduce 114 omitted module_eval(<<'.,.,', 'parser.y', 286) def _reduce_115(val, _values, result) return val[0].merge(val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 289) def _reduce_116(val, _values, result) return { val[0].to_s.to_sym => val[2] } result end .,., # reduce 117 omitted # reduce 118 omitted # reduce 119 omitted # reduce 120 omitted # reduce 121 omitted # reduce 122 omitted # reduce 123 omitted # reduce 124 omitted module_eval(<<'.,.,', 'parser.y', 304) def _reduce_125(val, _values, result) return make_node(:SchemaExtension, position_source: val[0], directives: val[2], **val[4]) result end .,., module_eval(<<'.,.,', 'parser.y', 305) def _reduce_126(val, _values, result) return make_node(:SchemaExtension, position_source: val[0], directives: val[2]) result end .,., # reduce 127 omitted # reduce 128 omitted # reduce 129 omitted # reduce 130 omitted # reduce 131 omitted # reduce 132 omitted module_eval(<<'.,.,', 'parser.y', 315) def _reduce_133(val, _values, result) return make_node(:ScalarTypeExtension, name: val[2], directives: val[3], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 319) def _reduce_134(val, _values, result) return make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: [], fields: val[5], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 320) def _reduce_135(val, _values, result) return make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: val[4], fields: val[6], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 321) def _reduce_136(val, _values, result) return make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: val[4], fields: [], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 322) def _reduce_137(val, _values, result) return make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: [], fields: [], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 325) def _reduce_138(val, _values, result) return make_node(:InterfaceTypeExtension, name: val[2], directives: val[3], fields: val[5], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 326) def _reduce_139(val, _values, result) return make_node(:InterfaceTypeExtension, name: val[2], directives: val[3], fields: [], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 329) def _reduce_140(val, _values, result) return make_node(:UnionTypeExtension, name: val[2], directives: val[3], types: val[5], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 330) def _reduce_141(val, _values, result) return make_node(:UnionTypeExtension, name: val[2], directives: val[3], types: [], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 333) def _reduce_142(val, _values, result) return make_node(:EnumTypeExtension, name: val[2], directives: val[3], values: val[5], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 334) def _reduce_143(val, _values, result) return make_node(:EnumTypeExtension, name: val[2], directives: val[3], values: [], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 337) def _reduce_144(val, _values, result) return make_node(:InputObjectTypeExtension, name: val[2], directives: val[3], fields: val[5], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 338) def _reduce_145(val, _values, result) return make_node(:InputObjectTypeExtension, name: val[2], directives: val[3], fields: [], position_source: val[0]) result end .,., # reduce 146 omitted # reduce 147 omitted # reduce 148 omitted module_eval(<<'.,.,', 'parser.y', 348) def _reduce_149(val, _values, result) return make_node(:ScalarTypeDefinition, name: val[2], directives: val[3], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 353) def _reduce_150(val, _values, result) return make_node(:ObjectTypeDefinition, name: val[2], interfaces: val[3], directives: val[4], fields: val[6], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 357) def _reduce_151(val, _values, result) return EMPTY_ARRAY result end .,., # reduce 152 omitted module_eval(<<'.,.,', 'parser.y', 361) def _reduce_153(val, _values, result) return val[2] result end .,., module_eval(<<'.,.,', 'parser.y', 362) def _reduce_154(val, _values, result) return val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 363) def _reduce_155(val, _values, result) return val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 366) def _reduce_156(val, _values, result) return [make_node(:TypeName, name: val[0], position_source: val[0])] result end .,., module_eval(<<'.,.,', 'parser.y', 367) def _reduce_157(val, _values, result) val[0] << make_node(:TypeName, name: val[2], position_source: val[2]) result end .,., module_eval(<<'.,.,', 'parser.y', 370) def _reduce_158(val, _values, result) return [make_node(:TypeName, name: val[0], position_source: val[0])] result end .,., module_eval(<<'.,.,', 'parser.y', 371) def _reduce_159(val, _values, result) val[0] << make_node(:TypeName, name: val[1], position_source: val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 375) def _reduce_160(val, _values, result) return make_node(:InputValueDefinition, name: val[1], type: val[3], default_value: val[4], directives: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 379) def _reduce_161(val, _values, result) return [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 380) def _reduce_162(val, _values, result) val[0] << val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 383) def _reduce_163(val, _values, result) return EMPTY_ARRAY result end .,., module_eval(<<'.,.,', 'parser.y', 384) def _reduce_164(val, _values, result) return val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 388) def _reduce_165(val, _values, result) return make_node(:FieldDefinition, name: val[1], arguments: val[2], type: val[4], directives: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 392) def _reduce_166(val, _values, result) return EMPTY_ARRAY result end .,., module_eval(<<'.,.,', 'parser.y', 393) def _reduce_167(val, _values, result) return [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 394) def _reduce_168(val, _values, result) val[0] << val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 398) def _reduce_169(val, _values, result) return make_node(:InterfaceTypeDefinition, name: val[2], directives: val[3], fields: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 402) def _reduce_170(val, _values, result) return [make_node(:TypeName, name: val[0], position_source: val[0])] result end .,., module_eval(<<'.,.,', 'parser.y', 403) def _reduce_171(val, _values, result) val[0] << make_node(:TypeName, name: val[2], position_source: val[2]) result end .,., module_eval(<<'.,.,', 'parser.y', 407) def _reduce_172(val, _values, result) return make_node(:UnionTypeDefinition, name: val[2], directives: val[3], types: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 412) def _reduce_173(val, _values, result) return make_node(:EnumTypeDefinition, name: val[2], directives: val[3], values: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 417) def _reduce_174(val, _values, result) return make_node(:InputObjectTypeDefinition, name: val[2], directives: val[3], fields: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 422) def _reduce_175(val, _values, result) return make_node(:DirectiveDefinition, name: val[3], arguments: val[4], locations: val[6], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 426) def _reduce_176(val, _values, result) return [make_node(:DirectiveLocation, name: val[0].to_s, position_source: val[0])] result end .,., module_eval(<<'.,.,', 'parser.y', 427) def _reduce_177(val, _values, result) val[0] << make_node(:DirectiveLocation, name: val[2].to_s, position_source: val[2]) result end .,., def _reduce_none(val, _values, result) val[0] end end # class Parser end # module Language end # module GraphQL ruby-graphql-1.9.19/lib/graphql/language/parser.y000066400000000000000000000470351362601351000217130ustar00rootroot00000000000000class GraphQL::Language::Parser rule target: document document: definitions_list { return make_node(:Document, definitions: val[0])} definitions_list: definition { return [val[0]]} | definitions_list definition { val[0] << val[1] } definition: executable_definition | type_system_definition | type_system_extension executable_definition: operation_definition | fragment_definition operation_definition: operation_type operation_name_opt variable_definitions_opt directives_list_opt selection_set { return make_node( :OperationDefinition, { operation_type: val[0], name: val[1], variables: val[2], directives: val[3], selections: val[4], position_source: val[0], } ) } | LCURLY selection_list RCURLY { return make_node( :OperationDefinition, { operation_type: "query", selections: val[1], position_source: val[0], } ) } | LCURLY RCURLY { return make_node( :OperationDefinition, { operation_type: "query", selections: [], position_source: val[0], } ) } operation_type: QUERY | MUTATION | SUBSCRIPTION operation_name_opt: /* none */ { return nil } | name variable_definitions_opt: /* none */ { return EMPTY_ARRAY } | LPAREN variable_definitions_list RPAREN { return val[1] } variable_definitions_list: variable_definition { return [val[0]] } | variable_definitions_list variable_definition { val[0] << val[1] } variable_definition: VAR_SIGN name COLON type default_value_opt { return make_node(:VariableDefinition, { name: val[1], type: val[3], default_value: val[4], position_source: val[0], }) } type: name { return make_node(:TypeName, name: val[0])} | type BANG { return make_node(:NonNullType, of_type: val[0]) } | LBRACKET type RBRACKET { return make_node(:ListType, of_type: val[1]) } default_value_opt: /* none */ { return nil } | EQUALS literal_value { return val[1] } selection_set: LCURLY selection_list RCURLY { return val[1] } selection_set_opt: /* none */ { return EMPTY_ARRAY } | selection_set { return val[0] } selection_list: selection { return [result] } | selection_list selection { val[0] << val[1] } selection: field | fragment_spread | inline_fragment field: name arguments_opt directives_list_opt selection_set_opt { return make_node( :Field, { name: val[0], arguments: val[1], directives: val[2], selections: val[3], position_source: val[0], } ) } | name COLON name arguments_opt directives_list_opt selection_set_opt { return make_node( :Field, { alias: val[0], name: val[2], arguments: val[3], directives: val[4], selections: val[5], position_source: val[0], } ) } name: name_without_on | ON schema_keyword: SCHEMA | SCALAR | TYPE | IMPLEMENTS | INTERFACE | UNION | ENUM | INPUT | DIRECTIVE name_without_on: IDENTIFIER | FRAGMENT | TRUE | FALSE | operation_type | schema_keyword enum_name: /* any identifier, but not "true", "false" or "null" */ IDENTIFIER | FRAGMENT | ON | operation_type | schema_keyword enum_value_definition: description_opt enum_name directives_list_opt { return make_node(:EnumValueDefinition, name: val[1], directives: val[2], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } enum_value_definitions: enum_value_definition { return [val[0]] } | enum_value_definitions enum_value_definition { return val[0] << val[1] } arguments_opt: /* none */ { return EMPTY_ARRAY } | LPAREN RPAREN { return EMPTY_ARRAY } | LPAREN arguments_list RPAREN { return val[1] } arguments_list: argument { return [val[0]] } | arguments_list argument { val[0] << val[1] } argument: name COLON input_value { return make_node(:Argument, name: val[0], value: val[2], position_source: val[0])} literal_value: FLOAT { return val[0].to_f } | INT { return val[0].to_i } | STRING { return val[0].to_s } | TRUE { return true } | FALSE { return false } | null_value | enum_value | list_value | object_literal_value input_value: | literal_value | variable | object_value null_value: NULL { return make_node(:NullValue, name: val[0], position_source: val[0]) } variable: VAR_SIGN name { return make_node(:VariableIdentifier, name: val[1], position_source: val[0]) } list_value: LBRACKET RBRACKET { return EMPTY_ARRAY } | LBRACKET list_value_list RBRACKET { return val[1] } list_value_list: input_value { return [val[0]] } | list_value_list input_value { val[0] << val[1] } object_value: LCURLY RCURLY { return make_node(:InputObject, arguments: [], position_source: val[0])} | LCURLY object_value_list RCURLY { return make_node(:InputObject, arguments: val[1], position_source: val[0])} object_value_list: object_value_field { return [val[0]] } | object_value_list object_value_field { val[0] << val[1] } object_value_field: name COLON input_value { return make_node(:Argument, name: val[0], value: val[2], position_source: val[0])} /* like the previous, but with literals only: */ object_literal_value: LCURLY RCURLY { return make_node(:InputObject, arguments: [], position_source: val[0])} | LCURLY object_literal_value_list RCURLY { return make_node(:InputObject, arguments: val[1], position_source: val[0])} object_literal_value_list: object_literal_value_field { return [val[0]] } | object_literal_value_list object_literal_value_field { val[0] << val[1] } object_literal_value_field: name COLON literal_value { return make_node(:Argument, name: val[0], value: val[2], position_source: val[0])} enum_value: enum_name { return make_node(:Enum, name: val[0], position_source: val[0]) } directives_list_opt: /* none */ { return EMPTY_ARRAY } | directives_list directives_list: directive { return [val[0]] } | directives_list directive { val[0] << val[1] } directive: DIR_SIGN name arguments_opt { return make_node(:Directive, name: val[1], arguments: val[2], position_source: val[0]) } fragment_spread: ELLIPSIS name_without_on directives_list_opt { return make_node(:FragmentSpread, name: val[1], directives: val[2], position_source: val[0]) } inline_fragment: ELLIPSIS ON type directives_list_opt selection_set { return make_node(:InlineFragment, { type: val[2], directives: val[3], selections: val[4], position_source: val[0] }) } | ELLIPSIS directives_list_opt selection_set { return make_node(:InlineFragment, { type: nil, directives: val[1], selections: val[2], position_source: val[0] }) } fragment_definition: FRAGMENT fragment_name_opt ON type directives_list_opt selection_set { return make_node(:FragmentDefinition, { name: val[1], type: val[3], directives: val[4], selections: val[5], position_source: val[0], } ) } fragment_name_opt: /* none */ { return nil } | name_without_on type_system_definition: schema_definition | type_definition | directive_definition schema_definition: SCHEMA directives_list_opt LCURLY operation_type_definition_list RCURLY { return make_node(:SchemaDefinition, position_source: val[0], definition_line: val[0].line, directives: val[1], **val[3]) } operation_type_definition_list: operation_type_definition | operation_type_definition_list operation_type_definition { return val[0].merge(val[1]) } operation_type_definition: operation_type COLON name { return { val[0].to_s.to_sym => val[2] } } type_definition: scalar_type_definition | object_type_definition | interface_type_definition | union_type_definition | enum_type_definition | input_object_type_definition type_system_extension: schema_extension | type_extension schema_extension: EXTEND SCHEMA directives_list_opt LCURLY operation_type_definition_list RCURLY { return make_node(:SchemaExtension, position_source: val[0], directives: val[2], **val[4]) } | EXTEND SCHEMA directives_list { return make_node(:SchemaExtension, position_source: val[0], directives: val[2]) } type_extension: scalar_type_extension | object_type_extension | interface_type_extension | union_type_extension | enum_type_extension | input_object_type_extension scalar_type_extension: EXTEND SCALAR name directives_list { return make_node(:ScalarTypeExtension, name: val[2], directives: val[3], position_source: val[0]) } object_type_extension: /* TODO - This first one shouldn't be necessary but parser is getting confused */ EXTEND TYPE name implements LCURLY field_definition_list RCURLY { return make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: [], fields: val[5], position_source: val[0]) } | EXTEND TYPE name implements_opt directives_list_opt LCURLY field_definition_list RCURLY { return make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: val[4], fields: val[6], position_source: val[0]) } | EXTEND TYPE name implements_opt directives_list { return make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: val[4], fields: [], position_source: val[0]) } | EXTEND TYPE name implements { return make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: [], fields: [], position_source: val[0]) } interface_type_extension: EXTEND INTERFACE name directives_list_opt LCURLY field_definition_list RCURLY { return make_node(:InterfaceTypeExtension, name: val[2], directives: val[3], fields: val[5], position_source: val[0]) } | EXTEND INTERFACE name directives_list { return make_node(:InterfaceTypeExtension, name: val[2], directives: val[3], fields: [], position_source: val[0]) } union_type_extension: EXTEND UNION name directives_list_opt EQUALS union_members { return make_node(:UnionTypeExtension, name: val[2], directives: val[3], types: val[5], position_source: val[0]) } | EXTEND UNION name directives_list { return make_node(:UnionTypeExtension, name: val[2], directives: val[3], types: [], position_source: val[0]) } enum_type_extension: EXTEND ENUM name directives_list_opt LCURLY enum_value_definitions RCURLY { return make_node(:EnumTypeExtension, name: val[2], directives: val[3], values: val[5], position_source: val[0]) } | EXTEND ENUM name directives_list { return make_node(:EnumTypeExtension, name: val[2], directives: val[3], values: [], position_source: val[0]) } input_object_type_extension: EXTEND INPUT name directives_list_opt LCURLY input_value_definition_list RCURLY { return make_node(:InputObjectTypeExtension, name: val[2], directives: val[3], fields: val[5], position_source: val[0]) } | EXTEND INPUT name directives_list { return make_node(:InputObjectTypeExtension, name: val[2], directives: val[3], fields: [], position_source: val[0]) } description: STRING description_opt: /* none */ | description scalar_type_definition: description_opt SCALAR name directives_list_opt { return make_node(:ScalarTypeDefinition, name: val[2], directives: val[3], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } object_type_definition: description_opt TYPE name implements_opt directives_list_opt LCURLY field_definition_list RCURLY { return make_node(:ObjectTypeDefinition, name: val[2], interfaces: val[3], directives: val[4], fields: val[6], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } implements_opt: /* none */ { return EMPTY_ARRAY } | implements implements: IMPLEMENTS AMP interfaces_list { return val[2] } | IMPLEMENTS interfaces_list { return val[1] } | IMPLEMENTS legacy_interfaces_list { return val[1] } interfaces_list: name { return [make_node(:TypeName, name: val[0], position_source: val[0])] } | interfaces_list AMP name { val[0] << make_node(:TypeName, name: val[2], position_source: val[2]) } legacy_interfaces_list: name { return [make_node(:TypeName, name: val[0], position_source: val[0])] } | legacy_interfaces_list name { val[0] << make_node(:TypeName, name: val[1], position_source: val[1]) } input_value_definition: description_opt name COLON type default_value_opt directives_list_opt { return make_node(:InputValueDefinition, name: val[1], type: val[3], default_value: val[4], directives: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } input_value_definition_list: input_value_definition { return [val[0]] } | input_value_definition_list input_value_definition { val[0] << val[1] } arguments_definitions_opt: /* none */ { return EMPTY_ARRAY } | LPAREN input_value_definition_list RPAREN { return val[1] } field_definition: description_opt name arguments_definitions_opt COLON type directives_list_opt { return make_node(:FieldDefinition, name: val[1], arguments: val[2], type: val[4], directives: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } field_definition_list: /* none */ { return EMPTY_ARRAY } | field_definition { return [val[0]] } | field_definition_list field_definition { val[0] << val[1] } interface_type_definition: description_opt INTERFACE name directives_list_opt LCURLY field_definition_list RCURLY { return make_node(:InterfaceTypeDefinition, name: val[2], directives: val[3], fields: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } union_members: name { return [make_node(:TypeName, name: val[0], position_source: val[0])]} | union_members PIPE name { val[0] << make_node(:TypeName, name: val[2], position_source: val[2]) } union_type_definition: description_opt UNION name directives_list_opt EQUALS union_members { return make_node(:UnionTypeDefinition, name: val[2], directives: val[3], types: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } enum_type_definition: description_opt ENUM name directives_list_opt LCURLY enum_value_definitions RCURLY { return make_node(:EnumTypeDefinition, name: val[2], directives: val[3], values: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } input_object_type_definition: description_opt INPUT name directives_list_opt LCURLY input_value_definition_list RCURLY { return make_node(:InputObjectTypeDefinition, name: val[2], directives: val[3], fields: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } directive_definition: description_opt DIRECTIVE DIR_SIGN name arguments_definitions_opt ON directive_locations { return make_node(:DirectiveDefinition, name: val[3], arguments: val[4], locations: val[6], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } directive_locations: name { return [make_node(:DirectiveLocation, name: val[0].to_s, position_source: val[0])] } | directive_locations PIPE name { val[0] << make_node(:DirectiveLocation, name: val[2].to_s, position_source: val[2]) } end ---- header ---- ---- inner ---- EMPTY_ARRAY = [].freeze def initialize(query_string, filename:, tracer: Tracing::NullTracer) raise GraphQL::ParseError.new("No query string was present", nil, nil, query_string) if query_string.nil? @query_string = query_string @filename = filename @tracer = tracer @reused_next_token = [nil, nil] end def parse_document @document ||= begin # Break the string into tokens @tracer.trace("lex", {query_string: @query_string}) do @tokens ||= GraphQL.scan(@query_string) end # From the tokens, build an AST @tracer.trace("parse", {query_string: @query_string}) do if @tokens.empty? make_node(:Document, definitions: [], filename: @filename) else do_parse end end end end def self.parse(query_string, filename: nil, tracer: GraphQL::Tracing::NullTracer) self.new(query_string, filename: filename, tracer: tracer).parse_document end private def next_token lexer_token = @tokens.shift if lexer_token.nil? nil else @reused_next_token[0] = lexer_token.name @reused_next_token[1] = lexer_token @reused_next_token end end def get_description(token) comments = [] loop do prev_token = token token = token.prev_token break if token.nil? break if token.name != :COMMENT break if prev_token.line != token.line + 1 comments.unshift(token.to_s.sub(/^#\s*/, "")) end return nil if comments.empty? comments.join("\n") end def on_error(parser_token_id, lexer_token, vstack) if lexer_token == "$" || lexer_token == nil raise GraphQL::ParseError.new("Unexpected end of document", nil, nil, @query_string, filename: @filename) else parser_token_name = token_to_str(parser_token_id) if parser_token_name.nil? raise GraphQL::ParseError.new("Parse Error on unknown token: {token_id: #{parser_token_id}, lexer_token: #{lexer_token}} from #{@query_string}", nil, nil, @query_string, filename: @filename) else line, col = lexer_token.line_and_column if lexer_token.name == :BAD_UNICODE_ESCAPE raise GraphQL::ParseError.new("Parse error on bad Unicode escape sequence: #{lexer_token.to_s.inspect} (#{parser_token_name}) at [#{line}, #{col}]", line, col, @query_string, filename: @filename) else raise GraphQL::ParseError.new("Parse error on #{lexer_token.to_s.inspect} (#{parser_token_name}) at [#{line}, #{col}]", line, col, @query_string, filename: @filename) end end end end def make_node(node_name, assigns) assigns.each do |key, value| if key != :position_source && value.is_a?(GraphQL::Language::Token) assigns[key] = value.to_s end end assigns[:filename] = @filename GraphQL::Language::Nodes.const_get(node_name).new(assigns) end ruby-graphql-1.9.19/lib/graphql/language/printer.rb000066400000000000000000000277461362601351000222440ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language class Printer # Turn an arbitrary AST node back into a string. # # @example Turning a document into a query string # document = GraphQL.parse(query_string) # GraphQL::Language::Printer.new.print(document) # # => "{ ... }" # # # @example Building a custom printer # # class MyPrinter < GraphQL::Language::Printer # def print_argument(arg) # "#{arg.name}: " # end # end # # MyPrinter.new.print(document) # # => "mutation { pay(creditCard: ) { success } }" # # # @param indent [String] Whitespace to add to the printed node # @return [String] Valid GraphQL for `node` def print(node, indent: "") print_node(node, indent: indent) end protected def print_document(document) document.definitions.map { |d| print_node(d) }.join("\n\n") end def print_argument(argument) "#{argument.name}: #{print_node(argument.value)}".dup end def print_directive(directive) out = "@#{directive.name}".dup if directive.arguments.any? out << "(#{directive.arguments.map { |a| print_argument(a) }.join(", ")})" end out end def print_enum(enum) "#{enum.name}".dup end def print_null_value "null".dup end def print_field(field, indent: "") out = "#{indent}".dup out << "#{field.alias}: " if field.alias out << "#{field.name}" out << "(#{field.arguments.map { |a| print_argument(a) }.join(", ")})" if field.arguments.any? out << print_directives(field.directives) out << print_selections(field.selections, indent: indent) out end def print_fragment_definition(fragment_def, indent: "") out = "#{indent}fragment #{fragment_def.name}".dup if fragment_def.type out << " on #{print_node(fragment_def.type)}" end out << print_directives(fragment_def.directives) out << print_selections(fragment_def.selections, indent: indent) out end def print_fragment_spread(fragment_spread, indent: "") out = "#{indent}...#{fragment_spread.name}".dup out << print_directives(fragment_spread.directives) out end def print_inline_fragment(inline_fragment, indent: "") out = "#{indent}...".dup if inline_fragment.type out << " on #{print_node(inline_fragment.type)}" end out << print_directives(inline_fragment.directives) out << print_selections(inline_fragment.selections, indent: indent) out end def print_input_object(input_object) "{#{input_object.arguments.map { |a| "#{a.name}: #{print(a.value)}" }.join(", ")}}" end def print_list_type(list_type) "[#{print_node(list_type.of_type)}]".dup end def print_non_null_type(non_null_type) "#{print_node(non_null_type.of_type)}!".dup end def print_operation_definition(operation_definition, indent: "") out = "#{indent}#{operation_definition.operation_type}".dup out << " #{operation_definition.name}" if operation_definition.name if operation_definition.variables.any? out << "(#{operation_definition.variables.map { |v| print_variable_definition(v) }.join(", ")})" end out << print_directives(operation_definition.directives) out << print_selections(operation_definition.selections, indent: indent) out end def print_type_name(type_name) "#{type_name.name}".dup end def print_variable_definition(variable_definition) out = "$#{variable_definition.name}: #{print_node(variable_definition.type)}".dup out << " = #{print_node(variable_definition.default_value)}" unless variable_definition.default_value.nil? out end def print_variable_identifier(variable_identifier) "$#{variable_identifier.name}".dup end def print_schema_definition(schema) if (schema.query.nil? || schema.query == 'Query') && (schema.mutation.nil? || schema.mutation == 'Mutation') && (schema.subscription.nil? || schema.subscription == 'Subscription') && (schema.directives.empty?) return end out = "schema".dup if schema.directives.any? schema.directives.each do |dir| out << "\n " out << print_node(dir) end out << "\n{" else out << " {\n" end out << " query: #{schema.query}\n" if schema.query out << " mutation: #{schema.mutation}\n" if schema.mutation out << " subscription: #{schema.subscription}\n" if schema.subscription out << "}" end def print_scalar_type_definition(scalar_type) out = print_description(scalar_type) out << "scalar #{scalar_type.name}" out << print_directives(scalar_type.directives) end def print_object_type_definition(object_type) out = print_description(object_type) out << "type #{object_type.name}" out << " implements " << object_type.interfaces.map(&:name).join(" & ") unless object_type.interfaces.empty? out << print_directives(object_type.directives) out << print_field_definitions(object_type.fields) end def print_input_value_definition(input_value) out = "#{input_value.name}: #{print_node(input_value.type)}".dup out << " = #{print_node(input_value.default_value)}" unless input_value.default_value.nil? out << print_directives(input_value.directives) end def print_arguments(arguments, indent: "") if arguments.all?{ |arg| !arg.description } return "(#{arguments.map{ |arg| print_input_value_definition(arg) }.join(", ")})" end out = "(\n".dup out << arguments.map.with_index{ |arg, i| "#{print_description(arg, indent: " " + indent, first_in_block: i == 0)} #{indent}"\ "#{print_input_value_definition(arg)}" }.join("\n") out << "\n#{indent})" end def print_field_definition(field) out = field.name.dup unless field.arguments.empty? out << print_arguments(field.arguments, indent: " ") end out << ": #{print_node(field.type)}" out << print_directives(field.directives) end def print_interface_type_definition(interface_type) out = print_description(interface_type) out << "interface #{interface_type.name}" out << print_directives(interface_type.directives) out << print_field_definitions(interface_type.fields) end def print_union_type_definition(union_type) out = print_description(union_type) out << "union #{union_type.name}" out << print_directives(union_type.directives) out << " = " + union_type.types.map(&:name).join(" | ") end def print_enum_type_definition(enum_type) out = print_description(enum_type) out << "enum #{enum_type.name}#{print_directives(enum_type.directives)} {\n" enum_type.values.each.with_index do |value, i| out << print_description(value, indent: ' ', first_in_block: i == 0) out << print_enum_value_definition(value) end out << "}" end def print_enum_value_definition(enum_value) out = " #{enum_value.name}".dup out << print_directives(enum_value.directives) out << "\n" end def print_input_object_type_definition(input_object_type) out = print_description(input_object_type) out << "input #{input_object_type.name}" out << print_directives(input_object_type.directives) out << " {\n" input_object_type.fields.each.with_index do |field, i| out << print_description(field, indent: ' ', first_in_block: i == 0) out << " #{print_input_value_definition(field)}\n" end out << "}" end def print_directive_definition(directive) out = print_description(directive) out << "directive @#{directive.name}" if directive.arguments.any? out << print_arguments(directive.arguments) end out << " on #{directive.locations.map(&:name).join(' | ')}" end def print_description(node, indent: "", first_in_block: true) return ''.dup unless node.description description = indent != '' && !first_in_block ? "\n".dup : "".dup description << GraphQL::Language::BlockString.print(node.description, indent: indent) end def print_field_definitions(fields) out = " {\n".dup fields.each.with_index do |field, i| out << print_description(field, indent: ' ', first_in_block: i == 0) out << " #{print_field_definition(field)}\n" end out << "}" end def print_directives(directives) if directives.any? directives.map { |d| " #{print_directive(d)}" }.join else "" end end def print_selections(selections, indent: "") if selections.any? out = " {\n".dup selections.each do |selection| out << print_node(selection, indent: indent + " ") << "\n" end out << "#{indent}}" else "" end end def print_node(node, indent: "") case node when Nodes::Document print_document(node) when Nodes::Argument print_argument(node) when Nodes::Directive print_directive(node) when Nodes::Enum print_enum(node) when Nodes::NullValue print_null_value when Nodes::Field print_field(node, indent: indent) when Nodes::FragmentDefinition print_fragment_definition(node, indent: indent) when Nodes::FragmentSpread print_fragment_spread(node, indent: indent) when Nodes::InlineFragment print_inline_fragment(node, indent: indent) when Nodes::InputObject print_input_object(node) when Nodes::ListType print_list_type(node) when Nodes::NonNullType print_non_null_type(node) when Nodes::OperationDefinition print_operation_definition(node, indent: indent) when Nodes::TypeName print_type_name(node) when Nodes::VariableDefinition print_variable_definition(node) when Nodes::VariableIdentifier print_variable_identifier(node) when Nodes::SchemaDefinition print_schema_definition(node) when Nodes::ScalarTypeDefinition print_scalar_type_definition(node) when Nodes::ObjectTypeDefinition print_object_type_definition(node) when Nodes::InputValueDefinition print_input_value_definition(node) when Nodes::FieldDefinition print_field_definition(node) when Nodes::InterfaceTypeDefinition print_interface_type_definition(node) when Nodes::UnionTypeDefinition print_union_type_definition(node) when Nodes::EnumTypeDefinition print_enum_type_definition(node) when Nodes::EnumValueDefinition print_enum_value_definition(node) when Nodes::InputObjectTypeDefinition print_input_object_type_definition(node) when Nodes::DirectiveDefinition print_directive_definition(node) when FalseClass, Float, Integer, NilClass, String, TrueClass, Symbol GraphQL::Language.serialize(node) when Array "[#{node.map { |v| print_node(v) }.join(", ")}]".dup when Hash "{#{node.map { |k, v| "#{k}: #{print_node(v)}" }.join(", ")}}".dup else GraphQL::Language.serialize(node.to_s) end end private attr_reader :node end end end ruby-graphql-1.9.19/lib/graphql/language/token.rb000066400000000000000000000016051362601351000216630ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language # Emitted by the lexer and passed to the parser. # Contains type, value and position data. class Token if !String.method_defined?(:-@) using GraphQL::StringDedupBackport end # @return [Symbol] The kind of token this is attr_reader :name # @return [String] The text of this token attr_reader :value attr_reader :prev_token, :line, :col def initialize(name, value, line, col, prev_token) @name = name @value = -value @line = line @col = col @prev_token = prev_token end alias to_s value def to_i; @value.to_i; end def to_f; @value.to_f; end def line_and_column [@line, @col] end def inspect "(#{@name} #{@value.inspect} [#{@line}:#{@col}])" end end end end ruby-graphql-1.9.19/lib/graphql/language/visitor.rb000066400000000000000000000214521362601351000222440ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language # Depth-first traversal through the tree, calling hooks at each stop. # # @example Create a visitor counting certain field names # class NameCounter < GraphQL::Language::Visitor # def initialize(document, field_name) # super(document) # @field_name = field_name # @count = 0 # end # # attr_reader :count # # def on_field(node, parent) # # if this field matches our search, increment the counter # if node.name == @field_name # @count += 1 # end # # Continue visiting subfields: # super # end # end # # # Initialize a visitor # visitor = NameCounter.new(document, "name") # # Run it # visitor.visit # # Check the result # visitor.count # # => 3 class Visitor # If any hook returns this value, the {Visitor} stops visiting this # node right away # @deprecated Use `super` to continue the visit; or don't call it to halt. SKIP = :_skip class DeleteNode; end # When this is returned from a visitor method, # Then the `node` passed into the method is removed from `parent`'s children. DELETE_NODE = DeleteNode.new def initialize(document) @document = document @visitors = {} @result = nil end # @return [GraphQL::Language::Nodes::Document] The document with any modifications applied attr_reader :result # Get a {NodeVisitor} for `node_class` # @param node_class [Class] The node class that you want to listen to # @return [NodeVisitor] # # @example Run a hook whenever you enter a new Field # visitor[GraphQL::Language::Nodes::Field] << ->(node, parent) { p "Here's a field" } # @deprecated see `on_` methods, like {#on_field} def [](node_class) @visitors[node_class] ||= NodeVisitor.new end # Visit `document` and all children, applying hooks as you go # @return [void] def visit result = on_node_with_modifications(@document, nil) @result = if result.is_a?(Array) result.first else # The node wasn't modified @document end end # Call the user-defined handler for `node`. def visit_node(node, parent) public_send(node.visit_method, node, parent) end # The default implementation for visiting an AST node. # It doesn't _do_ anything, but it continues to visiting the node's children. # To customize this hook, override one of its make_visit_methodes (or the base method?) # in your subclasses. # # For compatibility, it calls hook procs, too. # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node. # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`. def on_abstract_node(node, parent) if node == DELETE_NODE # This might be passed to `super(DELETE_NODE, ...)` # by a user hook, don't want to keep visiting in that case. nil else # Run hooks if there are any new_node = node no_hooks = !@visitors.key?(node.class) if no_hooks || begin_visit(new_node, parent) node.children.each do |child_node| new_child_and_node = on_node_with_modifications(child_node, new_node) # Reassign `node` in case the child hook makes a modification if new_child_and_node.is_a?(Array) new_node = new_child_and_node[1] end end end end_visit(new_node, parent) unless no_hooks if new_node.equal?(node) nil else [new_node, parent] end end end # We don't use `alias` here because it breaks `super` def self.make_visit_method(node_method) class_eval(<<-RUBY, __FILE__, __LINE__ + 1) def #{node_method}(node, parent) child_mod = on_abstract_node(node, parent) # If visiting the children returned changes, continue passing those. child_mod || [node, parent] end RUBY end make_visit_method :on_argument make_visit_method :on_directive make_visit_method :on_directive_definition make_visit_method :on_directive_location make_visit_method :on_document make_visit_method :on_enum make_visit_method :on_enum_type_definition make_visit_method :on_enum_type_extension make_visit_method :on_enum_value_definition make_visit_method :on_field make_visit_method :on_field_definition make_visit_method :on_fragment_definition make_visit_method :on_fragment_spread make_visit_method :on_inline_fragment make_visit_method :on_input_object make_visit_method :on_input_object_type_definition make_visit_method :on_input_object_type_extension make_visit_method :on_input_value_definition make_visit_method :on_interface_type_definition make_visit_method :on_interface_type_extension make_visit_method :on_list_type make_visit_method :on_non_null_type make_visit_method :on_null_value make_visit_method :on_object_type_definition make_visit_method :on_object_type_extension make_visit_method :on_operation_definition make_visit_method :on_scalar_type_definition make_visit_method :on_scalar_type_extension make_visit_method :on_schema_definition make_visit_method :on_schema_extension make_visit_method :on_type_name make_visit_method :on_union_type_definition make_visit_method :on_union_type_extension make_visit_method :on_variable_definition make_visit_method :on_variable_identifier private # Run the hooks for `node`, and if the hooks return a copy of `node`, # copy `parent` so that it contains the copy of that node as a child, # then return the copies # If a non-array value is returned, consuming functions should ignore # said value def on_node_with_modifications(node, parent) new_node_and_new_parent = visit_node(node, parent) if new_node_and_new_parent.is_a?(Array) new_node = new_node_and_new_parent[0] new_parent = new_node_and_new_parent[1] if new_node.is_a?(Nodes::AbstractNode) && !node.equal?(new_node) # The user-provided hook returned a new node. new_parent = new_parent && new_parent.replace_child(node, new_node) return new_node, new_parent elsif new_node == DELETE_NODE # The user-provided hook requested to remove this node new_parent = new_parent && new_parent.delete_child(node) return nil, new_parent elsif new_node_and_new_parent.none? { |n| n == nil || n.class < Nodes::AbstractNode } # The user-provided hook returned an array of who-knows-what # return nil here to signify that no changes should be made nil else new_node_and_new_parent end else # The user-provided hook didn't make any modifications. # In fact, the hook might have returned who-knows-what, so # ignore the return value and use the original values. new_node_and_new_parent end end def begin_visit(node, parent) node_visitor = self[node.class] self.class.apply_hooks(node_visitor.enter, node, parent) end # Should global `leave` visitors come first or last? def end_visit(node, parent) node_visitor = self[node.class] self.class.apply_hooks(node_visitor.leave, node, parent) end # If one of the visitors returns SKIP, stop visiting this node def self.apply_hooks(hooks, node, parent) hooks.each do |proc| return false if proc.call(node, parent) == SKIP end true end # Collect `enter` and `leave` hooks for classes in {GraphQL::Language::Nodes} # # Access {NodeVisitor}s via {GraphQL::Language::Visitor#[]} class NodeVisitor # @return [Array] Hooks to call when entering a node of this type attr_reader :enter # @return [Array] Hooks to call when leaving a node of this type attr_reader :leave def initialize @enter = [] @leave = [] end # Shorthand to add a hook to the {#enter} array # @param hook [Proc] A hook to add def <<(hook) enter << hook end end end end end ruby-graphql-1.9.19/lib/graphql/list_type.rb000066400000000000000000000040401362601351000207700ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # A list type modifies another type. # # List types can be created with the type helper (`types[InnerType]`) # or {BaseType#to_list_type} (`InnerType.to_list_type`) # # For return types, it says that the returned value will be a list of the modified. # # @example A field which returns a list of items # field :items, types[ItemType] # # or # field :items, ItemType.to_list_type # # For input types, it says that the incoming value will be a list of the modified type. # # @example A field which accepts a list of strings # field :newNames do # # ... # argument :values, types[types.String] # # or # argument :values, types.String.to_list_type # end # # Given a list type, you can always get the underlying type with {#unwrap}. # class ListType < GraphQL::BaseType include GraphQL::BaseType::ModifiesAnotherType attr_reader :of_type def initialize(of_type:) super() @of_type = of_type end def kind GraphQL::TypeKinds::LIST end def to_s "[#{of_type.to_s}]" end alias_method :inspect, :to_s alias :to_type_signature :to_s def coerce_result(value, ctx = nil) if ctx.nil? warn_deprecated_coerce("coerce_isolated_result") ctx = GraphQL::Query::NullContext end ensure_array(value).map { |item| item.nil? ? nil : of_type.coerce_result(item, ctx) } end def list? true end private def coerce_non_null_input(value, ctx) ensure_array(value).map { |item| of_type.coerce_input(item, ctx) } end def validate_non_null_input(value, ctx) result = GraphQL::Query::InputValidationResult.new ensure_array(value).each_with_index do |item, index| item_result = of_type.validate_input(item, ctx) if !item_result.valid? result.merge_result!(index, item_result) end end result end def ensure_array(value) value.is_a?(Array) ? value : [value] end end end ruby-graphql-1.9.19/lib/graphql/literal_validation_error.rb000066400000000000000000000002041362601351000240310ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class LiteralValidationError < GraphQL::Error attr_accessor :ast_value end end ruby-graphql-1.9.19/lib/graphql/load_application_object_failed_error.rb000066400000000000000000000016051362601351000263250ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Raised when a argument is configured with `loads:` and the client provides an `ID`, # but no object is loaded for that ID. # # @see GraphQL::Schema::Member::HasArguments::ArgumentObjectLoader#load_application_object_failed, A hook which you can override in resolvers, mutations and input objects. class LoadApplicationObjectFailedError < GraphQL::ExecutionError # @return [GraphQL::Schema::Argument] the argument definition for the argument that was looked up attr_reader :argument # @return [String] The ID provided by the client attr_reader :id # @return [Object] The value found with this ID attr_reader :object def initialize(argument:, id:, object:) @id = id @argument = argument @object = object super("No object found for `#{argument.graphql_name}: #{id.inspect}`") end end end ruby-graphql-1.9.19/lib/graphql/name_validator.rb000066400000000000000000000005121362601351000217410ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class NameValidator VALID_NAME_REGEX = /^[_a-zA-Z][_a-zA-Z0-9]*$/ def self.validate!(name) raise GraphQL::InvalidNameError.new(name, VALID_NAME_REGEX) unless valid?(name) end private def self.valid?(name) name =~ VALID_NAME_REGEX end end end ruby-graphql-1.9.19/lib/graphql/non_null_type.rb000066400000000000000000000041231362601351000216430ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class DoubleNonNullTypeError < GraphQL::Error end # A non-null type modifies another type. # # Non-null types can be created with `!` (`InnerType!`) # or {BaseType#to_non_null_type} (`InnerType.to_non_null_type`) # # For return types, it says that the returned value will _always_ be present. # # @example A field which _always_ returns an error # field :items, !ItemType # # or # field :items, ItemType.to_non_null_type # # (If the application fails to return a value, {InvalidNullError} will be passed to {Schema#type_error}.) # # For input types, it says that the incoming value _must_ be provided by the query. # # @example A field which _requires_ a string input # field :newNames do # # ... # argument :values, !types.String # # or # argument :values, types.String.to_non_null_type # end # # (If a value isn't provided, {Query::VariableValidationError} will be raised). # # Given a non-null type, you can always get the underlying type with {#unwrap}. # class NonNullType < GraphQL::BaseType include GraphQL::BaseType::ModifiesAnotherType extend Forwardable attr_reader :of_type def initialize(of_type:) if of_type.is_a?(GraphQL::NonNullType) raise( DoubleNonNullTypeError, "You tried to add a non-null constraint twice (!! instead of !)" ) end super() @of_type = of_type end def valid_input?(value, ctx) validate_input(value, ctx).valid? end def validate_input(value, ctx) if value.nil? result = GraphQL::Query::InputValidationResult.new result.add_problem("Expected value to not be null") result else of_type.validate_input(value, ctx) end end def_delegators :@of_type, :coerce_input, :coerce_result, :list? def kind GraphQL::TypeKinds::NON_NULL end def to_s "#{of_type.to_s}!" end alias_method :inspect, :to_s alias :to_type_signature :to_s def non_null? true end end end ruby-graphql-1.9.19/lib/graphql/object_type.rb000066400000000000000000000113151362601351000212660ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # This type exposes fields on an object. # # @example defining a type for your IMDB clone # MovieType = GraphQL::ObjectType.define do # name "Movie" # description "A full-length film or a short film" # interfaces [ProductionInterface, DurationInterface] # # field :runtimeMinutes, !types.Int, property: :runtime_minutes # field :director, PersonType # field :cast, CastType # field :starring, types[PersonType] do # argument :limit, types.Int # resolve ->(object, args, ctx) { # stars = object.cast.stars # args[:limit] && stars = stars.limit(args[:limit]) # stars # } # end # end # class ObjectType < GraphQL::BaseType accepts_definitions :interfaces, :fields, :mutation, :relay_node_type, field: GraphQL::Define::AssignObjectField accepts_definitions implements: ->(type, *interfaces, inherit: false) { type.implements(interfaces, inherit: inherit) } attr_accessor :fields, :mutation, :relay_node_type ensure_defined(:fields, :mutation, :interfaces, :relay_node_type) # @!attribute fields # @return [Hash GraphQL::Field>] Map String fieldnames to their {GraphQL::Field} implementations # @!attribute mutation # @return [GraphQL::Relay::Mutation, nil] The mutation this object type was derived from, if it is an auto-generated payload type. def initialize super @fields = {} @interface_fields = {} @dirty_interfaces = [] @dirty_inherited_interfaces = [] end def initialize_copy(other) super @clean_interfaces = nil @clean_inherited_interfaces = nil @dirty_interfaces = other.dirty_interfaces.dup @dirty_inherited_interfaces = other.dirty_inherited_interfaces.dup @fields = other.fields.dup end # This method declares interfaces for this type AND inherits any field definitions # @param new_interfaces [Array] interfaces that this type implements # @deprecated Use `implements` instead of `interfaces`. def interfaces=(new_interfaces) @clean_interfaces = nil @clean_inherited_interfaces = nil @clean_inherited_fields = nil @dirty_inherited_interfaces = [] @dirty_inherited_fields = {} implements(new_interfaces, inherit: true) end def interfaces load_interfaces @clean_interfaces end def kind GraphQL::TypeKinds::OBJECT end # This fields doesnt have instrumenation applied # @see [Schema#get_field] Get field with instrumentation # @return [GraphQL::Field] The field definition for `field_name` (may be inherited from interfaces) def get_field(field_name) fields[field_name] || interface_fields[field_name] end # These fields don't have instrumenation applied # @see [Schema#get_fields] Get fields with instrumentation # @return [Array] All fields, including ones inherited from interfaces def all_fields interface_fields.merge(self.fields).values end # Declare that this object implements this interface. # This declaration will be validated when the schema is defined. # @param interfaces [Array] add a new interface that this type implements # @param inherits [Boolean] If true, copy the interfaces' field definitions to this type def implements(interfaces, inherit: false) if !interfaces.is_a?(Array) raise ArgumentError, "`implements(interfaces)` must be an array, not #{interfaces.class} (#{interfaces})" end @clean_interfaces = nil @clean_inherited_fields = nil dirty_ifaces = inherit ? @dirty_inherited_interfaces : @dirty_interfaces dirty_ifaces.concat(interfaces) end def resolve_type_proc nil end protected attr_reader :dirty_interfaces, :dirty_inherited_interfaces private def normalize_interfaces(ifaces) ifaces.map { |i_type| GraphQL::BaseType.resolve_related_type(i_type) } end def interface_fields load_interfaces @clean_inherited_fields end def load_interfaces @clean_interfaces ||= begin ensure_defined clean_ifaces = normalize_interfaces(@dirty_interfaces) clean_inherited_ifaces = normalize_interfaces(@dirty_inherited_interfaces) inherited_fields = {} clean_inherited_ifaces.each do |iface| # This will be found later in schema validation: if iface.is_a?(GraphQL::InterfaceType) inherited_fields.merge!(iface.fields) end end @clean_inherited_fields = inherited_fields clean_inherited_ifaces + clean_ifaces end end end end ruby-graphql-1.9.19/lib/graphql/parse_error.rb000066400000000000000000000010341362601351000212770ustar00rootroot00000000000000# frozen_string_literal: true # test_via: language/parser.rb module GraphQL class ParseError < GraphQL::Error attr_reader :line, :col, :query def initialize(message, line, col, query, filename: nil) if filename message += " (#{filename})" end super(message) @line = line @col = col @query = query end def to_h locations = line ? [{ "line" => line, "column" => col }] : [] { "message" => message, "locations" => locations, } end end end ruby-graphql-1.9.19/lib/graphql/query.rb000066400000000000000000000314171362601351000201310ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/query/arguments" require "graphql/query/arguments_cache" require "graphql/query/context" require "graphql/query/executor" require "graphql/query/literal_input" require "graphql/query/null_context" require "graphql/query/result" require "graphql/query/serial_execution" require "graphql/query/variables" require "graphql/query/input_validation_result" require "graphql/query/variable_validation_error" require "graphql/query/validation_pipeline" module GraphQL # A combination of query string and {Schema} instance which can be reduced to a {#result}. class Query include Tracing::Traceable extend Forwardable class OperationNameMissingError < GraphQL::ExecutionError def initialize(name) msg = if name.nil? %|An operation name is required| else %|No operation named "#{name}"| end super(msg) end end attr_reader :schema, :context, :provided_variables # The value for root types attr_accessor :root_value # @return [nil, String] The operation name provided by client or the one inferred from the document. Used to determine which operation to run. attr_accessor :operation_name # @return [Boolean] if false, static validation is skipped (execution behavior for invalid queries is undefined) attr_accessor :validate attr_writer :query_string # @return [GraphQL::Language::Nodes::Document] def document # It's ok if this hasn't been assigned yet if @query_string || @document with_prepared_ast { @document } else nil end end def inspect "query ..." end # @return [String, nil] The name of the operation to run (may be inferred) def selected_operation_name return nil unless selected_operation selected_operation.name end # @return [String, nil] the triggered event, if this query is a subscription update attr_reader :subscription_topic attr_reader :tracers # Prepare query `query_string` on `schema` # @param schema [GraphQL::Schema] # @param query_string [String] # @param context [#[]] an arbitrary hash of values which you can access in {GraphQL::Field#resolve} # @param variables [Hash] values for `$variables` in the query # @param operation_name [String] if the query string contains many operations, this is the one which should be executed # @param root_value [Object] the object used to resolve fields on the root type # @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value) # @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value) # @param except [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns truthy # @param only [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns false def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, except: nil, only: nil) # Even if `variables: nil` is passed, use an empty hash for simpler logic variables ||= {} @schema = schema @filter = schema.default_filter.merge(except: except, only: only) @context = schema.context_class.new(query: self, object: root_value, values: context) @subscription_topic = subscription_topic @root_value = root_value @fragments = nil @operations = nil @validate = validate # TODO: remove support for global tracers @tracers = schema.tracers + GraphQL::Tracing.tracers + (context ? context.fetch(:tracers, []) : []) # Support `ctx[:backtrace] = true` for wrapping backtraces if context && context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer) @tracers << GraphQL::Backtrace::Tracer end @analysis_errors = [] if variables.is_a?(String) raise ArgumentError, "Query variables should be a Hash, not a String. Try JSON.parse to prepare variables." else @provided_variables = variables end @query_string = query_string || query @document = document if @query_string && @document raise ArgumentError, "Query should only be provided a query string or a document, not both." end # A two-layer cache of type resolution: # { abstract_type => { value => resolved_type } } @resolved_types_cache = Hash.new do |h1, k1| h1[k1] = Hash.new do |h2, k2| h2[k2] = @schema.resolve_type(k1, k2, @context) end end @arguments_cache = ArgumentsCache.build(self) # Trying to execute a document # with no operations returns an empty hash @ast_variables = [] @mutation = false @operation_name = operation_name @prepared_ast = false @validation_pipeline = nil @max_depth = max_depth @max_complexity = max_complexity @result_values = nil @executed = false # TODO add a general way to define schema-level filters if @schema.respond_to?(:visible?) merge_filters(only: @schema.method(:visible?)) end end # If a document was provided to `GraphQL::Schema#execute` instead of the raw query string, we will need to get it from the document def query_string @query_string ||= (document ? document.to_query_string : nil) end def_delegators :@schema, :interpreter? def subscription_update? @subscription_topic && subscription? end # A lookahead for the root selections of this query # @return [GraphQL::Execution::Lookahead] def lookahead @lookahead ||= begin ast_node = selected_operation root_type = warden.root_type_for_operation(ast_node.operation_type || "query") root_type = root_type.metadata[:type_class] || raise("Invariant: `lookahead` only works with class-based types") GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [ast_node]) end end # @api private def result_values=(result_hash) if @executed raise "Invariant: Can't reassign result" else @executed = true @result_values = result_hash end end # @api private attr_reader :result_values def fragments with_prepared_ast { @fragments } end def operations with_prepared_ast { @operations } end # Get the result for this query, executing it once # @return [Hash] A GraphQL response, with `"data"` and/or `"errors"` keys def result if !@executed with_prepared_ast { Execution::Multiplex.run_queries(@schema, [self], context: @context) } end @result ||= Query::Result.new(query: self, values: @result_values) end def executed? @executed end def static_errors validation_errors + analysis_errors + context.errors end # This is the operation to run for this query. # If more than one operation is present, it must be named at runtime. # @return [GraphQL::Language::Nodes::OperationDefinition, nil] def selected_operation with_prepared_ast { @selected_operation } end # Determine the values for variables of this query, using default values # if a value isn't provided at runtime. # # If some variable is invalid, errors are added to {#validation_errors}. # # @return [GraphQL::Query::Variables] Variables to apply to this query def variables @variables ||= begin with_prepared_ast { GraphQL::Query::Variables.new( @context, @ast_variables, @provided_variables, ) } end end def irep_selection @selection ||= begin if selected_operation && internal_representation internal_representation.operation_definitions[selected_operation.name] else nil end end end # Node-level cache for calculating arguments. Used during execution and query analysis. # @api private # @return [GraphQL::Query::Arguments] Arguments for this node, merging default values, literal values and query variables def arguments_for(irep_or_ast_node, definition) @arguments_cache[irep_or_ast_node][definition] end def validation_pipeline with_prepared_ast { @validation_pipeline } end def_delegators :validation_pipeline, :validation_errors, :internal_representation, :analyzers, :ast_analyzers, :max_depth, :max_complexity attr_accessor :analysis_errors def valid? validation_pipeline.valid? && analysis_errors.empty? end def warden with_prepared_ast { @warden } end def_delegators :warden, :get_type, :get_field, :possible_types, :root_type_for_operation # @param abstract_type [GraphQL::UnionType, GraphQL::InterfaceType] # @param value [Object] Any runtime value # @return [GraphQL::ObjectType, nil] The runtime type of `value` from {Schema#resolve_type} # @see {#possible_types} to apply filtering from `only` / `except` def resolve_type(abstract_type, value = :__undefined__) if value.is_a?(Symbol) && value == :__undefined__ # Old method signature value = abstract_type abstract_type = nil end if value.is_a?(GraphQL::Schema::Object) value = value.object end @resolved_types_cache[abstract_type][value] end def mutation? with_prepared_ast { @mutation } end def query? with_prepared_ast { @query } end # @return [void] def merge_filters(only: nil, except: nil) if @prepared_ast raise "Can't add filters after preparing the query" else @filter = @filter.merge(only: only, except: except) end nil end def subscription? with_prepared_ast { @subscription } end # @api private def with_error_handling schema.error_handler.with_error_handling(context) do yield end end private def find_operation(operations, operation_name) if operation_name.nil? && operations.length == 1 operations.values.first elsif !operations.key?(operation_name) nil else operations.fetch(operation_name) end end def prepare_ast @prepared_ast = true @warden = GraphQL::Schema::Warden.new(@filter, schema: @schema, context: @context) parse_error = nil @document ||= begin if query_string GraphQL.parse(query_string, tracer: self) end rescue GraphQL::ParseError => err parse_error = err @schema.parse_error(err, @context) nil end @fragments = {} @operations = {} if @document @document.definitions.each do |part| case part when GraphQL::Language::Nodes::FragmentDefinition @fragments[part.name] = part when GraphQL::Language::Nodes::OperationDefinition @operations[part.name] = part end end elsif parse_error # This will be handled later else parse_error = GraphQL::ExecutionError.new("No query string was present") @context.add_error(parse_error) end # Trying to execute a document # with no operations returns an empty hash @ast_variables = [] @mutation = false @subscription = false operation_name_error = nil if @operations.any? @selected_operation = find_operation(@operations, @operation_name) if @selected_operation.nil? operation_name_error = GraphQL::Query::OperationNameMissingError.new(@operation_name) else if @operation_name.nil? @operation_name = @selected_operation.name end @ast_variables = @selected_operation.variables @mutation = @selected_operation.operation_type == "mutation" @query = @selected_operation.operation_type == "query" @subscription = @selected_operation.operation_type == "subscription" end end @validation_pipeline = GraphQL::Query::ValidationPipeline.new( query: self, validate: @validate, parse_error: parse_error, operation_name_error: operation_name_error, max_depth: @max_depth, max_complexity: @max_complexity ) end # Since the query string is processed at the last possible moment, # any internal values which depend on it should be accessed within this wrapper. def with_prepared_ast if !@prepared_ast prepare_ast end yield end end end ruby-graphql-1.9.19/lib/graphql/query/000077500000000000000000000000001362601351000175765ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/query/arguments.rb000066400000000000000000000137051362601351000221360ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query # Read-only access to values, normalizing all keys to strings # # {Arguments} recursively wraps the input in {Arguments} instances. class Arguments extend Forwardable include GraphQL::Dig def self.construct_arguments_class(argument_owner) argument_definitions = argument_owner.arguments argument_owner.arguments_class = Class.new(self) do self.argument_definitions = argument_definitions argument_definitions.each do |_arg_name, arg_definition| if arg_definition.method_access? expose_as = arg_definition.expose_as.to_s.freeze expose_as_underscored = GraphQL::Schema::Member::BuildType.underscore(expose_as).freeze method_names = [expose_as, expose_as_underscored].uniq method_names.each do |method_name| # Don't define a helper method if it would override something. if method_defined?(method_name) warn( "Unable to define a helper for argument with name '#{method_name}' "\ "as this is a reserved name. Add `method_access: false` to stop this warning." ) else define_method(method_name) do # Always use `expose_as` here, since #[] doesn't accept underscored names self[expose_as] end end end end end end end attr_reader :argument_values def initialize(values, context:, defaults_used:) @argument_values = values.inject({}) do |memo, (inner_key, inner_value)| arg_name = inner_key.to_s arg_defn = self.class.argument_definitions[arg_name] || raise("Not found #{arg_name} among #{self.class.argument_definitions.keys}") arg_default_used = defaults_used.include?(arg_name) arg_value = wrap_value(inner_value, arg_defn.type, context) string_key = arg_defn.expose_as memo[string_key] = ArgumentValue.new(string_key, arg_value, arg_defn, arg_default_used) memo end end # @param key [String, Symbol] name or index of value to access # @return [Object] the argument at that key def [](key) key_s = key.is_a?(String) ? key : key.to_s @argument_values.fetch(key_s, NULL_ARGUMENT_VALUE).value end # @param key [String, Symbol] name of value to access # @return [Boolean] true if the argument was present in this field def key?(key) key_s = key.is_a?(String) ? key : key.to_s @argument_values.key?(key_s) end # @param key [String, Symbol] name of value to access # @return [Boolean] true if the argument default was passed as the argument value to the resolver def default_used?(key) key_s = key.is_a?(String) ? key : key.to_s @argument_values.fetch(key_s, NULL_ARGUMENT_VALUE).default_used? end # Get the hash of all values, with stringified keys # @return [Hash] the stringified hash def to_h @to_h ||= begin h = {} each_value do |arg_value| arg_key = arg_value.definition.expose_as h[arg_key] = unwrap_value(arg_value.value) end h end end def_delegators :to_h, :keys, :values, :each, :any? def prepare self end # Access each key, value and type for the arguments in this set. # @yield [argument_value] The {ArgumentValue} for each argument # @yieldparam argument_value [ArgumentValue] def each_value @argument_values.each_value do |argument_value| yield(argument_value) end end class << self attr_accessor :argument_definitions end NoArguments = Class.new(self) do self.argument_definitions = [] end NO_ARGS = NoArguments.new({}, context: nil, defaults_used: Set.new) # Convert this instance into valid Ruby keyword arguments # @return [{Symbol=>Object}] def to_kwargs ruby_kwargs = {} keys.each do |key| ruby_kwargs[Schema::Member::BuildType.underscore(key).to_sym] = self[key] end ruby_kwargs end alias :to_hash :to_kwargs private class ArgumentValue attr_reader :key, :value, :definition attr_writer :default_used def initialize(key, value, definition, default_used) @key = key @value = value @definition = definition @default_used = default_used end # @return [Boolean] true if the argument default was passed as the argument value to the resolver def default_used? @default_used end end NULL_ARGUMENT_VALUE = ArgumentValue.new(nil, nil, nil, nil) def wrap_value(value, arg_defn_type, context) if value.nil? nil else case arg_defn_type when GraphQL::ListType value.map { |item| wrap_value(item, arg_defn_type.of_type, context) } when GraphQL::NonNullType wrap_value(value, arg_defn_type.of_type, context) when GraphQL::InputObjectType if value.is_a?(Hash) result = arg_defn_type.arguments_class.new(value, context: context, defaults_used: Set.new) result.prepare else value end else value end end end def unwrap_value(value) case value when Array value.map { |item| unwrap_value(item) } when Hash value.inject({}) do |memo, (key, value)| memo[key] = unwrap_value(value) memo end when GraphQL::Query::Arguments, GraphQL::Schema::InputObject value.to_h else value end end end end end ruby-graphql-1.9.19/lib/graphql/query/arguments_cache.rb000066400000000000000000000016131362601351000232540ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../query.rb module GraphQL class Query module ArgumentsCache # @return [Hash Hash GraphQL::Query::Arguments>>] def self.build(query) Hash.new do |h1, irep_or_ast_node| h1[irep_or_ast_node] = Hash.new do |h2, definition| ast_node = irep_or_ast_node.is_a?(GraphQL::InternalRepresentation::Node) ? irep_or_ast_node.ast_node : irep_or_ast_node h2[definition] = if definition.arguments.empty? GraphQL::Query::Arguments::NO_ARGS else GraphQL::Query::LiteralInput.from_arguments( ast_node.arguments, definition, query.variables, ) end end end end end end end ruby-graphql-1.9.19/lib/graphql/query/context.rb000066400000000000000000000242011362601351000216060ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../execution/execute.rb # test_via: ../execution/lazy.rb module GraphQL class Query # Expose some query-specific info to field resolve functions. # It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`. class Context module SharedMethods # @return [Object] The target for field resolution attr_accessor :object # @return [Hash, Array, String, Integer, Float, Boolean, nil] The resolved value for this field attr_reader :value # @return [Boolean] were any fields of this selection skipped? attr_reader :skipped alias :skipped? :skipped # @api private attr_writer :skipped # Return this value to tell the runtime # to exclude this field from the response altogether def skip GraphQL::Execution::Execute::SKIP end # @return [Boolean] True if this selection has been nullified by a null child def invalid_null? @invalid_null end # Remove this child from the result value # (used for null propagation and skip) # @api private def delete(child_ctx) @value.delete(child_ctx.key) end # Create a child context to use for `key` # @param key [String, Integer] The key in the response (name or index) # @param irep_node [InternalRepresentation::Node] The node being evaluated # @api private def spawn_child(key:, irep_node:, object:) FieldResolutionContext.new( @context, key, irep_node, self, object ) end # Add error at query-level. # @param error [GraphQL::ExecutionError] an execution error # @return [void] def add_error(error) if !error.is_a?(ExecutionError) raise TypeError, "expected error to be a ExecutionError, but was #{error.class}" end errors << error nil end # @example Print the GraphQL backtrace during field resolution # puts ctx.backtrace # # @return [GraphQL::Backtrace] The backtrace for this point in query execution def backtrace GraphQL::Backtrace.new(self) end def execution_errors @execution_errors ||= ExecutionErrors.new(self) end def lookahead ast_nodes = irep_node.ast_nodes field = irep_node.definition.metadata[:type_class] || raise("Lookahead is only compatible with class-based schemas") Execution::Lookahead.new(query: query, ast_nodes: ast_nodes, field: field) end end class ExecutionErrors def initialize(ctx) @context = ctx end def add(err_or_msg) err = case err_or_msg when String GraphQL::ExecutionError.new(err_or_msg) when GraphQL::ExecutionError err_or_msg else raise ArgumentError, "expected String or GraphQL::ExecutionError, not #{err_or_msg.class} (#{err_or_msg.inspect})" end # This will assign ast_node and path @context.add_error(err) end alias :>> :add alias :push :add end include SharedMethods extend Forwardable attr_reader :execution_strategy # `strategy` is required by GraphQL::Batch alias_method :strategy, :execution_strategy def execution_strategy=(new_strategy) # GraphQL::Batch re-assigns this value but it was previously not used # (ExecutionContext#strategy was used instead) # now it _is_ used, but it breaks GraphQL::Batch tests @execution_strategy ||= new_strategy end # @return [GraphQL::InternalRepresentation::Node] The internal representation for this query node def irep_node @irep_node ||= query.irep_selection end # @return [GraphQL::Language::Nodes::Field] The AST node for the currently-executing field def ast_node @irep_node.ast_node end # @return [Array] errors returned during execution attr_reader :errors # @return [GraphQL::Query] The query whose context this is attr_reader :query # @return [GraphQL::Schema] attr_reader :schema # @return [Array] The current position in the result attr_reader :path # Make a new context which delegates key lookup to `values` # @param query [GraphQL::Query] the query who owns this context # @param values [Hash] A hash of arbitrary values which will be accessible at query-time def initialize(query:, schema: query.schema, values:, object:) @query = query @schema = schema @provided_values = values || {} @object = object # Namespaced storage, where user-provided values are in `nil` namespace: @storage = Hash.new { |h, k| h[k] = {} } @storage[nil] = @provided_values @errors = [] @path = [] @value = nil @context = self # for SharedMethods @scoped_context = {} end # @api private attr_writer :interpreter # @api private attr_writer :value # @api private attr_accessor :scoped_context def_delegators :@provided_values, :[]= def_delegators :to_h, :fetch, :dig def_delegators :@query, :trace, :interpreter? # @!method []=(key, value) # Reassign `key` to the hash passed to {Schema#execute} as `context:` # Lookup `key` from the hash passed to {Schema#execute} as `context:` def [](key) return @scoped_context[key] if @scoped_context.key?(key) @provided_values[key] end def to_h @provided_values.merge(@scoped_context) end alias :to_hash :to_h def key?(key) @scoped_context.key?(key) || @provided_values.key?(key) end # @return [GraphQL::Schema::Warden] def warden @warden ||= @query.warden end # Get an isolated hash for `ns`. Doesn't affect user-provided storage. # @param ns [Object] a usage-specific namespace identifier # @return [Hash] namespaced storage def namespace(ns) @storage[ns] end def inspect "#" end # @api private def received_null_child @invalid_null = true @value = nil end def scoped_merge!(hash) @scoped_context = @scoped_context.merge(hash) end def scoped_set!(key, value) scoped_merge!(key => value) nil end class FieldResolutionContext include SharedMethods include Tracing::Traceable extend Forwardable attr_reader :irep_node, :field, :parent_type, :query, :schema, :parent, :key, :type alias :selection :irep_node def initialize(context, key, irep_node, parent, object) @context = context @key = key @parent = parent @object = object @irep_node = irep_node @field = irep_node.definition @parent_type = irep_node.owner_type @type = field.type # This is needed constantly, so set it ahead of time: @query = context.query @schema = context.schema @tracers = @query.tracers # This hack flag is required by ConnectionResolve @wrapped_connection = false @wrapped_object = false end # @api private attr_accessor :wrapped_connection, :wrapped_object def path @path ||= @parent.path.dup << @key end def_delegators :@context, :[], :[]=, :key?, :fetch, :to_h, :namespace, :dig, :spawn, :warden, :errors, :execution_strategy, :strategy, :interpreter? # @return [GraphQL::Language::Nodes::Field] The AST node for the currently-executing field def ast_node @irep_node.ast_node end # Add error to current field resolution. # @param error [GraphQL::ExecutionError] an execution error # @return [void] def add_error(error) super error.ast_node ||= irep_node.ast_node error.path ||= path nil end def inspect "#" end # Set a new value for this field in the response. # It may be updated after resolving a {Lazy}. # If it is {Execute::PROPAGATE_NULL}, tell the owner to propagate null. # If it's {Execute::Execution::SKIP}, remove this field result from its parent # @param new_value [Any] The GraphQL-ready value # @api private def value=(new_value) case new_value when GraphQL::Execution::Execute::PROPAGATE_NULL, nil @invalid_null = true @value = nil if @type.kind.non_null? @parent.received_null_child end when GraphQL::Execution::Execute::SKIP @parent.skipped = true @parent.delete(self) else @value = new_value end end protected def received_null_child case @value when Hash self.value = GraphQL::Execution::Execute::PROPAGATE_NULL when Array if list_of_non_null_items?(@type) self.value = GraphQL::Execution::Execute::PROPAGATE_NULL end when nil # TODO This is a hack # It was already nulled out but it's getting reassigned else raise "Unexpected value for received_null_child (#{self.value.class}): #{value}" end end private def list_of_non_null_items?(type) case type when GraphQL::NonNullType # Unwrap [T]! list_of_non_null_items?(type.of_type) when GraphQL::ListType type.of_type.is_a?(GraphQL::NonNullType) else raise "Unexpected list_of_non_null_items check: #{type}" end end end end end end ruby-graphql-1.9.19/lib/graphql/query/executor.rb000066400000000000000000000026751362601351000217730ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../query.rb module GraphQL class Query class Executor class PropagateNull < StandardError; end # @return [GraphQL::Query] the query being executed attr_reader :query def initialize(query) @query = query end # Evaluate {operation_name} on {query}. # Handle {GraphQL::ExecutionError}s by putting them in the "errors" key. # @return [Hash] A GraphQL response, with either a "data" key or an "errors" key def result execute rescue GraphQL::ExecutionError => err query.context.errors << err {"errors" => [err.to_h]} end private def execute operation = query.selected_operation return {} if operation.nil? op_type = operation.operation_type root_type = query.root_type_for_operation(op_type) execution_strategy_class = query.schema.execution_strategy_for_operation(op_type) execution_strategy = execution_strategy_class.new query.context.execution_strategy = execution_strategy data_result = begin execution_strategy.execute(operation, root_type, query) rescue PropagateNull nil end result = { "data" => data_result } error_result = query.context.errors.map(&:to_h) if error_result.any? result["errors"] = error_result end result end end end end ruby-graphql-1.9.19/lib/graphql/query/input_validation_result.rb000066400000000000000000000011201362601351000250640ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query class InputValidationResult attr_accessor :problems def valid? @problems.nil? end def add_problem(explanation, path = nil) @problems ||= [] @problems.push({ "path" => path || [], "explanation" => explanation }) end def merge_result!(path, inner_result) return if inner_result.valid? inner_result.problems.each do |p| item_path = [path, *p["path"]] add_problem(p["explanation"], item_path) end end end end end ruby-graphql-1.9.19/lib/graphql/query/literal_input.rb000066400000000000000000000103631362601351000230010ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query # Turn query string values into something useful for query execution class LiteralInput def self.coerce(type, ast_node, variables) case ast_node when nil nil when Language::Nodes::NullValue nil when Language::Nodes::VariableIdentifier variables[ast_node.name] else case type when GraphQL::ScalarType # TODO smell # This gets used for plain values during subscriber.trigger if variables type.coerce_input(ast_node, variables.context) else type.coerce_isolated_input(ast_node) end when GraphQL::EnumType # TODO smell # This gets used for plain values sometimes v = ast_node.is_a?(GraphQL::Language::Nodes::Enum) ? ast_node.name : ast_node if variables type.coerce_input(v, variables.context) else type.coerce_isolated_input(v) end when GraphQL::NonNullType LiteralInput.coerce(type.of_type, ast_node, variables) when GraphQL::ListType if ast_node.is_a?(Array) ast_node.map { |element_ast| LiteralInput.coerce(type.of_type, element_ast, variables) } else [LiteralInput.coerce(type.of_type, ast_node, variables)] end when GraphQL::InputObjectType # TODO smell: handling AST vs handling plain Ruby next_args = ast_node.is_a?(Hash) ? ast_node : ast_node.arguments from_arguments(next_args, type, variables) end end end def self.from_arguments(ast_arguments, argument_owner, variables) context = variables ? variables.context : nil values_hash = {} defaults_used = Set.new indexed_arguments = case ast_arguments when Hash ast_arguments when Array ast_arguments.each_with_object({}) { |a, memo| memo[a.name] = a } else raise ArgumentError, "Unexpected ast_arguments: #{ast_arguments}" end argument_defns = argument_owner.arguments argument_defns.each do |arg_name, arg_defn| ast_arg = indexed_arguments[arg_name] # First, check the argument in the AST. # If the value is a variable, # only add a value if the variable is actually present. # Otherwise, coerce the value in the AST, prepare the value and add it. # # TODO: since indexed_arguments can come from a plain Ruby hash, # have to check for `false` or `nil` as hash values. This is getting smelly :S if indexed_arguments.key?(arg_name) arg_value = ast_arg.is_a?(GraphQL::Language::Nodes::Argument) ? ast_arg.value : ast_arg value_is_a_variable = arg_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) if (!value_is_a_variable || (value_is_a_variable && variables.key?(arg_value.name))) value = coerce(arg_defn.type, arg_value, variables) value = arg_defn.prepare(value, context) if value.is_a?(GraphQL::ExecutionError) value.ast_node = ast_arg raise value end values_hash[arg_name] = value end end # Then, the definition for a default value. # If the definition has a default value and # a value wasn't provided from the AST, # then add the default value. if arg_defn.default_value? && !values_hash.key?(arg_name) value = arg_defn.default_value defaults_used << arg_name # `context` isn't present when pre-calculating defaults if context value = arg_defn.prepare(value, context) if value.is_a?(GraphQL::ExecutionError) value.ast_node = ast_arg raise value end end values_hash[arg_name] = value end end result = argument_owner.arguments_class.new(values_hash, context: context, defaults_used: defaults_used) result.prepare end end end end ruby-graphql-1.9.19/lib/graphql/query/null_context.rb000066400000000000000000000015021362601351000226370ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query # This object can be `ctx` in places where there is no query class NullContext class NullWarden < GraphQL::Schema::Warden def visible?(t); true; end def visible_field?(t); true; end def visible_type?(t); true; end end attr_reader :schema, :query, :warden def initialize @query = nil @schema = GraphQL::Schema.new @warden = NullWarden.new( GraphQL::Filter.new, context: self, schema: @schema, ) end def [](key); end class << self extend Forwardable def [](key); end def instance @instance = self.new end def_delegators :instance, :query, :schema, :warden end end end end ruby-graphql-1.9.19/lib/graphql/query/result.rb000066400000000000000000000031631362601351000214440ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query # A result from {Schema#execute}. # It provides the requested data and # access to the {Query} and {Query::Context}. class Result extend Forwardable def initialize(query:, values:) @query = query @to_h = values end # @return [GraphQL::Query] The query that was executed attr_reader :query # @return [Hash] The resulting hash of "data" and/or "errors" attr_reader :to_h def_delegators :@query, :context, :mutation?, :query?, :subscription? def_delegators :@to_h, :[], :keys, :values, :to_json, :as_json # Delegate any hash-like method to the underlying hash. def method_missing(method_name, *args, &block) if @to_h.respond_to?(method_name) @to_h.public_send(method_name, *args, &block) else super end end def respond_to_missing?(method_name, include_private = false) @to_h.respond_to?(method_name) || super end def inspect "#" end # A result is equal to another object when: # # - The other object is a Hash whose value matches `result.to_h` # - The other object is a Result whose value matches `result.to_h` # # (The query is ignored for comparing result equality.) # # @return [Boolean] def ==(other) case other when Hash @to_h == other when Query::Result @to_h == other.to_h else super end end end end end ruby-graphql-1.9.19/lib/graphql/query/serial_execution.rb000066400000000000000000000024571362601351000234750ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/query/serial_execution/value_resolution" require "graphql/query/serial_execution/field_resolution" require "graphql/query/serial_execution/operation_resolution" require "graphql/query/serial_execution/selection_resolution" module GraphQL class Query class SerialExecution # This is the only required method for an Execution strategy. # You could create a custom execution strategy and configure your schema to # use that custom strategy instead. # # @param ast_operation [GraphQL::Language::Nodes::OperationDefinition] The operation definition to run # @param root_type [GraphQL::ObjectType] either the query type or the mutation type # @param query_object [GraphQL::Query] the query object for this execution # @return [Hash] a spec-compliant GraphQL result, as a hash def execute(ast_operation, root_type, query_object) operation_resolution.resolve( query_object.irep_selection, root_type, query_object ) end def field_resolution self.class::FieldResolution end def operation_resolution self.class::OperationResolution end def selection_resolution self.class::SelectionResolution end end end end ruby-graphql-1.9.19/lib/graphql/query/serial_execution/000077500000000000000000000000001362601351000231405ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/query/serial_execution/field_resolution.rb000066400000000000000000000054571362601351000270460ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query class SerialExecution class FieldResolution attr_reader :irep_node, :parent_type, :target, :field, :arguments, :query def initialize(selection, parent_type, target, query_ctx) @irep_node = selection @selection = selection @parent_type = parent_type @target = target @query = query_ctx.query @field = irep_node.definition @field_ctx = query_ctx.spawn_child( key: irep_node.name, object: target, irep_node: irep_node, ) @arguments = @query.arguments_for(irep_node, @field) end def result result_name = irep_node.name raw_value = get_raw_value if raw_value.is_a?(GraphQL::Execution::Execute::Skip) {} else { result_name => get_finished_value(raw_value) } end end # GraphQL::Batch depends on this def execution_context @field_ctx end private # After getting the value from the field's resolve method, # continue by "finishing" the value, eg. executing sub-fields or coercing values def get_finished_value(raw_value) case raw_value when GraphQL::ExecutionError raw_value.ast_node = @field_ctx.ast_node raw_value.path = @field_ctx.path @query.context.errors.push(raw_value) when Array list_errors = raw_value.each_with_index.select { |value, _| value.is_a?(GraphQL::ExecutionError) } if list_errors.any? list_errors.each do |error, index| error.ast_node = @field_ctx.ast_node error.path = @field_ctx.path + [index] @query.context.errors.push(error) end end end begin GraphQL::Query::SerialExecution::ValueResolution.resolve( parent_type, field, field.type, raw_value, @selection, @field_ctx, ) rescue GraphQL::Query::Executor::PropagateNull if field.type.kind.non_null? raise else nil end end end # Get the result of: # - Any middleware on this schema # - The field's resolve method # If the middleware chain returns a GraphQL::ExecutionError, its message # is added to the "errors" key. def get_raw_value begin @field_ctx.schema.middleware.invoke([parent_type, target, field, arguments, @field_ctx]) rescue GraphQL::ExecutionError => err err end end end end end end ruby-graphql-1.9.19/lib/graphql/query/serial_execution/operation_resolution.rb000066400000000000000000000006451362601351000277550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query class SerialExecution module OperationResolution def self.resolve(selection, target, query) result = query.context.execution_strategy.selection_resolution.resolve( query.root_value, target, selection, query.context, ) result end end end end end ruby-graphql-1.9.19/lib/graphql/query/serial_execution/selection_resolution.rb000066400000000000000000000011171362601351000277350ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query class SerialExecution module SelectionResolution def self.resolve(target, current_type, selection, query_ctx) selection_result = {} selection.typed_children[current_type].each do |name, subselection| selection_result.merge!(query_ctx.execution_strategy.field_resolution.new( subselection, current_type, target, query_ctx ).result) end selection_result end end end end end ruby-graphql-1.9.19/lib/graphql/query/serial_execution/value_resolution.rb000066400000000000000000000055611362601351000270730ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query class SerialExecution module ValueResolution def self.resolve(parent_type, field_defn, field_type, value, selection, query_ctx) if value.nil? || value.is_a?(GraphQL::ExecutionError) if field_type.kind.non_null? if value.nil? type_error = GraphQL::InvalidNullError.new(parent_type, field_defn, value) query_ctx.schema.type_error(type_error, query_ctx) end raise GraphQL::Query::Executor::PropagateNull else nil end else case field_type.kind when GraphQL::TypeKinds::SCALAR, GraphQL::TypeKinds::ENUM field_type.coerce_result(value, query_ctx) when GraphQL::TypeKinds::LIST wrapped_type = field_type.of_type result = [] i = 0 value.each do |inner_value| inner_ctx = query_ctx.spawn_child( key: i, object: inner_value, irep_node: selection, ) result << resolve( parent_type, field_defn, wrapped_type, inner_value, selection, inner_ctx, ) i += 1 end result when GraphQL::TypeKinds::NON_NULL wrapped_type = field_type.of_type resolve( parent_type, field_defn, wrapped_type, value, selection, query_ctx, ) when GraphQL::TypeKinds::OBJECT query_ctx.execution_strategy.selection_resolution.resolve( value, field_type, selection, query_ctx ) when GraphQL::TypeKinds::UNION, GraphQL::TypeKinds::INTERFACE query = query_ctx.query resolved_type = query.resolve_type(value) possible_types = query.possible_types(field_type) if !possible_types.include?(resolved_type) type_error = GraphQL::UnresolvedTypeError.new(value, field_defn, parent_type, resolved_type, possible_types) query.schema.type_error(type_error, query_ctx) raise GraphQL::Query::Executor::PropagateNull else resolve( parent_type, field_defn, resolved_type, value, selection, query_ctx, ) end else raise("Unknown type kind: #{field_type.kind}") end end end end end end end ruby-graphql-1.9.19/lib/graphql/query/validation_pipeline.rb000066400000000000000000000105501362601351000241430ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query # Contain the validation pipeline and expose the results. # # 0. Checks in {Query#initialize}: # - Rescue a ParseError, halt if there is one # - Check for selected operation, halt if not found # 1. Validate the AST, halt if errors # 2. Validate the variables, halt if errors # 3. Run query analyzers, halt if errors # # {#valid?} is false if any of the above checks halted the pipeline. # # @api private class ValidationPipeline attr_reader :max_depth, :max_complexity def initialize(query:, validate:, parse_error:, operation_name_error:, max_depth:, max_complexity:) @validation_errors = [] @internal_representation = nil @validate = validate @parse_error = parse_error @operation_name_error = operation_name_error @query = query @schema = query.schema @max_depth = max_depth @max_complexity = max_complexity @has_validated = false end # @return [Boolean] does this query have errors that should prevent it from running? def valid? ensure_has_validated @valid end # @return [Array] Static validation errors for the query string def validation_errors ensure_has_validated @validation_errors end # @return [Hash GraphQL::InternalRepresentation::Node] Operation name -> Irep node pairs def internal_representation ensure_has_validated @internal_representation end def analyzers ensure_has_validated @query_analyzers end private # If the pipeline wasn't run yet, run it. # If it was already run, do nothing. def ensure_has_validated return if @has_validated @has_validated = true if @parse_error # This is kind of crazy: we push the parse error into `ctx` # in {DefaultParseError} so that users can _opt out_ by redefining that hook. # That means we can't _re-add_ the error here (otherwise we'd either # add it twice _or_ override the user's choice to not add it). # So we just have to know that it was invalid and go from there. @valid = false return elsif @operation_name_error @validation_errors << @operation_name_error else validation_result = @schema.static_validator.validate(@query, validate: @validate) @validation_errors.concat(validation_result[:errors]) @internal_representation = validation_result[:irep] if @validation_errors.empty? @validation_errors.concat(@query.variables.errors) end if @validation_errors.empty? @query_analyzers = build_analyzers( @schema, @max_depth, @max_complexity ) end end @valid = @validation_errors.empty? end # If there are max_* values, add them, # otherwise reuse the schema's list of analyzers. def build_analyzers(schema, max_depth, max_complexity) qa = schema.query_analyzers.dup # Filter out the built in authorization analyzer. # It is deprecated and does not have an AST analyzer alternative. qa = qa.select do |analyzer| if analyzer == GraphQL::Authorization::Analyzer && schema.using_ast_analysis? raise "The Authorization analyzer is not supported with AST Analyzers" else true end end if max_depth || max_complexity # Depending on the analysis engine, we must use different analyzers # remove this once everything has switched over to AST analyzers if schema.using_ast_analysis? if max_depth qa << GraphQL::Analysis::AST::MaxQueryDepth end if max_complexity qa << GraphQL::Analysis::AST::MaxQueryComplexity end else if max_depth qa << GraphQL::Analysis::MaxQueryDepth.new(max_depth) end if max_complexity qa << GraphQL::Analysis::MaxQueryComplexity.new(max_complexity) end end qa else qa end end end end end ruby-graphql-1.9.19/lib/graphql/query/variable_validation_error.rb000066400000000000000000000024641362601351000253410ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query class VariableValidationError < GraphQL::ExecutionError attr_accessor :value, :validation_result def initialize(variable_ast, type, value, validation_result) @value = value @validation_result = validation_result msg = "Variable #{variable_ast.name} of type #{type} was provided invalid value" if problem_fields.any? msg += " for #{problem_fields.join(", ")}" end super(msg) self.ast_node = variable_ast end def to_h # It is possible there are other extension items in this error, so handle # a one level deep merge explicitly. However beyond that only show the # latest value and problems. super.merge({ "extensions" => { "value" => value, "problems" => validation_result.problems }}) do |key, oldValue, newValue| if oldValue.respond_to? merge oldValue.merge(newValue) else newValue end end end private def problem_fields @problem_fields ||= @validation_result .problems .reject { |problem| problem["path"].empty? } .map { |problem| "#{problem['path'].join('.')} (#{problem['explanation']})" } end end end end ruby-graphql-1.9.19/lib/graphql/query/variables.rb000066400000000000000000000057121362601351000221000ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query # Read-only access to query variables, applying default values if needed. class Variables extend Forwardable # @return [Array] Any errors encountered when parsing the provided variables and literal values attr_reader :errors attr_reader :context def initialize(ctx, ast_variables, provided_variables) schema = ctx.schema @context = ctx @provided_variables = GraphQL::Argument.deep_stringify(provided_variables) @errors = [] @storage = ast_variables.each_with_object({}) do |ast_variable, memo| # Find the right value for this variable: # - First, use the value provided at runtime # - Then, fall back to the default value from the query string # If it's still nil, raise an error if it's required. variable_type = schema.type_from_ast(ast_variable.type) if variable_type.nil? # Pass -- it will get handled by a validator else variable_name = ast_variable.name default_value = ast_variable.default_value provided_value = @provided_variables[variable_name] value_was_provided = @provided_variables.key?(variable_name) begin validation_result = variable_type.validate_input(provided_value, ctx) if validation_result.valid? if value_was_provided # Add the variable if a value was provided memo[variable_name] = schema.error_handler.with_error_handling(context) do variable_type.coerce_input(provided_value, ctx) end elsif default_value != nil # Add the variable if it wasn't provided but it has a default value (including `null`) memo[variable_name] = GraphQL::Query::LiteralInput.coerce(variable_type, default_value, self) end end rescue GraphQL::CoercionError, GraphQL::ExecutionError => ex # TODO: This should really include the path to the problematic node in the variable value # like InputValidationResults generated by validate_non_null_input but unfortunately we don't # have this information available in the coerce_input call chain. Note this path is the path # that appears under errors.extensions.problems.path and NOT the result path under errors.path. validation_result = GraphQL::Query::InputValidationResult.new validation_result.add_problem(ex.message) end if !validation_result.valid? @errors << GraphQL::Query::VariableValidationError.new(ast_variable, variable_type, provided_value, validation_result) end end end end def_delegators :@storage, :length, :key?, :[], :fetch, :to_h end end end ruby-graphql-1.9.19/lib/graphql/railtie.rb000066400000000000000000000104741362601351000204150ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Railtie < Rails::Railtie rake_tasks do # Defer this so that you only need the `parser` gem when you _run_ the upgrader def load_upgraders require_relative './upgrader/member' require_relative './upgrader/schema' end namespace :graphql do task :upgrade, [:dir] do |t, args| unless (dir = args[:dir]) fail 'You have to give me a directory where your GraphQL schema and types live. ' \ 'For example: `bin/rake graphql:upgrade[app/graphql/**/*]`' end Dir[dir].each do |file| # Members (types, interfaces, etc.) if file =~ /.*_(type|interface|enum|union|)\.rb$/ Rake::Task["graphql:upgrade:member"].execute(Struct.new(:member_file).new(file)) end end puts "Upgrade complete! Note that this is a best-effort approach, and may very well contain some bugs." puts "Don't forget to create the base objects. For example, you could run:" puts "\tbin/rake graphql:upgrade:create_base_objects[app/graphql]" end namespace :upgrade do task :create_base_objects, [:base_dir] do |t, args| unless (base_dir = args[:base_dir]) fail 'You have to give me a directory where your GraphQL types live. ' \ 'For example: `bin/rake graphql:upgrade:create_base_objects[app/graphql]`' end destination_file = File.join(base_dir, "types", "base_scalar.rb") unless File.exists?(destination_file) FileUtils.mkdir_p(File.dirname(destination_file)) File.open(destination_file, 'w') do |f| f.puts "class Types::BaseScalar < GraphQL::Schema::Scalar\nend" end end destination_file = File.join(base_dir, "types", "base_input_object.rb") unless File.exists?(destination_file) FileUtils.mkdir_p(File.dirname(destination_file)) File.open(destination_file, 'w') do |f| f.puts "class Types::BaseInputObject < GraphQL::Schema::InputObject\nend" end end destination_file = File.join(base_dir, "types", "base_enum.rb") unless File.exists?(destination_file) FileUtils.mkdir_p(File.dirname(destination_file)) File.open(destination_file, 'w') do |f| f.puts "class Types::BaseEnum < GraphQL::Schema::Enum\nend" end end destination_file = File.join(base_dir, "types", "base_union.rb") unless File.exists?(destination_file) FileUtils.mkdir_p(File.dirname(destination_file)) File.open(destination_file, 'w') do |f| f.puts "class Types::BaseUnion < GraphQL::Schema::Union\nend" end end destination_file = File.join(base_dir, "types", "base_interface.rb") unless File.exists?(destination_file) FileUtils.mkdir_p(File.dirname(destination_file)) File.open(destination_file, 'w') do |f| f.puts "module Types::BaseInterface\n include GraphQL::Schema::Interface\nend" end end destination_file = File.join(base_dir, "types", "base_object.rb") unless File.exists?(destination_file) File.open(destination_file, 'w') do |f| f.puts "class Types::BaseObject < GraphQL::Schema::Object\nend" end end end task :schema, [:schema_file] do |t, args| schema_file = args.schema_file load_upgraders upgrader = GraphQL::Upgrader::Schema.new File.read(schema_file) puts "- Transforming schema #{schema_file}" File.open(schema_file, 'w') { |f| f.write upgrader.upgrade } end task :member, [:member_file] do |t, args| member_file = args.member_file load_upgraders upgrader = GraphQL::Upgrader::Member.new File.read(member_file) next unless upgrader.upgradeable? puts "- Transforming member #{member_file}" File.open(member_file, 'w') { |f| f.write upgrader.upgrade } end end end end end end ruby-graphql-1.9.19/lib/graphql/rake_task.rb000066400000000000000000000076561362601351000207400ustar00rootroot00000000000000# frozen_string_literal: true require "fileutils" require "graphql/rake_task/validate" module GraphQL # A rake task for dumping a schema as IDL or JSON. # # By default, schemas are looked up by name as constants using `schema_name:`. # You can provide a `load_schema` function to return your schema another way. # # `load_context:`, `only:` and `except:` are supported so that # you can keep an eye on how filters affect your schema. # # @example Dump a Schema to .graphql + .json files # require "graphql/rake_task" # GraphQL::RakeTask.new(schema_name: "MySchema") # # # $ rake graphql:schema:dump # # Schema IDL dumped to ./schema.graphql # # Schema JSON dumped to ./schema.json # # @example Invoking the task from Ruby # require "rake" # Rake::Task["graphql:schema:dump"].invoke class RakeTask include Rake::DSL DEFAULT_OPTIONS = { namespace: "graphql", dependencies: nil, schema_name: nil, load_schema: ->(task) { Object.const_get(task.schema_name) }, load_context: ->(task) { {} }, only: nil, except: nil, directory: ".", idl_outfile: "schema.graphql", json_outfile: "schema.json", } # @return [String] Namespace for generated tasks attr_writer :namespace def rake_namespace @namespace end # @return [Array] attr_accessor :dependencies # @return [String] By default, used to find the schema as a constant. # @see {#load_schema} for loading a schema another way attr_accessor :schema_name # @return [<#call(task)>] A proc for loading the target GraphQL schema attr_accessor :load_schema # @return [<#call(task)>] A callable for loading the query context attr_accessor :load_context # @return [<#call(member, ctx)>, nil] A filter for this task attr_accessor :only # @return [<#call(member, ctx)>, nil] A filter for this task attr_accessor :except # @return [String] target for IDL task attr_accessor :idl_outfile # @return [String] target for JSON task attr_accessor :json_outfile # @return [String] directory for IDL & JSON files attr_accessor :directory # Set the parameters of this task by passing keyword arguments # or assigning attributes inside the block def initialize(options = {}) default_dependencies = if Rake::Task.task_defined?("environment") [:environment] else [] end all_options = DEFAULT_OPTIONS .merge(dependencies: default_dependencies) .merge(options) all_options.each do |k, v| self.public_send("#{k}=", v) end if block_given? yield(self) end define_task end private # Use the provided `method_name` to generate a string from the specified schema # then write it to `file`. def write_outfile(method_name, file) schema = @load_schema.call(self) context = @load_context.call(self) result = schema.public_send(method_name, only: @only, except: @except, context: context) dir = File.dirname(file) FileUtils.mkdir_p(dir) File.write(file, result) end def idl_path File.join(@directory, @idl_outfile) end def json_path File.join(@directory, @json_outfile) end # Use the Rake DSL to add tasks def define_task namespace(@namespace) do namespace("schema") do desc("Dump the schema to IDL in #{idl_path}") task :idl => @dependencies do write_outfile(:to_definition, idl_path) puts "Schema IDL dumped into #{idl_path}" end desc("Dump the schema to JSON in #{json_path}") task :json => @dependencies do write_outfile(:to_json, json_path) puts "Schema JSON dumped into #{json_path}" end desc("Dump the schema to JSON and IDL") task :dump => [:idl, :json] end end end end end ruby-graphql-1.9.19/lib/graphql/rake_task/000077500000000000000000000000001362601351000203755ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/rake_task/validate.rb000066400000000000000000000044201362601351000225130ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class RakeTask extend Rake::DSL desc "Get the checksum of a graphql-pro version and compare it to published versions on GitHub and graphql-ruby.org" task "graphql:pro:validate", [:gem_version] do |t, args| version = args[:gem_version] check = "\e[32m✓\e[0m" ex = "\e[31m✘\e[0m" puts "Validating graphql-pro v#{version}" puts " - Checking for graphql-pro credentials..." creds = `bundle config gems.graphql.pro`[/[a-z0-9]{11}:[a-z0-9]{11}/] if creds.nil? puts " #{ex} failed, please set with `bundle config gems.graphql.pro $MY_CREDENTIALS`" exit(1) else puts " #{check} found" end puts " - Fetching the gem..." fetch_result = `gem fetch graphql-pro -v #{version} --source https://#{creds}@gems.graphql.pro` if fetch_result.empty? puts " #{ex} failed to fetch v#{version}" exit(1) else puts " #{check} fetched" end puts " - Validating digest..." require "digest/sha2" gem_digest = Digest::SHA512.new.hexdigest(File.read("graphql-pro-#{version}.gem")) require "net/http" github_uri = URI("https://raw.githubusercontent.com/rmosolgo/graphql-ruby/master/guides/pro/checksums/graphql-pro-#{version}.txt") # Remove final newline from .txt file github_digest = Net::HTTP.get(github_uri).chomp docs_uri = URI("https://graphql-ruby.org/pro/checksums/graphql-pro-#{version}.txt") docs_digest = Net::HTTP.get(docs_uri).chomp if docs_digest == gem_digest && github_digest == gem_digest puts " #{check} validated from GitHub" puts " #{check} validated from graphql-ruby.org" else puts " #{ex} SHA mismatch:" puts " Downloaded: #{gem_digest}" puts " GitHub: #{github_digest}" puts " graphql-ruby.org: #{docs_digest}" puts "" puts " This download of graphql-pro is invalid, please open an issue:" puts " https://github.com/rmosolgo/graphql-ruby/issues/new?title=graphql-pro%20digest%20mismatch%20(#{version})" exit(1) end puts "\e[32m✔\e[0m graphql-pro #{version} validated successfully!" end end end ruby-graphql-1.9.19/lib/graphql/relay.rb000066400000000000000000000012251362601351000200720ustar00rootroot00000000000000# frozen_string_literal: true require 'graphql/relay/page_info' require 'graphql/relay/edge' require 'graphql/relay/edge_type' require 'graphql/relay/edges_instrumentation' require 'graphql/relay/base_connection' require 'graphql/relay/array_connection' require 'graphql/relay/range_add' require 'graphql/relay/relation_connection' require 'graphql/relay/mongo_relation_connection' require 'graphql/relay/global_id_resolve' require 'graphql/relay/mutation' require 'graphql/relay/node' require 'graphql/relay/connection_instrumentation' require 'graphql/relay/connection_resolve' require 'graphql/relay/connection_type' require 'graphql/relay/type_extensions' ruby-graphql-1.9.19/lib/graphql/relay/000077500000000000000000000000001362601351000175455ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/relay/array_connection.rb000066400000000000000000000044371362601351000234370ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay class ArrayConnection < BaseConnection def cursor_from_node(item) idx = (after ? index_from_cursor(after) : 0) + sliced_nodes.find_index(item) + 1 encode(idx.to_s) end def has_next_page if first # There are more items after these items sliced_nodes.count > first elsif GraphQL::Relay::ConnectionType.bidirectional_pagination && before # The original array is longer than the `before` index index_from_cursor(before) < nodes.length + 1 else false end end def has_previous_page if last # There are items preceding the ones in this result sliced_nodes.count > last elsif GraphQL::Relay::ConnectionType.bidirectional_pagination && after # We've paginated into the Array a bit, there are some behind us index_from_cursor(after) > 0 else false end end private def first return @first if defined? @first @first = get_limited_arg(:first) @first = max_page_size if @first && max_page_size && @first > max_page_size @first end def last return @last if defined? @last @last = get_limited_arg(:last) @last = max_page_size if @last && max_page_size && @last > max_page_size @last end # apply first / last limit results def paged_nodes @paged_nodes ||= begin items = sliced_nodes items = items.first(first) if first items = items.last(last) if last items = items.first(max_page_size) if max_page_size && !first && !last items end end # Apply cursors to edges def sliced_nodes @sliced_nodes ||= if before && after nodes[index_from_cursor(after)..index_from_cursor(before)-1] || [] elsif before nodes[0..index_from_cursor(before)-2] || [] elsif after nodes[index_from_cursor(after)..-1] || [] else nodes end end def index_from_cursor(cursor) decode(cursor).to_i end end BaseConnection.register_connection_implementation(Array, ArrayConnection) end end ruby-graphql-1.9.19/lib/graphql/relay/base_connection.rb000066400000000000000000000136101362601351000232240ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # Subclasses must implement: # - {#cursor_from_node}, which returns an opaque cursor for the given item # - {#sliced_nodes}, which slices by `before` & `after` # - {#paged_nodes}, which applies `first` & `last` limits # # In a subclass, you have access to # - {#nodes}, the collection which the connection will wrap # - {#first}, {#after}, {#last}, {#before} (arguments passed to the field) # - {#max_page_size} (the specified maximum page size that can be returned from a connection) # class BaseConnection # Just to encode data in the cursor, use something that won't conflict CURSOR_SEPARATOR = "---" # Map of collection class names -> connection_classes # eg `{"Array" => ArrayConnection}` CONNECTION_IMPLEMENTATIONS = {} class << self # Find a connection implementation suitable for exposing `nodes` # # @param nodes [Object] A collection of nodes (eg, Array, AR::Relation) # @return [subclass of BaseConnection] a connection Class for wrapping `nodes` def connection_for_nodes(nodes) # Check for class _names_ because classes can be redefined in Rails development nodes.class.ancestors.each do |ancestor| conn_impl = CONNECTION_IMPLEMENTATIONS[ancestor.name] if conn_impl return conn_impl end end # Should have found a connection during the loop: raise("No connection implementation to wrap #{nodes.class} (#{nodes})") end # Add `connection_class` as the connection wrapper for `nodes_class` # eg, `RelationConnection` is the implementation for `AR::Relation` # @param nodes_class [Class] A class representing a collection (eg, Array, AR::Relation) # @param connection_class [Class] A class implementing Connection methods def register_connection_implementation(nodes_class, connection_class) CONNECTION_IMPLEMENTATIONS[nodes_class.name] = connection_class end end attr_reader :nodes, :arguments, :max_page_size, :parent, :field, :context # Make a connection, wrapping `nodes` # @param nodes [Object] The collection of nodes # @param arguments [GraphQL::Query::Arguments] Query arguments # @param field [GraphQL::Field] The underlying field # @param max_page_size [Int] The maximum number of results to return # @param parent [Object] The object which this collection belongs to # @param context [GraphQL::Query::Context] The context from the field being resolved def initialize(nodes, arguments, field: nil, max_page_size: nil, parent: nil, context: nil) @context = context @nodes = nodes @arguments = arguments @field = field @parent = parent @encoder = context ? @context.schema.cursor_encoder : GraphQL::Schema::Base64Encoder @max_page_size = max_page_size.nil? && context ? @context.schema.default_max_page_size : max_page_size end def encode(data) @encoder.encode(data, nonce: true) end def decode(data) @encoder.decode(data, nonce: true) rescue ArgumentError raise GraphQL::ExecutionError, "Invalid cursor: #{data.inspect}" end # The value passed as `first:`, if there was one. Negative numbers become `0`. # @return [Integer, nil] def first @first ||= get_limited_arg(:first) end # The value passed as `after:`, if there was one # @return [String, nil] def after arguments[:after] end # The value passed as `last:`, if there was one. Negative numbers become `0`. # @return [Integer, nil] def last @last ||= get_limited_arg(:last) end # The value passed as `before:`, if there was one # @return [String, nil] def before arguments[:before] end # These are the nodes to render for this connection, # probably wrapped by {GraphQL::Relay::Edge} def edge_nodes @edge_nodes ||= paged_nodes end # Support the `pageInfo` field def page_info self end # Used by `pageInfo` def has_next_page !!(first && sliced_nodes.count > first) end # Used by `pageInfo` def has_previous_page !!(last && sliced_nodes.count > last) end # Used by `pageInfo` def start_cursor if start_node = (respond_to?(:paged_nodes_array, true) ? paged_nodes_array : paged_nodes).first return cursor_from_node(start_node) else return nil end end # Used by `pageInfo` def end_cursor if end_node = (respond_to?(:paged_nodes_array, true) ? paged_nodes_array : paged_nodes).last return cursor_from_node(end_node) else return nil end end # An opaque operation which returns a connection-specific cursor. def cursor_from_node(object) raise GraphQL::RequiredImplementationMissingError, "must return a cursor for this object/connection pair" end def inspect "#" end private # Return a sanitized `arguments[arg_name]` (don't allow negatives) def get_limited_arg(arg_name) arg_value = arguments[arg_name] if arg_value.nil? arg_value elsif arg_value < 0 0 else arg_value end end def paged_nodes raise GraphQL::RequiredImplementationMissingError, "must return nodes for this connection after paging" end def sliced_nodes raise GraphQL::RequiredImplementationMissingError, "must return all nodes for this connection after chopping off first and last" end end end end ruby-graphql-1.9.19/lib/graphql/relay/connection_instrumentation.rb000066400000000000000000000044111362601351000255540ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # Provided a GraphQL field which returns a collection of nodes, # wrap that field to expose those nodes as a connection. # # The original resolve proc is used to fetch nodes, # then a connection implementation is fetched with {BaseConnection.connection_for_nodes}. module ConnectionInstrumentation def self.default_arguments @default_arguments ||= begin argument_definitions = [ ["first", GraphQL::INT_TYPE, "Returns the first _n_ elements from the list."], ["after", GraphQL::STRING_TYPE, "Returns the elements in the list that come after the specified cursor."], ["last", GraphQL::INT_TYPE, "Returns the last _n_ elements from the list."], ["before", GraphQL::STRING_TYPE, "Returns the elements in the list that come before the specified cursor."], ] argument_definitions.reduce({}) do |memo, arg_defn| argument = GraphQL::Argument.new name, type, description = arg_defn argument.name = name argument.type = type argument.description = description memo[argument.name.to_s] = argument memo end end end # Build a connection field from a {GraphQL::Field} by: # - Merging in the default arguments # - Transforming its resolve function to return a connection object def self.instrument(type, field) # Don't apply the wrapper to class-based fields, since they # use Schema::Field::ConnectionFilter if field.connection? && !field.metadata[:type_class] connection_arguments = default_arguments.merge(field.arguments) original_resolve = field.resolve_proc original_lazy_resolve = field.lazy_resolve_proc connection_resolve = GraphQL::Relay::ConnectionResolve.new(field, original_resolve) connection_lazy_resolve = GraphQL::Relay::ConnectionResolve.new(field, original_lazy_resolve) field.redefine( resolve: connection_resolve, lazy_resolve: connection_lazy_resolve, arguments: connection_arguments, ) else field end end end end end ruby-graphql-1.9.19/lib/graphql/relay/connection_resolve.rb000066400000000000000000000024461362601351000237760ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay class ConnectionResolve def initialize(field, underlying_resolve) @field = field @underlying_resolve = underlying_resolve @max_page_size = field.connection_max_page_size end def call(obj, args, ctx) # in a lazy resolve hook, obj is the promise, # get the object that the promise was # originally derived from parent = ctx.object nodes = @underlying_resolve.call(obj, args, ctx) if nodes.nil? || ctx.schema.lazy?(nodes) || nodes.is_a?(GraphQL::Execution::Execute::Skip) || ctx.wrapped_connection nodes else ctx.wrapped_connection = true build_connection(nodes, args, parent, ctx) end end private def build_connection(nodes, args, parent, ctx) if nodes.is_a? GraphQL::ExecutionError ctx.add_error(nodes) nil else if parent.is_a?(GraphQL::Schema::Object) parent = parent.object end connection_class = GraphQL::Relay::BaseConnection.connection_for_nodes(nodes) connection_class.new(nodes, args, field: @field, max_page_size: @max_page_size, parent: parent, context: ctx) end end end end end ruby-graphql-1.9.19/lib/graphql/relay/connection_type.rb000066400000000000000000000031531362601351000232740ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay module ConnectionType class << self # @return [Boolean] If true, connection types get a `nodes` shortcut field attr_accessor :default_nodes_field # @return [Boolean] If true, connections check for reverse-direction `has*Page` values attr_accessor :bidirectional_pagination end self.default_nodes_field = false self.bidirectional_pagination = false # Create a connection which exposes edges of this type def self.create_type(wrapped_type, edge_type: nil, edge_class: GraphQL::Relay::Edge, nodes_field: ConnectionType.default_nodes_field, &block) custom_edge_class = edge_class # Any call that would trigger `wrapped_type.ensure_defined` # must be inside this lazy block, otherwise we get weird # cyclical dependency errors :S ObjectType.define do type_name = wrapped_type.is_a?(GraphQL::BaseType) ? wrapped_type.name : wrapped_type.graphql_name edge_type ||= wrapped_type.edge_type name("#{type_name}Connection") description("The connection type for #{type_name}.") field :edges, types[edge_type], "A list of edges.", edge_class: custom_edge_class, property: :edge_nodes if nodes_field field :nodes, types[wrapped_type], "A list of nodes.", property: :edge_nodes end field :pageInfo, !PageInfo, "Information to aid in pagination.", property: :page_info relay_node_type(wrapped_type) block && instance_eval(&block) end end end end end ruby-graphql-1.9.19/lib/graphql/relay/edge.rb000066400000000000000000000011321362601351000207730ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # Mostly an internal concern. # # Wraps an object as a `node`, and exposes a connection-specific `cursor`. class Edge attr_reader :node, :connection def initialize(node, connection) @node = node @connection = connection end def cursor @cursor ||= connection.cursor_from_node(node) end def parent @parent ||= connection.parent end def inspect "# #{node.inspect})>" end end end end ruby-graphql-1.9.19/lib/graphql/relay/edge_type.rb000066400000000000000000000011751362601351000220430ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay module EdgeType def self.create_type(wrapped_type, name: nil, &block) GraphQL::ObjectType.define do type_name = wrapped_type.is_a?(GraphQL::BaseType) ? wrapped_type.name : wrapped_type.graphql_name name("#{type_name}Edge") description "An edge in a connection." field :node, wrapped_type, "The item at the end of the edge." field :cursor, !types.String, "A cursor for use in pagination." relay_node_type(wrapped_type) block && instance_eval(&block) end end end end end ruby-graphql-1.9.19/lib/graphql/relay/edges_instrumentation.rb000066400000000000000000000021101362601351000244760ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay module EdgesInstrumentation def self.instrument(type, field) if field.edges? edges_resolve = EdgesResolve.new(edge_class: field.edge_class, resolve: field.resolve_proc) edges_lazy_resolve = EdgesResolve.new(edge_class: field.edge_class, resolve: field.lazy_resolve_proc) field.redefine( resolve: edges_resolve, lazy_resolve: edges_lazy_resolve, ) else field end end class EdgesResolve def initialize(edge_class:, resolve:) @edge_class = edge_class @resolve_proc = resolve end # A user's custom Connection may return a lazy object, # if so, handle it later. def call(obj, args, ctx) parent = ctx.object nodes = @resolve_proc.call(obj, args, ctx) if ctx.schema.lazy?(nodes) nodes else nodes.map { |item| @edge_class.new(item, parent) } end end end end end end ruby-graphql-1.9.19/lib/graphql/relay/global_id_resolve.rb000066400000000000000000000006631362601351000235520ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay class GlobalIdResolve def initialize(type:) @type = type end def call(obj, args, ctx) if obj.is_a?(GraphQL::Schema::Object) obj = obj.object end type = @type.respond_to?(:graphql_definition) ? @type.graphql_definition : @type ctx.query.schema.id_from_object(obj, type, ctx) end end end end ruby-graphql-1.9.19/lib/graphql/relay/mongo_relation_connection.rb000066400000000000000000000030311362601351000253220ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # A connection implementation to expose MongoDB collection objects. # It works for: # - `Mongoid::Criteria` class MongoRelationConnection < RelationConnection private def relation_offset(relation) relation.options.skip end def relation_limit(relation) relation.options.limit end def relation_count(relation) # Must perform query (hence #to_a) to count results https://jira.mongodb.org/browse/MONGOID-2325 relation.to_a.count end def limit_nodes(sliced_nodes, limit) if limit == 0 if sliced_nodes.respond_to?(:none) # added in Mongoid 4.0 sliced_nodes.without_options.none else sliced_nodes.where(id: nil) # trying to simulate #none for 3.1.7 end else sliced_nodes.limit(limit) end end end if defined?(Mongoid::Criteria) BaseConnection.register_connection_implementation(Mongoid::Criteria, MongoRelationConnection) end # Mongoid 5 and 6 if defined?(Mongoid::Relations::Targets::Enumerable) BaseConnection.register_connection_implementation(Mongoid::Relations::Targets::Enumerable, MongoRelationConnection) end # Mongoid 7 if defined?(Mongoid::Association::Referenced::HasMany::Targets::Enumerable) BaseConnection.register_connection_implementation(Mongoid::Association::Referenced::HasMany::Targets::Enumerable, MongoRelationConnection) end end end ruby-graphql-1.9.19/lib/graphql/relay/mutation.rb000066400000000000000000000141261362601351000217360ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/relay/mutation/instrumentation" require "graphql/relay/mutation/resolve" require "graphql/relay/mutation/result" module GraphQL module Relay # Define a Relay mutation: # - give it a name (used for derived inputs & outputs) # - declare its inputs # - declare its outputs # - declare the mutation procedure # # `resolve` should return a hash with a key for each of the `return_field`s # # Inputs may also contain a `clientMutationId` # # @example Updating the name of an item # UpdateNameMutation = GraphQL::Relay::Mutation.define do # name "UpdateName" # # input_field :name, !types.String # input_field :itemId, !types.ID # # return_field :item, ItemType # # resolve ->(inputs, ctx) { # item = Item.find_by_id(inputs[:id]) # item.update(name: inputs[:name]) # {item: item} # } # end # # MutationType = GraphQL::ObjectType.define do # # The mutation object exposes a field: # field :updateName, field: UpdateNameMutation.field # end # # # Then query it: # query_string = %| # mutation updateName { # updateName(input: {itemId: 1, name: "new name", clientMutationId: "1234"}) { # item { name } # clientMutationId # }| # # GraphQL::Query.new(MySchema, query_string).result # # {"data" => { # # "updateName" => { # # "item" => { "name" => "new name"}, # # "clientMutationId" => "1234" # # } # # }} # # @example Using a GraphQL::Function # class UpdateAttributes < GraphQL::Function # attr_reader :model, :return_as, :arguments # # def initialize(model:, return_as:, attributes:) # @model = model # @arguments = {} # attributes.each do |name, type| # arg_name = name.to_s # @arguments[arg_name] = GraphQL::Argument.define(name: arg_name, type: type) # end # @arguments["id"] = GraphQL::Argument.define(name: "id", type: !GraphQL::ID_TYPE) # @return_as = return_as # @attributes = attributes # end # # def type # fn = self # GraphQL::ObjectType.define do # name "Update#{fn.model.name}AttributesResponse" # field :clientMutationId, types.ID # field fn.return_as.keys[0], fn.return_as.values[0] # end # end # # def call(obj, args, ctx) # record = @model.find(args[:inputs][:id]) # new_values = {} # @attributes.each { |a| new_values[a] = args[a] } # record.update(new_values) # { @return_as => record } # end # end # # UpdateNameMutation = GraphQL::Relay::Mutation.define do # name "UpdateName" # function UpdateAttributes.new(model: Item, return_as: { item: ItemType }, attributes: {name: !types.String}) # end class Mutation include GraphQL::Define::InstanceDefinable accepts_definitions( :name, :description, :resolve, :return_type, :return_interfaces, input_field: GraphQL::Define::AssignArgument, return_field: GraphQL::Define::AssignObjectField, function: GraphQL::Define::AssignMutationFunction, ) attr_accessor :name, :description, :fields, :arguments attr_writer :return_type, :return_interfaces ensure_defined( :input_fields, :return_fields, :name, :description, :fields, :arguments, :return_type, :return_interfaces, :resolve=, :field, :result_class, :input_type ) # For backwards compat, but do we need this separate API? alias :return_fields :fields alias :input_fields :arguments def initialize @fields = {} @arguments = {} @has_generated_return_type = false end def has_generated_return_type? # Trigger the generation of the return type, if it is dynamically generated: return_type @has_generated_return_type end def resolve=(new_resolve_proc) @resolve_proc = new_resolve_proc end def field @field ||= begin relay_mutation = self field_resolve_proc = @resolve_proc GraphQL::Field.define do type(relay_mutation.return_type) description(relay_mutation.description) argument :input, !relay_mutation.input_type resolve(field_resolve_proc) mutation(relay_mutation) end end end def return_interfaces @return_interfaces ||= [] end def return_type @return_type ||= begin @has_generated_return_type = true relay_mutation = self GraphQL::ObjectType.define do name("#{relay_mutation.name}Payload") description("Autogenerated return type of #{relay_mutation.name}") field :clientMutationId, types.String, "A unique identifier for the client performing the mutation.", property: :client_mutation_id interfaces relay_mutation.return_interfaces relay_mutation.return_fields.each do |name, field_obj| field name, field: field_obj end mutation(relay_mutation) end end end def input_type @input_type ||= begin relay_mutation = self input_object_type = GraphQL::InputObjectType.define do name("#{relay_mutation.name}Input") description("Autogenerated input type of #{relay_mutation.name}") input_field :clientMutationId, types.String, "A unique identifier for the client performing the mutation." mutation(relay_mutation) end input_fields.each do |name, arg| input_object_type.arguments[name] = arg end input_object_type end end def result_class @result_class ||= Result.define_subclass(self) end end end end ruby-graphql-1.9.19/lib/graphql/relay/mutation/000077500000000000000000000000001362601351000214055ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/relay/mutation/instrumentation.rb000066400000000000000000000014751362601351000252040ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay class Mutation # @api private module Instrumentation # Modify mutation `return_field` resolves by wrapping the returned object # in a {Mutation::Result}. # # By using an instrumention, we can apply our wrapper _last_, # giving users access to the original resolve function in earlier instrumentation. def self.instrument(type, field) if field.mutation.is_a?(GraphQL::Relay::Mutation) || (field.mutation.is_a?(Class) && field.mutation < GraphQL::Schema::RelayClassicMutation) new_resolve = Mutation::Resolve.new(field.mutation, field.resolve_proc) field.redefine(resolve: new_resolve) else field end end end end end end ruby-graphql-1.9.19/lib/graphql/relay/mutation/resolve.rb000066400000000000000000000034031362601351000234110ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay class Mutation # Wrap a user-provided resolve function, # wrapping the returned value in a {Mutation::Result}. # Also, pass the `clientMutationId` to that result object. # @api private class Resolve def initialize(mutation, resolve) @mutation = mutation @resolve = resolve @wrap_result = mutation.is_a?(GraphQL::Relay::Mutation) && mutation.has_generated_return_type? @class_based = mutation.is_a?(Class) end def call(obj, args, ctx) mutation_result = begin @resolve.call(obj, args[:input], ctx) rescue GraphQL::ExecutionError => err err end ctx.schema.after_lazy(mutation_result) do |res| build_result(res, args, ctx) end end private def build_result(mutation_result, args, ctx) if mutation_result.is_a?(GraphQL::ExecutionError) ctx.add_error(mutation_result) mutation_result = nil end if mutation_result.nil? nil elsif @wrap_result if mutation_result && !mutation_result.is_a?(Hash) raise StandardError, "Expected `#{mutation_result}` to be a Hash."\ " Return a hash when using `return_field` or specify a custom `return_type`." end @mutation.result_class.new(client_mutation_id: args[:input][:clientMutationId], result: mutation_result) elsif @class_based mutation_result[:client_mutation_id] = args[:input][:client_mutation_id] mutation_result else mutation_result end end end end end end ruby-graphql-1.9.19/lib/graphql/relay/mutation/result.rb000066400000000000000000000022601362601351000232500ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay class Mutation # Use this when the mutation's return type was generated from `return_field`s. # It delegates field lookups to the hash returned from `resolve`. # @api private class Result attr_reader :client_mutation_id def initialize(client_mutation_id:, result:) @client_mutation_id = client_mutation_id result && result.each do |key, value| self.public_send("#{key}=", value) end end class << self attr_accessor :mutation end # Build a subclass whose instances have a method # for each of `mutation_defn`'s `return_field`s # @param mutation_defn [GraphQL::Relay::Mutation] # @return [Class] def self.define_subclass(mutation_defn) subclass = Class.new(self) do mutation_result_methods = mutation_defn.return_type.all_fields.map do |f| f.property || f.name end attr_accessor(*mutation_result_methods) self.mutation = mutation_defn end subclass end end end end end ruby-graphql-1.9.19/lib/graphql/relay/node.rb000066400000000000000000000020161362601351000210160ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # Helpers for working with Relay-specific Node objects. module Node # @return [GraphQL::Field] a field for finding objects by their global ID. def self.field(**kwargs, &block) # We have to define it fresh each time because # its name will be modified and its description # _may_ be modified. field = GraphQL::Types::Relay::NodeField.graphql_definition if kwargs.any? || block field = field.redefine(**kwargs, &block) end field end def self.plural_field(**kwargs, &block) field = GraphQL::Types::Relay::NodesField.graphql_definition if kwargs.any? || block field = field.redefine(**kwargs, &block) end field end # @return [GraphQL::InterfaceType] The interface which all Relay types must implement def self.interface @interface ||= GraphQL::Types::Relay::Node.graphql_definition end end end end ruby-graphql-1.9.19/lib/graphql/relay/page_info.rb000066400000000000000000000002711362601351000220210ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # Wrap a Connection and expose its page info PageInfo = GraphQL::Types::Relay::PageInfo.graphql_definition end end ruby-graphql-1.9.19/lib/graphql/relay/range_add.rb000066400000000000000000000034361362601351000220040ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # This provides some isolation from `GraphQL::Relay` internals. # # Given a list of items and a new item, it will provide a connection and an edge. # # The connection doesn't receive outside arguments, so the list of items # should be ordered and paginated before providing it here. # # @example Adding a comment to list of comments # post = Post.find(args[:postId]) # comments = post.comments # new_comment = comments.build(body: args[:body]) # new_comment.save! # # range_add = GraphQL::Relay::RangeAdd.new( # parent: post, # collection: comments, # item: new_comment, # context: ctx, # ) # # response = { # post: post, # commentsConnection: range_add.connection, # newCommentEdge: range_add.edge, # } class RangeAdd attr_reader :edge, :connection, :parent # @param collection [Object] The list of items to wrap in a connection # @param item [Object] The newly-added item (will be wrapped in `edge_class`) # @param parent [Object] The owner of `collection`, will be passed to the connection if provided # @param context [GraphQL::Query::Context] The surrounding `ctx`, will be passed to the connection if provided (this is required for cursor encoders) # @param edge_class [Class] The class to wrap `item` with def initialize(collection:, item:, parent: nil, context: nil, edge_class: Relay::Edge) connection_class = BaseConnection.connection_for_nodes(collection) @parent = parent @connection = connection_class.new(collection, {}, parent: parent, context: context) @edge = edge_class.new(item, @connection) end end end end ruby-graphql-1.9.19/lib/graphql/relay/relation_connection.rb000066400000000000000000000135131362601351000241310ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # A connection implementation to expose SQL collection objects. # It works for: # - `ActiveRecord::Relation` # - `Sequel::Dataset` class RelationConnection < BaseConnection def cursor_from_node(item) item_index = paged_nodes.index(item) if item_index.nil? raise("Can't generate cursor, item not found in connection: #{item}") else offset = item_index + 1 + ((paged_nodes_offset || 0) - (relation_offset(sliced_nodes) || 0)) if after offset += offset_from_cursor(after) elsif before offset += offset_from_cursor(before) - 1 - sliced_nodes_count end encode(offset.to_s) end end def has_next_page if first if defined?(ActiveRecord::Relation) && nodes.is_a?(ActiveRecord::Relation) initial_offset = after ? offset_from_cursor(after) : 0 return paged_nodes.length >= first && nodes.offset(first + initial_offset).exists? end return paged_nodes.length >= first && sliced_nodes_count > first end if GraphQL::Relay::ConnectionType.bidirectional_pagination && last return sliced_nodes_count >= last end false end def has_previous_page if last paged_nodes.length >= last && sliced_nodes_count > last elsif GraphQL::Relay::ConnectionType.bidirectional_pagination && after # We've already paginated through the collection a bit, # there are nodes behind us offset_from_cursor(after) > 0 else false end end def first return @first if defined? @first @first = get_limited_arg(:first) @first = max_page_size if @first && max_page_size && @first > max_page_size @first end def last return @last if defined? @last @last = get_limited_arg(:last) @last = max_page_size if @last && max_page_size && @last > max_page_size @last end private # apply first / last limit results # @return [Array] def paged_nodes return @paged_nodes if defined? @paged_nodes items = sliced_nodes if first if relation_limit(items).nil? || relation_limit(items) > first items = items.limit(first) end end if last if relation_limit(items) if last <= relation_limit(items) offset = (relation_offset(items) || 0) + (relation_limit(items) - last) items = items.offset(offset).limit(last) end else slice_count = relation_count(items) offset = (relation_offset(items) || 0) + slice_count - [last, slice_count].min items = items.offset(offset).limit(last) end end if max_page_size && !first && !last if relation_limit(items).nil? || relation_limit(items) > max_page_size items = items.limit(max_page_size) end end # Store this here so we can convert the relation to an Array # (this avoids an extra DB call on Sequel) @paged_nodes_offset = relation_offset(items) @paged_nodes = items.to_a end def paged_nodes_offset paged_nodes && @paged_nodes_offset end def relation_offset(relation) if relation.respond_to?(:offset_value) relation.offset_value else relation.opts[:offset] end end def relation_limit(relation) if relation.respond_to?(:limit_value) relation.limit_value else relation.opts[:limit] end end # If a relation contains a `.group` clause, a `.count` will return a Hash. def relation_count(relation) count_or_hash = if(defined?(ActiveRecord::Relation) && relation.is_a?(ActiveRecord::Relation)) relation.respond_to?(:unscope)? relation.unscope(:order).count(:all) : relation.count(:all) else # eg, Sequel::Dataset, don't mess up others relation.count end count_or_hash.is_a?(Integer) ? count_or_hash : count_or_hash.length end # Apply cursors to edges def sliced_nodes return @sliced_nodes if defined? @sliced_nodes @sliced_nodes = nodes if after offset = (relation_offset(@sliced_nodes) || 0) + offset_from_cursor(after) @sliced_nodes = @sliced_nodes.offset(offset) end if before && after if offset_from_cursor(after) < offset_from_cursor(before) @sliced_nodes = limit_nodes(@sliced_nodes, offset_from_cursor(before) - offset_from_cursor(after) - 1) else @sliced_nodes = limit_nodes(@sliced_nodes, 0) end elsif before @sliced_nodes = limit_nodes(@sliced_nodes, offset_from_cursor(before) - 1) end @sliced_nodes end def limit_nodes(sliced_nodes, limit) if limit > 0 || defined?(ActiveRecord::Relation) && sliced_nodes.is_a?(ActiveRecord::Relation) sliced_nodes.limit(limit) else sliced_nodes.where(false) end end def sliced_nodes_count return @sliced_nodes_count if defined? @sliced_nodes_count # If a relation contains a `.group` clause, a `.count` will return a Hash. @sliced_nodes_count = relation_count(sliced_nodes) end def offset_from_cursor(cursor) decode(cursor).to_i end end if defined?(ActiveRecord::Relation) BaseConnection.register_connection_implementation(ActiveRecord::Relation, RelationConnection) end if defined?(Sequel::Dataset) BaseConnection.register_connection_implementation(Sequel::Dataset, RelationConnection) end end end ruby-graphql-1.9.19/lib/graphql/relay/type_extensions.rb000066400000000000000000000017041362601351000233340ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # Mixin for Relay-related methods in type objects # (used by BaseType and Schema::Member). module TypeExtensions # @return [GraphQL::ObjectType] The default connection type for this object type def connection_type @connection_type ||= define_connection end # Define a custom connection type for this object type # @return [GraphQL::ObjectType] def define_connection(**kwargs, &block) GraphQL::Relay::ConnectionType.create_type(self, **kwargs, &block) end # @return [GraphQL::ObjectType] The default edge type for this object type def edge_type @edge_type ||= define_edge end # Define a custom edge type for this object type # @return [GraphQL::ObjectType] def define_edge(**kwargs, &block) GraphQL::Relay::EdgeType.create_type(self, **kwargs, &block) end end end end ruby-graphql-1.9.19/lib/graphql/runtime_type_error.rb000066400000000000000000000001411362601351000227070ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class RuntimeTypeError < GraphQL::Error end end ruby-graphql-1.9.19/lib/graphql/scalar_type.rb000066400000000000000000000077611362601351000212770ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # # GraphQL::ScalarType # # Scalars are plain values. They are leaf nodes in a GraphQL query tree. # # ## Built-in Scalars # # `GraphQL` comes with standard built-in scalars: # # |Constant | `.define` helper| # |-------|--------| # |`GraphQL::STRING_TYPE` | `types.String`| # |`GraphQL::INT_TYPE` | `types.Int`| # |`GraphQL::FLOAT_TYPE` | `types.Float`| # |`GraphQL::ID_TYPE` | `types.ID`| # |`GraphQL::BOOLEAN_TYPE` | `types.Boolean`| # # (`types` is an instance of `GraphQL::Definition::TypeDefiner`; `.String`, `.Float`, etc are methods which return built-in scalars.) # # ## Custom Scalars # # You can define custom scalars for your GraphQL server. It requires some special functions: # # - `coerce_input` is used to prepare incoming values for GraphQL execution. (Incoming values come from variables or literal values in the query string.) # - `coerce_result` is used to turn Ruby values _back_ into serializable values for query responses. # # @example defining a type for Time # TimeType = GraphQL::ScalarType.define do # name "Time" # description "Time since epoch in seconds" # # coerce_input ->(value, ctx) { Time.at(Float(value)) } # coerce_result ->(value, ctx) { value.to_f } # end # # # You can customize the error message for invalid input values by raising a `GraphQL::CoercionError` within `coerce_input`: # # @example raising a custom error message # TimeType = GraphQL::ScalarType.define do # name "Time" # description "Time since epoch in seconds" # # coerce_input ->(value, ctx) do # begin # Time.at(Float(value)) # rescue ArgumentError # raise GraphQL::CoercionError, "cannot coerce `#{value.inspect}` to Float" # end # end # # coerce_result ->(value, ctx) { value.to_f } # end # # This will result in the message of the `GraphQL::CoercionError` being used in the error response: # # @example custom error response # {"message"=>"cannot coerce `"2"` to Float", "locations"=>[{"line"=>3, "column"=>9}], "fields"=>["arg"]} # class ScalarType < GraphQL::BaseType accepts_definitions :coerce, :coerce_input, :coerce_result ensure_defined :coerce_non_null_input, :coerce_result module NoOpCoerce def self.call(val, ctx) val end end def initialize super self.coerce = NoOpCoerce end def coerce=(proc) self.coerce_input = proc self.coerce_result = proc end def coerce_input=(coerce_input_fn) if !coerce_input_fn.nil? @coerce_input_proc = ensure_two_arg(coerce_input_fn, :coerce_input) end end def coerce_result(value, ctx = nil) if ctx.nil? warn_deprecated_coerce("coerce_isolated_result") ctx = GraphQL::Query::NullContext end @coerce_result_proc.call(value, ctx) end def coerce_result=(coerce_result_fn) if !coerce_result_fn.nil? @coerce_result_proc = ensure_two_arg(coerce_result_fn, :coerce_result) end end def kind GraphQL::TypeKinds::SCALAR end private def ensure_two_arg(callable, method_name) GraphQL::BackwardsCompatibility.wrap_arity(callable, from: 1, to: 2, name: "#{name}.#{method_name}(val, ctx)") end def coerce_non_null_input(value, ctx) @coerce_input_proc.call(raw_coercion_input(value), ctx) end def raw_coercion_input(value) if value.is_a?(GraphQL::Language::Nodes::InputObject) value.to_h elsif value.is_a?(Array) value.map { |element| raw_coercion_input(element) } else value end end def validate_non_null_input(value, ctx) result = Query::InputValidationResult.new if value.is_a?(GraphQL::Language::Nodes::Enum) || coerce_non_null_input(value, ctx).nil? result.add_problem("Could not coerce value #{GraphQL::Language.serialize(value)} to #{name}") end result end end end ruby-graphql-1.9.19/lib/graphql/schema.rb000066400000000000000000001404041362601351000202210ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/schema/base_64_encoder" require "graphql/schema/catchall_middleware" require "graphql/schema/default_parse_error" require "graphql/schema/default_type_error" require "graphql/schema/find_inherited_value" require "graphql/schema/finder" require "graphql/schema/invalid_type_error" require "graphql/schema/introspection_system" require "graphql/schema/late_bound_type" require "graphql/schema/middleware_chain" require "graphql/schema/null_mask" require "graphql/schema/possible_types" require "graphql/schema/rescue_middleware" require "graphql/schema/timeout" require "graphql/schema/timeout_middleware" require "graphql/schema/traversal" require "graphql/schema/type_expression" require "graphql/schema/unique_within_type" require "graphql/schema/validation" require "graphql/schema/warden" require "graphql/schema/build_from_definition" require "graphql/schema/member" require "graphql/schema/wrapper" require "graphql/schema/list" require "graphql/schema/non_null" require "graphql/schema/argument" require "graphql/schema/enum_value" require "graphql/schema/enum" require "graphql/schema/field_extension" require "graphql/schema/field" require "graphql/schema/input_object" require "graphql/schema/interface" require "graphql/schema/scalar" require "graphql/schema/object" require "graphql/schema/union" require "graphql/schema/directive" require "graphql/schema/directive/include" require "graphql/schema/directive/skip" require "graphql/schema/directive/feature" require "graphql/schema/directive/transform" require "graphql/schema/type_membership" require "graphql/schema/resolver" require "graphql/schema/mutation" require "graphql/schema/relay_classic_mutation" require "graphql/schema/subscription" module GraphQL # A GraphQL schema which may be queried with {GraphQL::Query}. # # The {Schema} contains: # # - types for exposing your application # - query analyzers for assessing incoming queries (including max depth & max complexity restrictions) # - execution strategies for running incoming queries # - middleware for interacting with execution # # Schemas start with root types, {Schema#query}, {Schema#mutation} and {Schema#subscription}. # The schema will traverse the tree of fields & types, using those as starting points. # Any undiscoverable types may be provided with the `types` configuration. # # Schemas can restrict large incoming queries with `max_depth` and `max_complexity` configurations. # (These configurations can be overridden by specific calls to {Schema#execute}) # # Schemas can specify how queries should be executed against them. # `query_execution_strategy`, `mutation_execution_strategy` and `subscription_execution_strategy` # each apply to corresponding root types. # # A schema accepts a `Relay::GlobalNodeIdentification` instance for use with Relay IDs. # # @example defining a schema # MySchema = GraphQL::Schema.define do # query QueryType # middleware PermissionMiddleware # rescue_from(ActiveRecord::RecordNotFound) { "Not found" } # # If types are only connected by way of interfaces, they must be added here # orphan_types ImageType, AudioType # end # class Schema extend Forwardable extend GraphQL::Schema::Member::AcceptsDefinition include GraphQL::Define::InstanceDefinable extend GraphQL::Schema::FindInheritedValue accepts_definitions \ :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy, :max_depth, :max_complexity, :default_max_page_size, :orphan_types, :resolve_type, :type_error, :parse_error, :error_bubbling, :raise_definition_error, :object_from_id, :id_from_object, :default_mask, :cursor_encoder, # If these are given as classes, normalize them. Accept `nil` when building from string. query: ->(schema, t) { schema.query = t.respond_to?(:graphql_definition) ? t.graphql_definition : t }, mutation: ->(schema, t) { schema.mutation = t.respond_to?(:graphql_definition) ? t.graphql_definition : t }, subscription: ->(schema, t) { schema.subscription = t.respond_to?(:graphql_definition) ? t.graphql_definition : t }, disable_introspection_entry_points: ->(schema) { schema.disable_introspection_entry_points = true }, disable_schema_introspection_entry_point: ->(schema) { schema.disable_schema_introspection_entry_point = true }, disable_type_introspection_entry_point: ->(schema) { schema.disable_type_introspection_entry_point = true }, directives: ->(schema, directives) { schema.directives = directives.reduce({}) { |m, d| m[d.name] = d; m } }, directive: ->(schema, directive) { schema.directives[directive.graphql_name] = directive }, instrument: ->(schema, type, instrumenter, after_built_ins: false) { if type == :field && after_built_ins type = :field_after_built_ins end schema.instrumenters[type] << instrumenter }, query_analyzer: ->(schema, analyzer) { if analyzer == GraphQL::Authorization::Analyzer warn("The Authorization query analyzer is deprecated. Authorizing at query runtime is generally a better idea.") end schema.query_analyzers << analyzer }, multiplex_analyzer: ->(schema, analyzer) { schema.multiplex_analyzers << analyzer }, middleware: ->(schema, middleware) { schema.middleware << middleware }, lazy_resolve: ->(schema, lazy_class, lazy_value_method) { schema.lazy_methods.set(lazy_class, lazy_value_method) }, rescue_from: ->(schema, err_class, &block) { schema.rescue_from(err_class, &block) }, tracer: ->(schema, tracer) { schema.tracers.push(tracer) } ensure_defined :introspection_system attr_accessor \ :query, :mutation, :subscription, :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy, :max_depth, :max_complexity, :default_max_page_size, :orphan_types, :directives, :query_analyzers, :multiplex_analyzers, :instrumenters, :lazy_methods, :cursor_encoder, :ast_node, :raise_definition_error, :introspection_namespace, :analysis_engine # [Boolean] True if this object bubbles validation errors up from a field into its parent InputObject, if there is one. attr_accessor :error_bubbling # Single, long-lived instance of the provided subscriptions class, if there is one. # @return [GraphQL::Subscriptions] attr_accessor :subscriptions # @return [MiddlewareChain] MiddlewareChain which is applied to fields during execution attr_accessor :middleware # @return [<#call(member, ctx)>] A callable for filtering members of the schema # @see {Query.new} for query-specific filters with `except:` attr_accessor :default_mask # @see {GraphQL::Query::Context} The parent class of these classes # @return [Class] Instantiated for each query attr_accessor :context_class # [Boolean] True if this object disables the introspection entry point fields attr_accessor :disable_introspection_entry_points # [Boolean] True if this object disables the __schema introspection entry point field attr_accessor :disable_schema_introspection_entry_point # [Boolean] True if this object disables the __type introspection entry point field attr_accessor :disable_type_introspection_entry_point class << self attr_writer :default_execution_strategy end def default_filter GraphQL::Filter.new(except: default_mask) end # @return [Array<#trace(key, data)>] Tracers applied to every query # @see {Query#tracers} for query-specific tracers attr_reader :tracers DYNAMIC_FIELDS = ["__type", "__typename", "__schema"].freeze EMPTY_ARRAY = [].freeze EMPTY_HASH = {}.freeze attr_reader :static_validator, :object_from_id_proc, :id_from_object_proc, :resolve_type_proc def initialize @tracers = [] @definition_error = nil @orphan_types = [] @directives = self.class.default_directives @static_validator = GraphQL::StaticValidation::Validator.new(schema: self) @middleware = MiddlewareChain.new(final_step: GraphQL::Execution::Execute::FieldResolveStep) @query_analyzers = [] @multiplex_analyzers = [] @resolve_type_proc = nil @object_from_id_proc = nil @id_from_object_proc = nil @type_error_proc = DefaultTypeError @parse_error_proc = DefaultParseError @instrumenters = Hash.new { |h, k| h[k] = [] } @lazy_methods = GraphQL::Execution::Lazy::LazyMethodMap.new @lazy_methods.set(GraphQL::Execution::Lazy, :value) @cursor_encoder = Base64Encoder # Default to the built-in execution strategy: @analysis_engine = GraphQL::Analysis @query_execution_strategy = self.class.default_execution_strategy @mutation_execution_strategy = self.class.default_execution_strategy @subscription_execution_strategy = self.class.default_execution_strategy @default_mask = GraphQL::Schema::NullMask @rebuilding_artifacts = false @context_class = GraphQL::Query::Context @introspection_namespace = nil @introspection_system = nil @interpreter = false @error_bubbling = false @disable_introspection_entry_points = false @disable_schema_introspection_entry_point = false @disable_type_introspection_entry_point = false end # @return [Boolean] True if using the new {GraphQL::Execution::Interpreter} def interpreter? @interpreter end # @api private attr_writer :interpreter def inspect "#<#{self.class.name} ...>" end def initialize_copy(other) super @orphan_types = other.orphan_types.dup @directives = other.directives.dup @static_validator = GraphQL::StaticValidation::Validator.new(schema: self) @middleware = other.middleware.dup @query_analyzers = other.query_analyzers.dup @multiplex_analyzers = other.multiplex_analyzers.dup @tracers = other.tracers.dup @possible_types = GraphQL::Schema::PossibleTypes.new(self) @lazy_methods = other.lazy_methods.dup @instrumenters = Hash.new { |h, k| h[k] = [] } other.instrumenters.each do |key, insts| @instrumenters[key].concat(insts) end if other.rescues? @rescue_middleware = other.rescue_middleware end # This will be rebuilt when it's requested # or during a later `define` call @types = nil @introspection_system = nil end def rescue_from(*args, &block) rescue_middleware.rescue_from(*args, &block) end def remove_handler(*args, &block) rescue_middleware.remove_handler(*args, &block) end def using_ast_analysis? @analysis_engine == GraphQL::Analysis::AST end # For forwards-compatibility with Schema classes alias :graphql_definition :itself # Validate a query string according to this schema. # @param string_or_document [String, GraphQL::Language::Nodes::Document] # @return [Array] def validate(string_or_document, rules: nil, context: nil) doc = if string_or_document.is_a?(String) GraphQL.parse(string_or_document) else string_or_document end query = GraphQL::Query.new(self, document: doc, context: context) validator_opts = { schema: self } rules && (validator_opts[:rules] = rules) validator = GraphQL::StaticValidation::Validator.new(**validator_opts) res = validator.validate(query) res[:errors] end def define(**kwargs, &block) super ensure_defined # Assert that all necessary configs are present: validation_error = Validation.validate(self) validation_error && raise(GraphQL::RequiredImplementationMissingError, validation_error) rebuild_artifacts @definition_error = nil nil rescue StandardError => err if @raise_definition_error || err.is_a?(CyclicalDefinitionError) || err.is_a?(GraphQL::RequiredImplementationMissingError) raise else # Raise this error _later_ to avoid messing with Rails constant loading @definition_error = err end nil end # Attach `instrumenter` to this schema for instrumenting events of `instrumentation_type`. # @param instrumentation_type [Symbol] # @param instrumenter # @return [void] def instrument(instrumentation_type, instrumenter) @instrumenters[instrumentation_type] << instrumenter if instrumentation_type == :field rebuild_artifacts end end # @return [Array] The root types of this schema def root_types @root_types ||= begin rebuild_artifacts @root_types end end # @see [GraphQL::Schema::Warden] Restricted access to members of a schema # @return [GraphQL::Schema::TypeMap] `{ name => type }` pairs of types in this schema def types @types ||= begin rebuild_artifacts @types end end # @api private def introspection_system @introspection_system ||= begin rebuild_artifacts @introspection_system end end # Returns a list of Arguments and Fields referencing a certain type # @param type_name [String] # @return [Hash] def references_to(type_name) rebuild_artifacts unless defined?(@type_reference_map) @type_reference_map.fetch(type_name, []) end # Returns a list of Union types in which a type is a member # @param type [GraphQL::ObjectType] # @return [Array] list of union types of which the type is a member def union_memberships(type) rebuild_artifacts unless defined?(@union_memberships) @union_memberships.fetch(type.name, []) end # Execute a query on itself. Raises an error if the schema definition is invalid. # @see {Query#initialize} for arguments. # @return [Hash] query result, ready to be serialized as JSON def execute(query_str = nil, **kwargs) if query_str kwargs[:query] = query_str end # Some of the query context _should_ be passed to the multiplex, too multiplex_context = if (ctx = kwargs[:context]) { backtrace: ctx[:backtrace], tracers: ctx[:tracers], } else {} end # Since we're running one query, don't run a multiplex-level complexity analyzer all_results = multiplex([kwargs], max_complexity: nil, context: multiplex_context) all_results[0] end # Execute several queries on itself. Raises an error if the schema definition is invalid. # @example Run several queries at once # context = { ... } # queries = [ # { query: params[:query_1], variables: params[:variables_1], context: context }, # { query: params[:query_2], variables: params[:variables_2], context: context }, # ] # results = MySchema.multiplex(queries) # render json: { # result_1: results[0], # result_2: results[1], # } # # @see {Query#initialize} for query keyword arguments # @see {Execution::Multiplex#run_queries} for multiplex keyword arguments # @param queries [Array] Keyword arguments for each query # @param context [Hash] Multiplex-level context # @return [Array] One result for each query in the input def multiplex(queries, **kwargs) with_definition_error_check { GraphQL::Execution::Multiplex.run_all(self, queries, **kwargs) } end # Search for a schema member using a string path # @example Finding a Field # Schema.find("Ensemble.musicians") # # @see {GraphQL::Schema::Finder} for more examples # @param path [String] A dot-separated path to the member # @raise [Schema::Finder::MemberNotFoundError] if path could not be found # @return [GraphQL::BaseType, GraphQL::Field, GraphQL::Argument, GraphQL::Directive] A GraphQL Schema Member def find(path) rebuild_artifacts unless defined?(@finder) @find_cache[path] ||= @finder.find(path) end # Resolve field named `field_name` for type `parent_type`. # Handles dynamic fields `__typename`, `__type` and `__schema`, too # @param parent_type [String, GraphQL::BaseType] # @param field_name [String] # @return [GraphQL::Field, nil] The field named `field_name` on `parent_type` # @see [GraphQL::Schema::Warden] Restricted access to members of a schema def get_field(parent_type, field_name) with_definition_error_check do parent_type_name = case parent_type when GraphQL::BaseType parent_type.name when String parent_type else raise "Unexpected parent_type: #{parent_type}" end defined_field = @instrumented_field_map[parent_type_name][field_name] if defined_field defined_field elsif parent_type == query && (entry_point_field = introspection_system.entry_point(name: field_name)) entry_point_field elsif (dynamic_field = introspection_system.dynamic_field(name: field_name)) dynamic_field else nil end end end # Fields for this type, after instrumentation is applied # @return [Hash] def get_fields(type) @instrumented_field_map[type.graphql_name] end def type_from_ast(ast_node) GraphQL::Schema::TypeExpression.build_type(self.types, ast_node) end # @see [GraphQL::Schema::Warden] Restricted access to members of a schema # @param type_defn [GraphQL::InterfaceType, GraphQL::UnionType] the type whose members you want to retrieve # @param context [GraphQL::Query::Context] The context for the current query # @return [Array] types which belong to `type_defn` in this schema def possible_types(type_defn, context = GraphQL::Query::NullContext) @possible_types ||= GraphQL::Schema::PossibleTypes.new(self) @possible_types.possible_types(type_defn, context) end # @see [GraphQL::Schema::Warden] Resticted access to root types # @return [GraphQL::ObjectType, nil] def root_type_for_operation(operation) case operation when "query" query when "mutation" mutation when "subscription" subscription else raise ArgumentError, "unknown operation type: #{operation}" end end def execution_strategy_for_operation(operation) case operation when "query" query_execution_strategy when "mutation" mutation_execution_strategy when "subscription" subscription_execution_strategy else raise ArgumentError, "unknown operation type: #{operation}" end end # Determine the GraphQL type for a given object. # This is required for unions and interfaces (including Relay's `Node` interface) # @see [GraphQL::Schema::Warden] Restricted access to members of a schema # @param type [GraphQL::UnionType, GraphQL:InterfaceType] the abstract type which is being resolved # @param object [Any] An application object which GraphQL is currently resolving on # @param ctx [GraphQL::Query::Context] The context for the current query # @return [GraphQL::ObjectType] The type for exposing `object` in GraphQL def resolve_type(type, object, ctx = :__undefined__) check_resolved_type(type, object, ctx) do |ok_type, ok_object, ok_ctx| if @resolve_type_proc.nil? raise(GraphQL::RequiredImplementationMissingError, "Can't determine GraphQL type for: #{ok_object.inspect}, define `resolve_type (type, obj, ctx) -> { ... }` inside `Schema.define`.") end @resolve_type_proc.call(ok_type, ok_object, ok_ctx) end end # This is a compatibility hack so that instance-level and class-level # methods can get correctness checks without calling one another # @api private def check_resolved_type(type, object, ctx = :__undefined__) if ctx == :__undefined__ # Old method signature ctx = object object = type type = nil end if object.is_a?(GraphQL::Schema::Object) object = object.object end if type.respond_to?(:graphql_definition) type = type.graphql_definition end # Prefer a type-local function; fall back to the schema-level function type_proc = type && type.resolve_type_proc type_result = if type_proc type_proc.call(object, ctx) else yield(type, object, ctx) end if type_result.nil? nil else after_lazy(type_result) do |resolved_type_result| if resolved_type_result.respond_to?(:graphql_definition) resolved_type_result = resolved_type_result.graphql_definition end if !resolved_type_result.is_a?(GraphQL::BaseType) type_str = "#{resolved_type_result} (#{resolved_type_result.class.name})" raise "resolve_type(#{object}) returned #{type_str}, but it should return a GraphQL type" else resolved_type_result end end end end def resolve_type=(new_resolve_type_proc) callable = GraphQL::BackwardsCompatibility.wrap_arity(new_resolve_type_proc, from: 2, to: 3, last: true, name: "Schema#resolve_type(type, obj, ctx)") @resolve_type_proc = callable end # Fetch an application object by its unique id # @param id [String] A unique identifier, provided previously by this GraphQL schema # @param ctx [GraphQL::Query::Context] The context for the current query # @return [Any] The application object identified by `id` def object_from_id(id, ctx) if @object_from_id_proc.nil? raise(GraphQL::RequiredImplementationMissingError, "Can't fetch an object for id \"#{id}\" because the schema's `object_from_id (id, ctx) -> { ... }` function is not defined") else @object_from_id_proc.call(id, ctx) end end # @param new_proc [#call] A new callable for fetching objects by ID def object_from_id=(new_proc) @object_from_id_proc = new_proc end # When we encounter a type error during query execution, we call this hook. # # You can use this hook to write a log entry, # add a {GraphQL::ExecutionError} to the response (with `ctx.add_error`) # or raise an exception and halt query execution. # # @example A `nil` is encountered by a non-null field # type_error ->(err, query_ctx) { # err.is_a?(GraphQL::InvalidNullError) # => true # } # # @example An object doesn't resolve to one of a {UnionType}'s members # type_error ->(err, query_ctx) { # err.is_a?(GraphQL::UnresolvedTypeError) # => true # } # # @see {DefaultTypeError} is the default behavior. # @param err [GraphQL::TypeError] The error encountered during execution # @param ctx [GraphQL::Query::Context] The context for the field where the error occurred # @return void def type_error(err, ctx) @type_error_proc.call(err, ctx) end # @param new_proc [#call] A new callable for handling type errors during execution def type_error=(new_proc) @type_error_proc = new_proc end # Can't delegate to `class` alias :_schema_class :class def_delegators :_schema_class, :visible?, :accessible?, :authorized?, :unauthorized_object, :unauthorized_field, :inaccessible_fields def_delegators :_schema_class, :directive def_delegators :_schema_class, :error_handler # A function to call when {#execute} receives an invalid query string # # @see {DefaultParseError} is the default behavior. # @param err [GraphQL::ParseError] The error encountered during parsing # @param ctx [GraphQL::Query::Context] The context for the query where the error occurred # @return void def parse_error(err, ctx) @parse_error_proc.call(err, ctx) end # @param new_proc [#call] A new callable for handling parse errors during execution def parse_error=(new_proc) @parse_error_proc = new_proc end # Get a unique identifier from this object # @param object [Any] An application object # @param type [GraphQL::BaseType] The current type definition # @param ctx [GraphQL::Query::Context] the context for the current query # @return [String] a unique identifier for `object` which clients can use to refetch it def id_from_object(object, type, ctx) if @id_from_object_proc.nil? raise(GraphQL::RequiredImplementationMissingError, "Can't generate an ID for #{object.inspect} of type #{type}, schema's `id_from_object` must be defined") else @id_from_object_proc.call(object, type, ctx) end end # @param new_proc [#call] A new callable for generating unique IDs def id_from_object=(new_proc) @id_from_object_proc = new_proc end # Create schema with the result of an introspection query. # @param introspection_result [Hash] A response from {GraphQL::Introspection::INTROSPECTION_QUERY} # @return [GraphQL::Schema] the schema described by `input` def self.from_introspection(introspection_result) GraphQL::Schema::Loader.load(introspection_result) end # Create schema from an IDL schema or file containing an IDL definition. # @param definition_or_path [String] A schema definition string, or a path to a file containing the definition # @param default_resolve [<#call(type, field, obj, args, ctx)>] A callable for handling field resolution # @param parser [Object] An object for handling definition string parsing (must respond to `parse`) # @return [GraphQL::Schema] the schema described by `document` def self.from_definition(definition_or_path, default_resolve: BuildFromDefinition::DefaultResolve, parser: BuildFromDefinition::DefaultParser) # If the file ends in `.graphql`, treat it like a filepath definition = if definition_or_path.end_with?(".graphql") File.read(definition_or_path) else definition_or_path end GraphQL::Schema::BuildFromDefinition.from_definition(definition, default_resolve: default_resolve, parser: parser) end # Error that is raised when [#Schema#from_definition] is passed an invalid schema definition string. class InvalidDocumentError < Error; end; # @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered wtih {#lazy_resolve}. def lazy_method_name(obj) @lazy_methods.get(obj) end # @return [Boolean] True if this object should be lazily resolved def lazy?(obj) !!lazy_method_name(obj) end # Return the GraphQL IDL for the schema # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] # @return [String] def to_definition(only: nil, except: nil, context: {}) GraphQL::Schema::Printer.print_schema(self, only: only, except: except, context: context) end # Return the GraphQL::Language::Document IDL AST for the schema # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] # @return [GraphQL::Language::Document] def to_document(only: nil, except: nil, context: {}) GraphQL::Language::DocumentFromSchemaDefinition.new(self, only: only, except: except, context: context).document end # Return the Hash response of {Introspection::INTROSPECTION_QUERY}. # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] # @return [Hash] GraphQL result def as_json(only: nil, except: nil, context: {}) execute(Introspection::INTROSPECTION_QUERY, only: only, except: except, context: context).to_h end # Returns the JSON response of {Introspection::INTROSPECTION_QUERY}. # @see {#as_json} # @return [String] def to_json(*args) JSON.pretty_generate(as_json(*args)) end class << self extend Forwardable # For compatibility, these methods all: # - Cause the Schema instance to be created, if it hasn't been created yet # - Delegate to that instance # Eventually, the methods will be moved into this class, removing the need for the singleton. def_delegators :graphql_definition, # Schema structure :as_json, :to_json, :to_document, :to_definition, :ast_node, # Execution :execute, :multiplex, :static_validator, :introspection_system, :query_analyzers, :tracers, :instrumenters, :execution_strategy_for_operation, :validate, :multiplex_analyzers, :lazy?, :lazy_method_name, :after_lazy, :sync_lazy, # Configuration :analysis_engine, :analysis_engine=, :using_ast_analysis?, :interpreter?, :max_complexity=, :max_depth=, :error_bubbling=, :metadata, :default_mask, :default_filter, :redefine, :id_from_object_proc, :object_from_id_proc, :id_from_object=, :object_from_id=, :remove_handler, # Members :types, :get_fields, :find, :root_type_for_operation, :subscriptions, :union_memberships, :get_field, :root_types, :references_to, :type_from_ast, :possible_types, :disable_introspection_entry_points=, :disable_schema_introspection_entry_point=, :disable_type_introspection_entry_point= def graphql_definition @graphql_definition ||= to_graphql end def use(plugin, **options) own_plugins << [plugin, options] end def plugins find_inherited_value(:plugins, EMPTY_ARRAY) + own_plugins end def to_graphql schema_defn = self.new schema_defn.raise_definition_error = true schema_defn.query = query schema_defn.mutation = mutation schema_defn.subscription = subscription schema_defn.max_complexity = max_complexity schema_defn.error_bubbling = error_bubbling schema_defn.max_depth = max_depth schema_defn.default_max_page_size = default_max_page_size schema_defn.orphan_types = orphan_types schema_defn.disable_introspection_entry_points = disable_introspection_entry_points? schema_defn.disable_schema_introspection_entry_point = disable_schema_introspection_entry_point? schema_defn.disable_type_introspection_entry_point = disable_type_introspection_entry_point? prepped_dirs = {} directives.each { |k, v| prepped_dirs[k] = v.graphql_definition} schema_defn.directives = prepped_dirs schema_defn.introspection_namespace = introspection schema_defn.resolve_type = method(:resolve_type) schema_defn.object_from_id = method(:object_from_id) schema_defn.id_from_object = method(:id_from_object) schema_defn.type_error = method(:type_error) schema_defn.context_class = context_class schema_defn.cursor_encoder = cursor_encoder schema_defn.tracers.concat(tracers) schema_defn.query_analyzers.concat(query_analyzers) schema_defn.middleware.concat(all_middleware) schema_defn.multiplex_analyzers.concat(multiplex_analyzers) schema_defn.query_execution_strategy = query_execution_strategy schema_defn.mutation_execution_strategy = mutation_execution_strategy schema_defn.subscription_execution_strategy = subscription_execution_strategy all_instrumenters.each do |step, insts| insts.each do |inst| schema_defn.instrumenters[step] << inst end end lazy_classes.each do |lazy_class, value_method| schema_defn.lazy_methods.set(lazy_class, value_method) end rescues.each do |err_class, handler| schema_defn.rescue_from(err_class, &handler) end if plugins.any? schema_plugins = plugins # TODO don't depend on .define schema_defn = schema_defn.redefine do schema_plugins.each do |plugin, options| if options.any? use(plugin, **options) else use(plugin) end end end end # Do this after `plugins` since Interpreter is a plugin if schema_defn.query_execution_strategy != GraphQL::Execution::Interpreter schema_defn.instrumenters[:query] << GraphQL::Schema::Member::Instrumentation end schema_defn.send(:rebuild_artifacts) schema_defn end def query(new_query_object = nil) if new_query_object @query_object = new_query_object else query_object = @query_object || find_inherited_value(:query) query_object.respond_to?(:graphql_definition) ? query_object.graphql_definition : query_object end end def mutation(new_mutation_object = nil) if new_mutation_object @mutation_object = new_mutation_object else mutation_object = @mutation_object || find_inherited_value(:mutation) mutation_object.respond_to?(:graphql_definition) ? mutation_object.graphql_definition : mutation_object end end def subscription(new_subscription_object = nil) if new_subscription_object @subscription_object = new_subscription_object else subscription_object = @subscription_object || find_inherited_value(:subscription) subscription_object.respond_to?(:graphql_definition) ? subscription_object.graphql_definition : subscription_object end end def introspection(new_introspection_namespace = nil) if new_introspection_namespace @introspection = new_introspection_namespace else @introspection || find_inherited_value(:introspection) end end def cursor_encoder(new_encoder = nil) if new_encoder @cursor_encoder = new_encoder end @cursor_encoder || find_inherited_value(:cursor_encoder, Base64Encoder) end def default_max_page_size(new_default_max_page_size = nil) if new_default_max_page_size @default_max_page_size = new_default_max_page_size else @default_max_page_size || find_inherited_value(:default_max_page_size) end end def query_execution_strategy(new_query_execution_strategy = nil) if new_query_execution_strategy @query_execution_strategy = new_query_execution_strategy else @query_execution_strategy || find_inherited_value(:query_execution_strategy, self.default_execution_strategy) end end def mutation_execution_strategy(new_mutation_execution_strategy = nil) if new_mutation_execution_strategy @mutation_execution_strategy = new_mutation_execution_strategy else @mutation_execution_strategy || find_inherited_value(:mutation_execution_strategy, self.default_execution_strategy) end end def subscription_execution_strategy(new_subscription_execution_strategy = nil) if new_subscription_execution_strategy @subscription_execution_strategy = new_subscription_execution_strategy else @subscription_execution_strategy || find_inherited_value(:subscription_execution_strategy, self.default_execution_strategy) end end def max_complexity(max_complexity = nil) if max_complexity @max_complexity = max_complexity else @max_complexity || find_inherited_value(:max_complexity) end end def error_bubbling(new_error_bubbling = nil) if !new_error_bubbling.nil? @error_bubbling = new_error_bubbling else @error_bubbling.nil? ? find_inherited_value(:error_bubbling) : @error_bubbling end end def max_depth(new_max_depth = nil) if new_max_depth @max_depth = new_max_depth else @max_depth || find_inherited_value(:max_depth) end end def disable_introspection_entry_points @disable_introspection_entry_points = true end def disable_schema_introspection_entry_point @disable_schema_introspection_entry_point = true end def disable_type_introspection_entry_point @disable_type_introspection_entry_point = true end def disable_introspection_entry_points? if instance_variable_defined?(:@disable_introspection_entry_points) @disable_introspection_entry_points else find_inherited_value(:disable_introspection_entry_points?, false) end end def disable_schema_introspection_entry_point? if instance_variable_defined?(:@disable_schema_introspection_entry_point) @disable_schema_introspection_entry_point else find_inherited_value(:disable_schema_introspection_entry_point?, false) end end def disable_type_introspection_entry_point? if instance_variable_defined?(:@disable_type_introspection_entry_point) @disable_type_introspection_entry_point else find_inherited_value(:disable_type_introspection_entry_point?, false) end end def orphan_types(*new_orphan_types) if new_orphan_types.any? own_orphan_types.concat(new_orphan_types.flatten) end find_inherited_value(:orphan_types, EMPTY_ARRAY) + own_orphan_types end def default_execution_strategy if superclass <= GraphQL::Schema superclass.default_execution_strategy else @default_execution_strategy ||= GraphQL::Execution::Execute end end def context_class(new_context_class = nil) if new_context_class @context_class = new_context_class else @context_class || find_inherited_value(:context_class, GraphQL::Query::Context) end end def rescue_from(*err_classes, &handler_block) err_classes.each do |err_class| own_rescues[err_class] = handler_block end end def rescues find_inherited_value(:rescues, EMPTY_HASH).merge(own_rescues) end def resolve_type(type, obj, ctx) if type.kind.object? type else raise GraphQL::RequiredImplementationMissingError, "#{self.name}.resolve_type(type, obj, ctx) must be implemented to use Union types or Interface types (tried to resolve: #{type.name})" end end def object_from_id(node_id, ctx) raise GraphQL::RequiredImplementationMissingError, "#{self.name}.object_from_id(node_id, ctx) must be implemented to load by ID (tried to load from id `#{node_id}`)" end def id_from_object(object, type, ctx) raise GraphQL::RequiredImplementationMissingError, "#{self.name}.id_from_object(object, type, ctx) must be implemented to create global ids (tried to create an id for `#{object.inspect}`)" end def visible?(member, context) call_on_type_class(member, :visible?, context, default: true) end def accessible?(member, context) call_on_type_class(member, :accessible?, context, default: true) end # This hook is called when a client tries to access one or more # fields that fail the `accessible?` check. # # By default, an error is added to the response. Override this hook to # track metrics or return a different error to the client. # # @param error [InaccessibleFieldsError] The analysis error for this check # @return [AnalysisError, nil] Return an error to skip the query def inaccessible_fields(error) error end # This hook is called when an object fails an `authorized?` check. # You might report to your bug tracker here, so you can correct # the field resolvers not to return unauthorized objects. # # By default, this hook just replaces the unauthorized object with `nil`. # # Whatever value is returned from this method will be used instead of the # unauthorized object (accessible as `unauthorized_error.object`). If an # error is raised, then `nil` will be used. # # If you want to add an error to the `"errors"` key, raise a {GraphQL::ExecutionError} # in this hook. # # @param unauthorized_error [GraphQL::UnauthorizedError] # @return [Object] The returned object will be put in the GraphQL response def unauthorized_object(unauthorized_error) nil end # This hook is called when a field fails an `authorized?` check. # # By default, this hook implements the same behavior as unauthorized_object. # # Whatever value is returned from this method will be used instead of the # unauthorized field . If an error is raised, then `nil` will be used. # # If you want to add an error to the `"errors"` key, raise a {GraphQL::ExecutionError} # in this hook. # # @param unauthorized_error [GraphQL::UnauthorizedFieldError] # @return [Field] The returned field will be put in the GraphQL response def unauthorized_field(unauthorized_error) unauthorized_object(unauthorized_error) end def type_error(type_err, ctx) DefaultTypeError.call(type_err, ctx) end attr_writer :error_handler # @return [GraphQL::Execution::Errors, Class] def error_handler @error_handler ||= GraphQL::Execution::Errors::NullErrorHandler end def lazy_resolve(lazy_class, value_method) lazy_classes[lazy_class] = value_method end def instrument(instrument_step, instrumenter, options = {}) step = if instrument_step == :field && options[:after_built_ins] :field_after_built_ins else instrument_step end own_instrumenters[step] << instrumenter end def directives(new_directives = nil) if new_directives new_directives.each {|d| directive(d) } end find_inherited_value(:directives, default_directives).merge(own_directives) end def directive(new_directive) own_directives[new_directive.graphql_name] = new_directive end def default_directives { "include" => GraphQL::Directive::IncludeDirective, "skip" => GraphQL::Directive::SkipDirective, "deprecated" => GraphQL::Directive::DeprecatedDirective, } end def tracer(new_tracer) own_tracers << new_tracer end def tracers find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers end def query_analyzer(new_analyzer) if new_analyzer == GraphQL::Authorization::Analyzer warn("The Authorization query analyzer is deprecated. Authorizing at query runtime is generally a better idea.") end own_query_analyzers << new_analyzer end def query_analyzers find_inherited_value(:query_analyzers, EMPTY_ARRAY) + own_query_analyzers end def middleware(new_middleware = nil) if new_middleware own_middleware << new_middleware else graphql_definition.middleware end end def multiplex_analyzer(new_analyzer) own_multiplex_analyzers << new_analyzer end def multiplex_analyzers find_inherited_value(:multiplex_analyzers, EMPTY_ARRAY) + own_multiplex_analyzers end private def lazy_classes @lazy_classes ||= {} end def own_plugins @own_plugins ||= [] end def own_rescues @own_rescues ||= {} end def own_orphan_types @own_orphan_types ||= [] end def own_directives @own_directives ||= {} end def all_instrumenters inherited_instrumenters = find_inherited_value(:all_instrumenters) || Hash.new { |h,k| h[k] = [] } inherited_instrumenters.merge(own_instrumenters) do |_step, inherited, own| inherited + own end end def own_instrumenters @own_instrumenters ||= Hash.new { |h,k| h[k] = [] } end def own_tracers @own_tracers ||= [] end def own_query_analyzers @defined_query_analyzers ||= [] end def all_middleware find_inherited_value(:all_middleware, EMPTY_ARRAY) + own_middleware end def own_middleware @own_middleware ||= [] end def own_multiplex_analyzers @own_multiplex_analyzers ||= [] end # Given this schema member, find the class-based definition object # whose `method_name` should be treated as an application hook # @see {.visible?} # @see {.accessible?} # @see {.authorized?} def call_on_type_class(member, method_name, *args, default:) member = if member.respond_to?(:metadata) && member.metadata member.metadata[:type_class] || member else member end if member.respond_to?(:relay_node_type) && (t = member.relay_node_type) member = t end if member.respond_to?(method_name) member.public_send(method_name, *args) else default end end end def self.inherited(child_class) child_class.singleton_class.class_eval do prepend(MethodWrappers) end end module MethodWrappers # Wrap the user-provided resolve-type in a correctness check def resolve_type(type, obj, ctx = :__undefined__) graphql_definition.check_resolved_type(type, obj, ctx) do |ok_type, ok_obj, ok_ctx| super(ok_type, ok_obj, ok_ctx) end end end # Call the given block at the right time, either: # - Right away, if `value` is not registered with `lazy_resolve` # - After resolving `value`, if it's registered with `lazy_resolve` (eg, `Promise`) # @api private def after_lazy(value) if lazy?(value) GraphQL::Execution::Lazy.new do result = sync_lazy(value) # The returned result might also be lazy, so check it, too after_lazy(result) do |final_result| yield(final_result) if block_given? end end else yield(value) if block_given? end end # Override this method to handle lazy objects in a custom way. # @param value [Object] an instance of a class registered with {.lazy_resolve} # @param ctx [GraphQL::Query::Context] the context for this query # @return [Object] A GraphQL-ready (non-lazy) object def self.sync_lazy(value) if block_given? # This was already hit by the instance, just give it back yield(value) else # This was called directly on the class, hit the instance # which has the lazy method map self.graphql_definition.sync_lazy(value) end end # @see Schema.sync_lazy for a hook to override # @api private def sync_lazy(value) self.class.sync_lazy(value) { |v| lazy_method = lazy_method_name(v) if lazy_method synced_value = value.public_send(lazy_method) sync_lazy(synced_value) else v end } end protected def rescues? !!@rescue_middleware end # Lazily create a middleware and add it to the schema # (Don't add it if it's not used) def rescue_middleware @rescue_middleware ||= GraphQL::Schema::RescueMiddleware.new.tap { |m| middleware.insert(0, m) } end private def rebuild_artifacts if @rebuilding_artifacts raise CyclicalDefinitionError, "Part of the schema build process re-triggered the schema build process, causing an infinite loop. Avoid using Schema#types, Schema#possible_types, and Schema#get_field during schema build." else @rebuilding_artifacts = true @introspection_system = Schema::IntrospectionSystem.new(self) traversal = Traversal.new(self) @types = traversal.type_map @root_types = [query, mutation, subscription] @instrumented_field_map = traversal.instrumented_field_map @type_reference_map = traversal.type_reference_map @union_memberships = traversal.union_memberships @find_cache = {} @finder = Finder.new(self) end ensure @rebuilding_artifacts = false end class CyclicalDefinitionError < GraphQL::Error end def with_definition_error_check if @definition_error raise @definition_error else yield end end end end ruby-graphql-1.9.19/lib/graphql/schema/000077500000000000000000000000001362601351000176715ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/schema/argument.rb000066400000000000000000000114421362601351000220420ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Argument include GraphQL::Schema::Member::CachedGraphQLDefinition include GraphQL::Schema::Member::AcceptsDefinition include GraphQL::Schema::Member::HasPath NO_DEFAULT = :__no_default__ # @return [String] the GraphQL name for this argument, camelized unless `camelize: false` is provided attr_reader :name alias :graphql_name :name # @return [GraphQL::Schema::Field, Class] The field or input object this argument belongs to attr_reader :owner # @return [Symbol] A method to call to transform this value before sending it to field resolution method attr_reader :prepare # @return [Symbol] This argument's name in Ruby keyword arguments attr_reader :keyword # @return [Class, Module, nil] If this argument should load an application object, this is the type of object to load attr_reader :loads # @return [Boolean] true if a resolver defined this argument def from_resolver? @from_resolver end # @param arg_name [Symbol] # @param type_expr # @param desc [String] # @param required [Boolean] if true, this argument is non-null; if false, this argument is nullable # @param description [String] # @param default_value [Object] # @param as [Symbol] Override the keyword name when passed to a method # @param prepare [Symbol] A method to call to transform this argument's valuebefore sending it to field resolution # @param camelize [Boolean] if true, the name will be camelized when building the schema # @param from_resolver [Boolean] if true, a Resolver class defined this argument # @param method_access [Boolean] If false, don't build method access on legacy {Query::Arguments} instances. def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NO_DEFAULT, as: nil, from_resolver: false, camelize: true, prepare: nil, method_access: true, owner:, &definition_block) arg_name ||= name name_str = camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s @name = name_str.freeze @type_expr = type_expr || type @description = desc || description @null = !required @default_value = default_value @owner = owner @as = as @loads = loads @keyword = as || Schema::Member::BuildType.underscore(@name).to_sym @prepare = prepare @ast_node = ast_node @from_resolver = from_resolver @method_access = method_access if definition_block if definition_block.arity == 1 instance_exec(self, &definition_block) else instance_eval(&definition_block) end end end # @return [Object] the value used when the client doesn't provide a value for this argument attr_reader :default_value # @return [Boolean] True if this argument has a default value def default_value? @default_value != NO_DEFAULT end attr_writer :description # @return [String] Documentation for this argument def description(text = nil) if text @description = text else @description end end def visible?(context) true end def accessible?(context) true end def authorized?(obj, ctx) true end def to_graphql argument = GraphQL::Argument.new argument.name = @name argument.type = -> { type } argument.description = @description argument.metadata[:type_class] = self argument.as = @as argument.method_access = @method_access if NO_DEFAULT != @default_value argument.default_value = @default_value end argument end def type @type ||= Member::BuildType.parse_type(@type_expr, null: @null) rescue StandardError => err raise ArgumentError, "Couldn't build type for Argument #{@owner.name}.#{name}: #{err.class.name}: #{err.message}", err.backtrace end # Apply the {prepare} configuration to `value`, using methods from `obj`. # Used by the runtime. # @api private def prepare_value(obj, value) if value.is_a?(GraphQL::Schema::InputObject) value = value.prepare end if @prepare.nil? value elsif @prepare.is_a?(String) || @prepare.is_a?(Symbol) obj.public_send(@prepare, value) elsif @prepare.respond_to?(:call) @prepare.call(value, obj.context) else raise "Invalid prepare for #{@owner.name}.name: #{@prepare.inspect}" end end end end end ruby-graphql-1.9.19/lib/graphql/schema/base_64_bp.rb000066400000000000000000000010131362601351000221150ustar00rootroot00000000000000# frozen_string_literal: true require 'base64' # backport from ruby v2.5 to v2.2 that has no `padding` things # @api private module Base64Bp extend Base64 module_function def urlsafe_encode64(bin, padding:) str = strict_encode64(bin) str.tr!("+/", "-_") str.delete!("=") unless padding str end def urlsafe_decode64(str) str = str.tr("-_", "+/") if !str.end_with?("=") && str.length % 4 != 0 str = str.ljust((str.length + 3) & ~3, "=") end strict_decode64(str) end end ruby-graphql-1.9.19/lib/graphql/schema/base_64_encoder.rb000066400000000000000000000007051362601351000231420ustar00rootroot00000000000000# frozen_string_literal: true require 'graphql/schema/base_64_bp' module GraphQL class Schema # @api private module Base64Encoder def self.encode(unencoded_text, nonce: false) Base64Bp.urlsafe_encode64(unencoded_text, padding: false) end def self.decode(encoded_text, nonce: false) # urlsafe_decode64 is for forward compatibility Base64Bp.urlsafe_decode64(encoded_text) end end end end ruby-graphql-1.9.19/lib/graphql/schema/build_from_definition.rb000066400000000000000000000331241362601351000245530ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/schema/build_from_definition/resolve_map" module GraphQL class Schema module BuildFromDefinition class << self def from_definition(definition_string, default_resolve:, parser: DefaultParser) document = parser.parse(definition_string) Builder.build(document, default_resolve: default_resolve) end end # @api private DefaultParser = GraphQL::Language::Parser # @api private module DefaultResolve def self.call(type, field, obj, args, ctx) if field.arguments.any? obj.public_send(field.name, args, ctx) else obj.public_send(field.name) end end end # @api private module Builder extend self def build(document, default_resolve: DefaultResolve) raise InvalidDocumentError.new('Must provide a document ast.') if !document || !document.is_a?(GraphQL::Language::Nodes::Document) if default_resolve.is_a?(Hash) default_resolve = ResolveMap.new(default_resolve) end schema_definition = nil types = {} types.merge!(GraphQL::Schema::BUILT_IN_TYPES) directives = {} type_resolver = ->(type) { -> { resolve_type(types, type) } } document.definitions.each do |definition| case definition when GraphQL::Language::Nodes::SchemaDefinition raise InvalidDocumentError.new('Must provide only one schema definition.') if schema_definition schema_definition = definition when GraphQL::Language::Nodes::EnumTypeDefinition types[definition.name] = build_enum_type(definition, type_resolver) when GraphQL::Language::Nodes::ObjectTypeDefinition types[definition.name] = build_object_type(definition, type_resolver, default_resolve: default_resolve) when GraphQL::Language::Nodes::InterfaceTypeDefinition types[definition.name] = build_interface_type(definition, type_resolver) when GraphQL::Language::Nodes::UnionTypeDefinition types[definition.name] = build_union_type(definition, type_resolver) when GraphQL::Language::Nodes::ScalarTypeDefinition types[definition.name] = build_scalar_type(definition, type_resolver, default_resolve: default_resolve) when GraphQL::Language::Nodes::InputObjectTypeDefinition types[definition.name] = build_input_object_type(definition, type_resolver) when GraphQL::Language::Nodes::DirectiveDefinition directives[definition.name] = build_directive(definition, type_resolver) end end directives = GraphQL::Schema.default_directives.merge(directives) if schema_definition if schema_definition.query raise InvalidDocumentError.new("Specified query type \"#{schema_definition.query}\" not found in document.") unless types[schema_definition.query] query_root_type = types[schema_definition.query] end if schema_definition.mutation raise InvalidDocumentError.new("Specified mutation type \"#{schema_definition.mutation}\" not found in document.") unless types[schema_definition.mutation] mutation_root_type = types[schema_definition.mutation] end if schema_definition.subscription raise InvalidDocumentError.new("Specified subscription type \"#{schema_definition.subscription}\" not found in document.") unless types[schema_definition.subscription] subscription_root_type = types[schema_definition.subscription] end else query_root_type = types['Query'] mutation_root_type = types['Mutation'] subscription_root_type = types['Subscription'] end raise InvalidDocumentError.new('Must provide schema definition with query type or a type named Query.') unless query_root_type schema = Schema.define do raise_definition_error true query query_root_type mutation mutation_root_type subscription subscription_root_type orphan_types types.values if default_resolve.respond_to?(:resolve_type) resolve_type(default_resolve.method(:resolve_type)) else resolve_type(NullResolveType) end directives directives.values end schema.ast_node = schema_definition if schema_definition schema end NullResolveType = ->(type, obj, ctx) { raise(GraphQL::RequiredImplementationMissingError, "Generated Schema cannot use Interface or Union types for execution. Implement resolve_type on your resolver.") } NullScalarCoerce = ->(val, _ctx) { val } def build_enum_type(enum_type_definition, type_resolver) enum = GraphQL::EnumType.define( name: enum_type_definition.name, description: enum_type_definition.description, values: enum_type_definition.values.map do |enum_value_definition| value = EnumType::EnumValue.define( name: enum_value_definition.name, value: enum_value_definition.name, deprecation_reason: build_deprecation_reason(enum_value_definition.directives), description: enum_value_definition.description, ) value.ast_node = enum_value_definition value end ) enum.ast_node = enum_type_definition enum end def build_deprecation_reason(directives) deprecated_directive = directives.find{ |d| d.name == 'deprecated' } return unless deprecated_directive reason = deprecated_directive.arguments.find{ |a| a.name == 'reason' } return GraphQL::Directive::DEFAULT_DEPRECATION_REASON unless reason reason.value end def build_scalar_type(scalar_type_definition, type_resolver, default_resolve:) scalar_type = GraphQL::ScalarType.define( name: scalar_type_definition.name, description: scalar_type_definition.description, coerce: NullScalarCoerce, ) scalar_type.ast_node = scalar_type_definition if default_resolve.respond_to?(:coerce_input) scalar_type = scalar_type.redefine( coerce_input: ->(val, ctx) { default_resolve.coerce_input(scalar_type, val, ctx) }, coerce_result: ->(val, ctx) { default_resolve.coerce_result(scalar_type, val, ctx) }, ) end scalar_type end def build_union_type(union_type_definition, type_resolver) union = GraphQL::UnionType.define( name: union_type_definition.name, description: union_type_definition.description, possible_types: union_type_definition.types.map{ |type_name| type_resolver.call(type_name) }, ) union.ast_node = union_type_definition union end def build_object_type(object_type_definition, type_resolver, default_resolve:) type_def = nil typed_resolve_fn = ->(field, obj, args, ctx) { default_resolve.call(type_def, field, obj, args, ctx) } defns = { name: object_type_definition.name, description: object_type_definition.description, interfaces: object_type_definition.interfaces.map{ |interface_name| type_resolver.call(interface_name) }, } obj_fields = Hash[build_fields(object_type_definition.fields, type_resolver, default_resolve: typed_resolve_fn)] if obj_fields.any? defns[:fields] = obj_fields end type_def = GraphQL::ObjectType.define(**defns) type_def.ast_node = object_type_definition type_def end def build_input_object_type(input_object_type_definition, type_resolver) input = GraphQL::InputObjectType.define( name: input_object_type_definition.name, description: input_object_type_definition.description, arguments: Hash[build_input_arguments(input_object_type_definition, type_resolver)], ) input.ast_node = input_object_type_definition input end def build_default_value(default_value) case default_value when GraphQL::Language::Nodes::Enum default_value.name when GraphQL::Language::Nodes::NullValue nil when GraphQL::Language::Nodes::InputObject default_value.to_h when Array default_value.map { |v| build_default_value(v) } else default_value end end def build_input_arguments(input_object_type_definition, type_resolver) input_object_type_definition.fields.map do |input_argument| kwargs = {} if !input_argument.default_value.nil? kwargs[:default_value] = build_default_value(input_argument.default_value) end argument = GraphQL::Argument.define( name: input_argument.name, type: type_resolver.call(input_argument.type), description: input_argument.description, method_access: false, **kwargs, ) argument.ast_node = input_argument [ input_argument.name, argument ] end end def build_directive(directive_definition, type_resolver) directive_args = Hash[build_directive_arguments(directive_definition, type_resolver)] defn = { name: directive_definition.name, description: directive_definition.description, locations: directive_definition.locations.map { |location| location.name.to_sym }, } if directive_args.any? defn[:arguments] = directive_args end directive = GraphQL::Directive.define(**defn) directive.ast_node = directive_definition directive end def build_directive_arguments(directive_definition, type_resolver) directive_definition.arguments.map do |directive_argument| kwargs = {} if !directive_argument.default_value.nil? kwargs[:default_value] = build_default_value(directive_argument.default_value) end argument = GraphQL::Argument.define( name: directive_argument.name, type: type_resolver.call(directive_argument.type), description: directive_argument.description, method_access: false, **kwargs, ) argument.ast_node = directive_argument [ directive_argument.name, argument ] end end def build_interface_type(interface_type_definition, type_resolver) interface = GraphQL::InterfaceType.define( name: interface_type_definition.name, description: interface_type_definition.description, fields: Hash[build_fields(interface_type_definition.fields, type_resolver, default_resolve: nil)], ) interface.ast_node = interface_type_definition interface end def build_fields(field_definitions, type_resolver, default_resolve:) field_definitions.map do |field_definition| field_arguments = Hash[field_definition.arguments.map do |argument| kwargs = {} if !argument.default_value.nil? kwargs[:default_value] = build_default_value(argument.default_value) end arg = GraphQL::Argument.define( name: argument.name, description: argument.description, type: type_resolver.call(argument.type), method_access: false, **kwargs, ) arg.ast_node = argument [argument.name, arg] end] field = nil defns = { name: field_definition.name, description: field_definition.description, type: type_resolver.call(field_definition.type), resolve: ->(obj, args, ctx) { default_resolve.call(field, obj, args, ctx) }, deprecation_reason: build_deprecation_reason(field_definition.directives), } if field_arguments.any? defns[:arguments] = field_arguments end field = GraphQL::Field.define(**defns) field.ast_node = field_definition type_name = resolve_type_name(field_definition.type) field.connection = type_name.end_with?("Connection") [field_definition.name, field] end end def resolve_type(types, ast_node) type = GraphQL::Schema::TypeExpression.build_type(types, ast_node) if type.nil? while ast_node.respond_to?(:of_type) ast_node = ast_node.of_type end raise InvalidDocumentError.new("Type \"#{ast_node.name}\" not found in document.") end type end def resolve_type_name(type) case type when GraphQL::Language::Nodes::TypeName return type.name else resolve_type_name(type.of_type) end end end private_constant :Builder end end end ruby-graphql-1.9.19/lib/graphql/schema/build_from_definition/000077500000000000000000000000001362601351000242235ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/schema/build_from_definition/resolve_map.rb000066400000000000000000000047661362601351000271010ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/schema/build_from_definition/resolve_map/default_resolve" module GraphQL class Schema module BuildFromDefinition # Wrap a user-provided hash of resolution behavior for easy access at runtime. # # Coerce scalar values by: # - Checking for a function in the map like `{ Date: { coerce_input: ->(val, ctx) { ... }, coerce_result: ->(val, ctx) { ... } } }` # - Falling back to a passthrough # # Interface/union resolution can be provided as a `resolve_type:` key. # # @api private class ResolveMap def initialize(user_resolve_hash) @resolve_hash = Hash.new do |h, k| # For each type name, provide a new hash if one wasn't given: h[k] = Hash.new do |h2, k2| if k2 == "coerce_input" || k2 == "coerce_result" # This isn't an object field, it's a scalar coerce function. # Use a passthrough Builder::NullScalarCoerce else # For each field, provide a resolver that will # make runtime checks & replace itself h2[k2] = DefaultResolve.new(h2, k2) end end end @user_resolve_hash = user_resolve_hash # User-provided resolve functions take priority over the default: @user_resolve_hash.each do |type_name, fields| type_name_s = type_name.to_s case fields when Hash fields.each do |field_name, resolve_fn| @resolve_hash[type_name_s][field_name.to_s] = resolve_fn end when Proc # for example, __resolve_type @resolve_hash[type_name_s] = fields end end # Check the normalized hash, not the user input: if @resolve_hash.key?("resolve_type") define_singleton_method :resolve_type do |type, obj, ctx| @resolve_hash.fetch("resolve_type").call(type, obj, ctx) end end end def call(type, field, obj, args, ctx) resolver = @resolve_hash[type.name][field.name] resolver.call(obj, args, ctx) end def coerce_input(type, value, ctx) @resolve_hash[type.name]["coerce_input"].call(value, ctx) end def coerce_result(type, value, ctx) @resolve_hash[type.name]["coerce_result"].call(value, ctx) end end end end end ruby-graphql-1.9.19/lib/graphql/schema/build_from_definition/resolve_map/000077500000000000000000000000001362601351000265375ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb000066400000000000000000000032261362601351000322520ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema module BuildFromDefinition class ResolveMap class DefaultResolve def initialize(field_map, field_name) @field_map = field_map @field_name = field_name end # Make some runtime checks about # how `obj` implements the `field_name`. # # Create a new resolve function according to that implementation, then: # - update `field_map` with this implementation # - call the implementation now (to satisfy this field execution) # # If `obj` doesn't implement `field_name`, raise an error. def call(obj, args, ctx) method_name = @field_name if !obj.respond_to?(method_name) raise KeyError, "Can't resolve field #{method_name} on #{obj}" else method_arity = obj.method(method_name).arity resolver = case method_arity when 0, -1 # -1 Handles method_missing, eg openstruct ->(o, a, c) { o.public_send(method_name) } when 1 ->(o, a, c) { o.public_send(method_name, a) } when 2 ->(o, a, c) { o.public_send(method_name, a, c) } else raise "Unexpected resolve arity: #{method_arity}. Must be 0, 1, 2" end # Call the resolver directly next time @field_map[method_name] = resolver # Call through this time resolver.call(obj, args, ctx) end end end end end end end ruby-graphql-1.9.19/lib/graphql/schema/built_in_types.rb000066400000000000000000000003601362601351000232460ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema BUILT_IN_TYPES = { "Int" => INT_TYPE, "String" => STRING_TYPE, "Float" => FLOAT_TYPE, "Boolean" => BOOLEAN_TYPE, "ID" => ID_TYPE, } end end ruby-graphql-1.9.19/lib/graphql/schema/catchall_middleware.rb000066400000000000000000000025251362601351000241720ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # In early GraphQL versions, errors would be "automatically" # rescued and replaced with `"Internal error"`. That behavior # was undesirable but this middleware is offered for people who # want to preserve it. # # It has a couple of differences from the previous behavior: # # - Other parts of the query _will_ be run (previously, # execution would stop when the error was raised and the result # would have no `"data"` key at all) # - The entry in {Query::Context#errors} is a {GraphQL::ExecutionError}, _not_ # the originally-raised error. # - The entry in the `"errors"` key includes the location of the field # which raised the errors. # # @example Use CatchallMiddleware with your schema # # All errors will be suppressed and replaced with "Internal error" messages # MySchema.middleware << GraphQL::Schema::CatchallMiddleware # module CatchallMiddleware MESSAGE = "Internal error" # Rescue any error and replace it with a {GraphQL::ExecutionError} # whose message is {MESSAGE} def self.call(parent_type, parent_object, field_definition, field_args, query_context) yield rescue StandardError GraphQL::ExecutionError.new(MESSAGE) end end end end ruby-graphql-1.9.19/lib/graphql/schema/default_parse_error.rb000066400000000000000000000003001362601351000242360ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema module DefaultParseError def self.call(parse_error, ctx) ctx.errors.push(parse_error) end end end end ruby-graphql-1.9.19/lib/graphql/schema/default_type_error.rb000066400000000000000000000006111362601351000241120ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema module DefaultTypeError def self.call(type_error, ctx) case type_error when GraphQL::InvalidNullError ctx.errors << type_error when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError raise type_error end end end end end ruby-graphql-1.9.19/lib/graphql/schema/directive.rb000066400000000000000000000110271362601351000221750ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Subclasses of this can influence how {GraphQL::Execution::Interpreter} runs queries. # # - {.include?}: if it returns `false`, the field or fragment will be skipped altogether, as if it were absent # - {.resolve}: Wraps field resolution (so it should call `yield` to continue) class Directive < GraphQL::Schema::Member extend GraphQL::Schema::Member::HasArguments class << self # Return a name based on the class name, # but downcase the first letter. def default_graphql_name @default_graphql_name ||= begin camelized_name = super camelized_name[0] = camelized_name[0].downcase camelized_name end end def locations(*new_locations) if new_locations.any? @locations = new_locations else @locations ||= (superclass.respond_to?(:locations) ? superclass.locations : []) end end def default_directive(new_default_directive = nil) if new_default_directive != nil @default_directive = new_default_directive elsif @default_directive.nil? @default_directive = (superclass.respond_to?(:default_directive) ? superclass.default_directive : false) else @default_directive end end def to_graphql defn = GraphQL::Directive.new defn.name = self.graphql_name defn.description = self.description defn.locations = self.locations defn.default_directive = self.default_directive defn.metadata[:type_class] = self arguments.each do |name, arg_defn| arg_graphql = arg_defn.to_graphql defn.arguments[arg_graphql.name] = arg_graphql end defn end # If false, this part of the query won't be evaluated def include?(_object, _arguments, _context) true end # Continuing is passed as a block; `yield` to continue def resolve(object, arguments, context) yield end end LOCATIONS = [ QUERY = :QUERY, MUTATION = :MUTATION, SUBSCRIPTION = :SUBSCRIPTION, FIELD = :FIELD, FRAGMENT_DEFINITION = :FRAGMENT_DEFINITION, FRAGMENT_SPREAD = :FRAGMENT_SPREAD, INLINE_FRAGMENT = :INLINE_FRAGMENT, SCHEMA = :SCHEMA, SCALAR = :SCALAR, OBJECT = :OBJECT, FIELD_DEFINITION = :FIELD_DEFINITION, ARGUMENT_DEFINITION = :ARGUMENT_DEFINITION, INTERFACE = :INTERFACE, UNION = :UNION, ENUM = :ENUM, ENUM_VALUE = :ENUM_VALUE, INPUT_OBJECT = :INPUT_OBJECT, INPUT_FIELD_DEFINITION = :INPUT_FIELD_DEFINITION, ] DEFAULT_DEPRECATION_REASON = 'No longer supported' LOCATION_DESCRIPTIONS = { QUERY: 'Location adjacent to a query operation.', MUTATION: 'Location adjacent to a mutation operation.', SUBSCRIPTION: 'Location adjacent to a subscription operation.', FIELD: 'Location adjacent to a field.', FRAGMENT_DEFINITION: 'Location adjacent to a fragment definition.', FRAGMENT_SPREAD: 'Location adjacent to a fragment spread.', INLINE_FRAGMENT: 'Location adjacent to an inline fragment.', SCHEMA: 'Location adjacent to a schema definition.', SCALAR: 'Location adjacent to a scalar definition.', OBJECT: 'Location adjacent to an object type definition.', FIELD_DEFINITION: 'Location adjacent to a field definition.', ARGUMENT_DEFINITION: 'Location adjacent to an argument definition.', INTERFACE: 'Location adjacent to an interface definition.', UNION: 'Location adjacent to a union definition.', ENUM: 'Location adjacent to an enum definition.', ENUM_VALUE: 'Location adjacent to an enum value definition.', INPUT_OBJECT: 'Location adjacent to an input object type definition.', INPUT_FIELD_DEFINITION: 'Location adjacent to an input object field definition.', } end end end ruby-graphql-1.9.19/lib/graphql/schema/directive/000077500000000000000000000000001362601351000216475ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/schema/directive/feature.rb000066400000000000000000000054261362601351000236360ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member # An example directive to show how you might interact with the runtime. # # This directive might be used along with a server-side feature flag system like Flipper. # # With that system, you could use this directive to exclude parts of a query # if the current viewer doesn't have certain flags enabled. # (So, this flag would be for internal clients, like your iOS app, not third-party API clients.) # # To use it, you have to implement `.enabled?`, for example: # # @example Implementing the Feature directive # # app/graphql/directives/feature.rb # class Directives::Feature < GraphQL::Schema::Directive::Feature # def self.enabled?(flag_name, _obj, context) # # Translate some GraphQL data for Ruby: # flag_key = flag_name.underscore # current_user = context[:viewer] # # Check the feature flag however your app does it: # MyFeatureFlags.enabled?(current_user, flag_key) # end # end # # @example Flagging a part of the query # viewer { # # This field only runs if `.enabled?("recommendationEngine", obj, context)` # # returns true. Otherwise, it's treated as if it didn't exist. # recommendations @feature(flag: "recommendationEngine") { # name # rating # } # } class Feature < Schema::Directive description "Directs the executor to run this only if a certain server-side feature is enabled." locations( GraphQL::Schema::Directive::FIELD, GraphQL::Schema::Directive::FRAGMENT_SPREAD, GraphQL::Schema::Directive::INLINE_FRAGMENT ) argument :flag, String, required: true, description: "The name of the feature to check before continuing" # Implement the Directive API def self.include?(object, arguments, context) flag_name = arguments[:flag] self.enabled?(flag_name, object, context) end # Override this method in your app's subclass of this directive. # # @param flag_name [String] The client-provided string of a feature to check # @param object [GraphQL::Schema::Objct] The currently-evaluated GraphQL object instance # @param context [GraphQL::Query::Context] # @return [Boolean] If truthy, execution will continue def self.enabled?(flag_name, object, context) raise GraphQL::RequiredImplementationMissingError, "Implement `.enabled?(flag_name, object, context)` to return true or false for the feature flag (#{flag_name.inspect})" end end end end end ruby-graphql-1.9.19/lib/graphql/schema/directive/include.rb000066400000000000000000000012531362601351000236200ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member class Include < GraphQL::Schema::Directive description "Directs the executor to include this field or fragment only when the `if` argument is true." locations( GraphQL::Schema::Directive::FIELD, GraphQL::Schema::Directive::FRAGMENT_SPREAD, GraphQL::Schema::Directive::INLINE_FRAGMENT ) argument :if, Boolean, required: true, description: "Included when true." default_directive true def self.include?(obj, args, ctx) !!args[:if] end end end end end ruby-graphql-1.9.19/lib/graphql/schema/directive/skip.rb000066400000000000000000000012251362601351000231420ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member class Skip < Schema::Directive description "Directs the executor to skip this field or fragment when the `if` argument is true." locations( GraphQL::Schema::Directive::FIELD, GraphQL::Schema::Directive::FRAGMENT_SPREAD, GraphQL::Schema::Directive::INLINE_FRAGMENT ) argument :if, Boolean, required: true, description: "Skipped when true." default_directive true def self.include?(obj, args, ctx) !args[:if] end end end end end ruby-graphql-1.9.19/lib/graphql/schema/directive/transform.rb000066400000000000000000000031631362601351000242120ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member # An example directive to show how you might interact with the runtime. # # This directive takes the return value of the tagged part of the query, # and if the named transform is whitelisted and applies to the return value, # it's applied by calling a method with that name. # # @example Installing the directive # class MySchema < GraphQL::Schema # directive(GraphQL::Schema::Directive::Transform) # end # # @example Transforming strings # viewer { # username @transform(by: "upcase") # } class Transform < Schema::Directive description "Directs the executor to run named transform on the return value." locations( GraphQL::Schema::Directive::FIELD, ) argument :by, String, required: true, description: "The name of the transform to run if applicable" TRANSFORMS = [ "upcase", "downcase", # ?? ] # Implement the Directive API def self.resolve(object, arguments, context) path = context.namespace(:interpreter)[:current_path] return_value = yield transform_name = arguments[:by] if TRANSFORMS.include?(transform_name) && return_value.respond_to?(transform_name) return_value = return_value.public_send(transform_name) context.namespace(:interpreter)[:runtime].write_in_response(path, return_value) end end end end end end ruby-graphql-1.9.19/lib/graphql/schema/enum.rb000066400000000000000000000055401362601351000211660ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Extend this class to define GraphQL enums in your schema. # # By default, GraphQL enum values are translated into Ruby strings. # You can provide a custom value with the `value:` keyword. # # @example # # equivalent to # # enum PizzaTopping { # # MUSHROOMS # # ONIONS # # PEPPERS # # } # class PizzaTopping < GraphQL::Enum # value :MUSHROOMS # value :ONIONS # value :PEPPERS # end class Schema class Enum < GraphQL::Schema::Member extend GraphQL::Schema::Member::AcceptsDefinition class << self extend Forwardable def_delegators :graphql_definition, :coerce_isolated_input, :coerce_isolated_result, :coerce_input, :coerce_result # Define a value for this enum # @param graphql_name [String, Symbol] the GraphQL value for this, usually `SCREAMING_CASE` # @param description [String], the GraphQL description for this value, present in documentation # @param value [Object], the translated Ruby value for this object (defaults to `graphql_name`) # @param deprecation_reason [String] if this object is deprecated, include a message here # @return [void] # @see {Schema::EnumValue} which handles these inputs by default def value(*args, **kwargs, &block) kwargs[:owner] = self value = enum_value_class.new(*args, **kwargs, &block) own_values[value.graphql_name] = value nil end # @return [Hash GraphQL::Schema::Enum::Value>] Possible values of this enum, keyed by name def values inherited_values = superclass <= GraphQL::Schema::Enum ? superclass.values : {} # Local values take precedence over inherited ones inherited_values.merge(own_values) end # @return [GraphQL::EnumType] def to_graphql enum_type = GraphQL::EnumType.new enum_type.name = graphql_name enum_type.description = description enum_type.introspection = introspection values.each do |name, val| enum_type.add_value(val.to_graphql) end enum_type.metadata[:type_class] = self enum_type end # @return [Class] for handling `value(...)` inputs and building `GraphQL::Enum::EnumValue`s out of them def enum_value_class(new_enum_value_class = nil) if new_enum_value_class @enum_value_class = new_enum_value_class end @enum_value_class || (superclass <= GraphQL::Schema::Enum ? superclass.enum_value_class : nil) end def kind GraphQL::TypeKinds::ENUM end private def own_values @own_values ||= {} end end enum_value_class(GraphQL::Schema::EnumValue) end end end ruby-graphql-1.9.19/lib/graphql/schema/enum_value.rb000066400000000000000000000045751362601351000223710ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # A possible value for an {Enum}. # # You can extend this class to customize enum values in your schema. # # @example custom enum value class # # define a custom class: # class CustomEnumValue < GraphQL::Schema::EnumValue # def initialize(*args) # # arguments to `value(...)` in Enum classes are passed here # super # end # # def to_graphql # enum_value = super # # customize the derived GraphQL::EnumValue here # enum_value # end # end # # class BaseEnum < GraphQL::Schema::Enum # # use it for these enums: # enum_value_class CustomEnumValue # end class EnumValue < GraphQL::Schema::Member include GraphQL::Schema::Member::AcceptsDefinition include GraphQL::Schema::Member::HasPath attr_reader :graphql_name # @return [Class] The enum type that owns this value attr_reader :owner # @return [String] Explains why this value was deprecated (if present, this will be marked deprecated in introspection) attr_accessor :deprecation_reason def initialize(graphql_name, desc = nil, owner:, description: nil, value: nil, deprecation_reason: nil, &block) @graphql_name = graphql_name.to_s @description = desc || description @value = value.nil? ? @graphql_name : value @deprecation_reason = deprecation_reason @owner = owner if block_given? instance_eval(&block) end end def description(new_desc = nil) if new_desc @description = new_desc end @description end def value(new_val = nil) unless new_val.nil? @value = new_val end @value end # @return [GraphQL::EnumType::EnumValue] A runtime-ready object derived from this object def to_graphql enum_value = GraphQL::EnumType::EnumValue.new enum_value.name = @graphql_name enum_value.description = @description enum_value.value = @value enum_value.deprecation_reason = @deprecation_reason enum_value.metadata[:type_class] = self enum_value end def visible?(_ctx); true; end def accessible?(_ctx); true; end def authorized?(_ctx); true; end end end end ruby-graphql-1.9.19/lib/graphql/schema/field.rb000066400000000000000000000671201362601351000213070ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../object.rb require "graphql/schema/field/connection_extension" require "graphql/schema/field/scope_extension" module GraphQL class Schema class Field if !String.method_defined?(:-@) using GraphQL::StringDedupBackport end include GraphQL::Schema::Member::CachedGraphQLDefinition include GraphQL::Schema::Member::AcceptsDefinition include GraphQL::Schema::Member::HasArguments include GraphQL::Schema::Member::HasPath extend GraphQL::Schema::FindInheritedValue # @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided attr_reader :name alias :graphql_name :name attr_writer :description # @return [String, nil] If present, the field is marked as deprecated with this documentation attr_accessor :deprecation_reason # @return [Symbol] Method or hash key on the underlying object to look up attr_reader :method_sym # @return [String] Method or hash key on the underlying object to look up attr_reader :method_str # @return [Symbol] The method on the type to look up attr_reader :resolver_method # @return [Class] The type that this field belongs to attr_reader :owner # @return [Symobol] the original name of the field, passed in by the user attr_reader :original_name # @return [Class, nil] The {Schema::Resolver} this field was derived from, if there is one def resolver @resolver_class end alias :mutation :resolver # @return [Boolean] Apply tracing to this field? (Default: skip scalars, this is the override value) attr_reader :trace # @return [String, nil] attr_reader :subscription_scope # Create a field instance from a list of arguments, keyword arguments, and a block. # # This method implements prioritization between the `resolver` or `mutation` defaults # and the local overrides via other keywords. # # It also normalizes positional arguments into keywords for {Schema::Field#initialize}. # @param resolver [Class] A {GraphQL::Schema::Resolver} class to use for field configuration # @param mutation [Class] A {GraphQL::Schema::Mutation} class to use for field configuration # @param subscription [Class] A {GraphQL::Schema::Subscription} class to use for field configuration # @return [GraphQL::Schema:Field] an instance of `self # @see {.initialize} for other options def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block) if kwargs[:field] if kwargs[:field] == GraphQL::Relay::Node.field warn("Legacy-style `GraphQL::Relay::Node.field` is being added to a class-based type. See `GraphQL::Types::Relay::NodeField` for a replacement.") return GraphQL::Types::Relay::NodeField elsif kwargs[:field] == GraphQL::Relay::Node.plural_field warn("Legacy-style `GraphQL::Relay::Node.plural_field` is being added to a class-based type. See `GraphQL::Types::Relay::NodesField` for a replacement.") return GraphQL::Types::Relay::NodesField end end if (parent_config = resolver || mutation || subscription) # Get the parent config, merge in local overrides kwargs = parent_config.field_options.merge(kwargs) # Add a reference to that parent class kwargs[:resolver_class] = parent_config end if name kwargs[:name] = name end if !type.nil? if type.is_a?(GraphQL::Field) raise ArgumentError, "A GraphQL::Field was passed as the second argument, use the `field:` keyword for this instead." end if desc if kwargs[:description] raise ArgumentError, "Provide description as a positional argument or `description:` keyword, but not both (#{desc.inspect}, #{kwargs[:description].inspect})" end kwargs[:description] = desc kwargs[:type] = type elsif (kwargs[:field] || kwargs[:function] || resolver || mutation) && type.is_a?(String) # The return type should be copied from `field` or `function`, and the second positional argument is the description kwargs[:description] = type else kwargs[:type] = type end end new(**kwargs, &block) end # Can be set with `connection: true|false` or inferred from a type name ending in `*Connection` # @return [Boolean] if true, this field will be wrapped with Relay connection behavior def connection? if @connection.nil? # Provide default based on type name return_type_name = if (contains_type = @field || @function) Member::BuildType.to_type_name(contains_type.type) elsif @return_type_expr Member::BuildType.to_type_name(@return_type_expr) else # As a last ditch, try to force loading the return type: type.unwrap.name end @connection = return_type_name.end_with?("Connection") else @connection end end # @return [Boolean] if true, the return type's `.scope_items` method will be applied to this field's return value def scoped? if !@scope.nil? # The default was overridden @scope else @return_type_expr && (@return_type_expr.is_a?(Array) || (@return_type_expr.is_a?(String) && @return_type_expr.include?("[")) || connection?) end end # This extension is applied to fields when {#connection?} is true. # # You can override it in your base field definition. # @return [Class] A {FieldExtension} subclass for implementing pagination behavior. # @example Configuring a custom extension # class Types::BaseField < GraphQL::Schema::Field # connection_extension(MyCustomExtension) # end def self.connection_extension(new_extension_class = nil) if new_extension_class @connection_extension = new_extension_class else @connection_extension ||= find_inherited_value(:connection_extension, ConnectionExtension) end end # @param name [Symbol] The underscore-cased version of this field name (will be camelized for the GraphQL API) # @param type [Class, GraphQL::BaseType, Array] The return type of this field # @param owner [Class] The type that this field belongs to # @param null [Boolean] `true` if this field may return `null`, `false` if it is never `null` # @param description [String] Field description # @param deprecation_reason [String] If present, the field is marked "deprecated" with this message # @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`) # @param hash_key [String, Symbol] The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`) # @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`) # @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name # @param max_page_size [Integer] For connections, the maximum number of items to return from this field # @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__` # @param resolve [<#call(obj, args, ctx)>] **deprecated** for compatibility with <1.8.0 # @param field [GraphQL::Field, GraphQL::Schema::Field] **deprecated** for compatibility with <1.8.0 # @param function [GraphQL::Function] **deprecated** for compatibility with <1.8.0 # @param resolver_class [Class] (Private) A {Schema::Resolver} which this field was derived from. Use `resolver:` to create a field with a resolver. # @param arguments [{String=>GraphQL::Schema::Argument, Hash}] Arguments for this field (may be added in the block, also) # @param camelize [Boolean] If true, the field name will be camelized when building the schema # @param complexity [Numeric] When provided, set the complexity for this field # @param scope [Boolean] If true, the return type's `.scope_items` method will be called on the return value # @param subscription_scope [Symbol, String] A key in `context` which will be used to scope subscription payloads # @param extensions [Array Object>>] Named extensions to apply to this field (see also {#extension}) # @param trace [Boolean] If true, a {GraphQL::Tracing} tracer will measure this scalar field def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: nil, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, extras: [], extensions: [], resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, arguments: {}, &definition_block) if name.nil? raise ArgumentError, "missing first `name` argument or keyword `name:`" end if !(field || function || resolver_class) if type.nil? raise ArgumentError, "missing second `type` argument or keyword `type:`" end if null.nil? raise ArgumentError, "missing keyword argument null:" end end if (field || function || resolve) && extras.any? raise ArgumentError, "keyword `extras:` may only be used with method-based resolve and class-based field such as mutation class, please remove `field:`, `function:` or `resolve:`" end @original_name = name @underscored_name = -Member::BuildType.underscore(name.to_s) @name = -(camelize ? Member::BuildType.camelize(name.to_s) : name.to_s) @description = description if field.is_a?(GraphQL::Schema::Field) raise ArgumentError, "Instead of passing a field as `field:`, use `add_field(field)` to add an already-defined field." else @field = field end @function = function @resolve = resolve @deprecation_reason = deprecation_reason if method && hash_key raise ArgumentError, "Provide `method:` _or_ `hash_key:`, not both. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}`)" end if resolver_method if method raise ArgumentError, "Provide `method:` _or_ `resolver_method:`, not both. (called with: `method: #{method.inspect}, resolver_method: #{resolver_method.inspect}`)" end if hash_key raise ArgumentError, "Provide `hash_key:` _or_ `resolver_method:`, not both. (called with: `hash_key: #{hash_key.inspect}, resolver_method: #{resolver_method.inspect}`)" end end # TODO: I think non-string/symbol hash keys are wrongly normalized (eg `1` will not work) method_name = method || hash_key || @underscored_name resolver_method ||= @underscored_name.to_sym @method_str = method_name.to_s @method_sym = method_name.to_sym @resolver_method = resolver_method @complexity = complexity @return_type_expr = type @return_type_null = null @connection = connection @max_page_size = max_page_size @introspection = introspection @extras = extras @resolver_class = resolver_class @scope = scope @trace = trace @relay_node_field = relay_node_field @relay_nodes_field = relay_nodes_field # Override the default from HasArguments @own_arguments = {} arguments.each do |name, arg| if arg.is_a?(Hash) argument(name: name, **arg) else @own_arguments[name] = arg end end @owner = owner @subscription_scope = subscription_scope # Do this last so we have as much context as possible when initializing them: @extensions = [] if extensions.any? self.extensions(extensions) end # This should run before connection extension, # but should it run after the definition block? if scoped? self.extension(ScopeExtension) end # The problem with putting this after the definition_block # is that it would override arguments if connection? self.extension(self.class.connection_extension) end if definition_block if definition_block.arity == 1 yield self else instance_eval(&definition_block) end end end # @param text [String] # @return [String] def description(text = nil) if text @description = text else @description end end # Read extension instances from this field, # or add new classes/options to be initialized on this field. # Extensions are executed in the order they are added. # # @example adding an extension # extensions([MyExtensionClass]) # # @example adding multiple extensions # extensions([MyExtensionClass, AnotherExtensionClass]) # # @example adding an extension with options # extensions([MyExtensionClass, { AnotherExtensionClass => { filter: true } }]) # # @param extensions [Array Object>>] Add extensions to this field. For hash elements, only the first key/value is used. # @return [Array] extensions to apply to this field def extensions(new_extensions = nil) if new_extensions.nil? # Read the value @extensions else new_extensions.each do |extension| if extension.is_a?(Hash) extension = extension.to_a[0] extension_class, options = *extension @extensions << extension_class.new(field: self, options: options) else extension_class = extension @extensions << extension_class.new(field: self, options: nil) end end end end # Add `extension` to this field, initialized with `options` if provided. # # @example adding an extension # extension(MyExtensionClass) # # @example adding an extension with options # extension(MyExtensionClass, filter: true) # # @param extension [Class] subclass of {Schema::Fieldextension} # @param options [Object] if provided, given as `options:` when initializing `extension`. def extension(extension, options = nil) extensions([{extension => options}]) end # Read extras (as symbols) from this field, # or add new extras to be opted into by this field's resolver. # # @param new_extras [Array] Add extras to this field # @return [Array] def extras(new_extras = nil) if new_extras.nil? # Read the value @extras else # Append to the set of extras on this field @extras.concat(new_extras) end end def complexity(new_complexity) case new_complexity when Proc if new_complexity.parameters.size != 3 fail( "A complexity proc should always accept 3 parameters: ctx, args, child_complexity. "\ "E.g.: complexity ->(ctx, args, child_complexity) { child_complexity * args[:limit] }" ) else @complexity = new_complexity end when Numeric @complexity = new_complexity else raise("Invalid complexity: #{new_complexity.inspect} on #{@name}") end end # @return [Integer, nil] Applied to connections if present attr_reader :max_page_size # @return [GraphQL::Field] def to_graphql field_defn = if @field @field.dup elsif @function GraphQL::Function.build_field(@function) else GraphQL::Field.new end field_defn.name = @name if @return_type_expr field_defn.type = -> { type } end if @description field_defn.description = @description end if @deprecation_reason field_defn.deprecation_reason = @deprecation_reason end if @resolver_class if @resolver_class < GraphQL::Schema::Mutation field_defn.mutation = @resolver_class end field_defn.metadata[:resolver] = @resolver_class end if !@trace.nil? field_defn.trace = @trace end if @relay_node_field field_defn.relay_node_field = @relay_node_field end if @relay_nodes_field field_defn.relay_nodes_field = @relay_nodes_field end field_defn.resolve = self.method(:resolve_field) field_defn.connection = connection? field_defn.connection_max_page_size = max_page_size field_defn.introspection = @introspection field_defn.complexity = @complexity field_defn.subscription_scope = @subscription_scope arguments.each do |name, defn| arg_graphql = defn.to_graphql field_defn.arguments[arg_graphql.name] = arg_graphql end # Support a passed-in proc, one way or another @resolve_proc = if @resolve @resolve elsif @function @function elsif @field @field.resolve_proc end # Ok, `self` isn't a class, but this is for consistency with the classes field_defn.metadata[:type_class] = self field_defn end def type @type ||= Member::BuildType.parse_type(@return_type_expr, null: @return_type_null) rescue raise ArgumentError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: #{$!.message}", $!.backtrace end def visible?(context) if @resolver_class @resolver_class.visible?(context) else true end end def accessible?(context) if @resolver_class @resolver_class.accessible?(context) else true end end def authorized?(object, context) if @resolver_class # The resolver will check itself during `resolve()` @resolver_class.authorized?(object, context) else # Faster than `.any?` arguments.each_value do |arg| if !arg.authorized?(object, context) return false end end true end end # Implement {GraphQL::Field}'s resolve API. # # Eventually, we might hook up field instances to execution in another way. TBD. # @see #resolve for how the interpreter hooks up to it def resolve_field(obj, args, ctx) ctx.schema.after_lazy(obj) do |after_obj| # First, apply auth ... query_ctx = ctx.query.context # Some legacy fields can have `nil` here, not exactly sure why. # @see https://github.com/rmosolgo/graphql-ruby/issues/1990 before removing inner_obj = after_obj && after_obj.object if authorized?(inner_obj, query_ctx) ruby_args = to_ruby_args(after_obj, args, ctx) # Then if it passed, resolve the field if @resolve_proc # Might be nil, still want to call the func in that case with_extensions(inner_obj, ruby_args, query_ctx) do |extended_obj, extended_args| # Pass the GraphQL args here for compatibility: @resolve_proc.call(extended_obj, args, ctx) end else public_send_field(after_obj, ruby_args, ctx) end else err = GraphQL::UnauthorizedFieldError.new(object: inner_obj, type: obj.class, context: ctx, field: self) query_ctx.schema.unauthorized_field(err) end end end # This method is called by the interpreter for each field. # You can extend it in your base field classes. # @param object [GraphQL::Schema::Object] An instance of some type class, wrapping an application object # @param args [Hash] A symbol-keyed hash of Ruby keyword arguments. (Empty if no args) # @param ctx [GraphQL::Query::Context] def resolve(object, args, ctx) if @resolve_proc raise "Can't run resolve proc for #{path} when using GraphQL::Execution::Interpreter" end begin # Unwrap the GraphQL object to get the application object. application_object = object.object if self.authorized?(application_object, ctx) # Apply field extensions with_extensions(object, args, ctx) do |extended_obj, extended_args| field_receiver = if @resolver_class resolver_obj = if extended_obj.is_a?(GraphQL::Schema::Object) extended_obj.object else extended_obj end @resolver_class.new(object: resolver_obj, context: ctx, field: self) else extended_obj end if field_receiver.respond_to?(@resolver_method) # Call the method with kwargs, if there are any if extended_args.any? field_receiver.public_send(@resolver_method, **extended_args) else field_receiver.public_send(@resolver_method) end else resolve_field_method(field_receiver, extended_args, ctx) end end else err = GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self) ctx.schema.unauthorized_field(err) end rescue GraphQL::UnauthorizedFieldError => err err.field ||= self ctx.schema.unauthorized_field(err) rescue GraphQL::UnauthorizedError => err ctx.schema.unauthorized_object(err) end rescue GraphQL::ExecutionError => err err end # Find a way to resolve this field, checking: # # - Hash keys, if the wrapped object is a hash; # - A method on the wrapped object; # - Or, raise not implemented. # # This can be overridden by defining a method on the object type. # @param obj [GraphQL::Schema::Object] # @param ruby_kwargs [Hash Object>] # @param ctx [GraphQL::Query::Context] def resolve_field_method(obj, ruby_kwargs, ctx) if obj.object.is_a?(Hash) inner_object = obj.object if inner_object.key?(@method_sym) inner_object[@method_sym] else inner_object[@method_str] end elsif obj.object.respond_to?(@method_sym) if ruby_kwargs.any? obj.object.public_send(@method_sym, **ruby_kwargs) else obj.object.public_send(@method_sym) end else raise <<-ERR Failed to implement #{@owner.graphql_name}.#{@name}, tried: - `#{obj.class}##{@resolver_method}`, which did not exist - `#{obj.object.class}##{@method_sym}`, which did not exist - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash To implement this field, define one of the methods above (and check for typos) ERR end end # @param ctx [GraphQL::Query::Context::FieldResolutionContext] def fetch_extra(extra_name, ctx) if extra_name != :path && respond_to?(extra_name) self.public_send(extra_name) elsif ctx.respond_to?(extra_name) ctx.public_send(extra_name) else raise GraphQL::RequiredImplementationMissingError, "Unknown field extra for #{self.path}: #{extra_name.inspect}" end end private NO_ARGS = {}.freeze # Convert a GraphQL arguments instance into a Ruby-style hash. # # @param obj [GraphQL::Schema::Object] The object where this field is being resolved # @param graphql_args [GraphQL::Query::Arguments] # @param field_ctx [GraphQL::Query::Context::FieldResolutionContext] # @return [Hash Any>] def to_ruby_args(obj, graphql_args, field_ctx) if graphql_args.any? || @extras.any? # Splat the GraphQL::Arguments to Ruby keyword arguments ruby_kwargs = graphql_args.to_kwargs # Apply any `prepare` methods. Not great code organization, can this go somewhere better? arguments.each do |name, arg_defn| ruby_kwargs_key = arg_defn.keyword if ruby_kwargs.key?(ruby_kwargs_key) && arg_defn.prepare ruby_kwargs[ruby_kwargs_key] = arg_defn.prepare_value(obj, ruby_kwargs[ruby_kwargs_key]) end end @extras.each do |extra_arg| ruby_kwargs[extra_arg] = fetch_extra(extra_arg, field_ctx) end ruby_kwargs else NO_ARGS end end def public_send_field(obj, ruby_kwargs, field_ctx) query_ctx = field_ctx.query.context with_extensions(obj, ruby_kwargs, query_ctx) do |extended_obj, extended_args| if @resolver_class if extended_obj.is_a?(GraphQL::Schema::Object) extended_obj = extended_obj.object end extended_obj = @resolver_class.new(object: extended_obj, context: query_ctx, field: self) end if extended_obj.respond_to?(@resolver_method) if extended_args.any? extended_obj.public_send(@resolver_method, **extended_args) else extended_obj.public_send(@resolver_method) end else resolve_field_method(extended_obj, extended_args, query_ctx) end end end # Wrap execution with hooks. # Written iteratively to avoid big stack traces. # @return [Object] Whatever the def with_extensions(obj, args, ctx) if @extensions.empty? yield(obj, args) else # Save these so that the originals can be re-given to `after_resolve` handlers. original_args = args original_obj = obj memos = [] value = run_extensions_before_resolve(memos, obj, args, ctx) do |extended_obj, extended_args| yield(extended_obj, extended_args) end ctx.schema.after_lazy(value) do |resolved_value| @extensions.each_with_index do |ext, idx| memo = memos[idx] # TODO after_lazy? resolved_value = ext.after_resolve(object: original_obj, arguments: original_args, context: ctx, value: resolved_value, memo: memo) end resolved_value end end end def run_extensions_before_resolve(memos, obj, args, ctx, idx: 0) extension = @extensions[idx] if extension extension.resolve(object: obj, arguments: args, context: ctx) do |extended_obj, extended_args, memo| memos << memo run_extensions_before_resolve(memos, extended_obj, extended_args, ctx, idx: idx + 1) { |o, a| yield(o, a) } end else yield(obj, args) end end end end end ruby-graphql-1.9.19/lib/graphql/schema/field/000077500000000000000000000000001362601351000207545ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/schema/field/connection_extension.rb000066400000000000000000000034101362601351000255320ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Field class ConnectionExtension < GraphQL::Schema::FieldExtension def apply field.argument :after, "String", "Returns the elements in the list that come after the specified cursor.", required: false field.argument :before, "String", "Returns the elements in the list that come before the specified cursor.", required: false field.argument :first, "Int", "Returns the first _n_ elements from the list.", required: false field.argument :last, "Int", "Returns the last _n_ elements from the list.", required: false end # Remove pagination args before passing it to a user method def resolve(object:, arguments:, context:) next_args = arguments.dup next_args.delete(:first) next_args.delete(:last) next_args.delete(:before) next_args.delete(:after) yield(object, next_args) end def after_resolve(value:, object:, arguments:, context:, memo:) if value.is_a? GraphQL::ExecutionError # This isn't even going to work because context doesn't have ast_node anymore context.add_error(value) nil elsif value.nil? nil else if object.is_a?(GraphQL::Schema::Object) object = object.object end connection_class = GraphQL::Relay::BaseConnection.connection_for_nodes(value) connection_class.new( value, arguments, field: field, max_page_size: field.max_page_size, parent: object, context: context, ) end end end end end end ruby-graphql-1.9.19/lib/graphql/schema/field/scope_extension.rb000066400000000000000000000007611362601351000245120ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Field class ScopeExtension < GraphQL::Schema::FieldExtension def after_resolve(value:, context:, **rest) if value.nil? value else ret_type = @field.type.unwrap if ret_type.respond_to?(:scope_items) ret_type.scope_items(value, context) else value end end end end end end end ruby-graphql-1.9.19/lib/graphql/schema/field_extension.rb000066400000000000000000000057031362601351000234020ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Extend this class to make field-level customizations to resolve behavior. # # When a extension is added to a field with `extension(MyExtension)`, a `MyExtension` instance # is created, and its hooks are applied whenever that field is called. # # The instance is frozen so that instance variables aren't modified during query execution, # which could cause all kinds of issues due to race conditions. class FieldExtension # @return [GraphQL::Schema::Field] attr_reader :field # @return [Object] attr_reader :options # Called when the extension is mounted with `extension(name, options)`. # The instance is frozen to avoid improper use of state during execution. # @param field [GraphQL::Schema::Field] The field where this extension was mounted # @param options [Object] The second argument to `extension`, or `{}` if nothing was passed. def initialize(field:, options:) @field = field @options = options || {} apply freeze end # Called when this extension is attached to a field. # The field definition may be extended during this method. # @return [void] def apply end # Called before resolving {#field}. It should either: # # - `yield` values to continue execution; OR # - return something else to shortcut field execution. # # Whatever this method returns will be used for execution. # # @param object [Object] The object the field is being resolved on # @param arguments [Hash] Ruby keyword arguments for resolving this field # @param context [Query::Context] the context for this query # @yieldparam object [Object] The object to continue resolving the field on # @yieldparam arguments [Hash] The keyword arguments to continue resolving with # @yieldparam memo [Object] Any extension-specific value which will be passed to {#after_resolve} later # @return [Object] The return value for this field. def resolve(object:, arguments:, context:) yield(object, arguments, nil) end # Called after {#field} was resolved, and after any lazy values (like `Promise`s) were synced, # but before the value was added to the GraphQL response. # # Whatever this hook returns will be used as the return value. # # @param object [Object] The object the field is being resolved on # @param arguments [Hash] Ruby keyword arguments for resolving this field # @param context [Query::Context] the context for this query # @param value [Object] Whatever the field previously returned # @param memo [Object] The third value yielded by {#resolve}, or `nil` if there wasn't one # @return [Object] The return value for this field. def after_resolve(object:, arguments:, context:, value:, memo:) value end end end end ruby-graphql-1.9.19/lib/graphql/schema/find_inherited_value.rb000066400000000000000000000010141362601351000243610ustar00rootroot00000000000000module GraphQL class Schema module FindInheritedValue private def find_inherited_value(method_name, default_value = nil) if self.is_a?(Class) superclass.respond_to?(method_name, true) ? superclass.send(method_name) : default_value else ancestors[1..-1].each do |ancestor| if ancestor.respond_to?(method_name, true) return ancestor.send(method_name) end end default_value end end end end end ruby-graphql-1.9.19/lib/graphql/schema/finder.rb000066400000000000000000000106121362601351000214650ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Find schema members using string paths # # @example Finding object types # MySchema.find("SomeObjectType") # # @example Finding fields # MySchema.find("SomeObjectType.myField") # # @example Finding arguments # MySchema.find("SomeObjectType.myField.anArgument") # # @example Finding directives # MySchema.find("@include") # class Finder class MemberNotFoundError < ArgumentError; end def initialize(schema) @schema = schema end def find(path) path = path.split(".") type_or_directive = path.shift if type_or_directive.start_with?("@") directive = schema.directives[type_or_directive[1..-1]] if directive.nil? raise MemberNotFoundError, "Could not find directive `#{type_or_directive}` in schema." end return directive if path.empty? find_in_directive(directive, path: path) else type = schema.types[type_or_directive] if type.nil? raise MemberNotFoundError, "Could not find type `#{type_or_directive}` in schema." end return type if path.empty? find_in_type(type, path: path) end end private attr_reader :schema def find_in_directive(directive, path:) argument_name = path.shift argument = directive.arguments[argument_name] if argument.nil? raise MemberNotFoundError, "Could not find argument `#{argument_name}` on directive #{directive}." end argument end def find_in_type(type, path:) case type when GraphQL::ObjectType find_in_fields_type(type, kind: "object", path: path) when GraphQL::InterfaceType find_in_fields_type(type, kind: "interface", path: path) when GraphQL::InputObjectType find_in_input_object(type, path: path) when GraphQL::UnionType # Error out if path that was provided is too long # i.e UnionType.PossibleType.aField # Use PossibleType.aField instead. if invalid = path.first raise MemberNotFoundError, "Cannot select union possible type `#{invalid}`. Select the type directly instead." end when GraphQL::EnumType find_in_enum_type(type, path: path) end end def find_in_fields_type(type, kind:, path:) field_name = path.shift field = schema.get_field(type, field_name) if field.nil? raise MemberNotFoundError, "Could not find field `#{field_name}` on #{kind} type `#{type}`." end return field if path.empty? find_in_field(field, path: path) end def find_in_field(field, path:) argument_name = path.shift argument = field.arguments[argument_name] if argument.nil? raise MemberNotFoundError, "Could not find argument `#{argument_name}` on field `#{field.name}`." end # Error out if path that was provided is too long # i.e Type.field.argument.somethingBad if invalid = path.first raise MemberNotFoundError, "Cannot select member `#{invalid}` on a field." end argument end def find_in_input_object(input_object, path:) field_name = path.shift input_field = input_object.input_fields[field_name] if input_field.nil? raise MemberNotFoundError, "Could not find input field `#{field_name}` on input object type `#{input_object}`." end # Error out if path that was provided is too long # i.e InputType.inputField.bad if invalid = path.first raise MemberNotFoundError, "Cannot select member `#{invalid}` on an input field." end input_field end def find_in_enum_type(enum_type, path:) value_name = path.shift enum_value = enum_type.values[value_name] if enum_value.nil? raise MemberNotFoundError, "Could not find enum value `#{value_name}` on enum type `#{enum_type}`." end # Error out if path that was provided is too long # i.e Enum.VALUE.wat if invalid = path.first raise MemberNotFoundError, "Cannot select member `#{invalid}` on an enum value." end enum_value end end end end ruby-graphql-1.9.19/lib/graphql/schema/input_object.rb000066400000000000000000000102061362601351000227020ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class InputObject < GraphQL::Schema::Member extend GraphQL::Schema::Member::AcceptsDefinition extend Forwardable extend GraphQL::Schema::Member::HasArguments include GraphQL::Dig def initialize(values = nil, ruby_kwargs: nil, context:, defaults_used:) @context = context if ruby_kwargs @ruby_style_hash = ruby_kwargs else @arguments = self.class.arguments_class.new(values, context: context, defaults_used: defaults_used) # Symbolized, underscored hash: @ruby_style_hash = @arguments.to_kwargs end # Apply prepares, not great to have it duplicated here. @arguments_by_keyword = {} self.class.arguments.each do |name, arg_defn| @arguments_by_keyword[arg_defn.keyword] = arg_defn ruby_kwargs_key = arg_defn.keyword loads = arg_defn.loads if @ruby_style_hash.key?(ruby_kwargs_key) && loads && !arg_defn.from_resolver? value = @ruby_style_hash[ruby_kwargs_key] @ruby_style_hash[ruby_kwargs_key] = if arg_defn.type.list? GraphQL::Execution::Lazy.all(value.map { |val| load_application_object(arg_defn, loads, val) }) else load_application_object(arg_defn, loads, value) end end if @ruby_style_hash.key?(ruby_kwargs_key) && arg_defn.prepare @ruby_style_hash[ruby_kwargs_key] = arg_defn.prepare_value(self, @ruby_style_hash[ruby_kwargs_key]) end end end # @return [GraphQL::Query::Context] The context for this query attr_reader :context # @return [GraphQL::Query::Arguments] The underlying arguments instance attr_reader :arguments # Ruby-like hash behaviors, read-only def_delegators :@ruby_style_hash, :keys, :values, :each, :map, :any?, :empty? def to_h @ruby_style_hash.inject({}) do |h, (key, value)| h.merge(key => unwrap_value(value)) end end def to_hash to_h end def prepare self end def unwrap_value(value) case value when Array value.map { |item| unwrap_value(item) } when Hash value.inject({}) do |h, (key, value)| h.merge(key => unwrap_value(value)) end when InputObject value.to_h else value end end # Lookup a key on this object, it accepts new-style underscored symbols # Or old-style camelized identifiers. # @param key [Symbol, String] def [](key) if @ruby_style_hash.key?(key) @ruby_style_hash[key] elsif @arguments @arguments[key] else nil end end def key?(key) @ruby_style_hash.key?(key) || (@arguments && @arguments.key?(key)) end # A copy of the Ruby-style hash def to_kwargs @ruby_style_hash.dup end class << self # @return [Class] attr_accessor :arguments_class def argument(*args, **kwargs, &block) argument_defn = super(*args, **kwargs, &block) # Add a method access method_name = argument_defn.keyword define_method(method_name) do self[method_name] end end def to_graphql type_defn = GraphQL::InputObjectType.new type_defn.name = graphql_name type_defn.description = description type_defn.metadata[:type_class] = self type_defn.mutation = mutation arguments.each do |name, arg| type_defn.arguments[arg.graphql_definition.name] = arg.graphql_definition end # Make a reference to a classic-style Arguments class self.arguments_class = GraphQL::Query::Arguments.construct_arguments_class(type_defn) # But use this InputObject class at runtime type_defn.arguments_class = self type_defn end def kind GraphQL::TypeKinds::INPUT_OBJECT end end end end end ruby-graphql-1.9.19/lib/graphql/schema/interface.rb000066400000000000000000000107571362601351000221700ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema module Interface include GraphQL::Schema::Member::GraphQLTypeNames module DefinitionMethods include GraphQL::Schema::Member::CachedGraphQLDefinition include GraphQL::Relay::TypeExtensions include GraphQL::Schema::Member::BaseDSLMethods include GraphQL::Schema::Member::TypeSystemHelpers include GraphQL::Schema::Member::HasFields include GraphQL::Schema::Member::HasPath include GraphQL::Schema::Member::RelayShortcuts include GraphQL::Schema::Member::Scoped # Methods defined in this block will be: # - Added as class methods to this interface # - Added as class methods to all child interfaces def definition_methods(&block) self::DefinitionMethods.module_eval(&block) end # The interface is visible if any of its possible types are visible def visible?(context) context.schema.possible_types(self).each do |type| if context.schema.visible?(type, context) return true end end false end # The interface is accessible if any of its possible types are accessible def accessible?(context) context.schema.possible_types(self).each do |type| if context.schema.accessible?(type, context) return true end end false end # Here's the tricky part. Make sure behavior keeps making its way down the inheritance chain. def included(child_class) if !child_class.is_a?(Class) # In this case, it's been included into another interface. # This is how interface inheritance is implemented # We need this before we can call `own_interfaces` child_class.extend(Schema::Interface::DefinitionMethods) child_class.own_interfaces << self child_class.interfaces.reverse_each do |interface_defn| child_class.extend(interface_defn::DefinitionMethods) end # Use an instance variable to tell whether it's been included previously or not; # You can't use constant detection because constants are brought into scope # by `include`, which has already happened at this point. if !child_class.instance_variable_defined?(:@_definition_methods) defn_methods_module = Module.new child_class.instance_variable_set(:@_definition_methods, defn_methods_module) child_class.const_set(:DefinitionMethods, defn_methods_module) child_class.extend(child_class::DefinitionMethods) end elsif child_class < GraphQL::Schema::Object # This is being included into an object type, make sure it's using `implements(...)` backtrace_line = caller(0, 10).find { |line| line.include?("schema/object.rb") && line.include?("in `implements'")} if !backtrace_line raise "Attach interfaces using `implements(#{self})`, not `include(#{self})`" end end super end def orphan_types(*types) if types.any? @orphan_types = types else all_orphan_types = @orphan_types || [] all_orphan_types += super if defined?(super) all_orphan_types.uniq end end def to_graphql type_defn = GraphQL::InterfaceType.new type_defn.name = graphql_name type_defn.description = description type_defn.orphan_types = orphan_types fields.each do |field_name, field_inst| field_defn = field_inst.graphql_definition type_defn.fields[field_defn.name] = field_defn end type_defn.metadata[:type_class] = self if respond_to?(:resolve_type) type_defn.resolve_type = method(:resolve_type) end type_defn end def kind GraphQL::TypeKinds::INTERFACE end protected def own_interfaces @own_interfaces ||= [] end def interfaces own_interfaces + (own_interfaces.map { |i| i.own_interfaces }).flatten end end # Extend this _after_ `DefinitionMethods` is defined, so it will be used extend GraphQL::Schema::Member::AcceptsDefinition extend DefinitionMethods def unwrap self end end end end ruby-graphql-1.9.19/lib/graphql/schema/introspection_system.rb000066400000000000000000000065001362601351000245230ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class IntrospectionSystem attr_reader :schema_type, :type_type, :typename_field def initialize(schema) @schema = schema @built_in_namespace = GraphQL::Introspection @custom_namespace = schema.introspection_namespace || @built_in_namespace # Use to-graphql to avoid sharing with any previous instantiations @schema_type = load_constant(:SchemaType).to_graphql @type_type = load_constant(:TypeType).to_graphql @field_type = load_constant(:FieldType).to_graphql @directive_type = load_constant(:DirectiveType).to_graphql @enum_value_type = load_constant(:EnumValueType).to_graphql @input_value_type = load_constant(:InputValueType).to_graphql @type_kind_enum = load_constant(:TypeKindEnum).to_graphql @directive_location_enum = load_constant(:DirectiveLocationEnum).to_graphql @entry_point_fields = if schema.disable_introspection_entry_points {} else entry_point_fields = get_fields_from_class(class_sym: :EntryPoints) entry_point_fields.delete('__schema') if schema.disable_schema_introspection_entry_point entry_point_fields.delete('__type') if schema.disable_type_introspection_entry_point entry_point_fields end @dynamic_fields = get_fields_from_class(class_sym: :DynamicFields) end def object_types [ @schema_type, @type_type, @field_type, @directive_type, @enum_value_type, @input_value_type, @type_kind_enum, @directive_location_enum, ] end def entry_points @entry_point_fields.values end def entry_point(name:) @entry_point_fields[name] end def dynamic_fields @dynamic_fields.values end def dynamic_field(name:) @dynamic_fields[name] end private def load_constant(class_name) @custom_namespace.const_get(class_name) rescue NameError # Dup the built-in so that the cached fields aren't shared @built_in_namespace.const_get(class_name) end def get_fields_from_class(class_sym:) object_class = load_constant(class_sym) object_type_defn = object_class.to_graphql extracted_field_defns = {} object_type_defn.all_fields.each do |field_defn| inner_resolve = field_defn.resolve_proc resolve_with_instantiate = PerFieldProxyResolve.new(object_class: object_class, inner_resolve: inner_resolve) extracted_field_defns[field_defn.name] = field_defn.redefine(resolve: resolve_with_instantiate) end extracted_field_defns end class PerFieldProxyResolve def initialize(object_class:, inner_resolve:) @object_class = object_class @inner_resolve = inner_resolve end def call(obj, args, ctx) query_ctx = ctx.query.context # Remove the QueryType wrapper if obj.is_a?(GraphQL::Schema::Object) obj = obj.object end wrapped_object = @object_class.authorized_new(obj, query_ctx) @inner_resolve.call(wrapped_object, args, ctx) end end end end end ruby-graphql-1.9.19/lib/graphql/schema/invalid_type_error.rb000066400000000000000000000001721362601351000241160ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class InvalidTypeError < GraphQL::Error end end end ruby-graphql-1.9.19/lib/graphql/schema/late_bound_type.rb000066400000000000000000000012351362601351000233740ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # A stand-in for a type which will be resolved in a given schema, by name. # TODO: support argument types too, make this a public API somehow # @api Private class LateBoundType attr_reader :name def initialize(local_name) @name = local_name end def unwrap self end def to_non_null_type GraphQL::NonNullType.new(of_type: self) end def to_list_type GraphQL::ListType.new(of_type: self) end def inspect "#" end alias :to_s :inspect end end end ruby-graphql-1.9.19/lib/graphql/schema/list.rb000066400000000000000000000011261362601351000211710ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Represents a list type in the schema. # Wraps a {Schema::Member} as a list type. # @see {Schema::Member::TypeSystemHelpers#to_list_type} class List < GraphQL::Schema::Wrapper def to_graphql @of_type.graphql_definition.to_list_type end # @return [GraphQL::TypeKinds::LIST] def kind GraphQL::TypeKinds::LIST end # @return [true] def list? true end def to_type_signature "[#{@of_type.to_type_signature}]" end end end end ruby-graphql-1.9.19/lib/graphql/schema/loader.rb000066400000000000000000000155421362601351000214730ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # You can use the result of {GraphQL::Introspection::INTROSPECTION_QUERY} # to make a schema. This schema is missing some important details like # `resolve` functions, but it does include the full type system, # so you can use it to validate queries. module Loader extend self # Create schema with the result of an introspection query. # @param introspection_result [Hash] A response from {GraphQL::Introspection::INTROSPECTION_QUERY} # @return [GraphQL::Schema] the schema described by `input` # @deprecated Use {GraphQL::Schema.from_introspection} instead def load(introspection_result) schema = introspection_result.fetch("data").fetch("__schema") types = {} type_resolver = ->(type) { -> { resolve_type(types, type) } } schema.fetch("types").each do |type| next if type.fetch("name").start_with?("__") type_object = define_type(type, type_resolver) types[type_object.name] = type_object end kargs = { orphan_types: types.values, resolve_type: NullResolveType } [:query, :mutation, :subscription].each do |root| type = schema["#{root}Type"] kargs[root] = types.fetch(type.fetch("name")) if type end Schema.define(**kargs, raise_definition_error: true) end NullResolveType = ->(type, obj, ctx) { raise(GraphQL::RequiredImplementationMissingError, "This schema was loaded from string, so it can't resolve types for objects") } NullScalarCoerce = ->(val, _ctx) { val } class << self private def resolve_type(types, type) case kind = type.fetch("kind") when "ENUM", "INTERFACE", "INPUT_OBJECT", "OBJECT", "SCALAR", "UNION" types.fetch(type.fetch("name")) when "LIST" ListType.new(of_type: resolve_type(types, type.fetch("ofType"))) when "NON_NULL" NonNullType.new(of_type: resolve_type(types, type.fetch("ofType"))) else fail GraphQL::RequiredImplementationMissingError, "#{kind} not implemented" end end def extract_default_value(default_value_str, input_value_ast) case input_value_ast when String, Integer, Float, TrueClass, FalseClass input_value_ast when GraphQL::Language::Nodes::Enum input_value_ast.name when GraphQL::Language::Nodes::NullValue nil when GraphQL::Language::Nodes::InputObject input_value_ast.to_h when Array input_value_ast.map { |element| extract_default_value(default_value_str, element) } else raise( "Encountered unexpected type when loading default value. "\ "input_value_ast.class is #{input_value_ast.class} "\ "default_value is #{default_value_str}" ) end end def define_type(type, type_resolver) case type.fetch("kind") when "ENUM" EnumType.define( name: type["name"], description: type["description"], values: type["enumValues"].map { |enum| EnumType::EnumValue.define( name: enum["name"], description: enum["description"], deprecation_reason: enum["deprecationReason"], value: enum["name"] ) }) when "INTERFACE" InterfaceType.define( name: type["name"], description: type["description"], fields: Hash[(type["fields"] || []).map { |field| [field["name"], define_type(field.merge("kind" => "FIELD"), type_resolver)] }] ) when "INPUT_OBJECT" InputObjectType.define( name: type["name"], description: type["description"], arguments: Hash[type["inputFields"].map { |arg| [arg["name"], define_type(arg.merge("kind" => "ARGUMENT"), type_resolver)] }] ) when "OBJECT" ObjectType.define( name: type["name"], description: type["description"], interfaces: (type["interfaces"] || []).map { |interface| type_resolver.call(interface) }, fields: Hash[type["fields"].map { |field| [field["name"], define_type(field.merge("kind" => "FIELD"), type_resolver)] }] ) when "FIELD" defns = { name: type["name"], type: type_resolver.call(type["type"]), description: type["description"], } # Avoid passing an empty hash, which warns on Ruby 2.7 if type["args"].any? defns[:arguments] = Hash[type["args"].map { |arg| [arg["name"], define_type(arg.merge("kind" => "ARGUMENT"), type_resolver)] }] end GraphQL::Field.define(**defns) when "ARGUMENT" kwargs = {} if type["defaultValue"] kwargs[:default_value] = begin default_value_str = type["defaultValue"] dummy_query_str = "query getStuff($var: InputObj = #{default_value_str}) { __typename }" # Returns a `GraphQL::Language::Nodes::Document`: dummy_query_ast = GraphQL.parse(dummy_query_str) # Reach into the AST for the default value: input_value_ast = dummy_query_ast.definitions.first.variables.first.default_value extract_default_value(default_value_str, input_value_ast) end end GraphQL::Argument.define( name: type["name"], type: type_resolver.call(type["type"]), description: type["description"], method_access: false, **kwargs ) when "SCALAR" type_name = type.fetch("name") if GraphQL::Schema::BUILT_IN_TYPES[type_name] GraphQL::Schema::BUILT_IN_TYPES[type_name] else ScalarType.define( name: type["name"], description: type["description"], coerce: NullScalarCoerce, ) end when "UNION" UnionType.define( name: type["name"], description: type["description"], possible_types: type["possibleTypes"].map { |possible_type| type_resolver.call(possible_type) } ) else fail GraphQL::RequiredImplementationMissingError, "#{type["kind"]} not implemented" end end end end end end ruby-graphql-1.9.19/lib/graphql/schema/member.rb000066400000000000000000000020461362601351000214670ustar00rootroot00000000000000# frozen_string_literal: true require 'graphql/schema/member/accepts_definition' require 'graphql/schema/member/base_dsl_methods' require 'graphql/schema/member/cached_graphql_definition' require 'graphql/schema/member/graphql_type_names' require 'graphql/schema/member/has_path' require 'graphql/schema/member/relay_shortcuts' require 'graphql/schema/member/scoped' require 'graphql/schema/member/type_system_helpers' require "graphql/relay/type_extensions" module GraphQL class Schema # The base class for things that make up the schema, # eg objects, enums, scalars. # # @api private class Member include GraphQLTypeNames extend CachedGraphQLDefinition extend GraphQL::Relay::TypeExtensions extend BaseDSLMethods extend TypeSystemHelpers extend Scoped extend RelayShortcuts extend HasPath end end end require 'graphql/schema/member/has_arguments' require 'graphql/schema/member/has_fields' require 'graphql/schema/member/instrumentation' require 'graphql/schema/member/build_type' ruby-graphql-1.9.19/lib/graphql/schema/member/000077500000000000000000000000001362601351000211405ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/schema/member/accepts_definition.rb000066400000000000000000000123521362601351000253220ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member # Support for legacy `accepts_definitions` functions. # # Keep the legacy handler hooked up. Class-based types and fields # will call those legacy handlers during their `.to_graphql` # methods. # # This can help out while transitioning from one to the other. # Eventually, `GraphQL::{X}Type` objects will be removed entirely, # But this can help during the transition. # # @example Applying a function to base object class # # Here's the legacy-style config, which we're calling back to: # GraphQL::ObjectType.accepts_definition({ # permission_level: ->(defn, value) { defn.metadata[:permission_level] = value } # }) # # class BaseObject < GraphQL::Schema::Object # # Setup a named pass-through to the legacy config functions # accepts_definition :permission_level # end # # class Account < BaseObject # # This value will be passed to the legacy handler: # permission_level 1 # end # # # The class gets a reader method which returns the args, # # only marginally useful. # Account.permission_level # => [1] # # # The legacy handler is called, as before: # Account.graphql_definition.metadata[:permission_level] # => 1 module AcceptsDefinition def self.included(child) child.extend(AcceptsDefinitionDefinitionMethods) child.prepend(ToGraphQLExtension) child.prepend(InitializeExtension) end def self.extended(child) if defined?(child::DefinitionMethods) child::DefinitionMethods.include(AcceptsDefinitionDefinitionMethods) child::DefinitionMethods.prepend(ToGraphQLExtension) else child.extend(AcceptsDefinitionDefinitionMethods) # I tried to use `super`, but super isn't quite right # since the method is defined in the same class itself, # not the superclass child.class_eval do class << self prepend(ToGraphQLExtension) end end end end module AcceptsDefinitionDefinitionMethods def accepts_definition(name) own_accepts_definition_methods << name ivar_name = "@#{name}_args" if self.is_a?(Class) define_singleton_method(name) do |*args| if args.any? instance_variable_set(ivar_name, args) end instance_variable_get(ivar_name) || (superclass.respond_to?(name) ? superclass.public_send(name) : nil) end define_method(name) do |*args| if args.any? instance_variable_set(ivar_name, args) end instance_variable_get(ivar_name) end else # Special handling for interfaces, define it here # so it's appropriately passed down self::DefinitionMethods.module_eval do define_method(name) do |*args| if args.any? instance_variable_set(ivar_name, args) end instance_variable_get(ivar_name) || ((int = interfaces.first { |i| i.respond_to?()}) && int.public_send(name)) end end end end def accepts_definition_methods inherited_methods = if self.is_a?(Class) superclass.respond_to?(:accepts_definition_methods) ? superclass.accepts_definition_methods : [] elsif self.is_a?(Module) m = [] ancestors.each do |a| if a.respond_to?(:own_accepts_definition_methods) m.concat(a.own_accepts_definition_methods) end end m else self.class.accepts_definition_methods end own_accepts_definition_methods + inherited_methods end def own_accepts_definition_methods @own_accepts_definition_methods ||= [] end end module ToGraphQLExtension def to_graphql defn = super accepts_definition_methods.each do |method_name| value = public_send(method_name) if !value.nil? defn = defn.redefine { public_send(method_name, *value) } end end defn end end module InitializeExtension def initialize(*args, **kwargs, &block) self.class.accepts_definition_methods.each do |method_name| if kwargs.key?(method_name) value = kwargs.delete(method_name) if !value.is_a?(Array) value = [value] end instance_variable_set("@#{method_name}_args", value) end end super(*args, **kwargs, &block) end def accepts_definition_methods self.class.accepts_definition_methods end end end end end end ruby-graphql-1.9.19/lib/graphql/schema/member/base_dsl_methods.rb000066400000000000000000000071111362601351000247640ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/schema/find_inherited_value" module GraphQL class Schema class Member # DSL methods shared by lots of things in the GraphQL Schema. # @api private # @see Classes that extend this, eg {GraphQL::Schema::Object} module BaseDSLMethods include GraphQL::Schema::FindInheritedValue # Call this with a new name to override the default name for this schema member; OR # call it without an argument to get the name of this schema member # # The default name is implemented in default_graphql_name # @param new_name [String] # @return [String] def graphql_name(new_name = nil) case when new_name @graphql_name = new_name when overridden = overridden_graphql_name overridden else default_graphql_name end end # Just a convenience method to point out that people should use graphql_name instead def name(new_name = nil) return super() if new_name.nil? fail( "The new name override method is `graphql_name`, not `name`. Usage: "\ "graphql_name \"#{new_name}\"" ) end # Call this method to provide a new description; OR # call it without an argument to get the description # @param new_description [String] # @return [String] def description(new_description = nil) if new_description @description = new_description else @description || find_inherited_value(:description) end end # @return [Boolean] If true, this object is part of the introspection system def introspection(new_introspection = nil) if !new_introspection.nil? @introspection = new_introspection else @introspection || find_inherited_value(:introspection, false) end end def introspection? introspection end # The mutation this type was derived from, if it was derived from a mutation # @return [Class] def mutation(mutation_class = nil) if mutation_class @mutation = mutation_class end @mutation end # @return [GraphQL::BaseType] Convert this type to a legacy-style object. def to_graphql raise GraphQL::RequiredImplementationMissingError end alias :unwrap :itself def overridden_graphql_name @graphql_name || find_inherited_value(:overridden_graphql_name) end # Creates the default name for a schema member. # The default name is the Ruby constant name, # without any namespaces and with any `-Type` suffix removed def default_graphql_name @default_graphql_name ||= begin raise GraphQL::RequiredImplementationMissingError, 'Anonymous class should declare a `graphql_name`' if name.nil? name.split("::").last.sub(/Type\Z/, "") end end def visible?(context) if @mutation @mutation.visible?(context) else true end end def accessible?(context) if @mutation @mutation.accessible?(context) else true end end def authorized?(object, context) if @mutation @mutation.authorized?(object, context) else true end end end end end end ruby-graphql-1.9.19/lib/graphql/schema/member/build_type.rb000066400000000000000000000135521362601351000236330ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member # @api private module BuildType LIST_TYPE_ERROR = "Use an array of [T] or [T, null: true] for list types; other arrays are not supported" module_function # @param type_expr [String, Class, GraphQL::BaseType] # @return [GraphQL::BaseType] def parse_type(type_expr, null:) list_type = false return_type = case type_expr when String case type_expr when "String" GraphQL::Types::String when "Int", "Integer" GraphQL::Types::Int when "Float" GraphQL::Types::Float when "Boolean" GraphQL::Types::Boolean when "ID" GraphQL::Types::ID when /\A\[.*\]\Z/ list_type = true # List members are required by default parse_type(type_expr[1..-2], null: false) when /.*!\Z/ null = false parse_type(type_expr[0..-2], null: true) else maybe_type = constantize(type_expr) case maybe_type when GraphQL::BaseType maybe_type when Module # This is a way to check that it's the right kind of module: if maybe_type.respond_to?(:graphql_definition) maybe_type else raise ArgumentError, "Unexpected class/module found for GraphQL type: #{type_expr} (must be type definition class/module)" end end end when GraphQL::BaseType, GraphQL::Schema::LateBoundType type_expr when Array case type_expr.length when 1 list_type = true # List members are required by default parse_type(type_expr.first, null: false) when 2 inner_type, nullable_option = type_expr if nullable_option.keys != [:null] || nullable_option.values != [true] raise ArgumentError, LIST_TYPE_ERROR end list_type = true parse_type(inner_type, null: true) else raise ArgumentError, LIST_TYPE_ERROR end when Module # This is a way to check that it's the right kind of module: if type_expr.respond_to?(:graphql_definition) type_expr else # Eg `String` => GraphQL::STRING_TYPE parse_type(type_expr.name, null: true) end when false raise ArgumentError, "Received `false` instead of a type, maybe a `!` should be replaced with `null: true` (for fields) or `required: true` (for arguments)" end if return_type.nil? raise "Unexpected type input: #{type_expr} (#{type_expr.class})" end # Apply list_type first, that way the # .to_non_null_type applies to the list type, not the inner type if list_type return_type = return_type.to_list_type end if !null return_type = return_type.to_non_null_type end return_type end def to_type_name(something) case something when GraphQL::BaseType, GraphQL::Schema::LateBoundType something.unwrap.name when Array to_type_name(something.first) when Module if something.respond_to?(:graphql_name) something.graphql_name else to_type_name(something.name) end when String something.gsub(/\]\[\!/, "").split("::").last when GraphQL::Schema::NonNull, GraphQL::Schema::List to_type_name(something.unwrap) else raise "Unhandled to_type_name input: #{something} (#{something.class})" end end def camelize(string) return string unless string.include?("_") camelized = string.split('_').map(&:capitalize).join camelized[0] = camelized[0].downcase if (match_data = string.match(/\A(_+)/)) camelized = "#{match_data[0]}#{camelized}" end camelized end # Resolves constant from string (based on Rails `ActiveSupport::Inflector.constantize`) def constantize(string) names = string.split('::') # Trigger a built-in NameError exception including the ill-formed constant in the message. Object.const_get(string) if names.empty? # Remove the first blank element in case of '::ClassName' notation. names.shift if names.size > 1 && names.first.empty? names.inject(Object) do |constant, name| if constant == Object constant.const_get(name) else candidate = constant.const_get(name) next candidate if constant.const_defined?(name, false) next candidate unless Object.const_defined?(name) # Go down the ancestors to check if it is owned directly. The check # stops when we reach Object or the end of ancestors tree. constant = constant.ancestors.inject do |const, ancestor| break const if ancestor == Object break ancestor if ancestor.const_defined?(name, false) const end # Owner is in Object, so raise. constant.const_get(name, false) end end end def underscore(string) string .gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2') # URLDecoder -> URL_Decoder .gsub(/([a-z\d])([A-Z])/,'\1_\2') # someThing -> some_Thing .downcase end end end end end ruby-graphql-1.9.19/lib/graphql/schema/member/cached_graphql_definition.rb000066400000000000000000000015021362601351000266200ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member # Adds a layer of caching over user-supplied `.to_graphql` methods. # Users override `.to_graphql`, but all runtime code should use `.graphql_definition`. # @api private # @see concrete classes that extend this, eg {Schema::Object} module CachedGraphQLDefinition # A cached result of {.to_graphql}. # It's cached here so that user-overridden {.to_graphql} implementations # are also cached def graphql_definition @graphql_definition ||= to_graphql end # Wipe out the cached graphql_definition so that `.to_graphql` will be called again. def initialize_copy(original) super @graphql_definition = nil end end end end end ruby-graphql-1.9.19/lib/graphql/schema/member/graphql_type_names.rb000066400000000000000000000007311362601351000253500ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member # These constants are interpreted as GraphQL types when defining fields or arguments # # @example # field :is_draft, Boolean, null: false # field :id, ID, null: false # field :score, Int, null: false # # @api private module GraphQLTypeNames Boolean = "Boolean" ID = "ID" Int = "Int" end end end end ruby-graphql-1.9.19/lib/graphql/schema/member/has_arguments.rb000066400000000000000000000125641362601351000243350ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module HasArguments def self.included(cls) cls.extend(ArgumentClassAccessor) cls.include(ArgumentObjectLoader) end def self.extended(cls) cls.extend(ArgumentClassAccessor) cls.include(ArgumentObjectLoader) end # @see {GraphQL::Schema::Argument#initialize} for parameters # @return [GraphQL::Schema::Argument] An instance of {arguments_class}, created from `*args` def argument(*args, **kwargs, &block) kwargs[:owner] = self loads = kwargs[:loads] if loads name = args[0] name_as_string = name.to_s inferred_arg_name = case name_as_string when /_id$/ name_as_string.sub(/_id$/, "").to_sym when /_ids$/ name_as_string.sub(/_ids$/, "") .sub(/([^s])$/, "\\1s") .to_sym else name end kwargs[:as] ||= inferred_arg_name end arg_defn = self.argument_class.new(*args, **kwargs, &block) add_argument(arg_defn) end # Register this argument with the class. # @param arg_defn [GraphQL::Schema::Argument] # @return [GraphQL::Schema::Argument] def add_argument(arg_defn) own_arguments[arg_defn.name] = arg_defn arg_defn end # @return [Hash GraphQL::Schema::Argument] Arguments defined on this thing, keyed by name. Includes inherited definitions def arguments inherited_arguments = ((self.is_a?(Class) && superclass.respond_to?(:arguments)) ? superclass.arguments : nil) # Local definitions override inherited ones if inherited_arguments inherited_arguments.merge(own_arguments) else own_arguments end end # @param new_arg_class [Class] A class to use for building argument definitions def argument_class(new_arg_class = nil) self.class.argument_class(new_arg_class) end module ArgumentClassAccessor def argument_class(new_arg_class = nil) if new_arg_class @argument_class = new_arg_class else @argument_class || (superclass.respond_to?(:argument_class) ? superclass.argument_class : GraphQL::Schema::Argument) end end end module ArgumentObjectLoader # Look up the corresponding object for a provided ID. # By default, it uses Relay-style {Schema.object_from_id}, # override this to find objects another way. # # @param type [Class, Module] A GraphQL type definition # @param id [String] A client-provided to look up # @param context [GraphQL::Query::Context] the current context def object_from_id(type, id, context) context.schema.object_from_id(id, context) end def load_application_object(argument, lookup_as_type, id) # See if any object can be found for this ID loaded_application_object = object_from_id(lookup_as_type, id, context) context.schema.after_lazy(loaded_application_object) do |application_object| if application_object.nil? err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object) load_application_object_failed(err) end # Double-check that the located object is actually of this type # (Don't want to allow arbitrary access to objects this way) resolved_application_object_type = context.schema.resolve_type(lookup_as_type, application_object, context) context.schema.after_lazy(resolved_application_object_type) do |application_object_type| possible_object_types = context.schema.possible_types(lookup_as_type) if !possible_object_types.include?(application_object_type) err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object) load_application_object_failed(err) else # This object was loaded successfully # and resolved to the right type, # now apply the `.authorized?` class method if there is one if (class_based_type = application_object_type.metadata[:type_class]) context.schema.after_lazy(class_based_type.authorized?(application_object, context)) do |authed| if authed application_object else raise GraphQL::UnauthorizedError.new( object: application_object, type: class_based_type, context: context, ) end end else application_object end end end end end def load_application_object_failed(err) raise err end end def own_arguments @own_arguments ||= {} end end end end end ruby-graphql-1.9.19/lib/graphql/schema/member/has_fields.rb000066400000000000000000000074211362601351000235720ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member # Shared code for Object and Interface module HasFields # Add a field to this object or interface with the given definition # @see {GraphQL::Schema::Field#initialize} for method signature # @return [void] def field(*args, **kwargs, &block) field_defn = field_class.from_options(*args, owner: self, **kwargs, &block) add_field(field_defn) nil end # @return [Hash GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields def fields # Local overrides take precedence over inherited fields all_fields = {} ancestors.reverse_each do |ancestor| if ancestor.respond_to?(:own_fields) all_fields.merge!(ancestor.own_fields) end end all_fields end def get_field(field_name) if (f = own_fields[field_name]) f else for ancestor in ancestors if ancestor.respond_to?(:own_fields) && f = ancestor.own_fields[field_name] return f end end nil end end # A list of Ruby keywords. # # @api private RUBY_KEYWORDS = [:class, :module, :def, :undef, :begin, :rescue, :ensure, :end, :if, :unless, :then, :elsif, :else, :case, :when, :while, :until, :for, :break, :next, :redo, :retry, :in, :do, :return, :yield, :super, :self, :nil, :true, :false, :and, :or, :not, :alias, :defined?, :BEGIN, :END, :__LINE__, :__FILE__] # A list of GraphQL-Ruby keywords. # # @api private GRAPHQL_RUBY_KEYWORDS = [:context, :object, :method] # A list of field names that we should advise users to pick a different # resolve method name. # # @api private CONFLICT_FIELD_NAMES = Set.new(GRAPHQL_RUBY_KEYWORDS + RUBY_KEYWORDS) # Register this field with the class, overriding a previous one if needed. # @param field_defn [GraphQL::Schema::Field] # @return [void] def add_field(field_defn) if CONFLICT_FIELD_NAMES.include?(field_defn.original_name) && field_defn.original_name == field_defn.resolver_method warn "#{self.graphql_name}'s `field :#{field_defn.original_name}` conflicts with a built-in method, use `resolver_method:` to pick a different resolver method for this field (for example, `resolver_method: :resolve_#{field_defn.original_name}` and `def resolve_#{field_defn.original_name}`)" end own_fields[field_defn.name] = field_defn nil end # @return [Class] The class to initialize when adding fields to this kind of schema member def field_class(new_field_class = nil) if new_field_class @field_class = new_field_class elsif @field_class @field_class elsif self.is_a?(Class) superclass.respond_to?(:field_class) ? superclass.field_class : GraphQL::Schema::Field else ancestor = ancestors[1..-1].find { |a| a.respond_to?(:field_class) && a.field_class } ancestor ? ancestor.field_class : GraphQL::Schema::Field end end def global_id_field(field_name) id_resolver = GraphQL::Relay::GlobalIdResolve.new(type: self) field field_name, "ID", null: false define_method(field_name) do id_resolver.call(object, {}, context) end end # @return [Array] Fields defined on this class _specifically_, not parent classes def own_fields @own_fields ||= {} end end end end end ruby-graphql-1.9.19/lib/graphql/schema/member/has_path.rb000066400000000000000000000011551362601351000232560ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module HasPath # @return [String] A description of this member's place in the GraphQL schema def path path_str = if self.respond_to?(:graphql_name) self.graphql_name elsif self.class.respond_to?(:graphql_name) # Instances of resolvers self.class.graphql_name end if self.respond_to?(:owner) && owner.respond_to?(:path) path_str = "#{owner.path}.#{path_str}" end path_str end end end end end ruby-graphql-1.9.19/lib/graphql/schema/member/instrumentation.rb000066400000000000000000000112571362601351000247360ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../object.rb module GraphQL class Schema class Member module Instrumentation module_function def instrument(type, field) return_type = field.type.unwrap if (return_type.is_a?(GraphQL::ObjectType) && return_type.metadata[:type_class]) || return_type.is_a?(GraphQL::InterfaceType) || (return_type.is_a?(GraphQL::UnionType) && return_type.possible_types.any? { |t| t.metadata[:type_class] }) field = apply_proxy(field) end field end def before_query(query) # Get the root type for this query root_node = query.irep_selection if root_node.nil? # It's an invalid query, nothing to do here else root_type = query.irep_selection.return_type # If it has a wrapper, apply it wrapper_class = root_type.metadata[:type_class] if wrapper_class new_root_value = wrapper_class.authorized_new(query.root_value, query.context) new_root_value = query.schema.sync_lazy(new_root_value) if new_root_value.nil? # This is definitely a hack, # but we need some way to tell execute.rb not to run. query.context[:__root_unauthorized] = true end query.root_value = new_root_value end end end def after_query(_query) end private module_function def apply_proxy(field) resolve_proc = field.resolve_proc lazy_resolve_proc = field.lazy_resolve_proc inner_return_type = field.type.unwrap depth = list_depth(field.type) field.redefine( resolve: ProxiedResolve.new(inner_resolve: resolve_proc, list_depth: depth, inner_return_type: inner_return_type), lazy_resolve: ProxiedResolve.new(inner_resolve: lazy_resolve_proc, list_depth: depth, inner_return_type: inner_return_type), ) end def list_depth(type, starting_at = 0) case type when GraphQL::ListType list_depth(type.of_type, starting_at + 1) when GraphQL::NonNullType list_depth(type.of_type, starting_at) else starting_at end end class ProxiedResolve def initialize(inner_resolve:, list_depth:, inner_return_type:) @inner_resolve = inner_resolve @inner_return_type = inner_return_type @list_depth = list_depth end def call(obj, args, ctx) result = @inner_resolve.call(obj, args, ctx) if ctx.skip == result || ctx.schema.lazy?(result) || result.nil? || execution_errors?(result) || ctx.wrapped_object result else ctx.wrapped_object = true proxy_to_depth(result, @list_depth, ctx) end end private def execution_errors?(result) result.is_a?(GraphQL::ExecutionError) || (result.is_a?(Array) && result.any? && result.all? { |v| v.is_a?(GraphQL::ExecutionError) }) end def proxy_to_depth(inner_obj, depth, ctx) if depth > 0 inner_obj.map { |i| proxy_to_depth(i, depth - 1, ctx) } else ctx.schema.after_lazy(inner_obj) do |inner_obj| if inner_obj.nil? # For lists with nil, we need another nil check here nil else concrete_type_or_lazy = case @inner_return_type when GraphQL::UnionType, GraphQL::InterfaceType ctx.query.resolve_type(@inner_return_type, inner_obj) when GraphQL::ObjectType @inner_return_type else raise "unexpected proxying type #{@inner_return_type} for #{inner_obj} at #{ctx.owner_type}.#{ctx.field.name}" end # .resolve_type may have returned a lazy ctx.schema.after_lazy(concrete_type_or_lazy) do |concrete_type| if concrete_type && (object_class = concrete_type.metadata[:type_class]) # use the query-level context here, since it won't be field-specific anyways query_ctx = ctx.query.context object_class.authorized_new(inner_obj, query_ctx) else inner_obj end end end end end end end end end end end ruby-graphql-1.9.19/lib/graphql/schema/member/relay_shortcuts.rb000066400000000000000000000025351362601351000247240ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module RelayShortcuts def edge_type_class(new_edge_type_class = nil) if new_edge_type_class @edge_type_class = new_edge_type_class else @edge_type_class || find_inherited_value(:edge_type_class, Types::Relay::BaseEdge) end end def connection_type_class(new_connection_type_class = nil) if new_connection_type_class @connection_type_class = new_connection_type_class else @connection_type_class || find_inherited_value(:connection_type_class, Types::Relay::BaseConnection) end end def edge_type @edge_type ||= begin edge_name = self.graphql_name + "Edge" node_type_class = self Class.new(edge_type_class) do graphql_name(edge_name) node_type(node_type_class) end end end def connection_type @connection_type ||= begin conn_name = self.graphql_name + "Connection" edge_type_class = self.edge_type Class.new(connection_type_class) do graphql_name(conn_name) edge_type(edge_type_class) end end end end end end end ruby-graphql-1.9.19/lib/graphql/schema/member/scoped.rb000066400000000000000000000011621362601351000227420ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module Scoped # This is called when a field has `scope: true`. # The field's return type class receives this call. # # By default, it's a no-op. Override it to scope your objects. # # @param items [Object] Some list-like object (eg, Array, ActiveRecord::Relation) # @param context [GraphQL::Query::Context] # @return [Object] Another list-like object, scoped to the current context def scope_items(items, context) items end end end end end ruby-graphql-1.9.19/lib/graphql/schema/member/type_system_helpers.rb000066400000000000000000000017361362601351000256030ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module TypeSystemHelpers # @return [Schema::NonNull] Make a non-null-type representation of this type def to_non_null_type GraphQL::Schema::NonNull.new(self) end # @return [Schema::List] Make a list-type representation of this type def to_list_type GraphQL::Schema::List.new(self) end # @return [Boolean] true if this is a non-nullable type. A nullable list of non-nullables is considered nullable. def non_null? false end # @return [Boolean] true if this is a list type. A non-nullable list is considered a list. def list? false end def to_type_signature graphql_name end # @return [GraphQL::TypeKinds::TypeKind] def kind raise GraphQL::RequiredImplementationMissingError end end end end end ruby-graphql-1.9.19/lib/graphql/schema/middleware_chain.rb000066400000000000000000000042531362601351000235010ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Given {steps} and {arguments}, call steps in order, passing `(*arguments, next_step)`. # # Steps should call `next_step.call` to continue the chain, or _not_ call it to stop the chain. class MiddlewareChain extend Forwardable # @return [Array<#call(*args)>] Steps in this chain, will be called with arguments and `next_middleware` attr_reader :steps, :final_step def initialize(steps: [], final_step: nil) @steps = steps @final_step = final_step end def initialize_copy(other) super @steps = other.steps.dup end def_delegators :@steps, :[], :first, :insert, :delete def <<(callable) add_middleware(callable) end def push(callable) add_middleware(callable) end def ==(other) steps == other.steps && final_step == other.final_step end def invoke(arguments) invoke_core(0, arguments) end def concat(callables) callables.each { |c| add_middleware(c) } end private def invoke_core(index, arguments) if index >= steps.length final_step.call(*arguments) else steps[index].call(*arguments) { |next_args = arguments| invoke_core(index + 1, next_args) } end end def add_middleware(callable) # TODO: Stop wrapping callables once deprecated middleware becomes unsupported steps << wrap(callable) end # TODO: Remove this code once deprecated middleware becomes unsupported class MiddlewareWrapper attr_reader :callable def initialize(callable) @callable = callable end def call(*args, &next_middleware) callable.call(*args, next_middleware) end end def wrap(callable) if BackwardsCompatibility.get_arity(callable) == 6 warn("Middleware that takes a next_middleware parameter is deprecated (#{callable.inspect}); instead, accept a block and use yield.") MiddlewareWrapper.new(callable) else callable end end end end end ruby-graphql-1.9.19/lib/graphql/schema/mutation.rb000066400000000000000000000052251362601351000220620ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # This base class accepts configuration for a mutation root field, # then it can be hooked up to your mutation root object type. # # If you want to customize how this class generates types, in your base class, # override the various `generate_*` methods. # # @see {GraphQL::Schema::RelayClassicMutation} for an extension of this class with some conventions built-in. # # @example Creating a comment # # Define the mutation: # class Mutations::CreateComment < GraphQL::Schema::Mutation # argument :body, String, required: true # argument :post_id, ID, required: true # # field :comment, Types::Comment, null: true # field :errors, [String], null: false # # def resolve(body:, post_id:) # post = Post.find(post_id) # comment = post.comments.build(body: body, author: context[:current_user]) # if comment.save # # Successful creation, return the created object with no errors # { # comment: comment, # errors: [], # } # else # # Failed save, return the errors to the client # { # comment: nil, # errors: comment.errors.full_messages # } # end # end # end # # # Hook it up to your mutation: # class Types::Mutation < GraphQL::Schema::Object # field :create_comment, mutation: Mutations::CreateComment # end # # # Call it from GraphQL: # result = MySchema.execute <<-GRAPHQL # mutation { # createComment(postId: "1", body: "Nice Post!") { # errors # comment { # body # author { # login # } # } # } # } # GRAPHQL # class Mutation < GraphQL::Schema::Resolver extend GraphQL::Schema::Member::HasFields extend GraphQL::Schema::Resolver::HasPayloadType class << self # Override this method to handle legacy-style usages of `MyMutation.field` def field(*args, **kwargs, &block) if args.empty? raise ArgumentError, "#{name}.field is used for adding fields to this mutation. Use `mutation: #{name}` to attach this mutation instead." else super end end def visible?(context) true end private # Override this to attach self as `mutation` def generate_payload_type payload_class = super payload_class.mutation(self) payload_class end end end end end ruby-graphql-1.9.19/lib/graphql/schema/non_null.rb000066400000000000000000000015001362601351000220360ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Represents a non null type in the schema. # Wraps a {Schema::Member} when it is required. # @see {Schema::Member::TypeSystemHelpers#to_non_null_type} class NonNull < GraphQL::Schema::Wrapper def to_graphql @of_type.graphql_definition.to_non_null_type end # @return [GraphQL::TypeKinds::NON_NULL] def kind GraphQL::TypeKinds::NON_NULL end # @return [true] def non_null? true end # @return [Boolean] True if this type wraps a list type def list? @of_type.list? end def to_type_signature "#{@of_type.to_type_signature}!" end def inspect "#<#{self.class.name} @of_type=#{@of_type.inspect}>" end end end end ruby-graphql-1.9.19/lib/graphql/schema/null_mask.rb000066400000000000000000000002561362601351000222060ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # @api private module NullMask def self.call(member, ctx) false end end end end ruby-graphql-1.9.19/lib/graphql/schema/object.rb000066400000000000000000000113001362601351000214570ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Object < GraphQL::Schema::Member extend GraphQL::Schema::Member::AcceptsDefinition extend GraphQL::Schema::Member::HasFields # @return [Object] the application object this type is wrapping attr_reader :object # @return [GraphQL::Query::Context] the context instance for this query attr_reader :context class << self # This is protected so that we can be sure callers use the public method, {.authorized_new} # @see authorized_new to make instances protected :new # Make a new instance of this type _if_ the auth check passes, # otherwise, raise an error. # # Probably only the framework should call this method. # # This might return a {GraphQL::Execution::Lazy} if the user-provided `.authorized?` # hook returns some lazy value (like a Promise). # # The reason that the auth check is in this wrapper method instead of {.new} is because # of how it might return a Promise. It would be weird if `.new` returned a promise; # It would be a headache to try to maintain Promise-y state inside a {Schema::Object} # instance. So, hopefully this wrapper method will do the job. # # @param object [Object] The thing wrapped by this object # @param context [GraphQL::Query::Context] # @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy] # @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false` def authorized_new(object, context) auth_val = context.query.with_error_handling do begin authorized?(object, context) rescue GraphQL::UnauthorizedError => err context.schema.unauthorized_object(err) end end context.schema.after_lazy(auth_val) do |is_authorized| if is_authorized self.new(object, context) else # It failed the authorization check, so go to the schema's authorized object hook err = GraphQL::UnauthorizedError.new(object: object, type: self, context: context) # If a new value was returned, wrap that instead of the original value begin new_obj = context.schema.unauthorized_object(err) if new_obj self.new(new_obj, context) else nil end # rescue GraphQL::ExecutionError => err # err end end end # rescue GraphQL::ExecutionError => err # err end end def initialize(object, context) @object = object @context = context end class << self def implements(*new_interfaces) new_interfaces.each do |int| if int.is_a?(Module) unless int.include?(GraphQL::Schema::Interface) raise "#{int} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules." end # Include the methods here, # `.fields` will use the inheritance chain # to find inherited fields include(int) end end own_interfaces.concat(new_interfaces) end def interfaces own_interfaces + (superclass <= GraphQL::Schema::Object ? superclass.interfaces : []) end def own_interfaces @own_interfaces ||= [] end # Include legacy-style interfaces, too def fields all_fields = super interfaces.each do |int| if int.is_a?(GraphQL::InterfaceType) int_f = {} int.fields.each do |name, legacy_field| int_f[name] = field_class.from_options(name, field: legacy_field) end all_fields = int_f.merge(all_fields) end end all_fields end # @return [GraphQL::ObjectType] def to_graphql obj_type = GraphQL::ObjectType.new obj_type.name = graphql_name obj_type.description = description obj_type.interfaces = interfaces obj_type.introspection = introspection obj_type.mutation = mutation fields.each do |field_name, field_inst| field_defn = field_inst.to_graphql obj_type.fields[field_defn.name] = field_defn end obj_type.metadata[:type_class] = self obj_type end def kind GraphQL::TypeKinds::OBJECT end end end end end ruby-graphql-1.9.19/lib/graphql/schema/possible_types.rb000066400000000000000000000025331362601351000232650ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Find the members of a union or interface within a given schema. # # (Although its members never change, unions are handled this way to simplify execution code.) # # Internally, the calculation is cached. It's assumed that schema members _don't_ change after creating the schema! # # @example Get an interface's possible types # possible_types = GraphQL::Schema::PossibleTypes(MySchema) # possible_types.possible_types(MyInterface) # # => [MyObjectType, MyOtherObjectType] class PossibleTypes def initialize(schema) @object_types = schema.types.values.select { |type| type.kind.object? } @interface_implementers = Hash.new do |hash, key| hash[key] = @object_types.select { |type| type.interfaces.include?(key) }.sort_by(&:name) end end def possible_types(type_defn, ctx) case type_defn when Module possible_types(type_defn.graphql_definition, ctx) when GraphQL::UnionType type_defn.possible_types(ctx) when GraphQL::InterfaceType @interface_implementers[type_defn] when GraphQL::BaseType [type_defn] else raise "Unexpected possible_types object: #{type_defn.inspect}" end end end end end ruby-graphql-1.9.19/lib/graphql/schema/printer.rb000066400000000000000000000074521362601351000217110ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Used to convert your {GraphQL::Schema} to a GraphQL schema string # # @example print your schema to standard output (via helper) # MySchema = GraphQL::Schema.define(query: QueryType) # puts GraphQL::Schema::Printer.print_schema(MySchema) # # @example print your schema to standard output # MySchema = GraphQL::Schema.define(query: QueryType) # puts GraphQL::Schema::Printer.new(MySchema).print_schema # # @example print a single type to standard output # query_root = GraphQL::ObjectType.define do # name "Query" # description "The query root of this schema" # # field :post do # type post_type # resolve ->(obj, args, ctx) { Post.find(args["id"]) } # end # end # # post_type = GraphQL::ObjectType.define do # name "Post" # description "A blog post" # # field :id, !types.ID # field :title, !types.String # field :body, !types.String # end # # MySchema = GraphQL::Schema.define(query: query_root) # # printer = GraphQL::Schema::Printer.new(MySchema) # puts printer.print_type(post_type) # class Printer < GraphQL::Language::Printer attr_reader :schema, :warden # @param schema [GraphQL::Schema] # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] # @param introspection [Boolean] Should include the introspection types in the string? def initialize(schema, context: nil, only: nil, except: nil, introspection: false) @document_from_schema = GraphQL::Language::DocumentFromSchemaDefinition.new( schema, context: context, only: only, except: except, include_introspection_types: introspection, ) @document = @document_from_schema.document @schema = schema end # Return the GraphQL schema string for the introspection type system def self.print_introspection_schema query_root = ObjectType.define(name: "Root") do field :throwaway_field, types.String end schema = GraphQL::Schema.define(query: query_root) introspection_schema_ast = GraphQL::Language::DocumentFromSchemaDefinition.new( schema, except: ->(member, _) { member.name == "Root" }, include_introspection_types: true, include_built_in_directives: true, ).document introspection_schema_ast.to_query_string(printer: IntrospectionPrinter.new) end # Return a GraphQL schema string for the defined types in the schema # @param schema [GraphQL::Schema] # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] def self.print_schema(schema, **args) printer = new(schema, **args) printer.print_schema end # Return a GraphQL schema string for the defined types in the schema def print_schema print(@document) end def print_type(type) node = @document_from_schema.build_type_definition_node(type) print(node) end def print_directive(directive) if directive.name == "deprecated" reason = directive.arguments.find { |arg| arg.name == "reason" } if reason.value == GraphQL::Directive::DEFAULT_DEPRECATION_REASON "@deprecated" else "@deprecated(reason: #{reason.value.to_s.inspect})" end else super end end class IntrospectionPrinter < GraphQL::Language::Printer def print_schema_definition(schema) "schema {\n query: Root\n}" end end end end end ruby-graphql-1.9.19/lib/graphql/schema/relay_classic_mutation.rb000066400000000000000000000124331362601351000247560ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/types/string" module GraphQL class Schema # Mutations that extend this base class get some conventions added for free: # # - An argument called `clientMutationId` is _always_ added, but it's not passed # to the resolve method. The value is re-inserted to the response. (It's for # client libraries to manage optimistic updates.) # - The returned object type always has a field called `clientMutationId` to support that. # - The mutation accepts one argument called `input`, `argument`s defined in the mutation # class are added to that input object, which is generated by the mutation. # # These conventions were first specified by Relay Classic, but they come in handy: # # - `clientMutationId` supports optimistic updates and cache rollbacks on the client # - using a single `input:` argument makes it easy to post whole JSON objects to the mutation # using one GraphQL variable (`$input`) instead of making a separate variable for each argument. # # @see {GraphQL::Schema::Mutation} for an example, it's basically the same. # class RelayClassicMutation < GraphQL::Schema::Mutation # The payload should always include this field field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.", null: true) # Relay classic default: null(true) # Override {GraphQL::Schema::Resolver#resolve_with_support} to # delete `client_mutation_id` from the kwargs. def resolve_with_support(**inputs) # Without the interpreter, the inputs are unwrapped by an instrumenter. # But when using the interpreter, no instrumenters are applied. if context.interpreter? input = inputs[:input].to_kwargs new_extras = field ? field.extras : [] all_extras = self.class.extras + new_extras # Transfer these from the top-level hash to the # shortcutted `input:` object all_extras.each do |ext| # It's possible that the `extra` was not passed along by this point, # don't re-add it if it wasn't given here. if inputs.key?(ext) input[ext] = inputs[ext] end end else input = inputs end if input # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are. input_kwargs = input.to_h client_mutation_id = input_kwargs.delete(:client_mutation_id) else # Relay Classic Mutations with no `argument`s # don't require `input:` input_kwargs = {} end return_value = if input_kwargs.any? super(**input_kwargs) else super() end # Again, this is done by an instrumenter when using non-interpreter execution. if context.interpreter? context.schema.after_lazy(return_value) do |return_hash| # It might be an error if return_hash.is_a?(Hash) return_hash[:client_mutation_id] = client_mutation_id end return_hash end else return_value end end class << self # The base class for generated input object types # @param new_class [Class] The base class to use for generating input object definitions # @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject}) def input_object_class(new_class = nil) if new_class @input_object_class = new_class end @input_object_class || (superclass.respond_to?(:input_object_class) ? superclass.input_object_class : GraphQL::Schema::InputObject) end # @param new_input_type [Class, nil] If provided, it configures this mutation to accept `new_input_type` instead of generating an input type # @return [Class] The generated {Schema::InputObject} class for this mutation's `input` def input_type(new_input_type = nil) if new_input_type @input_type = new_input_type end @input_type ||= generate_input_type end # Extend {Schema::Mutation.field_options} to add the `input` argument def field_options sig = super # Arguments were added at the root, but they should be nested sig[:arguments].clear sig[:arguments][:input] = { type: input_type, required: true } sig end private # Generate the input type for the `input:` argument # To customize how input objects are generated, override this method # @return [Class] a subclass of {.input_object_class} def generate_input_type mutation_args = arguments mutation_name = graphql_name mutation_class = self Class.new(input_object_class) do graphql_name("#{mutation_name}Input") description("Autogenerated input type of #{mutation_name}") mutation(mutation_class) own_arguments.merge!(mutation_args) argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false end end end end end end ruby-graphql-1.9.19/lib/graphql/schema/rescue_middleware.rb000066400000000000000000000037021362601351000237030ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # - Store a table of errors & handlers # - Rescue errors in a middleware chain, then check for a handler # - If a handler is found, use it & return a {GraphQL::ExecutionError} # - If no handler is found, re-raise the error class RescueMiddleware # @return [Hash] `{class => proc}` pairs for handling errors attr_reader :rescue_table def initialize @rescue_table = {} end # @example Rescue from not-found by telling the user # MySchema.rescue_from(ActiveRecord::RecordNotFound) { "An item could not be found" } # # @param error_classes [Class] one or more classes of errors to rescue from # @yield [err] A handler to return a message for these error instances # @yieldparam [Exception] an error that was rescued # @yieldreturn [String] message to put in GraphQL response def rescue_from(*error_classes, &block) error_classes.map{ |error_class| rescue_table[error_class] = block } end # Remove the handler for `error_classs` # @param error_class [Class] the error class whose handler should be removed def remove_handler(*error_classes) error_classes.map{ |error_class| rescue_table.delete(error_class) } end # Implement the requirement for {GraphQL::Schema::MiddlewareChain} def call(*args) begin yield rescue StandardError => err attempt_rescue(err) end end private def attempt_rescue(err) rescue_table.each { |klass, handler| if klass.is_a?(Class) && err.is_a?(klass) && handler result = handler.call(err) case result when String return GraphQL::ExecutionError.new(result) when GraphQL::ExecutionError return result end end } raise(err) end end end end ruby-graphql-1.9.19/lib/graphql/schema/resolver.rb000066400000000000000000000323221362601351000220610ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/schema/resolver/has_payload_type" module GraphQL class Schema # A class-based container for field configuration and resolution logic. It supports: # # - Arguments, via `.argument(...)` helper, which will be applied to the field. # - Return type, via `.type(..., null: ...)`, which will be applied to the field. # - Description, via `.description(...)`, which will be applied to the field # - Resolution, via `#resolve(**args)` method, which will be called to resolve the field. # - `#object` and `#context` accessors for use during `#resolve`. # # Resolvers can be attached with the `resolver:` option in a `field(...)` call. # # A resolver's configuration may be overridden with other keywords in the `field(...)` call. # # See the {.field_options} to see how a Resolver becomes a set of field configuration options. # # @see {GraphQL::Schema::Mutation} for a concrete subclass of `Resolver`. # @see {GraphQL::Function} `Resolver` is a replacement for `GraphQL::Function` class Resolver include Schema::Member::GraphQLTypeNames # Really we only need description from here, but: extend Schema::Member::BaseDSLMethods extend GraphQL::Schema::Member::HasArguments include Schema::Member::HasPath extend Schema::Member::HasPath # @param object [Object] the initialize object, pass to {Query.initialize} as `root_value` # @param context [GraphQL::Query::Context] # @param field [GraphQL::Schema::Field] def initialize(object:, context:, field:) @object = object @context = context @field = field # Since this hash is constantly rebuilt, cache it for this call @arguments_by_keyword = {} self.class.arguments.each do |name, arg| @arguments_by_keyword[arg.keyword] = arg end @arguments_loads_as_type = self.class.arguments_loads_as_type end # @return [Object] The application object this field is being resolved on attr_reader :object # @return [GraphQL::Query::Context] attr_reader :context # @return [GraphQL::Schema::Field] attr_reader :field # This method is _actually_ called by the runtime, # it does some preparation and then eventually calls # the user-defined `#resolve` method. # @api private def resolve_with_support(**args) # First call the ready? hook which may raise ready_val = if args.any? ready?(**args) else ready? end context.schema.after_lazy(ready_val) do |is_ready, ready_early_return| if ready_early_return if is_ready != false raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{authorized_result.inspect}, #{ready_early_return.inspect}]" else ready_early_return end elsif is_ready # Then call each prepare hook, which may return a different value # for that argument, or may return a lazy object load_arguments_val = load_arguments(args) context.schema.after_lazy(load_arguments_val) do |loaded_args| # Then call `authorized?`, which may raise or may return a lazy object authorized_val = if loaded_args.any? authorized?(**loaded_args) else authorized? end context.schema.after_lazy(authorized_val) do |(authorized_result, early_return)| # If the `authorized?` returned two values, `false, early_return`, # then use the early return value instead of continuing if early_return if authorized_result == false early_return else raise "Unexpected result from #authorized? (expected `true`, `false` or `[false, {...}]`): [#{authorized_result.inspect}, #{early_return.inspect}]" end elsif authorized_result # Finally, all the hooks have passed, so resolve it if loaded_args.any? public_send(self.class.resolve_method, **loaded_args) else public_send(self.class.resolve_method) end else nil end end end end end end # Do the work. Everything happens here. # @return [Object] An object corresponding to the return type def resolve(**args) raise GraphQL::RequiredImplementationMissingError, "#{self.class.name}#resolve should execute the field's logic" end # Called before arguments are prepared. # Implement this hook to make checks before doing any work. # # If it returns a lazy object (like a promise), it will be synced by GraphQL # (but the resulting value won't be used). # # @param args [Hash] The input arguments, if there are any # @raise [GraphQL::ExecutionError] To add an error to the response # @raise [GraphQL::UnauthorizedError] To signal an authorization failure # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.) def ready?(**args) true end # Called after arguments are loaded, but before resolving. # # Override it to check everything before calling the mutation. # @param inputs [Hash] The input arguments # @raise [GraphQL::ExecutionError] To add an error to the response # @raise [GraphQL::UnauthorizedError] To signal an authorization failure # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.) def authorized?(**inputs) self.class.arguments.each_value do |argument| arg_keyword = argument.keyword if inputs.key?(arg_keyword) && !(value = inputs[arg_keyword]).nil? && (value != argument.default_value) loads_type = @arguments_loads_as_type[arg_keyword] # If this argument resulted in an object being loaded, # then authorize this loaded object with its own policy. # # But if this argument was "just" a plain argument, like # a boolean, then authorize it based on the mutation. authorization_value = if loads_type value else self end arg_auth, err = argument.authorized?(authorization_value, context) if !arg_auth return arg_auth, err else true end else true end end end private def load_arguments(args) prepared_args = {} prepare_lazies = [] args.each do |key, value| arg_defn = @arguments_by_keyword[key] if arg_defn if value.nil? prepared_args[key] = value else prepped_value = prepared_args[key] = load_argument(key, value) if context.schema.lazy?(prepped_value) prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value| prepared_args[key] = finished_prepped_value end end end else # These are `extras: [...]` prepared_args[key] = value end end # Avoid returning a lazy if none are needed if prepare_lazies.any? GraphQL::Execution::Lazy.all(prepare_lazies).then { prepared_args } else prepared_args end end def load_argument(name, value) public_send("load_#{name}", value) end class << self # Default `:resolve` set below. # @return [Symbol] The method to call on instances of this object to resolve the field def resolve_method(new_method = nil) if new_method @resolve_method = new_method end @resolve_method || (superclass.respond_to?(:resolve_method) ? superclass.resolve_method : :resolve) end # Additional info injected into {#resolve} # @see {GraphQL::Schema::Field#extras} def extras(new_extras = nil) if new_extras @own_extras = new_extras end own_extras = @own_extras || [] own_extras + (superclass.respond_to?(:extras) ? superclass.extras : []) end # Specifies whether or not the field is nullable. Defaults to `true` # TODO unify with {#type} # @param allow_null [Boolean] Whether or not the response can be null def null(allow_null = nil) if !allow_null.nil? @null = allow_null end @null.nil? ? (superclass.respond_to?(:null) ? superclass.null : true) : @null end # Call this method to get the return type of the field, # or use it as a configuration method to assign a return type # instead of generating one. # TODO unify with {#null} # @param new_type [Class, nil] If a type definition class is provided, it will be used as the return type of the field # @param null [true, false] Whether or not the field may return `nil` # @return [Class] The type which this field returns. def type(new_type = nil, null: nil) if new_type if null.nil? raise ArgumentError, "required argument `null:` is missing" end @type_expr = new_type @null = null else if @type_expr GraphQL::Schema::Member::BuildType.parse_type(@type_expr, null: @null) elsif superclass.respond_to?(:type) superclass.type else nil end end end # Specifies the complexity of the field. Defaults to `1` # @return [Integer, Proc] def complexity(new_complexity = nil) if new_complexity @complexity = new_complexity end @complexity || (superclass.respond_to?(:complexity) ? superclass.complexity : 1) end def field_options { type: type_expr, description: description, extras: extras, resolver_method: :resolve_with_support, resolver_class: self, arguments: arguments, null: null, complexity: complexity, extensions: extensions, } end # A non-normalized type configuration, without `null` applied def type_expr @type_expr || (superclass.respond_to?(:type_expr) ? superclass.type_expr : nil) end # Add an argument to this field's signature, but # also add some preparation hook methods which will be used for this argument # @see {GraphQL::Schema::Argument#initialize} for the signature def argument(*args, **kwargs, &block) loads = kwargs[:loads] # Use `from_resolver: true` to short-circuit the InputObject's own `loads:` implementation # so that we can support `#load_{x}` methods below. arg_defn = super(*args, from_resolver: true, **kwargs) own_arguments_loads_as_type[arg_defn.keyword] = loads if loads if loads && arg_defn.type.list? class_eval <<-RUBY, __FILE__, __LINE__ + 1 def load_#{arg_defn.keyword}(values) argument = @arguments_by_keyword[:#{arg_defn.keyword}] lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}] context.schema.after_lazy(values) do |values2| GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, lookup_as_type, value) }) end end RUBY elsif loads class_eval <<-RUBY, __FILE__, __LINE__ + 1 def load_#{arg_defn.keyword}(value) argument = @arguments_by_keyword[:#{arg_defn.keyword}] lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}] load_application_object(argument, lookup_as_type, value) end RUBY else class_eval <<-RUBY, __FILE__, __LINE__ + 1 def load_#{arg_defn.keyword}(value) value end RUBY end arg_defn end # @api private def arguments_loads_as_type inherited_lookups = superclass.respond_to?(:arguments_loads_as_type) ? superclass.arguments_loads_as_type : {} inherited_lookups.merge(own_arguments_loads_as_type) end # Registers new extension # @param extension [Class] Extension class # @param options [Hash] Optional extension options def extension(extension, **options) extensions << {extension => options} end # @api private def extensions @extensions ||= [] end private def own_arguments_loads_as_type @own_arguments_loads_as_type ||= {} end end end end end ruby-graphql-1.9.19/lib/graphql/schema/resolver/000077500000000000000000000000001362601351000215325ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/schema/resolver/has_payload_type.rb000066400000000000000000000044731362601351000254140ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Resolver # Adds `field(...)` helper to resolvers so that they can # generate payload types. # # Or, an already-defined one can be attached with `payload_type(...)`. module HasPayloadType # Call this method to get the derived return type of the mutation, # or use it as a configuration method to assign a return type # instead of generating one. # @param new_payload_type [Class, nil] If a type definition class is provided, it will be used as the return type of the mutation field # @return [Class] The object type which this mutation returns. def payload_type(new_payload_type = nil) if new_payload_type @payload_type = new_payload_type end @payload_type ||= generate_payload_type end alias :type :payload_type alias :type_expr :payload_type def field_class(new_class = nil) if new_class @field_class = new_class else @field_class || find_inherited_value(:field_class, GraphQL::Schema::Field) end end # An object class to use for deriving return types # @param new_class [Class, nil] Defaults to {GraphQL::Schema::Object} # @return [Class] def object_class(new_class = nil) if new_class @object_class = new_class else @object_class || find_inherited_value(:object_class, GraphQL::Schema::Object) end end private # Build a subclass of {.object_class} based on `self`. # This value will be cached as `{.payload_type}`. # Override this hook to customize return type generation. def generate_payload_type resolver_name = graphql_name resolver_fields = fields Class.new(object_class) do graphql_name("#{resolver_name}Payload") description("Autogenerated return type of #{resolver_name}") resolver_fields.each do |name, f| # Reattach the already-defined field here # (The field's `.owner` will still point to the mutation, not the object type, I think) add_field(f) end end end end end end end ruby-graphql-1.9.19/lib/graphql/schema/scalar.rb000066400000000000000000000021051362601351000214610ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Scalar < GraphQL::Schema::Member extend GraphQL::Schema::Member::AcceptsDefinition class << self extend Forwardable def_delegators :graphql_definition, :coerce_isolated_input, :coerce_isolated_result def coerce_input(val, ctx) val end def coerce_result(val, ctx) val end def to_graphql type_defn = GraphQL::ScalarType.new type_defn.name = graphql_name type_defn.description = description type_defn.coerce_result = method(:coerce_result) type_defn.coerce_input = method(:coerce_input) type_defn.metadata[:type_class] = self type_defn.default_scalar = default_scalar type_defn end def kind GraphQL::TypeKinds::SCALAR end def default_scalar(is_default = nil) if !is_default.nil? @default_scalar = is_default end @default_scalar end end end end end ruby-graphql-1.9.19/lib/graphql/schema/subscription.rb000066400000000000000000000072751362601351000227550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # This class can be extended to create fields on your subscription root. # # It provides hooks for the different parts of the subscription lifecycle: # # - `#authorized?`: called before initial subscription and subsequent updates # - `#subscribe`: called for the initial subscription # - `#update`: called for subsequent update # # Also, `#unsubscribe` terminates the subscription. class Subscription < GraphQL::Schema::Resolver class EarlyTerminationError < StandardError end # Raised when `unsubscribe` is called; caught by `subscriptions.rb` class UnsubscribedError < EarlyTerminationError end # Raised when `no_update` is returned; caught by `subscriptions.rb` class NoUpdateError < EarlyTerminationError end extend GraphQL::Schema::Resolver::HasPayloadType extend GraphQL::Schema::Member::HasFields # The generated payload type is required; If there's no payload, # propagate null. null false def initialize(object:, context:, field:) super # Figure out whether this is an update or an initial subscription @mode = context.query.subscription_update? ? :update : :subscribe end # Implement the {Resolve} API def resolve(**args) # Dispatch based on `@mode`, which will raise a `NoMethodError` if we ever # have an unexpected `@mode` public_send("resolve_#{@mode}", **args) end # Wrap the user-defined `#subscribe` hook def resolve_subscribe(**args) ret_val = args.any? ? subscribe(**args) : subscribe if ret_val == :no_response context.skip else ret_val end end # Default implementation returns the root object. # Override it to return an object or # `:no_response` to return nothing. # # The default is `:no_response`. def subscribe(args = {}) :no_response end # Wrap the user-provided `#update` hook def resolve_update(**args) ret_val = args.any? ? update(**args) : update if ret_val == :no_update raise NoUpdateError else ret_val end end # The default implementation returns the root object. # Override it to return `:no_update` if you want to # skip updates sometimes. Or override it to return a different object. def update(args = {}) object end # If an argument is flagged with `loads:` and no object is found for it, # remove this subscription (assuming that the object was deleted in the meantime, # or that it became inaccessible). def load_application_object_failed(err) if @mode == :update unsubscribe end super end # Call this to halt execution and remove this subscription from the system def unsubscribe raise UnsubscribedError end # Call this method to provide a new subscription_scope; OR # call it without an argument to get the subscription_scope # @param new_scope [Symbol] # @return [Symbol] READING_SCOPE = ::Object.new def self.subscription_scope(new_scope = READING_SCOPE) if new_scope != READING_SCOPE @subscription_scope = new_scope elsif defined?(@subscription_scope) @subscription_scope else find_inherited_value(:subscription_scope) end end # Overriding Resolver#field_options to include subscription_scope def self.field_options super.merge( subscription_scope: subscription_scope ) end end end end ruby-graphql-1.9.19/lib/graphql/schema/timeout.rb000066400000000000000000000076711362601351000217170ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # This plugin will stop resolving new fields after `max_seconds` have elapsed. # After the time has passed, any remaining fields will be `nil`, with errors added # to the `errors` key. Any already-resolved fields will be in the `data` key, so # you'll get a partial response. # # You can subclass `GraphQL::Schema::Timeout` and override the `handle_timeout` method # to provide custom logic when a timeout error occurs. # # Note that this will stop a query _in between_ field resolutions, but # it doesn't interrupt long-running `resolve` functions. Be sure to use # timeout options for external connections. For more info, see # www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/ # # @example Stop resolving fields after 2 seconds # class MySchema < GraphQL::Schema # use GraphQL::Schema::Timeout, max_seconds: 2 # end # # @example Notifying Bugsnag and logging a timeout # class MyTimeout < GraphQL::Schema::Timeout # def handle_timeout(error, query) # Rails.logger.warn("GraphQL Timeout: #{error.message}: #{query.query_string}") # Bugsnag.notify(error, {query_string: query.query_string}) # end # end # # class MySchema < GraphQL::Schema # use MyTimeout, max_seconds: 2 # end # class Timeout attr_reader :max_seconds def self.use(schema, **options) tracer = new(**options) schema.tracer(tracer) end # @param max_seconds [Numeric] how many seconds the query should be allowed to resolve new fields def initialize(max_seconds:) @max_seconds = max_seconds end def trace(key, data) case key when 'execute_multiplex' timeout_state = { timeout_at: Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) + max_seconds * 1000, timed_out: false } data.fetch(:multiplex).queries.each do |query| query.context.namespace(self.class)[:state] = timeout_state end yield when 'execute_field', 'execute_field_lazy' query = data[:context] ? data.fetch(:context).query : data.fetch(:query) timeout_state = query.context.namespace(self.class).fetch(:state) if Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at) error = if data[:context] context = data.fetch(:context) GraphQL::Schema::Timeout::TimeoutError.new(context.parent_type, context.field) else field = data.fetch(:field) GraphQL::Schema::Timeout::TimeoutError.new(field.owner, field) end # Only invoke the timeout callback for the first timeout unless timeout_state[:timed_out] timeout_state[:timed_out] = true handle_timeout(error, query) end error else yield end else yield end end # Invoked when a query times out. # @param error [GraphQL::Schema::Timeout::TimeoutError] # @param query [GraphQL::Error] def handle_timeout(error, query) # override to do something interesting end # This error is raised when a query exceeds `max_seconds`. # Since it's a child of {GraphQL::ExecutionError}, # its message will be added to the response's `errors` key. # # To raise an error that will stop query resolution, use a custom block # to take this error and raise a new one which _doesn't_ descend from {GraphQL::ExecutionError}, # such as `RuntimeError`. class TimeoutError < GraphQL::ExecutionError def initialize(parent_type, field) super("Timeout on #{parent_type.graphql_name}.#{field.graphql_name}") end end end end end ruby-graphql-1.9.19/lib/graphql/schema/timeout_middleware.rb000066400000000000000000000071101362601351000241000ustar00rootroot00000000000000# frozen_string_literal: true require "delegate" module GraphQL class Schema # This middleware will stop resolving new fields after `max_seconds` have elapsed. # After the time has passed, any remaining fields will be `nil`, with errors added # to the `errors` key. Any already-resolved fields will be in the `data` key, so # you'll get a partial response. # # You can provide a block which will be called with any timeout errors that occur. # # Note that this will stop a query _in between_ field resolutions, but # it doesn't interrupt long-running `resolve` functions. Be sure to use # timeout options for external connections. For more info, see # www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/ # # @example Stop resolving fields after 2 seconds # MySchema.middleware << GraphQL::Schema::TimeoutMiddleware.new(max_seconds: 2) # # @example Notifying Bugsnag on a timeout # MySchema.middleware << GraphQL::Schema::TimeoutMiddleware(max_seconds: 1.5) do |timeout_error, query| # Bugsnag.notify(timeout_error, {query_string: query_ctx.query.query_string}) # end # class TimeoutMiddleware # @param max_seconds [Numeric] how many seconds the query should be allowed to resolve new fields def initialize(max_seconds:, context_key: nil, &block) @max_seconds = max_seconds if context_key warn("TimeoutMiddleware's `context_key` is ignored, timeout data is now stored in isolated storage") end @error_handler = block end def call(parent_type, parent_object, field_definition, field_args, query_context) ns = query_context.namespace(self.class) now = Process.clock_gettime(Process::CLOCK_MONOTONIC) timeout_at = ns[:timeout_at] ||= now + @max_seconds if timeout_at < now on_timeout(parent_type, parent_object, field_definition, field_args, query_context) else yield end end # This is called when a field _would_ be resolved, except that we're over the time limit. # @return [GraphQL::Schema::TimeoutMiddleware::TimeoutError] An error whose message will be added to the `errors` key def on_timeout(parent_type, parent_object, field_definition, field_args, field_context) err = GraphQL::Schema::TimeoutMiddleware::TimeoutError.new(parent_type, field_definition) if @error_handler query_proxy = TimeoutQueryProxy.new(field_context.query, field_context) @error_handler.call(err, query_proxy) end err end # This behaves like {GraphQL::Query} but {#context} returns # the _field-level_ context, not the query-level context. # This means you can reliably get the `irep_node` and `path` # from it after the fact. class TimeoutQueryProxy < SimpleDelegator def initialize(query, ctx) @context = ctx super(query) end attr_reader :context end # This error is raised when a query exceeds `max_seconds`. # Since it's a child of {GraphQL::ExecutionError}, # its message will be added to the response's `errors` key. # # To raise an error that will stop query resolution, use a custom block # to take this error and raise a new one which _doesn't_ descend from {GraphQL::ExecutionError}, # such as `RuntimeError`. class TimeoutError < GraphQL::ExecutionError def initialize(parent_type, field_defn) super("Timeout on #{parent_type.name}.#{field_defn.name}") end end end end end ruby-graphql-1.9.19/lib/graphql/schema/traversal.rb000066400000000000000000000236161362601351000222310ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Visit the members of this schema and build up artifacts for runtime. # @api private class Traversal # @return [Hash GraphQL::BaseType] attr_reader :type_map # @return [Hash Hash GraphQL::Field>>] attr_reader :instrumented_field_map # @return [Hash Array] attr_reader :type_reference_map # @return [Hash Array] attr_reader :union_memberships # @param schema [GraphQL::Schema] def initialize(schema, introspection: true) @schema = schema @introspection = introspection built_in_insts = [ GraphQL::Relay::ConnectionInstrumentation, GraphQL::Relay::EdgesInstrumentation, GraphQL::Relay::Mutation::Instrumentation, ] if schema.query_execution_strategy != GraphQL::Execution::Interpreter built_in_insts << GraphQL::Schema::Member::Instrumentation end @field_instrumenters = schema.instrumenters[:field] + built_in_insts + schema.instrumenters[:field_after_built_ins] # These fields have types specified by _name_, # So we need to inspect the schema and find those types, # then update their references. @late_bound_fields = [] @type_map = {} @instrumented_field_map = Hash.new { |h, k| h[k] = {} } @type_reference_map = Hash.new { |h, k| h[k] = [] } @union_memberships = Hash.new { |h, k| h[k] = [] } visit(schema, schema, nil) resolve_late_bound_fields end private # A brute-force appraoch to late binding. # Just keep trying the whole list, hoping that they # eventually all resolve. # This could be replaced with proper dependency tracking. def resolve_late_bound_fields # This is a bit tricky, with the writes going to internal state. prev_late_bound_fields = @late_bound_fields # Things might get added here during `visit...` # or they might be added manually if we can't find them by hand @late_bound_fields = [] prev_late_bound_fields.each do |(owner_type, field_defn, dynamic_field)| if @type_map.key?(field_defn.type.unwrap.name) late_bound_return_type = field_defn.type resolved_type = @type_map.fetch(late_bound_return_type.unwrap.name) wrapped_resolved_type = rewrap_resolved_type(late_bound_return_type, resolved_type) # Update the field definition in place? :thinking_face: field_defn.type = wrapped_resolved_type visit_field_on_type(@schema, owner_type, field_defn, dynamic_field: dynamic_field) else @late_bound_fields << [owner_type, field_defn, dynamic_field] end end if @late_bound_fields.any? # If we visited each field and failed to resolve _any_, # then we're stuck. if @late_bound_fields == prev_late_bound_fields type_names = prev_late_bound_fields.map { |f| f[1] }.map(&:type).map(&:unwrap).map(&:name).uniq raise <<-ERR Some late-bound types couldn't be resolved: - #{type_names} - Found __* types: #{@type_map.keys.select { |k| k.start_with?("__") }} ERR else resolve_late_bound_fields end end end # The late-bound type may be wrapped with list or non-null types. # Apply the same wrapping to the resolve type and # return the maybe-wrapped type def rewrap_resolved_type(late_bound_type, resolved_inner_type) case late_bound_type when GraphQL::NonNullType rewrap_resolved_type(late_bound_type.of_type, resolved_inner_type).to_non_null_type when GraphQL::ListType rewrap_resolved_type(late_bound_type.of_type, resolved_inner_type).to_list_type when GraphQL::Schema::LateBoundType resolved_inner_type else raise "Unexpected late_bound_type: #{late_bound_type.inspect} (#{late_bound_type.class})" end end def visit(schema, member, context_description) case member when GraphQL::Schema member.directives.each { |name, directive| visit(schema, directive, "Directive #{name}") } # Find the starting points, then visit them visit_roots = [member.query, member.mutation, member.subscription] if @introspection introspection_types = schema.introspection_system.object_types visit_roots.concat(introspection_types) if member.query member.introspection_system.entry_points.each do |introspection_field| # Visit this so that arguments class is preconstructed # Skip validation since it begins with "__" visit_field_on_type(schema, member.query, introspection_field, dynamic_field: true) end end end visit_roots.concat(member.orphan_types) visit_roots.compact! visit_roots.each { |t| visit(schema, t, t.name) } when GraphQL::Directive member.arguments.each do |name, argument| @type_reference_map[argument.type.unwrap.to_s] << argument visit(schema, argument.type, "Directive argument #{member.name}.#{name}") end # Construct arguments class here, which is later used to generate GraphQL::Query::Arguments # to be passed to a resolver proc GraphQL::Query::Arguments.construct_arguments_class(member) when GraphQL::BaseType type_defn = member.unwrap prev_type = @type_map[type_defn.name] # Continue to visit this type if it's the first time we've seen it: if prev_type.nil? validate_type(type_defn, context_description) @type_map[type_defn.name] = type_defn case type_defn when GraphQL::ObjectType type_defn.interfaces.each { |i| visit(schema, i, "Interface on #{type_defn.name}") } visit_fields(schema, type_defn) when GraphQL::InterfaceType visit_fields(schema, type_defn) type_defn.orphan_types.each do |t| visit(schema, t, "Orphan type for #{type_defn.name}") end when GraphQL::UnionType type_defn.possible_types.each do |t| @union_memberships[t.name] << type_defn visit(schema, t, "Possible type for #{type_defn.name}") end when GraphQL::InputObjectType type_defn.arguments.each do |name, arg| @type_reference_map[arg.type.unwrap.to_s] << arg visit(schema, arg.type, "Input field #{type_defn.name}.#{name}") end # Construct arguments class here, which is later used to generate GraphQL::Query::Arguments # to be passed to a resolver proc if type_defn.arguments_class.nil? GraphQL::Query::Arguments.construct_arguments_class(type_defn) end end elsif !prev_type.equal?(type_defn) # If the previous entry in the map isn't the same object we just found, raise. raise("Duplicate type definition found for name '#{type_defn.name}' at '#{context_description}' (#{prev_type.metadata[:type_class] || prev_type}, #{type_defn.metadata[:type_class] || type_defn})") end when Class if member.respond_to?(:graphql_definition) graphql_member = member.graphql_definition visit(schema, graphql_member, context_description) else raise GraphQL::Schema::InvalidTypeError.new("Unexpected traversal member: #{member} (#{member.class.name})") end else message = "Unexpected schema traversal member: #{member} (#{member.class.name})" raise GraphQL::Schema::InvalidTypeError.new(message) end end def visit_fields(schema, type_defn) type_defn.all_fields.each do |field_defn| visit_field_on_type(schema, type_defn, field_defn) end end def visit_field_on_type(schema, type_defn, field_defn, dynamic_field: false) base_return_type = field_defn.type.unwrap if base_return_type.is_a?(GraphQL::Schema::LateBoundType) @late_bound_fields << [type_defn, field_defn, dynamic_field] return end if dynamic_field # Don't apply instrumentation to dynamic fields since they're shared constants instrumented_field_defn = field_defn else instrumented_field_defn = @field_instrumenters.reduce(field_defn) do |defn, inst| inst.instrument(type_defn, defn) end @instrumented_field_map[type_defn.name][instrumented_field_defn.name] = instrumented_field_defn end @type_reference_map[instrumented_field_defn.type.unwrap.name] << instrumented_field_defn visit(schema, instrumented_field_defn.type, "Field #{type_defn.name}.#{instrumented_field_defn.name}'s return type") instrumented_field_defn.arguments.each do |name, arg| @type_reference_map[arg.type.unwrap.to_s] << arg visit(schema, arg.type, "Argument #{name} on #{type_defn.name}.#{instrumented_field_defn.name}") end # Construct arguments class here, which is later used to generate GraphQL::Query::Arguments # to be passed to a resolver proc GraphQL::Query::Arguments.construct_arguments_class(instrumented_field_defn) end def validate_type(member, context_description) error_message = GraphQL::Schema::Validation.validate(member) if error_message raise GraphQL::Schema::InvalidTypeError.new("#{context_description} is invalid: #{error_message}") end end end end end ruby-graphql-1.9.19/lib/graphql/schema/type_expression.rb000066400000000000000000000021221362601351000234530ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # @api private module TypeExpression # Fetch a type from a type map by its AST specification. # Return `nil` if not found. # @param types [GraphQL::Schema::TypeMap] # @param ast_node [GraphQL::Language::Nodes::AbstractNode] # @return [GraphQL::BaseType, nil] def self.build_type(types, ast_node) case ast_node when GraphQL::Language::Nodes::TypeName types.fetch(ast_node.name, nil) when GraphQL::Language::Nodes::NonNullType ast_inner_type = ast_node.of_type inner_type = build_type(types, ast_inner_type) wrap_type(inner_type, GraphQL::NonNullType) when GraphQL::Language::Nodes::ListType ast_inner_type = ast_node.of_type inner_type = build_type(types, ast_inner_type) wrap_type(inner_type, GraphQL::ListType) end end def self.wrap_type(type, wrapper) if type.nil? nil else wrapper.new(of_type: type) end end end end end ruby-graphql-1.9.19/lib/graphql/schema/type_membership.rb000066400000000000000000000022641362601351000234160ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # This class joins an object type to an abstract type (interface or union) of which # it is a member. # # TODO: Not yet implemented for interfaces. class TypeMembership # @return [Class] attr_reader :object_type # @return [Class, Module] attr_reader :abstract_type # Called when an object is hooked up to an abstract type, such as {Schema::Union.possible_types} # or {Schema::Object.implements} (for interfaces). # # @param abstract_type [Class, Module] # @param object_type [Class] # @param options [Hash] Any options passed to `.possible_types` or `.implements` def initialize(abstract_type, object_type, **options) @abstract_type = abstract_type @object_type = object_type @options = options end # @return [Boolean] if false, {#object_type} will be treated as _not_ a member of {#abstract_type} def visible?(_ctx) true end end end end ruby-graphql-1.9.19/lib/graphql/schema/union.rb000066400000000000000000000031051362601351000213450ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Union < GraphQL::Schema::Member extend GraphQL::Schema::Member::AcceptsDefinition class << self def possible_types(*types, context: GraphQL::Query::NullContext, **options) if types.any? types.each do |t| type_memberships << type_membership_class.new(self, t, **options) end else visible_types = [] type_memberships.each do |type_membership| if type_membership.visible?(context) visible_types << type_membership.object_type end end visible_types end end def to_graphql type_defn = GraphQL::UnionType.new type_defn.name = graphql_name type_defn.description = description type_defn.type_memberships = type_memberships if respond_to?(:resolve_type) type_defn.resolve_type = method(:resolve_type) end type_defn.metadata[:type_class] = self type_defn end def type_membership_class(membership_class = nil) if membership_class @type_membership_class = membership_class else @type_membership_class || find_inherited_value(:type_membership_class, GraphQL::Schema::TypeMembership) end end def kind GraphQL::TypeKinds::UNION end private def type_memberships @type_memberships ||= [] end end end end end ruby-graphql-1.9.19/lib/graphql/schema/unique_within_type.rb000066400000000000000000000022201362601351000241430ustar00rootroot00000000000000# frozen_string_literal: true require 'graphql/schema/base_64_bp' module GraphQL class Schema module UniqueWithinType class << self attr_accessor :default_id_separator end self.default_id_separator = "-" module_function # @param type_name [String] # @param object_value [Any] # @return [String] a unique, opaque ID generated as a function of the two inputs def encode(type_name, object_value, separator: self.default_id_separator) object_value_str = object_value.to_s if type_name.include?(separator) raise "encode(#{type_name}, #{object_value_str}) contains reserved characters `#{separator}` in the type name" end Base64.strict_encode64([type_name, object_value_str].join(separator)) end # @param node_id [String] A unique ID generated by {.encode} # @return [Array<(String, String)>] The type name & value passed to {.encode} def decode(node_id, separator: self.default_id_separator) # urlsafe_decode64 is for forward compatibility Base64Bp.urlsafe_decode64(node_id).split(separator, 2) end end end end ruby-graphql-1.9.19/lib/graphql/schema/validation.rb000066400000000000000000000322541362601351000223560ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # This module provides a function for validating GraphQL types. # # Its {RULES} contain objects that respond to `#call(type)`. Rules are # looked up for given types (by class ancestry), then applied to # the object until an error is returned. class Validation # Lookup the rules for `object` based on its class, # Then returns an error message or `nil` # @param object [Object] something to be validated # @return [String, Nil] error message, if there was one def self.validate(object) RULES.each do |parent_class, validations| if object.is_a?(parent_class) validations.each do |rule| if error = rule.call(object) return error end end end end nil end module Rules # @param property_name [Symbol] The method to validate # @param allowed_classes [Class] Classes which the return value may be an instance of # @return [Proc] A proc which will validate the input by calling `property_name` and asserting it is an instance of one of `allowed_classes` def self.assert_property(property_name, *allowed_classes) # Hide LateBoundType from user-facing errors allowed_classes_message = allowed_classes.map(&:name).reject {|n| n.include?("LateBoundType") }.join(" or ") ->(obj) { property_value = obj.public_send(property_name) is_valid_value = allowed_classes.any? { |allowed_class| property_value.is_a?(allowed_class) } is_valid_value ? nil : "#{property_name} must return #{allowed_classes_message}, not #{property_value.class.name} (#{property_value.inspect})" } end # @param property_name [Symbol] The method whose return value will be validated # @param from_class [Class] The class for keys in the return value # @param to_class [Class] The class for values in the return value # @return [Proc] A proc to validate that validates the input by calling `property_name` and asserting that the return value is a Hash of `{from_class => to_class}` pairs def self.assert_property_mapping(property_name, from_class, to_class) ->(obj) { property_value = obj.public_send(property_name) if !property_value.is_a?(Hash) "#{property_name} must be a hash of {#{from_class.name} => #{to_class.name}}, not a #{property_value.class.name} (#{property_value.inspect})" else invalid_key, invalid_value = property_value.find { |key, value| !key.is_a?(from_class) || !value.is_a?(to_class) } if invalid_key "#{property_name} must map #{from_class} => #{to_class}, not #{invalid_key.class.name} => #{invalid_value.class.name} (#{invalid_key.inspect} => #{invalid_value.inspect})" else nil # OK end end } end # @param property_name [Symbol] The method whose return value will be validated # @param list_member_class [Class] The class which each member of the returned array should be an instance of # @return [Proc] A proc to validate the input by calling `property_name` and asserting that the return is an Array of `list_member_class` instances def self.assert_property_list_of(property_name, list_member_class) ->(obj) { property_value = obj.public_send(property_name) if !property_value.is_a?(Array) "#{property_name} must be an Array of #{list_member_class.name}, not a #{property_value.class.name} (#{property_value.inspect})" else invalid_member = property_value.find { |value| !value.is_a?(list_member_class) } if invalid_member "#{property_name} must contain #{list_member_class.name}, not #{invalid_member.class.name} (#{invalid_member.inspect})" else nil # OK end end } end def self.count_at_least(item_name, minimum_count, get_items_proc) ->(type) { items = get_items_proc.call(type) if items.size < minimum_count "#{type.name} must define at least #{minimum_count} #{item_name}. #{items.size} defined." else nil end } end def self.assert_named_items_are_valid(item_name, get_items_proc) ->(type) { items = get_items_proc.call(type) error_message = nil items.each do |item| item_message = GraphQL::Schema::Validation.validate(item) if item_message error_message = "#{item_name} #{item.name.inspect} #{item_message}" break end end error_message } end HAS_AT_LEAST_ONE_FIELD = Rules.count_at_least("field", 1, ->(type) { type.all_fields }) FIELDS_ARE_VALID = Rules.assert_named_items_are_valid("field", ->(type) { type.all_fields }) HAS_AT_LEAST_ONE_ARGUMENT = Rules.count_at_least("argument", 1, ->(type) { type.arguments }) HAS_ONE_OR_MORE_POSSIBLE_TYPES = ->(type) { type.possible_types.length >= 1 ? nil : "must have at least one possible type" } NAME_IS_STRING = Rules.assert_property(:name, String) DESCRIPTION_IS_STRING_OR_NIL = Rules.assert_property(:description, String, NilClass) ARGUMENTS_ARE_STRING_TO_ARGUMENT = Rules.assert_property_mapping(:arguments, String, GraphQL::Argument) ARGUMENTS_ARE_VALID = Rules.assert_named_items_are_valid("argument", ->(type) { type.arguments.values }) DEFAULT_VALUE_IS_VALID_FOR_TYPE = ->(type) { if !type.default_value.nil? coerced_value = begin type.type.coerce_isolated_result(type.default_value) rescue => ex ex end if coerced_value.nil? || coerced_value.is_a?(StandardError) msg = "default value #{type.default_value.inspect} is not valid for type #{type.type}" msg += " (#{coerced_value})" if coerced_value.is_a?(StandardError) msg end end } TYPE_IS_VALID_INPUT_TYPE = ->(type) { outer_type = type.type inner_type = outer_type.is_a?(GraphQL::BaseType) ? outer_type.unwrap : nil case inner_type when GraphQL::ScalarType, GraphQL::InputObjectType, GraphQL::EnumType # OK else "type must be a valid input type (Scalar or InputObject), not #{outer_type.class} (#{outer_type})" end } SCHEMA_CAN_RESOLVE_TYPES = ->(schema) { if schema.types.values.any? { |type| type.kind.abstract? } && schema.resolve_type_proc.nil? "schema contains Interfaces or Unions, so you must define a `resolve_type -> (obj, ctx) { ... }` function" else # :+1: end } SCHEMA_CAN_FETCH_IDS = ->(schema) { has_node_field = schema.query && schema.query.all_fields.any?(&:relay_node_field) if has_node_field && schema.object_from_id_proc.nil? "schema contains `node(id:...)` field, so you must define a `object_from_id -> (id, ctx) { ... }` function" else # :rocket: end } SCHEMA_CAN_GENERATE_IDS = ->(schema) { has_id_field = schema.types.values.any? { |t| t.kind.fields? && t.all_fields.any? { |f| f.resolve_proc.is_a?(GraphQL::Relay::GlobalIdResolve) } } if has_id_field && schema.id_from_object_proc.nil? "schema contains `global_id_field`, so you must define a `id_from_object -> (obj, type, ctx) { ... }` function" else # :ok_hand: end } SCHEMA_INSTRUMENTERS_ARE_VALID = ->(schema) { errs = [] schema.instrumenters[:query].each do |inst| if !inst.respond_to?(:before_query) || !inst.respond_to?(:after_query) errs << "`instrument(:query, #{inst})` is invalid: must respond to `before_query(query)` and `after_query(query)` " end end schema.instrumenters[:field].each do |inst| if !inst.respond_to?(:instrument) errs << "`instrument(:field, #{inst})` is invalid: must respond to `instrument(type, field)`" end end if errs.any? errs.join("Invalid instrumenters:\n" + errs.join("\n")) else nil end } RESERVED_TYPE_NAME = ->(type) { if type.name.start_with?('__') && !type.introspection? # TODO: make this a hard failure in a later version warn("Name #{type.name.inspect} must not begin with \"__\", which is reserved by GraphQL introspection.") nil else # ok name end } RESERVED_NAME = ->(named_thing) { if named_thing.name.start_with?('__') # TODO: make this a hard failure in a later version warn("Name #{named_thing.name.inspect} must not begin with \"__\", which is reserved by GraphQL introspection.") nil else # no worries end } INTERFACES_ARE_IMPLEMENTED = ->(obj_type) { field_errors = [] obj_type.interfaces.each do |interface_type| interface_type.fields.each do |field_name, field_defn| object_field = obj_type.get_field(field_name) if object_field.nil? field_errors << %|"#{field_name}" is required by #{interface_type.name} but not implemented by #{obj_type.name}| elsif !GraphQL::Execution::Typecast.subtype?(field_defn.type, object_field.type) field_errors << %|"#{field_name}" is required by #{interface_type.name} to return #{field_defn.type} but #{obj_type.name}.#{field_name} returns #{object_field.type}| else field_defn.arguments.each do |arg_name, arg_defn| object_field_arg = object_field.arguments[arg_name] if object_field_arg.nil? field_errors << %|"#{arg_name}" argument is required by #{interface_type.name}.#{field_name} but not accepted by #{obj_type.name}.#{field_name}| elsif arg_defn.type != object_field_arg.type field_errors << %|"#{arg_name}" is required by #{interface_type.name}.#{field_defn.name} to accept #{arg_defn.type} but #{obj_type.name}.#{field_name} accepts #{object_field_arg.type} for "#{arg_name}"| end end object_field.arguments.each do |arg_name, arg_defn| if field_defn.arguments[arg_name].nil? && arg_defn.type.is_a?(GraphQL::NonNullType) field_errors << %|"#{arg_name}" is not accepted by #{interface_type.name}.#{field_name} but required by #{obj_type.name}.#{field_name}| end end end end end if field_errors.any? "#{obj_type.name} failed to implement some interfaces: #{field_errors.join(", ")}" else nil end } end # A mapping of `{Class => [Proc, Proc...]}` pairs. # To validate an instance, find entries where `object.is_a?(key)` is true. # Then apply each rule from the matching values. RULES = { GraphQL::Field => [ Rules::NAME_IS_STRING, Rules::RESERVED_NAME, Rules::DESCRIPTION_IS_STRING_OR_NIL, Rules.assert_property(:deprecation_reason, String, NilClass), Rules.assert_property(:type, GraphQL::BaseType, GraphQL::Schema::LateBoundType), Rules.assert_property(:property, Symbol, NilClass), Rules::ARGUMENTS_ARE_STRING_TO_ARGUMENT, Rules::ARGUMENTS_ARE_VALID, ], GraphQL::Argument => [ Rules::NAME_IS_STRING, Rules::RESERVED_NAME, Rules::DESCRIPTION_IS_STRING_OR_NIL, Rules::TYPE_IS_VALID_INPUT_TYPE, Rules::DEFAULT_VALUE_IS_VALID_FOR_TYPE, ], GraphQL::BaseType => [ Rules::NAME_IS_STRING, Rules::RESERVED_TYPE_NAME, Rules::DESCRIPTION_IS_STRING_OR_NIL, ], GraphQL::ObjectType => [ Rules::HAS_AT_LEAST_ONE_FIELD, Rules.assert_property_list_of(:interfaces, GraphQL::InterfaceType), Rules::FIELDS_ARE_VALID, Rules::INTERFACES_ARE_IMPLEMENTED, ], GraphQL::InputObjectType => [ Rules::HAS_AT_LEAST_ONE_ARGUMENT, Rules::ARGUMENTS_ARE_STRING_TO_ARGUMENT, Rules::ARGUMENTS_ARE_VALID, ], GraphQL::UnionType => [ Rules.assert_property_list_of(:possible_types, GraphQL::ObjectType), Rules::HAS_ONE_OR_MORE_POSSIBLE_TYPES, ], GraphQL::InterfaceType => [ Rules::FIELDS_ARE_VALID, ], GraphQL::Schema => [ Rules::SCHEMA_INSTRUMENTERS_ARE_VALID, Rules::SCHEMA_CAN_RESOLVE_TYPES, Rules::SCHEMA_CAN_FETCH_IDS, Rules::SCHEMA_CAN_GENERATE_IDS, ], } end end end ruby-graphql-1.9.19/lib/graphql/schema/warden.rb000066400000000000000000000213251362601351000215010ustar00rootroot00000000000000# frozen_string_literal: true require 'set' module GraphQL class Schema # Restrict access to a {GraphQL::Schema} with a user-defined filter. # # When validating and executing a query, all access to schema members # should go through a warden. If you access the schema directly, # you may show a client something that it shouldn't be allowed to see. # # @example Hidding private fields # private_members = -> (member, ctx) { member.metadata[:private] } # result = Schema.execute(query_string, except: private_members) # # @example Custom filter implementation # # It must respond to `#call(member)`. # class MissingRequiredFlags # def initialize(user) # @user = user # end # # # Return `false` if any required flags are missing # def call(member, ctx) # member.metadata[:required_flags].any? do |flag| # !@user.has_flag?(flag) # end # end # end # # # Then, use the custom filter in query: # missing_required_flags = MissingRequiredFlags.new(current_user) # # # This query can only access members which match the user's flags # result = Schema.execute(query_string, except: missing_required_flags) # # @api private class Warden # @param filter [<#call(member)>] Objects are hidden when `.call(member, ctx)` returns true # @param context [GraphQL::Query::Context] # @param schema [GraphQL::Schema] # @param deep_check [Boolean] def initialize(filter, context:, schema:) @schema = schema @context = context @visibility_cache = read_through { |m| filter.call(m, @context) } end # @return [Array] Visible types in the schema def types @types ||= @schema.types.each_value.select { |t| visible_type?(t) } end # @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`) def get_type(type_name) @visible_types ||= read_through do |name| type_defn = @schema.types.fetch(name, nil) if type_defn && visible_type?(type_defn) type_defn else nil end end @visible_types[type_name] end # @return [Array] Visible and reachable types in the schema def reachable_types @reachable_types ||= reachable_type_set.to_a end # @return Boolean True if the type is visible and reachable in the schema def reachable_type?(type_name) type = get_type(type_name) type && reachable_type_set.include?(type) end # @return [GraphQL::Field, nil] The field named `field_name` on `parent_type`, if it exists def get_field(parent_type, field_name) @visible_parent_fields ||= read_through do |type| read_through do |f_name| field_defn = @schema.get_field(type, f_name) if field_defn && visible_field?(field_defn) field_defn else nil end end end @visible_parent_fields[parent_type][field_name] end # @return [Array] The types which may be member of `type_defn` def possible_types(type_defn) @visible_possible_types ||= read_through { |type_defn| @schema.possible_types(type_defn, @context).select { |t| visible_type?(t) } } @visible_possible_types[type_defn] end # @param type_defn [GraphQL::ObjectType, GraphQL::InterfaceType] # @return [Array] Fields on `type_defn` def fields(type_defn) @visible_fields ||= read_through { |t| @schema.get_fields(t).each_value.select { |f| visible_field?(f) } } @visible_fields[type_defn] end # @param argument_owner [GraphQL::Field, GraphQL::InputObjectType] # @return [Array] Visible arguments on `argument_owner` def arguments(argument_owner) @visible_arguments ||= read_through { |o| o.arguments.each_value.select { |a| visible_field?(a) } } @visible_arguments[argument_owner] end # @return [Array] Visible members of `enum_defn` def enum_values(enum_defn) @visible_enum_values ||= read_through { |e| e.values.each_value.select { |enum_value_defn| visible?(enum_value_defn) } } @visible_enum_values[enum_defn] end # @return [Array] Visible interfaces implemented by `obj_type` def interfaces(obj_type) @visible_interfaces ||= read_through { |t| t.interfaces.select { |i| visible?(i) } } @visible_interfaces[obj_type] end def directives @schema.directives.each_value.select { |d| visible?(d) } end def root_type_for_operation(op_name) root_type = @schema.root_type_for_operation(op_name) if root_type && visible?(root_type) root_type else nil end end private def union_memberships(obj_type) @unions ||= read_through { |obj_type| @schema.union_memberships(obj_type).select { |u| visible?(u) } } @unions[obj_type] end def visible_field?(field_defn) visible?(field_defn) && visible_type?(field_defn.type.unwrap) end def visible_type?(type_defn) return false unless visible?(type_defn) return true if root_type?(type_defn) return true if type_defn.introspection? if type_defn.kind.union? visible_possible_types?(type_defn) && (referenced?(type_defn) || orphan_type?(type_defn)) elsif type_defn.kind.interface? visible_possible_types?(type_defn) else referenced?(type_defn) || visible_abstract_type?(type_defn) end end def root_type?(type_defn) @schema.root_types.include?(type_defn) end def referenced?(type_defn) members = @schema.references_to(type_defn.unwrap.name) members.any? { |m| visible?(m) } end def orphan_type?(type_defn) @schema.orphan_types.include?(type_defn) end def visible_abstract_type?(type_defn) type_defn.kind.object? && ( interfaces(type_defn).any? || union_memberships(type_defn).any? ) end def visible_possible_types?(type_defn) @schema.possible_types(type_defn, @context).any? { |t| visible_type?(t) } end def visible?(member) @visibility_cache[member] end def read_through Hash.new { |h, k| h[k] = yield(k) } end def reachable_type_set return @reachable_type_set if defined?(@reachable_type_set) @reachable_type_set = Set.new unvisited_types = [] ['query', 'mutation', 'subscription'].each do |op_name| root_type = root_type_for_operation(op_name) unvisited_types << root_type if root_type end unvisited_types.concat(@schema.introspection_system.object_types) @schema.orphan_types.each do |orphan_type| unvisited_types << orphan_type.graphql_definition if get_type(orphan_type.graphql_name) end until unvisited_types.empty? type = unvisited_types.pop if @reachable_type_set.add?(type) if type.kind.input_object? # recurse into visible arguments arguments(type).each do |argument| argument_type = argument.type.unwrap unvisited_types << argument_type end elsif type.kind.union? # recurse into visible possible types possible_types(type).each do |possible_type| unvisited_types << possible_type end elsif type.kind.fields? if type.kind.interface? # recurse into visible possible types possible_types(type).each do |possible_type| unvisited_types << possible_type end elsif type.kind.object? # recurse into visible implemented interfaces interfaces(type).each do |interface| unvisited_types << interface end end # recurse into visible fields fields(type).each do |field| field_type = field.type.unwrap unvisited_types << field_type # recurse into visible arguments arguments(field).each do |argument| argument_type = argument.type.unwrap unvisited_types << argument_type end end end end end @reachable_type_set end end end end ruby-graphql-1.9.19/lib/graphql/schema/wrapper.rb000066400000000000000000000012401362601351000216730ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Wrapper include GraphQL::Schema::Member::CachedGraphQLDefinition include GraphQL::Schema::Member::TypeSystemHelpers # @return [Class, Module] The inner type of this wrapping type, the type of which one or more objects may be present. attr_reader :of_type def initialize(of_type) @of_type = of_type end def to_graphql raise GraphQL::RequiredImplementationMissingError end def unwrap @of_type.unwrap end def ==(other) self.class == other.class && of_type == other.of_type end end end end ruby-graphql-1.9.19/lib/graphql/static_validation.rb000066400000000000000000000013261362601351000224610ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/static_validation/error" require "graphql/static_validation/definition_dependencies" require "graphql/static_validation/type_stack" require "graphql/static_validation/validator" require "graphql/static_validation/validation_context" require "graphql/static_validation/literal_validator" require "graphql/static_validation/base_visitor" require "graphql/static_validation/no_validate_visitor" rules_glob = File.expand_path("../static_validation/rules/*.rb", __FILE__) Dir.glob(rules_glob).each do |file| require(file) end require "graphql/static_validation/all_rules" require "graphql/static_validation/default_visitor" require "graphql/static_validation/interpreter_visitor" ruby-graphql-1.9.19/lib/graphql/static_validation/000077500000000000000000000000001362601351000221325ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/static_validation/all_rules.rb000066400000000000000000000036611362601351000244470ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # Default rules for {GraphQL::StaticValidation::Validator} # # Order is important here. Some validators return {GraphQL::Language::Visitor::SKIP} # which stops the visit on that node. That way it doesn't try to find fields on types that # don't exist, etc. ALL_RULES = [ GraphQL::StaticValidation::NoDefinitionsArePresent, GraphQL::StaticValidation::DirectivesAreDefined, GraphQL::StaticValidation::DirectivesAreInValidLocations, GraphQL::StaticValidation::UniqueDirectivesPerLocation, GraphQL::StaticValidation::OperationNamesAreValid, GraphQL::StaticValidation::FragmentNamesAreUnique, GraphQL::StaticValidation::FragmentsAreFinite, GraphQL::StaticValidation::FragmentsAreNamed, GraphQL::StaticValidation::FragmentsAreUsed, GraphQL::StaticValidation::FragmentTypesExist, GraphQL::StaticValidation::FragmentsAreOnCompositeTypes, GraphQL::StaticValidation::FragmentSpreadsArePossible, GraphQL::StaticValidation::FieldsAreDefinedOnType, GraphQL::StaticValidation::FieldsWillMerge, GraphQL::StaticValidation::FieldsHaveAppropriateSelections, GraphQL::StaticValidation::ArgumentsAreDefined, GraphQL::StaticValidation::ArgumentLiteralsAreCompatible, GraphQL::StaticValidation::RequiredArgumentsArePresent, GraphQL::StaticValidation::RequiredInputObjectAttributesArePresent, GraphQL::StaticValidation::ArgumentNamesAreUnique, GraphQL::StaticValidation::VariableNamesAreUnique, GraphQL::StaticValidation::VariablesAreInputTypes, GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTyped, GraphQL::StaticValidation::VariablesAreUsedAndDefined, GraphQL::StaticValidation::VariableUsagesAreAllowed, GraphQL::StaticValidation::MutationRootExists, GraphQL::StaticValidation::SubscriptionRootExists, ] end end ruby-graphql-1.9.19/lib/graphql/static_validation/base_visitor.rb000066400000000000000000000141211362601351000251470ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class BaseVisitor < GraphQL::Language::Visitor def initialize(document, context) @path = [] @object_types = [] @directives = [] @field_definitions = [] @argument_definitions = [] @directive_definitions = [] @context = context @schema = context.schema super(document) end # This will be overwritten by {InternalRepresentation::Rewrite} if it's included def rewrite_document nil end attr_reader :context # @return [Array] Types whose scope we've entered attr_reader :object_types # @return [Array] The nesting of the current position in the AST def path @path.dup end # Build a class to visit the AST and perform validation, # or use a pre-built class if rules is `ALL_RULES` or empty. # @param rules [Array] # @param rewrite [Boolean] if `false`, don't include rewrite # @return [Class] A class for validating `rules` during visitation def self.including_rules(rules, rewrite: true) if rules.empty? if rewrite NoValidateVisitor else # It's not doing _anything?!?_ BaseVisitor end elsif rules == ALL_RULES if rewrite DefaultVisitor else InterpreterVisitor end else visitor_class = Class.new(self) do include(GraphQL::StaticValidation::DefinitionDependencies) end rules.reverse_each do |r| # If it's a class, it gets attached later. if !r.is_a?(Class) visitor_class.include(r) end end if rewrite visitor_class.include(GraphQL::InternalRepresentation::Rewrite) end visitor_class.include(ContextMethods) visitor_class end end module ContextMethods def on_operation_definition(node, parent) object_type = @schema.root_type_for_operation(node.operation_type) @object_types.push(object_type) @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}") super @object_types.pop @path.pop end def on_fragment_definition(node, parent) on_fragment_with_type(node) do @path.push("fragment #{node.name}") super end end def on_inline_fragment(node, parent) on_fragment_with_type(node) do @path.push("...#{node.type ? " on #{node.type.to_query_string}" : ""}") super end end def on_field(node, parent) parent_type = @object_types.last field_definition = @schema.get_field(parent_type, node.name) @field_definitions.push(field_definition) if !field_definition.nil? next_object_type = field_definition.type.unwrap @object_types.push(next_object_type) else @object_types.push(nil) end @path.push(node.alias || node.name) super @field_definitions.pop @object_types.pop @path.pop end def on_directive(node, parent) directive_defn = @schema.directives[node.name] @directive_definitions.push(directive_defn) super @directive_definitions.pop end def on_argument(node, parent) argument_defn = if (arg = @argument_definitions.last) arg_type = arg.type.unwrap if arg_type.kind.input_object? arg_type.input_fields[node.name] else nil end elsif (directive_defn = @directive_definitions.last) directive_defn.arguments[node.name] elsif (field_defn = @field_definitions.last) field_defn.arguments[node.name] else nil end @argument_definitions.push(argument_defn) @path.push(node.name) super @argument_definitions.pop @path.pop end def on_fragment_spread(node, parent) @path.push("... #{node.name}") super @path.pop end def on_input_object(node, parent) arg_defn = @argument_definitions.last if arg_defn && arg_defn.type.list? @path.push(parent.children.index(node)) super @path.pop else super end end # @return [GraphQL::BaseType] The current object type def type_definition @object_types.last end # @return [GraphQL::BaseType] The type which the current type came from def parent_type_definition @object_types[-2] end # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one def field_definition @field_definitions.last end # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one def directive_definition @directive_definitions.last end # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one def argument_definition # Don't get the _last_ one because that's the current one. # Get the second-to-last one, which is the parent of the current one. @argument_definitions[-2] end private def on_fragment_with_type(node) object_type = if node.type @schema.types.fetch(node.type.name, nil) else @object_types.last end @object_types.push(object_type) yield(node) @object_types.pop @path.pop end end private def add_error(error, path: nil) error.path ||= (path || @path.dup) context.errors << error end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/default_visitor.rb000066400000000000000000000005541362601351000256660ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class DefaultVisitor < BaseVisitor include(GraphQL::StaticValidation::DefinitionDependencies) StaticValidation::ALL_RULES.reverse_each do |r| include(r) end include(GraphQL::InternalRepresentation::Rewrite) include(ContextMethods) end end end ruby-graphql-1.9.19/lib/graphql/static_validation/definition_dependencies.rb000066400000000000000000000173431362601351000273250ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # Track fragment dependencies for operations # and expose the fragment definitions which # are used by a given operation module DefinitionDependencies attr_reader :dependencies def initialize(*) super @defdep_node_paths = {} # { name => [node, ...] } pairs for fragments (although duplicate-named fragments are _invalid_, they are _possible_) @defdep_fragment_definitions = Hash.new{ |h, k| h[k] = [] } # This tracks dependencies from fragment to Node where it was used # { fragment_definition_name => [dependent_node, dependent_node]} @defdep_dependent_definitions = Hash.new { |h, k| h[k] = Set.new } # First-level usages of spreads within definitions # (When a key has an empty list as its value, # we can resolve that key's dependents) # { definition_node => [node, node ...] } @defdep_immediate_dependencies = Hash.new { |h, k| h[k] = Set.new } # When we encounter a spread, # this node is the one who depends on it @defdep_current_parent = nil end def on_document(node, parent) node.definitions.each do |definition| if definition.is_a? GraphQL::Language::Nodes::FragmentDefinition @defdep_fragment_definitions[definition.name] << definition end end super @dependencies = dependency_map { |defn, spreads, frag| context.on_dependency_resolve_handlers.each { |h| h.call(defn, spreads, frag) } } end def on_operation_definition(node, prev_node) @defdep_node_paths[node.name] = NodeWithPath.new(node, context.path) @defdep_current_parent = node super @defdep_current_parent = nil end def on_fragment_definition(node, parent) @defdep_node_paths[node] = NodeWithPath.new(node, context.path) @defdep_current_parent = node super @defdep_current_parent = nil end def on_fragment_spread(node, parent) @defdep_node_paths[node] = NodeWithPath.new(node, context.path) # Track both sides of the dependency @defdep_dependent_definitions[node.name] << @defdep_current_parent @defdep_immediate_dependencies[@defdep_current_parent] << node super end # A map of operation definitions to an array of that operation's dependencies # @return [DependencyMap] def dependency_map(&block) @dependency_map ||= resolve_dependencies(&block) end # Map definition AST nodes to the definition AST nodes they depend on. # Expose circular dependencies. class DependencyMap # @return [Array] attr_reader :cyclical_definitions # @return [Hash>] attr_reader :unmet_dependencies # @return [Array] attr_reader :unused_dependencies def initialize @dependencies = Hash.new { |h, k| h[k] = [] } @cyclical_definitions = [] @unmet_dependencies = Hash.new { |h, k| h[k] = [] } @unused_dependencies = [] end # @return [Array] dependencies for `definition_node` def [](definition_node) @dependencies[definition_node] end end class NodeWithPath extend Forwardable attr_reader :node, :path def initialize(node, path) @node = node @path = path end def_delegators :@node, :name, :eql?, :hash end private # Return a hash of { node => [node, node ... ]} pairs # Keys are top-level definitions # Values are arrays of flattened dependencies def resolve_dependencies dependency_map = DependencyMap.new # Don't allow the loop to run more times # than the number of fragments in the document max_loops = 0 @defdep_fragment_definitions.each_value do |v| max_loops += v.size end loops = 0 # Instead of tracking independent fragments _as you visit_, # determine them at the end. This way, we can treat fragments with the # same name as if they were the same name. If _any_ of the fragments # with that name has a dependency, we record it. independent_fragment_nodes = @defdep_fragment_definitions.values.flatten - @defdep_immediate_dependencies.keys while fragment_node = independent_fragment_nodes.pop loops += 1 if loops > max_loops raise("Resolution loops exceeded the number of definitions; infinite loop detected. (Max: #{max_loops}, Current: #{loops})") end # Since it's independent, let's remove it from here. # That way, we can use the remainder to identify cycles @defdep_immediate_dependencies.delete(fragment_node) fragment_usages = @defdep_dependent_definitions[fragment_node.name] if fragment_usages.empty? # If we didn't record any usages during the visit, # then this fragment is unused. dependency_map.unused_dependencies << @defdep_node_paths[fragment_node] else fragment_usages.each do |definition_node| # Register the dependency AND second-order dependencies dependency_map[definition_node] << fragment_node dependency_map[definition_node].concat(dependency_map[fragment_node]) # Since we've registered it, remove it from our to-do list deps = @defdep_immediate_dependencies[definition_node] # Can't find a way to _just_ delete from `deps` and return the deleted entries removed, remaining = deps.partition { |spread| spread.name == fragment_node.name } @defdep_immediate_dependencies[definition_node] = remaining if block_given? yield(definition_node, removed, fragment_node) end if remaining.empty? && definition_node.is_a?(GraphQL::Language::Nodes::FragmentDefinition) && definition_node.name != fragment_node.name # If all of this definition's dependencies have # been resolved, we can now resolve its # own dependents. # # But, it's possible to have a duplicate-named fragment here. # Skip it in that case independent_fragment_nodes << definition_node end end end end # If any dependencies were _unmet_ # (eg, spreads with no corresponding definition) # then they're still in there @defdep_immediate_dependencies.each do |defn_node, deps| deps.each do |spread| if !@defdep_fragment_definitions.key?(spread.name) dependency_map.unmet_dependencies[@defdep_node_paths[defn_node]] << @defdep_node_paths[spread] deps.delete(spread) end end if deps.empty? @defdep_immediate_dependencies.delete(defn_node) end end # Anything left in @immediate_dependencies is cyclical cyclical_nodes = @defdep_immediate_dependencies.keys.map { |n| @defdep_node_paths[n] } # @immediate_dependencies also includes operation names, but we don't care about # those. They became nil when we looked them up on `@fragment_definitions`, so remove them. cyclical_nodes.compact! dependency_map.cyclical_definitions.concat(cyclical_nodes) dependency_map end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/error.rb000066400000000000000000000021661362601351000236150ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # Generates GraphQL-compliant validation message. class Error # Convenience for validators module ErrorHelper # Error `error_message` is located at `node` def error(error_message, nodes, context: nil, path: nil, extensions: {}) path ||= context.path nodes = Array(nodes) GraphQL::StaticValidation::Error.new(error_message, nodes: nodes, path: path) end end attr_reader :message attr_accessor :path def initialize(message, path: nil, nodes: []) @message = message @nodes = Array(nodes) @path = path end # A hash representation of this Message def to_h { "message" => message, "locations" => locations }.tap { |h| h["path"] = path unless path.nil? } end private def locations @nodes.map do |node| h = {"line" => node.line, "column" => node.col} h["filename"] = node.filename if node.filename h end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/interpreter_visitor.rb000066400000000000000000000004701362601351000266020ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class InterpreterVisitor < BaseVisitor include(GraphQL::StaticValidation::DefinitionDependencies) StaticValidation::ALL_RULES.reverse_each do |r| include(r) end include(ContextMethods) end end end ruby-graphql-1.9.19/lib/graphql/static_validation/literal_validator.rb000066400000000000000000000101301362601351000261530ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # Test whether `ast_value` is a valid input for `type` class LiteralValidator def initialize(context:) @context = context @warden = context.warden end def validate(ast_value, type) if type.nil? # this means we're an undefined argument, see #present_input_field_values_are_valid maybe_raise_if_invalid(ast_value) do false end elsif ast_value.is_a?(GraphQL::Language::Nodes::NullValue) maybe_raise_if_invalid(ast_value) do !type.kind.non_null? end elsif type.kind.non_null? maybe_raise_if_invalid(ast_value) do (!ast_value.nil?) end && validate(ast_value, type.of_type) elsif type.kind.list? item_type = type.of_type ensure_array(ast_value).all? { |val| validate(val, item_type) } elsif ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) true elsif type.kind.scalar? && constant_scalar?(ast_value) maybe_raise_if_invalid(ast_value) do type.valid_input?(ast_value, @context) end elsif type.kind.enum? maybe_raise_if_invalid(ast_value) do if ast_value.is_a?(GraphQL::Language::Nodes::Enum) type.valid_input?(ast_value.name, @context) else # if our ast_value isn't an Enum it's going to be invalid so return false false end end elsif type.kind.input_object? && ast_value.is_a?(GraphQL::Language::Nodes::InputObject) maybe_raise_if_invalid(ast_value) do required_input_fields_are_present(type, ast_value) && present_input_field_values_are_valid(type, ast_value) end else maybe_raise_if_invalid(ast_value) do false end end end private def maybe_raise_if_invalid(ast_value) ret = yield if !@context.schema.error_bubbling && !ret e = LiteralValidationError.new e.ast_value = ast_value raise e else ret end end # The GraphQL grammar supports variables embedded within scalars but graphql.js # doesn't support it so we won't either for simplicity def constant_scalar?(ast_value) if ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) false elsif ast_value.is_a?(Array) ast_value.all? { |element| constant_scalar?(element) } elsif ast_value.is_a?(GraphQL::Language::Nodes::InputObject) ast_value.arguments.all? { |arg| constant_scalar?(arg.value) } else true end end def required_input_fields_are_present(type, ast_node) # TODO - would be nice to use these to create an error message so the caller knows # that required fields are missing required_field_names = @warden.arguments(type) .select { |f| f.type.kind.non_null? } .map(&:name) present_field_names = ast_node.arguments.map(&:name) missing_required_field_names = required_field_names - present_field_names if @context.schema.error_bubbling missing_required_field_names.empty? else missing_required_field_names.all? do |name| validate(GraphQL::Language::Nodes::NullValue.new(name: name), @warden.arguments(type).find { |f| f.name == name }.type ) end end end def present_input_field_values_are_valid(type, ast_node) field_map = @warden.arguments(type).reduce({}) { |m, f| m[f.name] = f; m} ast_node.arguments.all? do |value| field = field_map[value.name] # we want to call validate on an argument even if it's an invalid one # so that our raise exception is on it instead of the entire InputObject type = field && field.type validate(value.value, type) end end def ensure_array(value) value.is_a?(Array) ? value : [value] end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/no_validate_visitor.rb000066400000000000000000000004541362601351000265260ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class NoValidateVisitor < StaticValidation::BaseVisitor include(GraphQL::InternalRepresentation::Rewrite) include(GraphQL::StaticValidation::DefinitionDependencies) include(ContextMethods) end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/000077500000000000000000000000001362601351000232645ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb000066400000000000000000000074661362601351000323750ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module ArgumentLiteralsAreCompatible # TODO dedup with ArgumentsAreDefined def on_argument(node, parent) parent_defn = case parent when GraphQL::Language::Nodes::InputObject arg_defn = context.argument_definition if arg_defn.nil? nil else arg_ret_type = arg_defn.type.unwrap if !arg_ret_type.is_a?(GraphQL::InputObjectType) nil else arg_ret_type end end when GraphQL::Language::Nodes::Directive context.schema.directives[parent.name] when GraphQL::Language::Nodes::Field context.field_definition else raise "Unexpected argument parent: #{parent.class} (##{parent})" end if parent_defn && !node.value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) arg_defn = parent_defn.arguments[node.name] if arg_defn begin valid = context.valid_literal?(node.value, arg_defn.type) rescue GraphQL::CoercionError => err context.schema.error_bubbling if !context.schema.error_bubbling && !arg_defn.type.unwrap.kind.scalar? # if error bubbling is disabled and the arg that caused this error isn't a scalar then # short-circuit here so we avoid bubbling this up to whatever input_object / array contains us return super end error = GraphQL::StaticValidation::ArgumentLiteralsAreCompatibleError.new(err.message, nodes: parent, type: "CoercionError", extensions: err.extensions) rescue GraphQL::LiteralValidationError => err # check to see if the ast node that caused the error to be raised is # the same as the node we were checking here. arg_type = arg_defn.type if arg_type.kind.non_null? arg_type = arg_type.of_type end matched = if arg_type.kind.list? # for a list we claim an error if the node is contained in our list Array(node.value).include?(err.ast_value) elsif arg_type.kind.input_object? && node.value.is_a?(GraphQL::Language::Nodes::InputObject) # for an input object we check the arguments node.value.arguments.include?(err.ast_value) else # otherwise we just check equality node.value == (err.ast_value) end if !matched # This node isn't the node that caused the error, # So halt this visit but continue visiting the rest of the tree return super end end if !valid error ||= begin kind_of_node = node_type(parent) error_arg_name = parent_name(parent, parent_defn) GraphQL::StaticValidation::ArgumentLiteralsAreCompatibleError.new( "Argument '#{node.name}' on #{kind_of_node} '#{error_arg_name}' has an invalid value. Expected type '#{arg_defn.type}'.", nodes: parent, type: kind_of_node, argument: node.name ) end add_error(error) end end end super end private def parent_name(parent, type_defn) if parent.is_a?(GraphQL::Language::Nodes::Field) parent.alias || parent.name elsif parent.is_a?(GraphQL::Language::Nodes::InputObject) type_defn.name else parent.name end end def node_type(parent) parent.class.name.split("::").last end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb000066400000000000000000000016051362601351000335730ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class ArgumentLiteralsAreCompatibleError < StaticValidation::Error attr_reader :type_name attr_reader :argument_name def initialize(message, path: nil, nodes: [], type:, argument: nil, extensions: nil) super(message, path: path, nodes: nodes) @type_name = type @argument_name = argument @extensions = extensions end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name }.tap { |h| h["argumentName"] = argument_name unless argument_name.nil? } extensions.merge!(@extensions) unless @extensions.nil? super.merge({ "extensions" => extensions }) end def code "argumentLiteralsIncompatible" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/argument_names_are_unique.rb000066400000000000000000000015641362601351000310410ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module ArgumentNamesAreUnique include GraphQL::StaticValidation::Error::ErrorHelper def on_field(node, parent) validate_arguments(node) super end def on_directive(node, parent) validate_arguments(node) super end def validate_arguments(node) argument_defns = node.arguments if argument_defns.any? args_by_name = Hash.new { |h, k| h[k] = [] } argument_defns.each { |a| args_by_name[a.name] << a } args_by_name.each do |name, defns| if defns.size > 1 add_error(GraphQL::StaticValidation::ArgumentNamesAreUniqueError.new("There can be only one argument named \"#{name}\"", nodes: defns, name: name)) end end end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/argument_names_are_unique_error.rb000066400000000000000000000011271362601351000322450ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class ArgumentNamesAreUniqueError < StaticValidation::Error attr_reader :name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "name" => name } super.merge({ "extensions" => extensions }) end def code "argumentNotUnique" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/arguments_are_defined.rb000066400000000000000000000034651362601351000301330ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module ArgumentsAreDefined def on_argument(node, parent) parent_defn = case parent when GraphQL::Language::Nodes::InputObject arg_defn = context.argument_definition if arg_defn.nil? nil else arg_ret_type = arg_defn.type.unwrap if !arg_ret_type.is_a?(GraphQL::InputObjectType) nil else arg_ret_type end end when GraphQL::Language::Nodes::Directive context.schema.directives[parent.name] when GraphQL::Language::Nodes::Field context.field_definition else raise "Unexpected argument parent: #{parent.class} (##{parent})" end if parent_defn && context.warden.arguments(parent_defn).any? { |arg| arg.name == node.name } super elsif parent_defn kind_of_node = node_type(parent) error_arg_name = parent_name(parent, parent_defn) add_error(GraphQL::StaticValidation::ArgumentsAreDefinedError.new( "#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'", nodes: node, name: error_arg_name, type: kind_of_node, argument: node.name )) else # Some other weird error super end end private def parent_name(parent, type_defn) if parent.is_a?(GraphQL::Language::Nodes::Field) parent.alias || parent.name elsif parent.is_a?(GraphQL::Language::Nodes::InputObject) type_defn.name else parent.name end end def node_type(parent) parent.class.name.split("::").last end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/arguments_are_defined_error.rb000066400000000000000000000014571362601351000313430ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class ArgumentsAreDefinedError < StaticValidation::Error attr_reader :name attr_reader :type_name attr_reader :argument_name def initialize(message, path: nil, nodes: [], name:, type:, argument:) super(message, path: path, nodes: nodes) @name = name @type_name = type @argument_name = argument end # A hash representation of this Message def to_h extensions = { "code" => code, "name" => name, "typeName" => type_name, "argumentName" => argument_name } super.merge({ "extensions" => extensions }) end def code "argumentNotAccepted" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/directives_are_defined.rb000066400000000000000000000010701362601351000302550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module DirectivesAreDefined def initialize(*) super @directive_names = context.schema.directives.keys end def on_directive(node, parent) if !@directive_names.include?(node.name) add_error(GraphQL::StaticValidation::DirectivesAreDefinedError.new( "Directive @#{node.name} is not defined", nodes: node, directive: node.name )) else super end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/directives_are_defined_error.rb000066400000000000000000000012061362601351000314670ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class DirectivesAreDefinedError < StaticValidation::Error attr_reader :directive_name def initialize(message, path: nil, nodes: [], directive:) super(message, path: path, nodes: nodes) @directive_name = directive end # A hash representation of this Message def to_h extensions = { "code" => code, "directiveName" => directive_name } super.merge({ "extensions" => extensions }) end def code "undefinedDirective" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb000066400000000000000000000052421362601351000325240ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module DirectivesAreInValidLocations include GraphQL::Language def on_directive(node, parent) validate_location(node, parent, context.schema.directives) super end private LOCATION_MESSAGE_NAMES = { GraphQL::Directive::QUERY => "queries", GraphQL::Directive::MUTATION => "mutations", GraphQL::Directive::SUBSCRIPTION => "subscriptions", GraphQL::Directive::FIELD => "fields", GraphQL::Directive::FRAGMENT_DEFINITION => "fragment definitions", GraphQL::Directive::FRAGMENT_SPREAD => "fragment spreads", GraphQL::Directive::INLINE_FRAGMENT => "inline fragments", } SIMPLE_LOCATIONS = { Nodes::Field => GraphQL::Directive::FIELD, Nodes::InlineFragment => GraphQL::Directive::INLINE_FRAGMENT, Nodes::FragmentSpread => GraphQL::Directive::FRAGMENT_SPREAD, Nodes::FragmentDefinition => GraphQL::Directive::FRAGMENT_DEFINITION, } SIMPLE_LOCATION_NODES = SIMPLE_LOCATIONS.keys def validate_location(ast_directive, ast_parent, directives) directive_defn = directives[ast_directive.name] case ast_parent when Nodes::OperationDefinition required_location = GraphQL::Directive.const_get(ast_parent.operation_type.upcase) assert_includes_location(directive_defn, ast_directive, required_location) when *SIMPLE_LOCATION_NODES required_location = SIMPLE_LOCATIONS[ast_parent.class] assert_includes_location(directive_defn, ast_directive, required_location) else add_error(GraphQL::StaticValidation::DirectivesAreInValidLocationsError.new( "Directives can't be applied to #{ast_parent.class.name}s", nodes: ast_directive, target: ast_parent.class.name )) end end def assert_includes_location(directive_defn, directive_ast, required_location) if !directive_defn.locations.include?(required_location) location_name = LOCATION_MESSAGE_NAMES[required_location] allowed_location_names = directive_defn.locations.map { |loc| LOCATION_MESSAGE_NAMES[loc] } add_error(GraphQL::StaticValidation::DirectivesAreInValidLocationsError.new( "'@#{directive_defn.graphql_name}' can't be applied to #{location_name} (allowed: #{allowed_location_names.join(", ")})", nodes: directive_ast, target: location_name, name: directive_defn.name )) end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/directives_are_in_valid_locations_error.rb000066400000000000000000000013511362601351000337320ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class DirectivesAreInValidLocationsError < StaticValidation::Error attr_reader :target_name attr_reader :name def initialize(message, path: nil, nodes: [], target:, name: nil) super(message, path: path, nodes: nodes) @target_name = target @name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "targetName" => target_name }.tap { |h| h["name"] = name unless name.nil? } super.merge({ "extensions" => extensions }) end def code "directiveCannotBeApplied" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb000066400000000000000000000016721362601351000311270ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FieldsAreDefinedOnType def on_field(node, parent) parent_type = @object_types[-2] field = context.warden.get_field(parent_type, node.name) if field.nil? if parent_type.kind.union? add_error(GraphQL::StaticValidation::FieldsHaveAppropriateSelectionsError.new( "Selections can't be made directly on unions (see selections on #{parent_type.name})", nodes: parent, node_name: parent_type.name )) else add_error(GraphQL::StaticValidation::FieldsAreDefinedOnTypeError.new( "Field '#{node.name}' doesn't exist on type '#{parent_type.name}'", nodes: node, field: node.name, type: parent_type.name )) end else super end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fields_are_defined_on_type_error.rb000066400000000000000000000013151362601351000323320ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FieldsAreDefinedOnTypeError < StaticValidation::Error attr_reader :type_name attr_reader :field_name def initialize(message, path: nil, nodes: [], type:, field:) super(message, path: path, nodes: nodes) @type_name = type @field_name = field end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name, "fieldName" => field_name } super.merge({ "extensions" => extensions }) end def code "undefinedField" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb000066400000000000000000000047461362601351000327330ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # Scalars _can't_ have selections # Objects _must_ have selections module FieldsHaveAppropriateSelections include GraphQL::StaticValidation::Error::ErrorHelper def on_field(node, parent) field_defn = field_definition if validate_field_selections(node, field_defn.type.unwrap) super end end def on_operation_definition(node, _parent) if validate_field_selections(node, type_definition) super end end private def validate_field_selections(ast_node, resolved_type) msg = if resolved_type.nil? nil elsif resolved_type.kind.scalar? && ast_node.selections.any? if ast_node.selections.first.is_a?(GraphQL::Language::Nodes::InlineFragment) "Selections can't be made on scalars (%{node_name} returns #{resolved_type.name} but has inline fragments [#{ast_node.selections.map(&:type).map(&:name).join(", ")}])" else "Selections can't be made on scalars (%{node_name} returns #{resolved_type.name} but has selections [#{ast_node.selections.map(&:name).join(", ")}])" end elsif resolved_type.kind.fields? && ast_node.selections.empty? "Field must have selections (%{node_name} returns #{resolved_type.name} but has no selections. Did you mean '#{ast_node.name} { ... }'?)" else nil end if msg node_name = case ast_node when GraphQL::Language::Nodes::Field "field '#{ast_node.name}'" when GraphQL::Language::Nodes::OperationDefinition if ast_node.name.nil? "anonymous query" else "#{ast_node.operation_type} '#{ast_node.name}'" end else raise("Unexpected node #{ast_node}") end extensions = { "rule": "StaticValidation::FieldsHaveAppropriateSelections", "name": node_name.to_s } unless resolved_type.nil? extensions["type"] = resolved_type.to_s end add_error(GraphQL::StaticValidation::FieldsHaveAppropriateSelectionsError.new( msg % { node_name: node_name }, nodes: ast_node, node_name: node_name.to_s, type: resolved_type.nil? ? nil : resolved_type.to_s )) false else true end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fields_have_appropriate_selections_error.rb000066400000000000000000000013721362601351000341340ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FieldsHaveAppropriateSelectionsError < StaticValidation::Error attr_reader :type_name attr_reader :node_name def initialize(message, path: nil, nodes: [], node_name:, type: nil) super(message, path: path, nodes: nodes) @node_name = node_name @type_name = type end # A hash representation of this Message def to_h extensions = { "code" => code, "nodeName" => node_name }.tap { |h| h["typeName"] = type_name unless type_name.nil? } super.merge({ "extensions" => extensions }) end def code "selectionMismatch" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fields_will_merge.rb000066400000000000000000000330431362601351000272700ustar00rootroot00000000000000 # frozen_string_literal: true # frozen_string_literal: true module GraphQL module StaticValidation module FieldsWillMerge # Validates that a selection set is valid if all fields (including spreading any # fragments) either correspond to distinct response names or can be merged # without ambiguity. # # Original Algorithm: https://github.com/graphql/graphql-js/blob/master/src/validation/rules/OverlappingFieldsCanBeMerged.js NO_ARGS = {}.freeze Field = Struct.new(:node, :definition, :owner_type, :parents) FragmentSpread = Struct.new(:name, :parents) def initialize(*) super @visited_fragments = {} @compared_fragments = {} end def on_operation_definition(node, _parent) conflicts_within_selection_set(node, type_definition) super end def on_field(node, _parent) conflicts_within_selection_set(node, type_definition) super end private def conflicts_within_selection_set(node, parent_type) return if parent_type.nil? fields, fragment_spreads = fields_and_fragments_from_selection(node, owner_type: parent_type, parents: []) # (A) Find find all conflicts "within" the fields of this selection set. find_conflicts_within(fields) fragment_spreads.each_with_index do |fragment_spread, i| are_mutually_exclusive = mutually_exclusive?( fragment_spread.parents, [parent_type] ) # (B) Then find conflicts between these fields and those represented by # each spread fragment name found. find_conflicts_between_fields_and_fragment( fragment_spread, fields, mutually_exclusive: are_mutually_exclusive, ) # (C) Then compare this fragment with all other fragments found in this # selection set to collect conflicts between fragments spread together. # This compares each item in the list of fragment names to every other # item in that same list (except for itself). fragment_spreads[i + 1..-1].each do |fragment_spread2| are_mutually_exclusive = mutually_exclusive?( fragment_spread.parents, fragment_spread2.parents ) find_conflicts_between_fragments( fragment_spread, fragment_spread2, mutually_exclusive: are_mutually_exclusive, ) end end end def find_conflicts_between_fragments(fragment_spread1, fragment_spread2, mutually_exclusive:) fragment_name1 = fragment_spread1.name fragment_name2 = fragment_spread2.name return if fragment_name1 == fragment_name2 cache_key = compared_fragments_key( fragment_name1, fragment_name2, mutually_exclusive, ) if @compared_fragments.key?(cache_key) return else @compared_fragments[cache_key] = true end fragment1 = context.fragments[fragment_name1] fragment2 = context.fragments[fragment_name2] return if fragment1.nil? || fragment2.nil? fragment_type1 = context.schema.types[fragment1.type.name] fragment_type2 = context.schema.types[fragment2.type.name] return if fragment_type1.nil? || fragment_type2.nil? fragment_fields1, fragment_spreads1 = fields_and_fragments_from_selection( fragment1, owner_type: fragment_type1, parents: [*fragment_spread1.parents, fragment_type1] ) fragment_fields2, fragment_spreads2 = fields_and_fragments_from_selection( fragment2, owner_type: fragment_type1, parents: [*fragment_spread2.parents, fragment_type2] ) # (F) First, find all conflicts between these two collections of fields # (not including any nested fragments). find_conflicts_between( fragment_fields1, fragment_fields2, mutually_exclusive: mutually_exclusive, ) # (G) Then collect conflicts between the first fragment and any nested # fragments spread in the second fragment. fragment_spreads2.each do |fragment_spread| find_conflicts_between_fragments( fragment_spread1, fragment_spread, mutually_exclusive: mutually_exclusive, ) end # (G) Then collect conflicts between the first fragment and any nested # fragments spread in the second fragment. fragment_spreads1.each do |fragment_spread| find_conflicts_between_fragments( fragment_spread2, fragment_spread, mutually_exclusive: mutually_exclusive, ) end end def find_conflicts_between_fields_and_fragment(fragment_spread, fields, mutually_exclusive:) fragment_name = fragment_spread.name return if @visited_fragments.key?(fragment_name) @visited_fragments[fragment_name] = true fragment = context.fragments[fragment_name] return if fragment.nil? fragment_type = context.schema.types[fragment.type.name] return if fragment_type.nil? fragment_fields, fragment_spreads = fields_and_fragments_from_selection(fragment, owner_type: fragment_type, parents: [*fragment_spread.parents, fragment_type]) # (D) First find any conflicts between the provided collection of fields # and the collection of fields represented by the given fragment. find_conflicts_between( fields, fragment_fields, mutually_exclusive: mutually_exclusive, ) # (E) Then collect any conflicts between the provided collection of fields # and any fragment names found in the given fragment. fragment_spreads.each do |fragment_spread| find_conflicts_between_fields_and_fragment( fragment_spread, fields, mutually_exclusive: mutually_exclusive, ) end end def find_conflicts_within(response_keys) response_keys.each do |key, fields| next if fields.size < 2 # find conflicts within nodes for i in 0..fields.size - 1 for j in i + 1..fields.size - 1 find_conflict(key, fields[i], fields[j]) end end end end def find_conflict(response_key, field1, field2, mutually_exclusive: false) node1 = field1.node node2 = field2.node are_mutually_exclusive = mutually_exclusive || mutually_exclusive?(field1.parents, field2.parents) if !are_mutually_exclusive if node1.name != node2.name errored_nodes = [node1.name, node2.name].sort.join(" or ") msg = "Field '#{response_key}' has a field conflict: #{errored_nodes}?" context.errors << GraphQL::StaticValidation::FieldsWillMergeError.new( msg, nodes: [node1, node2], path: [], field_name: response_key, conflicts: errored_nodes ) end args = possible_arguments(node1, node2) if args.size > 1 msg = "Field '#{response_key}' has an argument conflict: #{args.map { |arg| GraphQL::Language.serialize(arg) }.join(" or ")}?" context.errors << GraphQL::StaticValidation::FieldsWillMergeError.new( msg, nodes: [node1, node2], path: [], field_name: response_key, conflicts: args.map { |arg| GraphQL::Language.serialize(arg) }.join(" or ") ) end end find_conflicts_between_sub_selection_sets( field1, field2, mutually_exclusive: are_mutually_exclusive, ) end def find_conflicts_between_sub_selection_sets(field1, field2, mutually_exclusive:) return if field1.definition.nil? || field2.definition.nil? return_type1 = field1.definition.type.unwrap return_type2 = field2.definition.type.unwrap parents1 = [return_type1] parents2 = [return_type2] fields, fragment_spreads = fields_and_fragments_from_selection( field1.node, owner_type: return_type1, parents: parents1 ) fields2, fragment_spreads2 = fields_and_fragments_from_selection( field2.node, owner_type: return_type2, parents: parents2 ) # (H) First, collect all conflicts between these two collections of field. find_conflicts_between(fields, fields2, mutually_exclusive: mutually_exclusive) # (I) Then collect conflicts between the first collection of fields and # those referenced by each fragment name associated with the second. fragment_spreads2.each do |fragment_spread| find_conflicts_between_fields_and_fragment( fragment_spread, fields, mutually_exclusive: mutually_exclusive, ) end # (I) Then collect conflicts between the second collection of fields and # those referenced by each fragment name associated with the first. fragment_spreads.each do |fragment_spread| find_conflicts_between_fields_and_fragment( fragment_spread, fields2, mutually_exclusive: mutually_exclusive, ) end # (J) Also collect conflicts between any fragment names by the first and # fragment names by the second. This compares each item in the first set of # names to each item in the second set of names. fragment_spreads.each do |frag1| fragment_spreads2.each do |frag2| find_conflicts_between_fragments( frag1, frag2, mutually_exclusive: mutually_exclusive ) end end end def find_conflicts_between(response_keys, response_keys2, mutually_exclusive:) response_keys.each do |key, fields| fields2 = response_keys2[key] if fields2 fields.each do |field| fields2.each do |field2| find_conflict( key, field, field2, mutually_exclusive: mutually_exclusive, ) end end end end end NO_SELECTIONS = [{}.freeze, [].freeze].freeze def fields_and_fragments_from_selection(node, owner_type:, parents:) if node.selections.empty? NO_SELECTIONS else fields, fragment_spreads = find_fields_and_fragments(node.selections, owner_type: owner_type, parents: parents, fields: [], fragment_spreads: []) response_keys = fields.group_by { |f| f.node.alias || f.node.name } [response_keys, fragment_spreads] end end def find_fields_and_fragments(selections, owner_type:, parents:, fields:, fragment_spreads:) selections.each do |node| case node when GraphQL::Language::Nodes::Field definition = context.schema.get_field(owner_type, node.name) fields << Field.new(node, definition, owner_type, parents) when GraphQL::Language::Nodes::InlineFragment fragment_type = node.type ? context.schema.types[node.type.name] : owner_type find_fields_and_fragments(node.selections, parents: [*parents, fragment_type], owner_type: owner_type, fields: fields, fragment_spreads: fragment_spreads) if fragment_type when GraphQL::Language::Nodes::FragmentSpread fragment_spreads << FragmentSpread.new(node.name, parents) end end [fields, fragment_spreads] end def possible_arguments(field1, field2) # Check for incompatible / non-identical arguments on this node: [field1, field2].map do |n| if n.arguments.any? serialized_args = {} n.arguments.each do |a| arg_value = a.value serialized_args[a.name] = serialize_arg(arg_value) end serialized_args else NO_ARGS end end.uniq end def serialize_arg(arg_value) case arg_value when GraphQL::Language::Nodes::AbstractNode arg_value.to_query_string when Array "[#{arg_value.map { |a| serialize_arg(a) }.join(", ")}]" else GraphQL::Language.serialize(arg_value) end end def compared_fragments_key(frag1, frag2, exclusive) # Cache key to not compare two fragments more than once. # The key includes both fragment names sorted (this way we # avoid computing "A vs B" and "B vs A"). It also includes # "exclusive" since the result may change depending on the parent_type "#{[frag1, frag2].sort.join('-')}-#{exclusive}" end # Given two list of parents, find out if they are mutually exclusive # In this context, `parents` represends the "self scope" of the field, # what types may be found at this point in the query. def mutually_exclusive?(parents1, parents2) parents1.each do |type1| parents2.each do |type2| # If the types we're comparing are both different object types, # they have to be mutually exclusive. if type1 != type2 && type1.kind.object? && type2.kind.object? return true end end end false end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fields_will_merge_error.rb000066400000000000000000000013321362601351000304750ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FieldsWillMergeError < StaticValidation::Error attr_reader :field_name attr_reader :conflicts def initialize(message, path: nil, nodes: [], field_name:, conflicts:) super(message, path: path, nodes: nodes) @field_name = field_name @conflicts = conflicts end # A hash representation of this Message def to_h extensions = { "code" => code, "fieldName" => field_name, "conflicts" => conflicts } super.merge({ "extensions" => extensions }) end def code "fieldConflict" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fragment_names_are_unique.rb000066400000000000000000000013301362601351000310110ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentNamesAreUnique def initialize(*) super @fragments_by_name = Hash.new { |h, k| h[k] = [] } end def on_fragment_definition(node, parent) @fragments_by_name[node.name] << node super end def on_document(_n, _p) super @fragments_by_name.each do |name, fragments| if fragments.length > 1 add_error(GraphQL::StaticValidation::FragmentNamesAreUniqueError.new( %|Fragment name "#{name}" must be unique|, nodes: fragments, name: name )) end end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fragment_names_are_unique_error.rb000066400000000000000000000011711362601351000322250ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentNamesAreUniqueError < StaticValidation::Error attr_reader :fragment_name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @fragment_name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "fragmentName" => fragment_name } super.merge({ "extensions" => extensions }) end def code "fragmentNotUnique" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb000066400000000000000000000046371362601351000316760ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentSpreadsArePossible def initialize(*) super @spreads_to_validate = [] end def on_inline_fragment(node, parent) fragment_parent = context.object_types[-2] fragment_child = context.object_types.last if fragment_child validate_fragment_in_scope(fragment_parent, fragment_child, node, context, context.path) end super end def on_fragment_spread(node, parent) fragment_parent = context.object_types.last @spreads_to_validate << FragmentSpread.new(node: node, parent_type: fragment_parent, path: context.path) super end def on_document(node, parent) super @spreads_to_validate.each do |frag_spread| frag_node = context.fragments[frag_spread.node.name] if frag_node fragment_child_name = frag_node.type.name fragment_child = context.warden.get_type(fragment_child_name) # Might be non-existent type name if fragment_child validate_fragment_in_scope(frag_spread.parent_type, fragment_child, frag_spread.node, context, frag_spread.path) end end end end private def validate_fragment_in_scope(parent_type, child_type, node, context, path) if !child_type.kind.fields? # It's not a valid fragment type, this error was handled someplace else return end parent_types = context.warden.possible_types(parent_type.unwrap) child_types = context.warden.possible_types(child_type.unwrap) if child_types.none? { |c| parent_types.include?(c) } name = node.respond_to?(:name) ? " #{node.name}" : "" add_error(GraphQL::StaticValidation::FragmentSpreadsArePossibleError.new( "Fragment#{name} on #{child_type.name} can't be spread inside #{parent_type.name}", nodes: node, path: path, fragment_name: name.empty? ? "unknown" : name, type: child_type.name, parent: parent_type.name )) end end class FragmentSpread attr_reader :node, :parent_type, :path def initialize(node:, parent_type:, path:) @node = node @parent_type = parent_type @path = path end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fragment_spreads_are_possible_error.rb000066400000000000000000000015401362601351000330750ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentSpreadsArePossibleError < StaticValidation::Error attr_reader :type_name attr_reader :fragment_name attr_reader :parent_name def initialize(message, path: nil, nodes: [], type:, fragment_name:, parent:) super(message, path: path, nodes: nodes) @type_name = type @fragment_name = fragment_name @parent_name = parent end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name, "fragmentName" => fragment_name, "parentName" => parent_name } super.merge({ "extensions" => extensions }) end def code "cannotSpreadFragment" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fragment_types_exist.rb000066400000000000000000000016431362601351000300600ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentTypesExist def on_fragment_definition(node, _parent) if validate_type_exists(node) super end end def on_inline_fragment(node, _parent) if validate_type_exists(node) super end end private def validate_type_exists(fragment_node) if !fragment_node.type true else type_name = fragment_node.type.name type = context.warden.get_type(type_name) if type.nil? add_error(GraphQL::StaticValidation::FragmentTypesExistError.new( "No such type #{type_name}, so it can't be a fragment condition", nodes: fragment_node, type: type_name )) false else true end end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fragment_types_exist_error.rb000066400000000000000000000011411362601351000312620ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentTypesExistError < StaticValidation::Error attr_reader :type_name def initialize(message, path: nil, nodes: [], type:) super(message, path: path, nodes: nodes) @type_name = type end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name } super.merge({ "extensions" => extensions }) end def code "undefinedType" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fragments_are_finite.rb000066400000000000000000000011751362601351000277700ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentsAreFinite def on_document(_n, _p) super dependency_map = context.dependencies dependency_map.cyclical_definitions.each do |defn| if defn.node.is_a?(GraphQL::Language::Nodes::FragmentDefinition) context.errors << GraphQL::StaticValidation::FragmentsAreFiniteError.new( "Fragment #{defn.name} contains an infinite loop", nodes: defn.node, path: defn.path, name: defn.name ) end end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fragments_are_finite_error.rb000066400000000000000000000011601362601351000311730ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentsAreFiniteError < StaticValidation::Error attr_reader :fragment_name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @fragment_name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "fragmentName" => fragment_name } super.merge({ "extensions" => extensions }) end def code "infiniteLoop" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fragments_are_named.rb000066400000000000000000000006031362601351000275710ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentsAreNamed def on_fragment_definition(node, _parent) if node.name.nil? add_error(GraphQL::StaticValidation::FragmentsAreNamedError.new( "Fragment definition has no name", nodes: node )) end super end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fragments_are_named_error.rb000066400000000000000000000010041362601351000307760ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentsAreNamedError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "anonymousFragment" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb000066400000000000000000000020021362601351000324220ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentsAreOnCompositeTypes def on_fragment_definition(node, parent) validate_type_is_composite(node) && super end def on_inline_fragment(node, parent) validate_type_is_composite(node) && super end private def validate_type_is_composite(node) node_type = node.type if node_type.nil? # Inline fragment on the same type true else type_name = node_type.to_query_string type_def = context.warden.get_type(type_name) if type_def.nil? || !type_def.kind.composite? add_error(GraphQL::StaticValidation::FragmentsAreOnCompositeTypesError.new( "Invalid fragment on type #{type_name} (must be Union, Interface or Object)", nodes: node, type: type_name )) false else true end end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fragments_are_on_composite_types_error.rb000066400000000000000000000012311362601351000336360ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentsAreOnCompositeTypesError < StaticValidation::Error attr_reader :type_name attr_reader :argument_name def initialize(message, path: nil, nodes: [], type:) super(message, path: path, nodes: nodes) @type_name = type end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name } super.merge({ "extensions" => extensions }) end def code "fragmentOnNonCompositeType" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fragments_are_used.rb000066400000000000000000000020461362601351000274500ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentsAreUsed def on_document(node, parent) super dependency_map = context.dependencies dependency_map.unmet_dependencies.each do |op_defn, spreads| spreads.each do |fragment_spread| add_error(GraphQL::StaticValidation::FragmentsAreUsedError.new( "Fragment #{fragment_spread.name} was used, but not defined", nodes: fragment_spread.node, path: fragment_spread.path, fragment: fragment_spread.name )) end end dependency_map.unused_dependencies.each do |fragment| if fragment && !fragment.name.nil? add_error(GraphQL::StaticValidation::FragmentsAreUsedError.new( "Fragment #{fragment.name} was defined, but not used", nodes: fragment.node, path: fragment.path, fragment: fragment.name )) end end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/fragments_are_used_error.rb000066400000000000000000000011761362601351000306640ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentsAreUsedError < StaticValidation::Error attr_reader :fragment_name def initialize(message, path: nil, nodes: [], fragment:) super(message, path: path, nodes: nodes) @fragment_name = fragment end # A hash representation of this Message def to_h extensions = { "code" => code, "fragmentName" => fragment_name } super.merge({ "extensions" => extensions }) end def code "useAndDefineFragment" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/mutation_root_exists.rb000066400000000000000000000007521362601351000301170ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module MutationRootExists def on_operation_definition(node, _parent) if node.operation_type == 'mutation' && context.warden.root_type_for_operation("mutation").nil? add_error(GraphQL::StaticValidation::MutationRootExistsError.new( 'Schema is not configured for mutations', nodes: node )) else super end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/mutation_root_exists_error.rb000066400000000000000000000010201362601351000313150ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class MutationRootExistsError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "missingMutationConfiguration" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/no_definitions_are_present.rb000066400000000000000000000027111362601351000312100ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module NoDefinitionsArePresent include GraphQL::StaticValidation::Error::ErrorHelper def initialize(*) super @schema_definition_nodes = [] end def on_invalid_node(node, parent) @schema_definition_nodes << node nil end alias :on_directive_definition :on_invalid_node alias :on_schema_definition :on_invalid_node alias :on_scalar_type_definition :on_invalid_node alias :on_object_type_definition :on_invalid_node alias :on_input_object_type_definition :on_invalid_node alias :on_interface_type_definition :on_invalid_node alias :on_union_type_definition :on_invalid_node alias :on_enum_type_definition :on_invalid_node alias :on_schema_extension :on_invalid_node alias :on_scalar_type_extension :on_invalid_node alias :on_object_type_extension :on_invalid_node alias :on_input_object_type_extension :on_invalid_node alias :on_interface_type_extension :on_invalid_node alias :on_union_type_extension :on_invalid_node alias :on_enum_type_extension :on_invalid_node def on_document(node, parent) super if @schema_definition_nodes.any? add_error(GraphQL::StaticValidation::NoDefinitionsArePresentError.new(%|Query cannot contain schema definitions|, nodes: @schema_definition_nodes)) end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/no_definitions_are_present_error.rb000066400000000000000000000010261362601351000324170ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class NoDefinitionsArePresentError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "queryContainsSchemaDefinitions" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/operation_names_are_valid.rb000066400000000000000000000020211362601351000307750ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module OperationNamesAreValid def initialize(*) super @operation_names = Hash.new { |h, k| h[k] = [] } end def on_operation_definition(node, parent) @operation_names[node.name] << node super end def on_document(node, parent) super op_count = @operation_names.values.inject(0) { |m, v| m + v.size } @operation_names.each do |name, nodes| if name.nil? && op_count > 1 add_error(GraphQL::StaticValidation::OperationNamesAreValidError.new( %|Operation name is required when multiple operations are present|, nodes: nodes )) elsif nodes.length > 1 add_error(GraphQL::StaticValidation::OperationNamesAreValidError.new( %|Operation name "#{name}" must be unique|, nodes: nodes, name: name )) end end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/operation_names_are_valid_error.rb000066400000000000000000000012451362601351000322150ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class OperationNamesAreValidError < StaticValidation::Error attr_reader :operation_name def initialize(message, path: nil, nodes: [], name: nil) super(message, path: path, nodes: nodes) @operation_name = name end # A hash representation of this Message def to_h extensions = { "code" => code }.tap { |h| h["operationName"] = operation_name unless operation_name.nil? } super.merge({ "extensions" => extensions }) end def code "uniquelyNamedOperations" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/required_arguments_are_present.rb000066400000000000000000000023211362601351000321030ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module RequiredArgumentsArePresent def on_field(node, _parent) assert_required_args(node, field_definition) super end def on_directive(node, _parent) directive_defn = context.schema.directives[node.name] assert_required_args(node, directive_defn) super end private def assert_required_args(ast_node, defn) present_argument_names = ast_node.arguments.map(&:name) required_argument_names = context.warden.arguments(defn) .select { |a| a.type.kind.non_null? && !a.default_value? } .map(&:name) missing_names = required_argument_names - present_argument_names if missing_names.any? add_error(GraphQL::StaticValidation::RequiredArgumentsArePresentError.new( "#{ast_node.class.name.split("::").last} '#{ast_node.name}' is missing required arguments: #{missing_names.join(", ")}", nodes: ast_node, class_name: ast_node.class.name.split("::").last, name: ast_node.name, arguments: "#{missing_names.join(", ")}" )) end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/required_arguments_are_present_error.rb000066400000000000000000000014771362601351000333270ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class RequiredArgumentsArePresentError < StaticValidation::Error attr_reader :class_name attr_reader :name attr_reader :arguments def initialize(message, path: nil, nodes: [], class_name:, name:, arguments:) super(message, path: path, nodes: nodes) @class_name = class_name @name = name @arguments = arguments end # A hash representation of this Message def to_h extensions = { "code" => code, "className" => class_name, "name" => name, "arguments" => arguments } super.merge({ "extensions" => extensions }) end def code "missingRequiredArguments" end end end end required_input_object_attributes_are_present.rb000066400000000000000000000040671362601351000347630ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/static_validation/rules# frozen_string_literal: true module GraphQL module StaticValidation module RequiredInputObjectAttributesArePresent def on_input_object(node, parent) if parent.is_a? GraphQL::Language::Nodes::Argument validate_input_object(node, context, parent) end super end private def get_parent_type(context, parent) # If argument_definition is defined we're at nested object # and need to refer to the containing input object type rather # than the field_definition. # h/t @rmosolgo arg_defn = context.argument_definition # Double checking that arg_defn is an input object as nested # scalars, namely JSON, can make it to this branch defn = if arg_defn && arg_defn.type.unwrap.kind.input_object? arg_defn.type.unwrap else context.field_definition end parent_type = context.warden.arguments(defn) .find{|f| f.name == parent_name(parent, defn) } parent_type ? parent_type.type.unwrap : nil end def validate_input_object(ast_node, context, parent) parent_type = get_parent_type(context, parent) return unless parent_type && parent_type.kind.input_object? required_fields = parent_type.arguments .select{|k,v| v.type.kind.non_null?} .keys present_fields = ast_node.arguments.map(&:name) missing_fields = required_fields - present_fields missing_fields.each do |missing_field| path = [*context.path, missing_field] missing_field_type = parent_type.arguments[missing_field].type add_error(RequiredInputObjectAttributesArePresentError.new( "Argument '#{missing_field}' on InputObject '#{parent_type}' is required. Expected type #{missing_field_type}", argument_name: missing_field, argument_type: missing_field_type.to_s, input_object_type: parent_type.to_s, path: path, nodes: ast_node, )) end end end end end required_input_object_attributes_are_present_error.rb000066400000000000000000000017051362601351000361700ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/static_validation/rules# frozen_string_literal: true module GraphQL module StaticValidation class RequiredInputObjectAttributesArePresentError < StaticValidation::Error attr_reader :argument_type attr_reader :argument_name attr_reader :input_object_type def initialize(message, path:, nodes:, argument_type:, argument_name:, input_object_type:) super(message, path: path, nodes: nodes) @argument_type = argument_type @argument_name = argument_name @input_object_type = input_object_type end # A hash representation of this Message def to_h extensions = { "code" => code, "argumentName" => argument_name, "argumentType" => argument_type, "inputObjectType" => input_object_type, } super.merge({ "extensions" => extensions }) end def code "missingRequiredInputObjectAttribute" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/subscription_root_exists.rb000066400000000000000000000007761362601351000310110ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module SubscriptionRootExists def on_operation_definition(node, _parent) if node.operation_type == "subscription" && context.warden.root_type_for_operation("subscription").nil? add_error(GraphQL::StaticValidation::SubscriptionRootExistsError.new( 'Schema is not configured for subscriptions', nodes: node )) else super end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/subscription_root_exists_error.rb000066400000000000000000000010301362601351000322020ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class SubscriptionRootExistsError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "missingSubscriptionConfiguration" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/unique_directives_per_location.rb000066400000000000000000000027711362601351000321050ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module UniqueDirectivesPerLocation DIRECTIVE_NODE_HOOKS = [ :on_fragment_definition, :on_fragment_spread, :on_inline_fragment, :on_operation_definition, :on_scalar_type_definition, :on_object_type_definition, :on_input_value_definition, :on_field_definition, :on_interface_type_definition, :on_union_type_definition, :on_enum_type_definition, :on_enum_value_definition, :on_input_object_type_definition, :on_field, ] DIRECTIVE_NODE_HOOKS.each do |method_name| define_method(method_name) do |node, parent| if node.directives.any? validate_directive_location(node) end super(node, parent) end end private def validate_directive_location(node) used_directives = {} node.directives.each do |ast_directive| directive_name = ast_directive.name if used_directives[directive_name] add_error(GraphQL::StaticValidation::UniqueDirectivesPerLocationError.new( "The directive \"#{directive_name}\" can only be used once at this location.", nodes: [used_directives[directive_name], ast_directive], directive: directive_name, )) else used_directives[directive_name] = ast_directive end end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/unique_directives_per_location_error.rb000066400000000000000000000012301362601351000333030ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class UniqueDirectivesPerLocationError < StaticValidation::Error attr_reader :directive_name def initialize(message, path: nil, nodes: [], directive:) super(message, path: path, nodes: nodes) @directive_name = directive end # A hash representation of this Message def to_h extensions = { "code" => code, "directiveName" => directive_name } super.merge({ "extensions" => extensions }) end def code "directiveNotUniqueForLocation" end end end end variable_default_values_are_correctly_typed.rb000066400000000000000000000034131362601351000345250ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/static_validation/rules# frozen_string_literal: true module GraphQL module StaticValidation module VariableDefaultValuesAreCorrectlyTyped def on_variable_definition(node, parent) if !node.default_value.nil? value = node.default_value if node.type.is_a?(GraphQL::Language::Nodes::NonNullType) add_error(GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTypedError.new( "Non-null variable $#{node.name} can't have a default value", nodes: node, name: node.name, error_type: VariableDefaultValuesAreCorrectlyTypedError::VIOLATIONS[:INVALID_ON_NON_NULL] )) else type = context.schema.type_from_ast(node.type) if type.nil? # This is handled by another validator else begin valid = context.valid_literal?(value, type) rescue GraphQL::CoercionError => err error_message = err.message rescue GraphQL::LiteralValidationError # noop, we just want to stop any LiteralValidationError from propagating end if !valid error_message ||= "Default value for $#{node.name} doesn't match type #{type}" VariableDefaultValuesAreCorrectlyTypedError add_error(GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTypedError.new( error_message, nodes: node, name: node.name, type: type.to_s, error_type: VariableDefaultValuesAreCorrectlyTypedError::VIOLATIONS[:INVALID_TYPE] )) end end end end super end end end end variable_default_values_are_correctly_typed_error.rb000066400000000000000000000021141362601351000357330ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/static_validation/rules# frozen_string_literal: true module GraphQL module StaticValidation class VariableDefaultValuesAreCorrectlyTypedError < StaticValidation::Error attr_reader :variable_name attr_reader :type_name attr_reader :violation VIOLATIONS = { :INVALID_TYPE => "defaultValueInvalidType", :INVALID_ON_NON_NULL => "defaultValueInvalidOnNonNullVariable", } def initialize(message, path: nil, nodes: [], name:, type: nil, error_type:) super(message, path: path, nodes: nodes) @variable_name = name @type_name = type raise("Unexpected error type: #{error_type}") if !VIOLATIONS.values.include?(error_type) @violation = error_type end # A hash representation of this Message def to_h extensions = { "code" => code, "variableName" => variable_name }.tap { |h| h["typeName"] = type_name unless type_name.nil? } super.merge({ "extensions" => extensions }) end def code @violation end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/variable_names_are_unique.rb000066400000000000000000000013111362601351000307720ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module VariableNamesAreUnique def on_operation_definition(node, parent) var_defns = node.variables if var_defns.any? vars_by_name = Hash.new { |h, k| h[k] = [] } var_defns.each { |v| vars_by_name[v.name] << v } vars_by_name.each do |name, defns| if defns.size > 1 add_error(GraphQL::StaticValidation::VariableNamesAreUniqueError.new( "There can only be one variable named \"#{name}\"", nodes: defns, name: name )) end end end super end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/variable_names_are_unique_error.rb000066400000000000000000000011711362601351000322070ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class VariableNamesAreUniqueError < StaticValidation::Error attr_reader :variable_name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @variable_name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "variableName" => variable_name } super.merge({ "extensions" => extensions }) end def code "variableNotUnique" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb000066400000000000000000000122321362601351000313030ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module VariableUsagesAreAllowed def initialize(*) super # holds { name => ast_node } pairs @declared_variables = {} end def on_operation_definition(node, parent) @declared_variables = node.variables.each_with_object({}) { |var, memo| memo[var.name] = var } super end def on_argument(node, parent) node_values = if node.value.is_a?(Array) node.value else [node.value] end node_values = node_values.select { |value| value.is_a? GraphQL::Language::Nodes::VariableIdentifier } if node_values.any? arguments = case parent when GraphQL::Language::Nodes::Field context.field_definition.arguments when GraphQL::Language::Nodes::Directive context.directive_definition.arguments when GraphQL::Language::Nodes::InputObject arg_type = context.argument_definition.type.unwrap if arg_type.is_a?(GraphQL::InputObjectType) arguments = arg_type.input_fields else # This is some kind of error nil end else raise("Unexpected argument parent: #{parent}") end node_values.each do |node_value| var_defn_ast = @declared_variables[node_value.name] # Might be undefined :( # VariablesAreUsedAndDefined can't finalize its search until the end of the document. var_defn_ast && arguments && validate_usage(arguments, node, var_defn_ast) end end super end private def validate_usage(arguments, arg_node, ast_var) var_type = context.schema.type_from_ast(ast_var.type) if var_type.nil? return end if !ast_var.default_value.nil? unless var_type.is_a?(GraphQL::NonNullType) # If the value is required, but the argument is not, # and yet there's a non-nil default, then we impliclty # make the argument also a required type. var_type = GraphQL::NonNullType.new(of_type: var_type) end end arg_defn = arguments[arg_node.name] arg_defn_type = arg_defn.type var_inner_type = var_type.unwrap arg_inner_type = arg_defn_type.unwrap var_type = wrap_var_type_with_depth_of_arg(var_type, arg_node) if var_inner_type != arg_inner_type create_error("Type mismatch", var_type, ast_var, arg_defn, arg_node) elsif list_dimension(var_type) != list_dimension(arg_defn_type) create_error("List dimension mismatch", var_type, ast_var, arg_defn, arg_node) elsif !non_null_levels_match(arg_defn_type, var_type) create_error("Nullability mismatch", var_type, ast_var, arg_defn, arg_node) end end def create_error(error_message, var_type, ast_var, arg_defn, arg_node) add_error(GraphQL::StaticValidation::VariableUsagesAreAllowedError.new( "#{error_message} on variable $#{ast_var.name} and argument #{arg_node.name} (#{var_type.to_s} / #{arg_defn.type.to_s})", nodes: arg_node, name: ast_var.name, type: var_type.to_s, argument: arg_node.name, error: error_message )) end def wrap_var_type_with_depth_of_arg(var_type, arg_node) arg_node_value = arg_node.value return var_type unless arg_node_value.is_a?(Array) new_var_type = var_type depth_of_array(arg_node_value).times do # Since the array _is_ present, treat it like a non-null type # (It satisfies a non-null requirement AND a nullable requirement) new_var_type = new_var_type.to_list_type.to_non_null_type end new_var_type end # @return [Integer] Returns the max depth of `array`, or `0` if it isn't an array at all def depth_of_array(array) case array when Array max_child_depth = 0 array.each do |item| item_depth = depth_of_array(item) if item_depth > max_child_depth max_child_depth = item_depth end end 1 + max_child_depth else 0 end end def list_dimension(type) if type.kind.list? 1 + list_dimension(type.of_type) elsif type.kind.non_null? list_dimension(type.of_type) else 0 end end def non_null_levels_match(arg_type, var_type) if arg_type.kind.non_null? && !var_type.kind.non_null? false elsif arg_type.kind.wraps? && var_type.kind.wraps? # If var_type is a non-null wrapper for a type, and arg_type is nullable, peel off the wrapper # That way, a var_type of `[DairyAnimal]!` works with an arg_type of `[DairyAnimal]` if var_type.kind.non_null? && !arg_type.kind.non_null? var_type = var_type.of_type end non_null_levels_match(arg_type.of_type, var_type.of_type) else true end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/variable_usages_are_allowed_error.rb000066400000000000000000000017071362601351000325210ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class VariableUsagesAreAllowedError < StaticValidation::Error attr_reader :type_name attr_reader :variable_name attr_reader :argument_name attr_reader :error_message def initialize(message, path: nil, nodes: [], type:, name:, argument:, error:) super(message, path: path, nodes: nodes) @type_name = type @variable_name = name @argument_name = argument @error_message = error end # A hash representation of this Message def to_h extensions = { "code" => code, "variableName" => variable_name, "typeName" => type_name, "argumentName" => argument_name, "errorMessage" => error_message } super.merge({ "extensions" => extensions }) end def code "variableMismatch" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/variables_are_input_types.rb000066400000000000000000000020251362601351000310520ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module VariablesAreInputTypes def on_variable_definition(node, parent) type_name = get_type_name(node.type) type = context.warden.get_type(type_name) if type.nil? add_error(GraphQL::StaticValidation::VariablesAreInputTypesError.new( "#{type_name} isn't a defined input type (on $#{node.name})", nodes: node, name: node.name, type: type_name )) elsif !type.kind.input? add_error(GraphQL::StaticValidation::VariablesAreInputTypesError.new( "#{type.name} isn't a valid input type (on $#{node.name})", nodes: node, name: node.name, type: type_name )) end super end private def get_type_name(ast_type) if ast_type.respond_to?(:of_type) get_type_name(ast_type.of_type) else ast_type.name end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/variables_are_input_types_error.rb000066400000000000000000000013421362601351000322640ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class VariablesAreInputTypesError < StaticValidation::Error attr_reader :type_name attr_reader :variable_name def initialize(message, path: nil, nodes: [], type:, name:) super(message, path: path, nodes: nodes) @type_name = type @variable_name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name, "variableName" => variable_name } super.merge({ "extensions" => extensions }) end def code "variableRequiresValidType" end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb000066400000000000000000000133601362601351000317330ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # The problem is # - Variable usage must be determined at the OperationDefinition level # - You can't tell how fragments use variables until you visit FragmentDefinitions (which may be at the end of the document) # # So, this validator includes some crazy logic to follow fragment spreads recursively, while avoiding infinite loops. # # `graphql-js` solves this problem by: # - re-visiting the AST for each validator # - allowing validators to say `followSpreads: true` # module VariablesAreUsedAndDefined class VariableUsage attr_accessor :ast_node, :used_by, :declared_by, :path def used? !!@used_by end def declared? !!@declared_by end end def initialize(*) super @variable_usages_for_context = Hash.new {|hash, key| hash[key] = Hash.new {|h, k| h[k] = VariableUsage.new } } @spreads_for_context = Hash.new {|hash, key| hash[key] = [] } @variable_context_stack = [] end def on_operation_definition(node, parent) # initialize the hash of vars for this context: @variable_usages_for_context[node] @variable_context_stack.push(node) # mark variables as defined: var_hash = @variable_usages_for_context[node] node.variables.each { |var| var_usage = var_hash[var.name] var_usage.declared_by = node var_usage.path = context.path } super @variable_context_stack.pop end def on_fragment_definition(node, parent) # initialize the hash of vars for this context: @variable_usages_for_context[node] @variable_context_stack.push(node) super @variable_context_stack.pop end # For FragmentSpreads: # - find the context on the stack # - mark the context as containing this spread def on_fragment_spread(node, parent) variable_context = @variable_context_stack.last @spreads_for_context[variable_context] << node.name super end # For VariableIdentifiers: # - mark the variable as used # - assign its AST node def on_variable_identifier(node, parent) usage_context = @variable_context_stack.last declared_variables = @variable_usages_for_context[usage_context] usage = declared_variables[node.name] usage.used_by = usage_context usage.ast_node = node usage.path = context.path super end def on_document(node, parent) super fragment_definitions = @variable_usages_for_context.select { |key, value| key.is_a?(GraphQL::Language::Nodes::FragmentDefinition) } operation_definitions = @variable_usages_for_context.select { |key, value| key.is_a?(GraphQL::Language::Nodes::OperationDefinition) } operation_definitions.each do |node, node_variables| follow_spreads(node, node_variables, @spreads_for_context, fragment_definitions, []) create_errors(node_variables) end end private # Follow spreads in `node`, looking them up from `spreads_for_context` and finding their match in `fragment_definitions`. # Use those fragments to update {VariableUsage}s in `parent_variables`. # Avoid infinite loops by skipping anything in `visited_fragments`. def follow_spreads(node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments) spreads = spreads_for_context[node] - visited_fragments spreads.each do |spread_name| def_node = nil variables = nil # Implement `.find` by hand to avoid Ruby's internal allocations fragment_definitions.each do |frag_def_node, vars| if frag_def_node.name == spread_name def_node = frag_def_node variables = vars break end end next if !def_node visited_fragments << spread_name variables.each do |name, child_usage| parent_usage = parent_variables[name] if child_usage.used? parent_usage.ast_node = child_usage.ast_node parent_usage.used_by = child_usage.used_by parent_usage.path = child_usage.path end end follow_spreads(def_node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments) end end # Determine all the error messages, # Then push messages into the validation context def create_errors(node_variables) # Declared but not used: node_variables .select { |name, usage| usage.declared? && !usage.used? } .each { |var_name, usage| add_error(GraphQL::StaticValidation::VariablesAreUsedAndDefinedError.new( "Variable $#{var_name} is declared by #{usage.declared_by.name} but not used", nodes: usage.declared_by, path: usage.path, name: var_name, error_type: VariablesAreUsedAndDefinedError::VIOLATIONS[:VARIABLE_NOT_USED] )) } # Used but not declared: node_variables .select { |name, usage| usage.used? && !usage.declared? } .each { |var_name, usage| add_error(GraphQL::StaticValidation::VariablesAreUsedAndDefinedError.new( "Variable $#{var_name} is used by #{usage.used_by.name} but not declared", nodes: usage.ast_node, path: usage.path, name: var_name, error_type: VariablesAreUsedAndDefinedError::VIOLATIONS[:VARIABLE_NOT_DEFINED] )) } end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/rules/variables_are_used_and_defined_error.rb000066400000000000000000000016521362601351000331450ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class VariablesAreUsedAndDefinedError < StaticValidation::Error attr_reader :variable_name attr_reader :violation VIOLATIONS = { :VARIABLE_NOT_USED => "variableNotUsed", :VARIABLE_NOT_DEFINED => "variableNotDefined", } def initialize(message, path: nil, nodes: [], name:, error_type:) super(message, path: path, nodes: nodes) @variable_name = name raise("Unexpected error type: #{error_type}") if !VIOLATIONS.values.include?(error_type) @violation = error_type end # A hash representation of this Message def to_h extensions = { "code" => code, "variableName" => variable_name } super.merge({ "extensions" => extensions }) end def code @violation end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/type_stack.rb000066400000000000000000000154411362601351000246320ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # - Ride along with `GraphQL::Language::Visitor` # - Track type info, expose it to validators class TypeStack # These are jumping-off points for infering types down the tree TYPE_INFERRENCE_ROOTS = [ GraphQL::Language::Nodes::OperationDefinition, GraphQL::Language::Nodes::FragmentDefinition, ] # @return [GraphQL::Schema] the schema whose types are present in this document attr_reader :schema # When it enters an object (starting with query or mutation root), it's pushed on this stack. # When it exits, it's popped off. # @return [Array] attr_reader :object_types # When it enters a field, it's pushed on this stack (useful for nested fields, args). # When it exits, it's popped off. # @return [Array] fields which have been entered attr_reader :field_definitions # Directives are pushed on, then popped off while traversing the tree # @return [Array] directives which have been entered attr_reader :directive_definitions # @return [Array] arguments which have been entered attr_reader :argument_definitions # @return [Array] fields which have been entered (by their AST name) attr_reader :path # @param schema [GraphQL::Schema] the schema whose types to use when climbing this document # @param visitor [GraphQL::Language::Visitor] a visitor to follow & watch the types def initialize(schema, visitor) @schema = schema @object_types = [] @field_definitions = [] @directive_definitions = [] @argument_definitions = [] @path = [] PUSH_STRATEGIES.each do |node_class, strategy| visitor[node_class].enter << EnterWithStrategy.new(self, strategy) visitor[node_class].leave << LeaveWithStrategy.new(self, strategy) end end private module FragmentWithTypeStrategy def push(stack, node) object_type = if node.type stack.schema.types.fetch(node.type.name, nil) else stack.object_types.last end if !object_type.nil? object_type = object_type.unwrap end stack.object_types.push(object_type) push_path_member(stack, node) end def pop(stack, node) stack.object_types.pop stack.path.pop end end module FragmentDefinitionStrategy extend FragmentWithTypeStrategy module_function def push_path_member(stack, node) stack.path.push("fragment #{node.name}") end end module InlineFragmentStrategy extend FragmentWithTypeStrategy module_function def push_path_member(stack, node) stack.path.push("...#{node.type ? " on #{node.type.to_query_string}" : ""}") end end module OperationDefinitionStrategy module_function def push(stack, node) # eg, QueryType, MutationType object_type = stack.schema.root_type_for_operation(node.operation_type) stack.object_types.push(object_type) stack.path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}") end def pop(stack, node) stack.object_types.pop stack.path.pop end end module FieldStrategy module_function def push(stack, node) parent_type = stack.object_types.last parent_type = parent_type.unwrap field_definition = stack.schema.get_field(parent_type, node.name) stack.field_definitions.push(field_definition) if !field_definition.nil? next_object_type = field_definition.type stack.object_types.push(next_object_type) else stack.object_types.push(nil) end stack.path.push(node.alias || node.name) end def pop(stack, node) stack.field_definitions.pop stack.object_types.pop stack.path.pop end end module DirectiveStrategy module_function def push(stack, node) directive_defn = stack.schema.directives[node.name] stack.directive_definitions.push(directive_defn) end def pop(stack, node) stack.directive_definitions.pop end end module ArgumentStrategy module_function # Push `argument_defn` onto the stack. # It's possible that `argument_defn` will be nil. # Push it anyways so `pop` has something to pop. def push(stack, node) if stack.argument_definitions.last arg_type = stack.argument_definitions.last.type.unwrap if arg_type.kind.input_object? argument_defn = arg_type.input_fields[node.name] else argument_defn = nil end elsif stack.directive_definitions.last argument_defn = stack.directive_definitions.last.arguments[node.name] elsif stack.field_definitions.last argument_defn = stack.field_definitions.last.arguments[node.name] else argument_defn = nil end stack.argument_definitions.push(argument_defn) stack.path.push(node.name) end def pop(stack, node) stack.argument_definitions.pop stack.path.pop end end module FragmentSpreadStrategy module_function def push(stack, node) stack.path.push("... #{node.name}") end def pop(stack, node) stack.path.pop end end PUSH_STRATEGIES = { GraphQL::Language::Nodes::FragmentDefinition => FragmentDefinitionStrategy, GraphQL::Language::Nodes::InlineFragment => InlineFragmentStrategy, GraphQL::Language::Nodes::FragmentSpread => FragmentSpreadStrategy, GraphQL::Language::Nodes::Argument => ArgumentStrategy, GraphQL::Language::Nodes::Field => FieldStrategy, GraphQL::Language::Nodes::Directive => DirectiveStrategy, GraphQL::Language::Nodes::OperationDefinition => OperationDefinitionStrategy, } class EnterWithStrategy def initialize(stack, strategy) @stack = stack @strategy = strategy end def call(node, parent) @strategy.push(@stack, node) end end class LeaveWithStrategy def initialize(stack, strategy) @stack = stack @strategy = strategy end def call(node, parent) @strategy.pop(@stack, node) end end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/validation_context.rb000066400000000000000000000027271362601351000263650ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # The validation context gets passed to each validator. # # It exposes a {GraphQL::Language::Visitor} where validators may add hooks. ({Language::Visitor#visit} is called in {Validator#validate}) # # It provides access to the schema & fragments which validators may read from. # # It holds a list of errors which each validator may add to. # # It also provides limited access to the {TypeStack} instance, # which tracks state as you climb in and out of different fields. class ValidationContext extend Forwardable attr_reader :query, :errors, :visitor, :on_dependency_resolve_handlers def_delegators :@query, :schema, :document, :fragments, :operations, :warden def initialize(query, visitor_class) @query = query @literal_validator = LiteralValidator.new(context: query.context) @errors = [] @on_dependency_resolve_handlers = [] @visitor = visitor_class.new(document, self) end def_delegators :@visitor, :path, :type_definition, :field_definition, :argument_definition, :parent_type_definition, :directive_definition, :object_types, :dependencies def on_dependency_resolve(&handler) @on_dependency_resolve_handlers << handler end def valid_literal?(ast_value, type) @literal_validator.validate(ast_value, type) end end end end ruby-graphql-1.9.19/lib/graphql/static_validation/validator.rb000066400000000000000000000041161362601351000244460ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # Initialized with a {GraphQL::Schema}, then it can validate {GraphQL::Language::Nodes::Documents}s based on that schema. # # By default, it's used by {GraphQL::Query} # # @example Validate a query # validator = GraphQL::StaticValidation::Validator.new(schema: MySchema) # query = GraphQL::Query.new(MySchema, query_string) # errors = validator.validate(query)[:errors] # class Validator # @param schema [GraphQL::Schema] # @param rules [Array<#validate(context)>] a list of rules to use when validating def initialize(schema:, rules: GraphQL::StaticValidation::ALL_RULES) @schema = schema @rules = rules end # Validate `query` against the schema. Returns an array of message hashes. # @param query [GraphQL::Query] # @return [Array] def validate(query, validate: true) query.trace("validate", { validate: validate, query: query }) do can_skip_rewrite = query.context.interpreter? && query.schema.using_ast_analysis? errors = if validate == false && can_skip_rewrite [] else rules_to_use = validate ? @rules : [] visitor_class = BaseVisitor.including_rules(rules_to_use, rewrite: !can_skip_rewrite) context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class) # Attach legacy-style rules rules_to_use.each do |rule_class_or_module| if rule_class_or_module.method_defined?(:validate) rule_class_or_module.new.validate(context) end end context.visitor.visit context.errors end irep = if errors.empty? && context # Only return this if there are no errors and validation was actually run context.visitor.rewrite_document else nil end { errors: errors, irep: irep, } end end end end end ruby-graphql-1.9.19/lib/graphql/string_encoding_error.rb000066400000000000000000000004601362601351000233430ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class StringEncodingError < GraphQL::RuntimeTypeError attr_reader :string def initialize(str) @string = str super("String \"#{str}\" was encoded as #{str.encoding}! GraphQL requires an encoding compatible with UTF-8.") end end end ruby-graphql-1.9.19/lib/graphql/string_type.rb000066400000000000000000000001371362601351000213260ustar00rootroot00000000000000# frozen_string_literal: true GraphQL::STRING_TYPE = GraphQL::Types::String.graphql_definition ruby-graphql-1.9.19/lib/graphql/subscriptions.rb000066400000000000000000000201021362601351000216600ustar00rootroot00000000000000# frozen_string_literal: true require "securerandom" require "graphql/subscriptions/event" require "graphql/subscriptions/instrumentation" require "graphql/subscriptions/serialize" if defined?(ActionCable) require "graphql/subscriptions/action_cable_subscriptions" end require "graphql/subscriptions/subscription_root" module GraphQL class Subscriptions # Raised when either: # - the triggered `event_name` doesn't match a field in the schema; or # - one or more arguments don't match the field arguments class InvalidTriggerError < GraphQL::Error end # @see {Subscriptions#initialize} for options, concrete implementations may add options. def self.use(defn, options = {}) schema = defn.target options[:schema] = schema schema.subscriptions = self.new(**options) instrumentation = Subscriptions::Instrumentation.new(schema: schema) defn.instrument(:field, instrumentation) defn.instrument(:query, instrumentation) nil end # @param schema [Class] the GraphQL schema this manager belongs to def initialize(schema:, **rest) @schema = schema end # Fetch subscriptions matching this field + arguments pair # And pass them off to the queue. # @param event_name [String] # @param args [Hash Object] # @param object [Object] # @param scope [Symbol, String] # @return [void] def trigger(event_name, args, object, scope: nil) event_name = event_name.to_s # Try with the verbatim input first: field = @schema.get_field(@schema.subscription, event_name) if field.nil? # And if it wasn't found, normalize it: normalized_event_name = normalize_name(event_name) field = @schema.get_field(@schema.subscription, normalized_event_name) if field.nil? raise InvalidTriggerError, "No subscription matching trigger: #{event_name} (looked for #{@schema.subscription.graphql_name}.#{normalized_event_name})" end else # Since we found a field, the original input was already normalized normalized_event_name = event_name end # Normalize symbol-keyed args to strings, try camelizing them normalized_args = normalize_arguments(normalized_event_name, field, args) event = Subscriptions::Event.new( name: normalized_event_name, arguments: normalized_args, field: field, scope: scope, ) execute_all(event, object) end # `event` was triggered on `object`, and `subscription_id` was subscribed, # so it should be updated. # # Load `subscription_id`'s GraphQL data, re-evaluate the query, and deliver the result. # # This is where a queue may be inserted to push updates in the background. # # @param subscription_id [String] # @param event [GraphQL::Subscriptions::Event] The event which was triggered # @param object [Object] The value for the subscription field # @return [void] def execute(subscription_id, event, object) # Lookup the saved data for this subscription query_data = read_subscription(subscription_id) # Fetch the required keys from the saved data query_string = query_data.fetch(:query_string) variables = query_data.fetch(:variables) context = query_data.fetch(:context) operation_name = query_data.fetch(:operation_name) # Re-evaluate the saved query result = @schema.execute( **{ query: query_string, context: context, subscription_topic: event.topic, operation_name: operation_name, variables: variables, root_value: object, } ) deliver(subscription_id, result) rescue GraphQL::Schema::Subscription::NoUpdateError # This update was skipped in user code; do nothing. rescue GraphQL::Schema::Subscription::UnsubscribedError # `unsubscribe` was called, clean up on our side # TODO also send `{more: false}` to client? delete_subscription(subscription_id) end # Event `event` occurred on `object`, # Update all subscribers. # @param event [Subscriptions::Event] # @param object [Object] # @return [void] def execute_all(event, object) each_subscription_id(event) do |subscription_id| execute(subscription_id, event, object) end end # Get each `subscription_id` subscribed to `event.topic` and yield them # @param event [GraphQL::Subscriptions::Event] # @yieldparam subscription_id [String] # @return [void] def each_subscription_id(event) raise GraphQL::RequiredImplementationMissingError end # The system wants to send an update to this subscription. # Read its data and return it. # @param subscription_id [String] # @return [Hash] Containing required keys def read_subscription(subscription_id) raise GraphQL::RequiredImplementationMissingError end # A subscription query was re-evaluated, returning `result`. # The result should be send to `subscription_id`. # @param subscription_id [String] # @param result [Hash] # @return [void] def deliver(subscription_id, result) raise GraphQL::RequiredImplementationMissingError end # `query` was executed and found subscriptions to `events`. # Update the database to reflect this new state. # @param query [GraphQL::Query] # @param events [Array] # @return [void] def write_subscription(query, events) raise GraphQL::RequiredImplementationMissingError end # A subscription was terminated server-side. # Clean up the database. # @param subscription_id [String] # @return void. def delete_subscription(subscription_id) raise GraphQL::RequiredImplementationMissingError end # @return [String] A new unique identifier for a subscription def build_id SecureRandom.uuid end # Convert a user-provided event name or argument # to the equivalent in GraphQL. # # By default, it converts the identifier to camelcase. # Override this in a subclass to change the transformation. # # @param event_or_arg_name [String, Symbol] # @return [String] def normalize_name(event_or_arg_name) Schema::Member::BuildType.camelize(event_or_arg_name.to_s) end private # Recursively normalize `args` as belonging to `arg_owner`: # - convert symbols to strings, # - if needed, camelize the string (using {#normalize_name}) # @param arg_owner [GraphQL::Field, GraphQL::BaseType] # @param args [Hash, Array, Any] some GraphQL input value to coerce as `arg_owner` # @return [Any] normalized arguments value def normalize_arguments(event_name, arg_owner, args) case arg_owner when GraphQL::Field, GraphQL::InputObjectType normalized_args = {} missing_arg_names = [] args.each do |k, v| arg_name = k.to_s arg_defn = arg_owner.arguments[arg_name] if arg_defn normalized_arg_name = arg_name else normalized_arg_name = normalize_name(arg_name) arg_defn = arg_owner.arguments[normalized_arg_name] end if arg_defn normalized_args[normalized_arg_name] = normalize_arguments(event_name, arg_defn.type, v) else # Couldn't find a matching argument definition missing_arg_names << arg_name end end if missing_arg_names.any? arg_owner_name = if arg_owner.is_a?(GraphQL::Field) "Subscription.#{arg_owner.name}" else arg_owner.to_s end raise InvalidTriggerError, "Can't trigger Subscription.#{event_name}, received undefined arguments: #{missing_arg_names.join(", ")}. (Should match arguments of #{arg_owner_name}.)" end normalized_args when GraphQL::ListType args.map { |a| normalize_arguments(event_name, arg_owner.of_type, a) } when GraphQL::NonNullType normalize_arguments(event_name, arg_owner.of_type, args) else args end end end end ruby-graphql-1.9.19/lib/graphql/subscriptions/000077500000000000000000000000001362601351000213405ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/subscriptions/action_cable_subscriptions.rb000066400000000000000000000120141362601351000272550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Subscriptions # A subscriptions implementation that sends data # as ActionCable broadcastings. # # Experimental, some things to keep in mind: # # - No queueing system; ActiveJob should be added # - Take care to reload context when re-delivering the subscription. (see {Query#subscription_update?}) # # @example Adding ActionCableSubscriptions to your schema # MySchema = GraphQL::Schema.define do # # ... # use GraphQL::Subscriptions::ActionCableSubscriptions # end # # @example Implementing a channel for GraphQL Subscriptions # class GraphqlChannel < ApplicationCable::Channel # def subscribed # @subscription_ids = [] # end # # def execute(data) # query = data["query"] # variables = ensure_hash(data["variables"]) # operation_name = data["operationName"] # context = { # # Re-implement whatever context methods you need # # in this channel or ApplicationCable::Channel # # current_user: current_user, # # Make sure the channel is in the context # channel: self, # } # # result = MySchema.execute({ # query: query, # context: context, # variables: variables, # operation_name: operation_name # }) # # payload = { # result: result.subscription? ? { data: nil } : result.to_h, # more: result.subscription?, # } # # # Track the subscription here so we can remove it # # on unsubscribe. # if result.context[:subscription_id] # @subscription_ids << result.context[:subscription_id] # end # # transmit(payload) # end # # def unsubscribed # @subscription_ids.each { |sid| # MySchema.subscriptions.delete_subscription(sid) # } # end # # private # # def ensure_hash(ambiguous_param) # case ambiguous_param # when String # if ambiguous_param.present? # ensure_hash(JSON.parse(ambiguous_param)) # else # {} # end # when Hash, ActionController::Parameters # ambiguous_param # when nil # {} # else # raise ArgumentError, "Unexpected parameter: #{ambiguous_param}" # end # end # end # class ActionCableSubscriptions < GraphQL::Subscriptions SUBSCRIPTION_PREFIX = "graphql-subscription:" EVENT_PREFIX = "graphql-event:" # @param serializer [<#dump(obj), #load(string)] Used for serializing messages before handing them to `.broadcast(msg)` def initialize(serializer: Serialize, **rest) # A per-process map of subscriptions to deliver. # This is provided by Rails, so let's use it @subscriptions = Concurrent::Map.new @serializer = serializer super end # An event was triggered; Push the data over ActionCable. # Subscribers will re-evaluate locally. def execute_all(event, object) stream = EVENT_PREFIX + event.topic message = @serializer.dump(object) ActionCable.server.broadcast(stream, message) end # This subscription was re-evaluated. # Send it to the specific stream where this client was waiting. def deliver(subscription_id, result) payload = { result: result.to_h, more: true } ActionCable.server.broadcast(SUBSCRIPTION_PREFIX + subscription_id, payload) end # A query was run where these events were subscribed to. # Store them in memory in _this_ ActionCable frontend. # It will receive notifications when events come in # and re-evaluate the query locally. def write_subscription(query, events) channel = query.context.fetch(:channel) subscription_id = query.context[:subscription_id] ||= build_id stream = query.context[:action_cable_stream] ||= SUBSCRIPTION_PREFIX + subscription_id channel.stream_from(stream) @subscriptions[subscription_id] = query events.each do |event| channel.stream_from(EVENT_PREFIX + event.topic, coder: ActiveSupport::JSON) do |message| execute(subscription_id, event, @serializer.load(message)) nil end end end # Return the query from "storage" (in memory) def read_subscription(subscription_id) query = @subscriptions[subscription_id] { query_string: query.query_string, variables: query.provided_variables, context: query.context.to_h, operation_name: query.operation_name, } end # The channel was closed, forget about it. def delete_subscription(subscription_id) @subscriptions.delete(subscription_id) end end end end ruby-graphql-1.9.19/lib/graphql/subscriptions/event.rb000066400000000000000000000055441362601351000230160ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../subscriptions.rb module GraphQL class Subscriptions # This thing can be: # - Subscribed to by `subscription { ... }` # - Triggered by `MySchema.subscriber.trigger(name, arguments, obj)` # # An array of `Event`s are passed to `store.register(query, events)`. class Event # @return [String] Corresponds to the Subscription root field name attr_reader :name # @return [GraphQL::Query::Arguments] attr_reader :arguments # @return [GraphQL::Query::Context] attr_reader :context # @return [String] An opaque string which identifies this event, derived from `name` and `arguments` attr_reader :topic def initialize(name:, arguments:, field: nil, context: nil, scope: nil) @name = name @arguments = arguments @context = context field ||= context.field scope_val = scope || (context && field.subscription_scope && context[field.subscription_scope]) @topic = self.class.serialize(name, arguments, field, scope: scope_val) end # @return [String] an identifier for this unit of subscription def self.serialize(name, arguments, field, scope:) normalized_args = case arguments when GraphQL::Query::Arguments arguments when Hash if field.is_a?(GraphQL::Schema::Field) stringify_args(field, arguments) else GraphQL::Query::LiteralInput.from_arguments( arguments, field, nil, ) end else raise ArgumentError, "Unexpected arguments: #{arguments}, must be Hash or GraphQL::Arguments" end sorted_h = normalized_args.to_h.sort.to_h Serialize.dump_recursive([scope, name, sorted_h]) end class << self private def stringify_args(arg_owner, args) case args when Hash next_args = {} args.each do |k, v| arg_name = k.to_s camelized_arg_name = GraphQL::Schema::Member::BuildType.camelize(arg_name) arg_defn = get_arg_definition(arg_owner, camelized_arg_name) if arg_defn normalized_arg_name = camelized_arg_name else normalized_arg_name = arg_name arg_defn = get_arg_definition(arg_owner, normalized_arg_name) end next_args[normalized_arg_name] = stringify_args(arg_defn[1].type, v) end next_args when Array args.map { |a| stringify_args(arg_owner, a) } else args end end def get_arg_definition(arg_owner, arg_name) arg_owner.arguments.find { |k, v| k == arg_name || v.keyword.to_s == arg_name } end end end end end ruby-graphql-1.9.19/lib/graphql/subscriptions/instrumentation.rb000066400000000000000000000046341362601351000251370ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../subscriptions.rb module GraphQL class Subscriptions # Wrap the root fields of the subscription type with special logic for: # - Registering the subscription during the first execution # - Evaluating the triggered portion(s) of the subscription during later execution class Instrumentation def initialize(schema:) @schema = schema end def instrument(type, field) if type == @schema.subscription # This is a root field of `subscription` subscribing_resolve_proc = SubscriptionRegistrationResolve.new(field.resolve_proc) field.redefine(resolve: subscribing_resolve_proc) else field end end # If needed, prepare to gather events which this query subscribes to def before_query(query) if query.subscription? && !query.subscription_update? query.context.namespace(:subscriptions)[:events] = [] end end # After checking the root fields, pass the gathered events to the store def after_query(query) events = query.context.namespace(:subscriptions)[:events] if events && events.any? @schema.subscriptions.write_subscription(query, events) end end private class SubscriptionRegistrationResolve def initialize(inner_proc) @inner_proc = inner_proc end # Wrap the proc with subscription registration logic def call(obj, args, ctx) @inner_proc.call(obj, args, ctx) if @inner_proc && !@inner_proc.is_a?(GraphQL::Field::Resolve::BuiltInResolve) events = ctx.namespace(:subscriptions)[:events] if events # This is the first execution, so gather an Event # for the backend to register: events << Subscriptions::Event.new( name: ctx.field.name, arguments: args, context: ctx, ) ctx.skip elsif ctx.irep_node.subscription_topic == ctx.query.subscription_topic # The root object is _already_ the subscription update: if obj.is_a?(GraphQL::Schema::Object) obj.object else obj end else # This is a subscription update, but this event wasn't triggered. ctx.skip end end end end end end ruby-graphql-1.9.19/lib/graphql/subscriptions/serialize.rb000066400000000000000000000065231362601351000236620ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../subscriptions.rb require "set" module GraphQL class Subscriptions # Serialization helpers for passing subscription data around. # @api private module Serialize GLOBALID_KEY = "__gid__" SYMBOL_KEY = "__sym__" SYMBOL_KEYS_KEY = "__sym_keys__" module_function # @param str [String] A serialized object from {.dump} # @return [Object] An object equivalent to the one passed to {.dump} def load(str) parsed_obj = JSON.parse(str) load_value(parsed_obj) end # @param obj [Object] Some subscription-related data to dump # @return [String] The stringified object def dump(obj) JSON.generate(dump_value(obj), quirks_mode: true) end # This is for turning objects into subscription scopes. # It's a one-way transformation, can't reload this :'( # @param obj [Object] # @return [String] def dump_recursive(obj) case when obj.is_a?(Array) obj.map { |i| dump_recursive(i) }.join(':') when obj.is_a?(Hash) obj.map { |k, v| "#{dump_recursive(k)}:#{dump_recursive(v)}" }.join(":") when obj.is_a?(GraphQL::Schema::InputObject) dump_recursive(obj.to_h) when obj.respond_to?(:to_gid_param) obj.to_gid_param when obj.respond_to?(:to_param) obj.to_param else obj.to_s end end class << self private # @param value [Object] A parsed JSON object # @return [Object] An object that load Global::Identification recursive def load_value(value) if value.is_a?(Array) value.map{|item| load_value(item)} elsif value.is_a?(Hash) if value.size == 1 && value.key?(GLOBALID_KEY) GlobalID::Locator.locate(value[GLOBALID_KEY]) elsif value.size == 1 && value.key?(SYMBOL_KEY) value[SYMBOL_KEY].to_sym else loaded_h = {} sym_keys = value.fetch(SYMBOL_KEYS_KEY, []) value.each do |k, v| if k == SYMBOL_KEYS_KEY next end if sym_keys.include?(k) k = k.to_sym end loaded_h[k] = load_value(v) end loaded_h end else value end end # @param obj [Object] Some subscription-related data to dump # @return [Object] The object that converted Global::Identification def dump_value(obj) if obj.is_a?(Array) obj.map{|item| dump_value(item)} elsif obj.is_a?(Hash) symbol_keys = nil dumped_h = {} obj.each do |k, v| dumped_h[k.to_s] = dump_value(v) if k.is_a?(Symbol) symbol_keys ||= Set.new symbol_keys << k.to_s end end if symbol_keys dumped_h[SYMBOL_KEYS_KEY] = symbol_keys.to_a end dumped_h elsif obj.is_a?(Symbol) { SYMBOL_KEY => obj.to_s } elsif obj.respond_to?(:to_gid_param) {GLOBALID_KEY => obj.to_gid_param} else obj end end end end end end ruby-graphql-1.9.19/lib/graphql/subscriptions/subscription_root.rb000066400000000000000000000043621362601351000254610ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Subscriptions # Extend this module in your subscription root when using {GraphQL::Execution::Interpreter}. module SubscriptionRoot def self.extended(child_cls) child_cls.include(InstanceMethods) end # This is for maintaining backwards compatibility: # if a subscription field is created without a `subscription:` resolver class, # then implement the method with the previous default behavior. module InstanceMethods def skip_subscription_root(*) if context.query.subscription_update? object else context.skip end end end def field(*args, extensions: [], **rest, &block) extensions += [Extension] # Backwards-compat for schemas if !rest[:subscription] name = args.first alias_method(name, :skip_subscription_root) end super(*args, extensions: extensions, **rest, &block) end class Extension < GraphQL::Schema::FieldExtension def after_resolve(value:, context:, object:, arguments:, **rest) if value.is_a?(GraphQL::ExecutionError) value elsif (events = context.namespace(:subscriptions)[:events]) # This is the first execution, so gather an Event # for the backend to register: events << Subscriptions::Event.new( name: field.name, arguments: arguments, context: context, field: field, ) # TODO compat with non-class-based subscriptions? value elsif context.query.subscription_topic == Subscriptions::Event.serialize( field.name, arguments, field, scope: (field.subscription_scope ? context[field.subscription_scope] : nil), ) # This is a subscription update. The resolver returned `skip` if it should be skipped, # or else it returned an object to resolve the update. value else # This is a subscription update, but this event wasn't triggered. context.skip end end end end end end ruby-graphql-1.9.19/lib/graphql/tracing.rb000066400000000000000000000101251362601351000204040ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/tracing/active_support_notifications_tracing" require "graphql/tracing/platform_tracing" require "graphql/tracing/appsignal_tracing" require "graphql/tracing/data_dog_tracing" require "graphql/tracing/new_relic_tracing" require "graphql/tracing/scout_tracing" require "graphql/tracing/skylight_tracing" require "graphql/tracing/prometheus_tracing" if defined?(PrometheusExporter::Server) require "graphql/tracing/prometheus_tracing/graphql_collector" end module GraphQL # Library entry point for performance metric reporting. # # __Warning:__ Installing/uninstalling tracers is not thread-safe. Do it during application boot only. # # @example Sending custom events # GraphQL::Tracing.trace("my_custom_event", { ... }) do # # do stuff ... # end # # @example Adding a tracer to a schema # MySchema = GraphQL::Schema.define do # tracer MyTracer # <= responds to .trace(key, data, &block) # end # # @example Adding a tracer to a single query # MySchema.execute(query_str, context: { backtrace: true }) # # Events: # # Key | Metadata # ----|--------- # lex | `{ query_string: String }` # parse | `{ query_string: String }` # validate | `{ query: GraphQL::Query, validate: Boolean }` # analyze_multiplex | `{ multiplex: GraphQL::Execution::Multiplex }` # analyze_query | `{ query: GraphQL::Query }` # execute_multiplex | `{ multiplex: GraphQL::Execution::Multiplex }` # execute_query | `{ query: GraphQL::Query }` # execute_query_lazy | `{ query: GraphQL::Query?, multiplex: GraphQL::Execution::Multiplex? }` # execute_field | `{ context: GraphQL::Query::Context::FieldResolutionContext?, owner: Class?, field: GraphQL::Schema::Field?, query: GraphQL::Query?, path: Array?}` # execute_field_lazy | `{ context: GraphQL::Query::Context::FieldResolutionContext?, owner: Class?, field: GraphQL::Schema::Field?, query: GraphqL::Query?, path: Array?}` # # Note that `execute_field` and `execute_field_lazy` receive different data in different settings: # # - When using {GraphQL::Execution::Interpreter}, they receive `{field:, path:, query:}` # - Otherwise, they receive `{context: ...}` # module Tracing # Objects may include traceable to gain a `.trace(...)` method. # The object must have a `@tracers` ivar of type `Array<<#trace(k, d, &b)>>`. # @api private module Traceable # @param key [String] The name of the event in GraphQL internals # @param metadata [Hash] Event-related metadata (can be anything) # @return [Object] Must return the value of the block def trace(key, metadata) call_tracers(0, key, metadata) { yield } end private # If there's a tracer at `idx`, call it and then increment `idx`. # Otherwise, yield. # # @param idx [Integer] Which tracer to call # @param key [String] The current event name # @param metadata [Object] The current event object # @return Whatever the block returns def call_tracers(idx, key, metadata) if idx == @tracers.length yield else @tracers[idx].trace(key, metadata) { call_tracers(idx + 1, key, metadata) { yield } } end end end class << self # Install a tracer to receive events. # @param tracer [<#trace(key, metadata)>] # @return [void] # @deprecated See {Schema#tracer} or use `context: { tracers: [...] }` def install(tracer) warn("GraphQL::Tracing.install is deprecated, add it to the schema with `tracer(my_tracer)` instead.") if !tracers.include?(tracer) @tracers << tracer end end # @deprecated See {Schema#tracer} or use `context: { tracers: [...] }` def uninstall(tracer) @tracers.delete(tracer) end # @deprecated See {Schema#tracer} or use `context: { tracers: [...] }` def tracers @tracers ||= [] end end # Initialize the array tracers module NullTracer module_function def trace(k, v) yield end end end end ruby-graphql-1.9.19/lib/graphql/tracing/000077500000000000000000000000001362601351000200605ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/tracing/active_support_notifications_tracing.rb000066400000000000000000000017371362601351000301240ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing # This implementation forwards events to ActiveSupport::Notifications # with a `graphql.` prefix. # module ActiveSupportNotificationsTracing # A cache of frequently-used keys to avoid needless string allocations KEYS = { "lex" => "graphql.lex", "parse" => "graphql.parse", "validate" => "graphql.validate", "analyze_multiplex" => "graphql.analyze_multiplex", "analyze_query" => "graphql.analyze_query", "execute_query" => "graphql.execute_query", "execute_query_lazy" => "graphql.execute_query_lazy", "execute_field" => "graphql.execute_field", "execute_field_lazy" => "graphql.execute_field_lazy", } def self.trace(key, metadata) prefixed_key = KEYS[key] || "graphql.#{key}" ActiveSupport::Notifications.instrument(prefixed_key, metadata) do yield end end end end end ruby-graphql-1.9.19/lib/graphql/tracing/appsignal_tracing.rb000066400000000000000000000013751362601351000241000ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class AppsignalTracing < PlatformTracing self.platform_keys = { "lex" => "lex.graphql", "parse" => "parse.graphql", "validate" => "validate.graphql", "analyze_query" => "analyze.graphql", "analyze_multiplex" => "analyze.graphql", "execute_multiplex" => "execute.graphql", "execute_query" => "execute.graphql", "execute_query_lazy" => "execute.graphql", } def platform_trace(platform_key, key, data) Appsignal.instrument(platform_key) do yield end end def platform_field_key(type, field) "#{type.graphql_name}.#{field.graphql_name}.graphql" end end end end ruby-graphql-1.9.19/lib/graphql/tracing/data_dog_tracing.rb000066400000000000000000000042101362601351000236530ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class DataDogTracing < PlatformTracing self.platform_keys = { 'lex' => 'lex.graphql', 'parse' => 'parse.graphql', 'validate' => 'validate.graphql', 'analyze_query' => 'analyze.graphql', 'analyze_multiplex' => 'analyze.graphql', 'execute_multiplex' => 'execute.graphql', 'execute_query' => 'execute.graphql', 'execute_query_lazy' => 'execute.graphql', } def platform_trace(platform_key, key, data) tracer.trace(platform_key, service: service_name) do |span| span.span_type = 'custom' if key == 'execute_multiplex' operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ') span.resource = operations unless operations.empty? # For top span of query, set the analytics sample rate tag, if available. if analytics_enabled? Datadog::Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) end end if key == 'execute_query' span.set_tag(:selected_operation_name, data[:query].selected_operation_name) span.set_tag(:selected_operation_type, data[:query].selected_operation.operation_type) span.set_tag(:query_string, data[:query].query_string) end yield end end def service_name options.fetch(:service, 'ruby-graphql') end def tracer options.fetch(:tracer, Datadog.tracer) end def analytics_available? defined?(Datadog::Contrib::Analytics) \ && Datadog::Contrib::Analytics.respond_to?(:enabled?) \ && Datadog::Contrib::Analytics.respond_to?(:set_sample_rate) end def analytics_enabled? analytics_available? && Datadog::Contrib::Analytics.enabled?(options.fetch(:analytics_enabled, false)) end def analytics_sample_rate options.fetch(:analytics_sample_rate, 1.0) end def platform_field_key(type, field) "#{type.graphql_name}.#{field.graphql_name}" end end end end ruby-graphql-1.9.19/lib/graphql/tracing/new_relic_tracing.rb000066400000000000000000000037201362601351000240650ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class NewRelicTracing < PlatformTracing self.platform_keys = { "lex" => "GraphQL/lex", "parse" => "GraphQL/parse", "validate" => "GraphQL/validate", "analyze_query" => "GraphQL/analyze", "analyze_multiplex" => "GraphQL/analyze", "execute_multiplex" => "GraphQL/execute", "execute_query" => "GraphQL/execute", "execute_query_lazy" => "GraphQL/execute", } # @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name. # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing. # It can also be specified per-query with `context[:set_new_relic_transaction_name]`. def initialize(options = {}) @set_transaction_name = options.fetch(:set_transaction_name, false) super end def platform_trace(platform_key, key, data) if key == "execute_query" set_this_txn_name = data[:query].context[:set_new_relic_transaction_name] if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name) query = data[:query] # Set the transaction name based on the operation type and name selected_op = query.selected_operation if selected_op op_type = selected_op.operation_type op_name = selected_op.name || "anonymous" else op_type = "query" op_name = "anonymous" end NewRelic::Agent.set_transaction_name("GraphQL/#{op_type}.#{op_name}") end end NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(platform_key) do yield end end def platform_field_key(type, field) "GraphQL/#{type.graphql_name}/#{field.graphql_name}" end end end end ruby-graphql-1.9.19/lib/graphql/tracing/platform_tracing.rb000066400000000000000000000054471362601351000237520ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing # Each platform provides: # - `.platform_keys` # - `#platform_trace` # - `#platform_field_key(type, field)` # @api private class PlatformTracing class << self attr_accessor :platform_keys end def initialize(options = {}) @options = options @platform_keys = self.class.platform_keys @trace_scalars = options.fetch(:trace_scalars, false) end def trace(key, data) case key when "lex", "parse", "validate", "analyze_query", "analyze_multiplex", "execute_query", "execute_query_lazy", "execute_multiplex" data.fetch(:query).context.namespace(self.class)[:platform_key_cache] = {} if key == "execute_query" platform_key = @platform_keys.fetch(key) platform_trace(platform_key, key, data) do yield end when "execute_field", "execute_field_lazy" if data[:context] field = data[:context].field platform_key = field.metadata[:platform_key] trace_field = true # implemented with instrumenter else field = data[:field] platform_key_cache = data.fetch(:query).context.namespace(self.class).fetch(:platform_key_cache) platform_key = platform_key_cache.fetch(field) do platform_key_cache[field] = platform_field_key(data[:owner], field) end return_type = field.type.unwrap # Handle LateBoundTypes, which don't have `#kind` trace_field = if return_type.respond_to?(:kind) && (return_type.kind.scalar? || return_type.kind.enum?) (field.trace.nil? && @trace_scalars) || field.trace else true end end if platform_key && trace_field platform_trace(platform_key, key, data) do yield end else yield end else # it's a custom key yield end end def instrument(type, field) return_type = field.type.unwrap case return_type when GraphQL::ScalarType, GraphQL::EnumType if field.trace || (field.trace.nil? && @trace_scalars) trace_field(type, field) else field end else trace_field(type, field) end end def trace_field(type, field) new_f = field.redefine new_f.metadata[:platform_key] = platform_field_key(type, field) new_f end def self.use(schema_defn, options = {}) tracer = self.new(options) schema_defn.instrument(:field, tracer) schema_defn.tracer(tracer) end private attr_reader :options end end end ruby-graphql-1.9.19/lib/graphql/tracing/prometheus_tracing.rb000066400000000000000000000034641362601351000243160ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class PrometheusTracing < PlatformTracing DEFAULT_WHITELIST = ['execute_field', 'execute_field_lazy'].freeze DEFAULT_COLLECTOR_TYPE = 'graphql'.freeze self.platform_keys = { 'lex' => "graphql.lex", 'parse' => "graphql.parse", 'validate' => "graphql.validate", 'analyze_query' => "graphql.analyze", 'analyze_multiplex' => "graphql.analyze", 'execute_multiplex' => "graphql.execute", 'execute_query' => "graphql.execute", 'execute_query_lazy' => "graphql.execute", 'execute_field' => "graphql.execute", 'execute_field_lazy' => "graphql.execute" } def initialize(opts = {}) @client = opts[:client] || PrometheusExporter::Client.default @keys_whitelist = opts[:keys_whitelist] || DEFAULT_WHITELIST @collector_type = opts[:collector_type] || DEFAULT_COLLECTOR_TYPE super opts end def platform_trace(platform_key, key, data, &block) return yield unless @keys_whitelist.include?(key) instrument_execution(platform_key, key, data, &block) end def platform_field_key(type, field) "#{type.graphql_name}.#{field.graphql_name}" end private def instrument_execution(platform_key, key, data, &block) start = ::Process.clock_gettime ::Process::CLOCK_MONOTONIC result = block.call duration = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start observe platform_key, key, duration result end def observe(platform_key, key, duration) @client.send_json( type: @collector_type, duration: duration, platform_key: platform_key, key: key ) end end end end ruby-graphql-1.9.19/lib/graphql/tracing/prometheus_tracing/000077500000000000000000000000001362601351000237625ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb000066400000000000000000000013151362601351000300130ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class PrometheusTracing < PlatformTracing class GraphQLCollector < ::PrometheusExporter::Server::TypeCollector def initialize @graphql_gauge = PrometheusExporter::Metric::Summary.new( 'graphql_duration_seconds', 'Time spent in GraphQL operations, in seconds' ) end def type 'graphql' end def collect(object) labels = { key: object['key'], platform_key: object['platform_key'] } @graphql_gauge.observe object['duration'], labels end def metrics [@graphql_gauge] end end end end end ruby-graphql-1.9.19/lib/graphql/tracing/scout_tracing.rb000066400000000000000000000016501362601351000232530ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class ScoutTracing < PlatformTracing INSTRUMENT_OPTS = { scope: true } self.platform_keys = { "lex" => "lex.graphql", "parse" => "parse.graphql", "validate" => "validate.graphql", "analyze_query" => "analyze.graphql", "analyze_multiplex" => "analyze.graphql", "execute_multiplex" => "execute.graphql", "execute_query" => "execute.graphql", "execute_query_lazy" => "execute.graphql", } def initialize(options = {}) self.class.include ScoutApm::Tracer super(options) end def platform_trace(platform_key, key, data) self.class.instrument("GraphQL", platform_key, INSTRUMENT_OPTS) do yield end end def platform_field_key(type, field) "#{type.graphql_name}.#{field.graphql_name}" end end end end ruby-graphql-1.9.19/lib/graphql/tracing/skylight_tracing.rb000066400000000000000000000046371362601351000237640ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class SkylightTracing < PlatformTracing self.platform_keys = { "lex" => "graphql.language", "parse" => "graphql.language", "validate" => "graphql.prepare", "analyze_query" => "graphql.prepare", "analyze_multiplex" => "graphql.prepare", "execute_multiplex" => "graphql.execute", "execute_query" => "graphql.execute", "execute_query_lazy" => "graphql.execute", } # @param set_endpoint_name [Boolean] If true, the GraphQL operation name will be used as the endpoint name. # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing. # It can also be specified per-query with `context[:set_skylight_endpoint_name]`. def initialize(options = {}) warn("GraphQL::Tracing::SkylightTracing is deprecated, please enable Skylight's GraphQL probe instead: https://www.skylight.io/support/getting-more-from-skylight#graphql.") @set_endpoint_name = options.fetch(:set_endpoint_name, false) super end def platform_trace(platform_key, key, data) if key == "execute_query" query = data[:query] title = query.selected_operation_name || "" category = platform_key set_endpoint_name_override = query.context[:set_skylight_endpoint_name] if set_endpoint_name_override == true || (set_endpoint_name_override.nil? && @set_endpoint_name) # Assign the endpoint so that queries will be grouped instrumenter = Skylight.instrumenter if instrumenter current_trace = instrumenter.current_trace if current_trace op_type = query.selected_operation ? query.selected_operation.operation_type : "query" endpoint = "GraphQL/#{op_type}.#{title}" current_trace.endpoint = endpoint end end end elsif key.start_with?("execute_field") title = platform_key category = key else title = key category = platform_key end Skylight.instrument(category: category, title: title) do yield end end def platform_field_key(type, field) "graphql.#{type.graphql_name}.#{field.graphql_name}" end end end end ruby-graphql-1.9.19/lib/graphql/type_kinds.rb000066400000000000000000000056111362601351000211320ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Type kinds are the basic categories which a type may belong to (`Object`, `Scalar`, `Union`...) module TypeKinds # These objects are singletons, eg `GraphQL::TypeKinds::UNION`, `GraphQL::TypeKinds::SCALAR`. class TypeKind attr_reader :name, :description def initialize(name, abstract: false, fields: false, wraps: false, input: false, description: nil) @name = name @abstract = abstract @fields = fields @wraps = wraps @input = input @composite = fields? || abstract? @description = description end # Does this TypeKind have multiple possible implementors? # @deprecated Use `abstract?` instead of `resolves?`. def resolves?; @abstract; end # Is this TypeKind abstract? def abstract?; @abstract; end # Does this TypeKind have queryable fields? def fields?; @fields; end # Does this TypeKind modify another type? def wraps?; @wraps; end # Is this TypeKind a valid query input? def input?; @input; end def to_s; @name; end # Is this TypeKind composed of many values? def composite?; @composite; end def scalar? self == TypeKinds::SCALAR end def object? self == TypeKinds::OBJECT end def interface? self == TypeKinds::INTERFACE end def union? self == TypeKinds::UNION end def enum? self == TypeKinds::ENUM end def input_object? self == TypeKinds::INPUT_OBJECT end def list? self == TypeKinds::LIST end def non_null? self == TypeKinds::NON_NULL end end TYPE_KINDS = [ SCALAR = TypeKind.new("SCALAR", input: true, description: 'Indicates this type is a scalar.'), OBJECT = TypeKind.new("OBJECT", fields: true, description: 'Indicates this type is an object. `fields` and `interfaces` are valid fields.'), INTERFACE = TypeKind.new("INTERFACE", abstract: true, fields: true, description: 'Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.'), UNION = TypeKind.new("UNION", abstract: true, description: 'Indicates this type is a union. `possibleTypes` is a valid field.'), ENUM = TypeKind.new("ENUM", input: true, description: 'Indicates this type is an enum. `enumValues` is a valid field.'), INPUT_OBJECT = TypeKind.new("INPUT_OBJECT", input: true, description: 'Indicates this type is an input object. `inputFields` is a valid field.'), LIST = TypeKind.new("LIST", wraps: true, description: 'Indicates this type is a list. `ofType` is a valid field.'), NON_NULL = TypeKind.new("NON_NULL", wraps: true, description: 'Indicates this type is a non-null. `ofType` is a valid field.'), ] end end ruby-graphql-1.9.19/lib/graphql/types.rb000066400000000000000000000005361362601351000201260ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/types/boolean" require "graphql/types/big_int" require "graphql/types/float" require "graphql/types/id" require "graphql/types/int" require "graphql/types/iso_8601_date" require "graphql/types/iso_8601_date_time" require "graphql/types/json" require "graphql/types/string" require "graphql/types/relay" ruby-graphql-1.9.19/lib/graphql/types/000077500000000000000000000000001362601351000175755ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/types/big_int.rb000066400000000000000000000007271362601351000215430ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types class BigInt < GraphQL::Schema::Scalar description "Represents non-fractional signed whole numeric values. Since the value may exceed the size of a 32-bit integer, it's encoded as a string." def self.coerce_input(value, _ctx) Integer(value) rescue ArgumentError nil end def self.coerce_result(value, _ctx) value.to_i.to_s end end end end ruby-graphql-1.9.19/lib/graphql/types/boolean.rb000066400000000000000000000005771362601351000215520ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types class Boolean < GraphQL::Schema::Scalar description "Represents `true` or `false` values." def self.coerce_input(value, _ctx) (value == true || value == false) ? value : nil end def self.coerce_result(value, _ctx) !!value end default_scalar true end end end ruby-graphql-1.9.19/lib/graphql/types/float.rb000066400000000000000000000007301362601351000212270ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types class Float < GraphQL::Schema::Scalar description "Represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point)." def self.coerce_input(value, _ctx) value.is_a?(Numeric) ? value.to_f : nil end def self.coerce_result(value, _ctx) value.to_f end default_scalar true end end end ruby-graphql-1.9.19/lib/graphql/types/id.rb000066400000000000000000000015051362601351000205170ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types class ID < GraphQL::Schema::Scalar graphql_name "ID" description "Represents a unique identifier that is Base64 obfuscated. It is often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"VXNlci0xMA==\"`) or integer (such as `4`) input value will be accepted as an ID." default_scalar true def self.coerce_result(value, _ctx) value.is_a?(::String) ? value : value.to_s end def self.coerce_input(value, _ctx) case value when ::String value when Integer value.to_s else nil end end end end end ruby-graphql-1.9.19/lib/graphql/types/int.rb000066400000000000000000000013601362601351000207140ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types # @see {Types::BigInt} for handling integers outside 32-bit range. class Int < GraphQL::Schema::Scalar description "Represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1." MIN = -(2**31) MAX = (2**31) - 1 def self.coerce_input(value, _ctx) value.is_a?(Numeric) ? value.to_i : nil end def self.coerce_result(value, ctx) value = value.to_i if value >= MIN && value <= MAX value else err = GraphQL::IntegerEncodingError.new(value) ctx.schema.type_error(err, ctx) end end default_scalar true end end end ruby-graphql-1.9.19/lib/graphql/types/iso_8601_date.rb000066400000000000000000000016141362601351000223710ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types # This scalar takes `Date`s and transmits them as strings, # using ISO 8601 format. # # Use it for fields or arguments as follows: # # field :published_at, GraphQL::Types::ISO8601Date, null: false # # argument :deliver_at, GraphQL::Types::ISO8601Date, null: false # # Alternatively, use this built-in scalar as inspiration for your # own Date type. class ISO8601Date < GraphQL::Schema::Scalar description "An ISO 8601-encoded date" # @param value [Date] # @return [String] def self.coerce_result(value, _ctx) value.iso8601 end # @param str_value [String] # @return [Date] def self.coerce_input(str_value, _ctx) Date.iso8601(str_value) rescue ArgumentError # Invalid input nil end end end end ruby-graphql-1.9.19/lib/graphql/types/iso_8601_date_time.rb000066400000000000000000000027561362601351000234170ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types # This scalar takes `DateTime`s and transmits them as strings, # using ISO 8601 format. # # Use it for fields or arguments as follows: # # field :created_at, GraphQL::Types::ISO8601DateTime, null: false # # argument :deliver_at, GraphQL::Types::ISO8601DateTime, null: false # # Alternatively, use this built-in scalar as inspiration for your # own DateTime type. class ISO8601DateTime < GraphQL::Schema::Scalar description "An ISO 8601-encoded datetime" # It's not compatible with Rails' default, # i.e. ActiveSupport::JSON::Encoder.time_precision (3 by default) DEFAULT_TIME_PRECISION = 0 # @return [Integer] def self.time_precision @time_precision || DEFAULT_TIME_PRECISION end # @param [Integer] value def self.time_precision=(value) @time_precision = value end # @param value [DateTime] # @return [String] def self.coerce_result(value, _ctx) value.iso8601(time_precision) rescue ArgumentError raise GraphQL::Error, "An incompatible object (#{value.class}) was given to #{self}. Make sure that only DateTimes are used with this type." end # @param str_value [String] # @return [DateTime] def self.coerce_input(str_value, _ctx) DateTime.iso8601(str_value) rescue ArgumentError # Invalid input nil end end end end ruby-graphql-1.9.19/lib/graphql/types/json.rb000066400000000000000000000013141362601351000210720ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types # An untyped JSON scalar that maps to Ruby hashes, arrays, strings, integers, floats, booleans and nils. # This should be used judiciously because it subverts the GraphQL type system. # # Use it for fields or arguments as follows: # # field :template_parameters, GraphQL::Types::JSON, null: false # # argument :template_parameters, GraphQL::Types::JSON, null: false # class JSON < GraphQL::Schema::Scalar description "Represents untyped JSON" def self.coerce_input(value, _context) value end def self.coerce_result(value, _context) value end end end end ruby-graphql-1.9.19/lib/graphql/types/relay.rb000066400000000000000000000025121362601351000212360ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/types/relay/base_field" require "graphql/types/relay/base_object" require "graphql/types/relay/base_interface" require "graphql/types/relay/page_info" require "graphql/types/relay/base_connection" require "graphql/types/relay/base_edge" require "graphql/types/relay/node" require "graphql/types/relay/node_field" require "graphql/types/relay/nodes_field" module GraphQL module Types # This module contains some types and fields that could support Relay conventions in GraphQL. # # You can use these classes out of the box if you want, but if you want to use your _own_ # GraphQL extensions along with the features in this code, you could also # open up the source files and copy the relevant methods and configuration into # your own classes. # # For example, the provided object types extend {Types::Relay::BaseObject}, # but you might want to: # # 1. Migrate the extensions from {Types::Relay::BaseObject} into _your app's_ base object # 2. Copy {Relay::BaseConnection}, {Relay::BaseEdge}, etc into _your app_, and # change them to extend _your_ base object. # # Similarly, `BaseField`'s extensions could be migrated to your app # and `Node` could be implemented to mix in your base interface module. module Relay end end end ruby-graphql-1.9.19/lib/graphql/types/relay/000077500000000000000000000000001362601351000207115ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/types/relay/base_connection.rb000066400000000000000000000076511362601351000244000ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay # Use this to implement Relay connections, or take it as inspiration # for Relay classes in your own app. # # You may wish to copy this code into your own base class, # so you can extend your own `BaseObject` instead of `GraphQL::Schema::Object`. # # @example Implementation a connection and edge # # Given some object in your app ... # class Types::Post < BaseObject # end # # # Make a couple of base classes: # class Types::BaseEdge < GraphQL::Types::Relay::BaseEdge; end # class Types::BaseConnection < GraphQL::Types::Relay::BaseConnection; end # # # Then extend them for the object in your app # class Types::PostEdge < Types::BaseEdge # node_type(Types::Post) # end # class Types::PostConnection < Types::BaseConnection # edge_type(Types::PostEdge) # end # # @see Relay::BaseEdge for edge types class BaseConnection < Types::Relay::BaseObject extend Forwardable def_delegators :@object, :cursor_from_node, :parent class << self # @return [Class] attr_reader :node_type # @return [Class] attr_reader :edge_class # Configure this connection to return `edges` and `nodes` based on `edge_type_class`. # # This method will use the inputs to create: # - `edges` field # - `nodes` field # - description # # It's called when you subclass this base connection, trying to use the # class name to set defaults. You can call it again in the class definition # to override the default (or provide a value, if the default lookup failed). def edge_type(edge_type_class, edge_class: GraphQL::Relay::Edge, node_type: edge_type_class.node_type, nodes_field: true) # Set this connection's graphql name node_type_name = node_type.graphql_name @node_type = node_type @edge_type = edge_type_class @edge_class = edge_class field :edges, [edge_type_class, null: true], null: true, description: "A list of edges.", edge_class: edge_class define_nodes_field if nodes_field description("The connection type for #{node_type_name}.") end # Filter this list according to the way its node type would scope them def scope_items(items, context) node_type.scope_items(items, context) end # Add the shortcut `nodes` field to this connection and its subclasses def nodes_field define_nodes_field end def authorized?(obj, ctx) true # Let nodes be filtered out end def accessible?(ctx) node_type.accessible?(ctx) end def visible?(ctx) node_type.visible?(ctx) end private def define_nodes_field field :nodes, [@node_type, null: true], null: true, description: "A list of nodes." end end field :page_info, GraphQL::Types::Relay::PageInfo, null: false, description: "Information to aid in pagination." # By default this calls through to the ConnectionWrapper's edge nodes method, # but sometimes you need to override it to support the `nodes` field def nodes @object.edge_nodes end def edges if context.interpreter? context.schema.after_lazy(object.edge_nodes) do |nodes| nodes.map { |n| self.class.edge_class.new(n, object) } end else # This is done by edges_instrumentation @object.edge_nodes end end end end end end ruby-graphql-1.9.19/lib/graphql/types/relay/base_edge.rb000066400000000000000000000033311362601351000231340ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay # A class-based definition for Relay edges. # # Use this as a parent class in your app, or use it as inspiration for your # own base `Edge` class. # # For example, you may want to extend your own `BaseObject` instead of the # built-in `GraphQL::Schema::Object`. # # @example Making a UserEdge type # # Make a base class for your app # class Types::BaseEdge < GraphQL::Types::Relay::BaseEdge # end # # # Then extend your own base class # class Types::UserEdge < Types::BaseEdge # node_type(Types::User) # end # # @see {Relay::BaseConnection} for connection types class BaseEdge < Types::Relay::BaseObject description "An edge in a connection." class << self # Get or set the Object type that this edge wraps. # # @param node_type [Class] A `Schema::Object` subclass # @param null [Boolean] def node_type(node_type = nil, null: true) if node_type @node_type = node_type # Add a default `node` field field :node, node_type, null: null, description: "The item at the end of the edge." end @node_type end def authorized?(obj, ctx) true end def accessible?(ctx) node_type.accessible?(ctx) end def visible?(ctx) node_type.visible?(ctx) end end field :cursor, String, null: false, description: "A cursor for use in pagination." end end end end ruby-graphql-1.9.19/lib/graphql/types/relay/base_field.rb000066400000000000000000000006761362601351000233240ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay class BaseField < GraphQL::Schema::Field def initialize(edge_class: nil, **rest, &block) @edge_class = edge_class super(**rest, &block) end def to_graphql field = super if @edge_class field.edge_class = @edge_class end field end end end end end ruby-graphql-1.9.19/lib/graphql/types/relay/base_interface.rb000066400000000000000000000010771362601351000241750ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay module BaseInterface include GraphQL::Schema::Interface field_class(Types::Relay::BaseField) definition_methods do def default_relay(new_value) @default_relay = new_value end def default_relay? !!@default_relay end def to_graphql type_defn = super type_defn.default_relay = default_relay? type_defn end end end end end end ruby-graphql-1.9.19/lib/graphql/types/relay/base_object.rb000066400000000000000000000010401362601351000234710ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay class BaseObject < GraphQL::Schema::Object field_class(Types::Relay::BaseField) class << self def default_relay(new_value) @default_relay = new_value end def default_relay? !!@default_relay end def to_graphql type_defn = super type_defn.default_relay = default_relay? type_defn end end end end end end ruby-graphql-1.9.19/lib/graphql/types/relay/node.rb000066400000000000000000000007241362601351000221660ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay # This can be used for Relay's `Node` interface, # or you can take it as inspiration for your own implementation # of the `Node` interface. module Node include Types::Relay::BaseInterface default_relay(true) description "An object with an ID." field(:id, ID, null: false, description: "ID of the object.") end end end end ruby-graphql-1.9.19/lib/graphql/types/relay/node_field.rb000066400000000000000000000023431362601351000233300ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay # This can be used for implementing `Query.node(id: ...)`, # or use it for inspiration for your own field definition. # # @example Adding this field directly # add_field(GraphQL::Types::Relay::NodeField) # # @example Implementing a similar field in your own Query root # # field :node, GraphQL::Types::Relay::Node, null: true, # description: "Fetches an object given its ID" do # argument :id, ID, required: true # end # # def node(id:) # context.schema.object_from_id(id, context) # end # NodeField = GraphQL::Schema::Field.new( name: "node", owner: nil, type: GraphQL::Types::Relay::Node, null: true, description: "Fetches an object given its ID.", relay_node_field: true, ) do argument :id, "ID!", required: true, description: "ID of the object." def resolve(obj, args, ctx) ctx.schema.object_from_id(args[:id], ctx) end def resolve_field(obj, args, ctx) resolve(obj, args, ctx) end end end end end ruby-graphql-1.9.19/lib/graphql/types/relay/nodes_field.rb000066400000000000000000000025601362601351000235140ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay # This can be used for implementing `Query.nodes(ids: ...)`, # or use it for inspiration for your own field definition. # # @example Adding this field directly # add_field(GraphQL::Types::Relay::NodesField) # # @example Implementing a similar field in your own Query root # # field :nodes, [GraphQL::Types::Relay::Node, null: true], null: false, # description: Fetches a list of objects given a list of IDs." do # argument :ids, [ID], required: true # end # # def nodes(ids:) # ids.map do |id| # context.schema.object_from_id(context, id) # end # end # NodesField = GraphQL::Schema::Field.new( name: "nodes", owner: nil, type: [GraphQL::Types::Relay::Node, null: true], null: false, description: "Fetches a list of objects given a list of IDs.", relay_nodes_field: true, ) do argument :ids, "[ID!]!", required: true, description: "IDs of the objects." def resolve(obj, args, ctx) args[:ids].map { |id| ctx.schema.object_from_id(id, ctx) } end def resolve_field(obj, args, ctx) resolve(obj, args, ctx) end end end end end ruby-graphql-1.9.19/lib/graphql/types/relay/page_info.rb000066400000000000000000000014541362601351000231710ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay # The return type of a connection's `pageInfo` field class PageInfo < Types::Relay::BaseObject default_relay true description "Information about pagination in a connection." field :has_next_page, Boolean, null: false, description: "When paginating forwards, are there more items?" field :has_previous_page, Boolean, null: false, description: "When paginating backwards, are there more items?" field :start_cursor, String, null: true, description: "When paginating backwards, the cursor to continue." field :end_cursor, String, null: true, description: "When paginating forwards, the cursor to continue." end end end end ruby-graphql-1.9.19/lib/graphql/types/string.rb000066400000000000000000000012431362601351000214300ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types class String < GraphQL::Schema::Scalar description "Represents textual data as UTF-8 character sequences. This type is most often used by GraphQL to represent free-form human-readable text." def self.coerce_result(value, ctx) str = value.to_s str.encoding == Encoding::UTF_8 ? str : str.encode(Encoding::UTF_8) rescue EncodingError err = GraphQL::StringEncodingError.new(str) ctx.schema.type_error(err, ctx) end def self.coerce_input(value, _ctx) value.is_a?(::String) ? value : nil end default_scalar true end end end ruby-graphql-1.9.19/lib/graphql/unauthorized_error.rb000066400000000000000000000021041362601351000227050ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # When an `authorized?` hook returns false, this error is used to communicate the failure. # It's passed to {Schema.unauthorized_object}. # # Alternatively, custom code in `authorized?` may raise this error. It will be routed the same way. class UnauthorizedError < GraphQL::Error # @return [Object] the application object that failed the authorization check attr_reader :object # @return [Class] the GraphQL object type whose `.authorized?` method was called (and returned false) attr_reader :type # @return [GraphQL::Query::Context] the context for the current query attr_reader :context def initialize(message = nil, object: nil, type: nil, context: nil) if message.nil? && object.nil? && type.nil? raise ArgumentError, "#{self.class.name} requires either a message or keywords" end @object = object @type = type @context = context message ||= "An instance of #{object.class} failed #{type.name}'s authorization check" super(message) end end end ruby-graphql-1.9.19/lib/graphql/unauthorized_field_error.rb000066400000000000000000000014421362601351000240540ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class UnauthorizedFieldError < GraphQL::UnauthorizedError # @return [Field] the field that failed the authorization check attr_accessor :field def initialize(message = nil, object: nil, type: nil, context: nil, field: nil) if message.nil? && [field, type].any?(&:nil?) raise ArgumentError, "#{self.class.name} requires either a message or keywords" end @field = field message ||= begin if object "An instance of #{object.class} failed #{type.name}'s authorization check on field #{field.name}" else "Failed #{type.name}'s authorization check on field #{field.name}" end end super(message, object: object, type: type, context: context) end end end ruby-graphql-1.9.19/lib/graphql/union_type.rb000066400000000000000000000110131362601351000211430ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # A Union is is a collection of object types which may appear in the same place. # # The members of a union are declared with `possible_types`. # # @example A union of object types # MediaUnion = GraphQL::UnionType.define do # name "Media" # description "Media objects which you can enjoy" # possible_types [AudioType, ImageType, VideoType] # end # # A union itself has no fields; only its members have fields. # So, when you query, you must use fragment spreads to access fields. # # @example Querying for fields on union members # { # searchMedia(name: "Jens Lekman") { # ... on Audio { name, duration } # ... on Image { name, height, width } # ... on Video { name, length, quality } # } # } # class UnionType < GraphQL::BaseType # Rubocop was unhappy about the syntax when this was a proc literal class AcceptPossibleTypesDefinition def self.call(target, possible_types, options = {}) target.add_possible_types(possible_types, **options) end end accepts_definitions :resolve_type, :type_membership_class, possible_types: AcceptPossibleTypesDefinition ensure_defined :possible_types, :resolve_type, :resolve_type_proc, :type_membership_class attr_accessor :resolve_type_proc attr_reader :type_memberships attr_accessor :type_membership_class def initialize super @type_membership_class = GraphQL::Schema::TypeMembership @type_memberships = [] @cached_possible_types = nil @resolve_type_proc = nil end def initialize_copy(other) super @type_membership_class = other.type_membership_class @type_memberships = other.type_memberships.dup @cached_possible_types = nil end def kind GraphQL::TypeKinds::UNION end # @return [Boolean] True if `child_type_defn` is a member of this {UnionType} def include?(child_type_defn, ctx = GraphQL::Query::NullContext) possible_types(ctx).include?(child_type_defn) end # @return [Array] Types which may be found in this union def possible_types(ctx = GraphQL::Query::NullContext) if ctx == GraphQL::Query::NullContext # Only cache the default case; if we cached for every `ctx`, it would be a memory leak # (The warden should cache calls to this method, so it's called only once per query, # unless user code calls it directly.) @cached_possible_types ||= possible_types_for_context(ctx) else possible_types_for_context(ctx) end end def possible_types=(types) # This is a re-assignment, so clear the previous values @type_memberships = [] @cached_possible_types = nil add_possible_types(types, **{}) end def add_possible_types(types, **options) @type_memberships ||= [] Array(types).each { |t| @type_memberships << self.type_membership_class.new(self, t, **options) } nil end # Get a possible type of this {UnionType} by type name # @param type_name [String] # @param ctx [GraphQL::Query::Context] The context for the current query # @return [GraphQL::ObjectType, nil] The type named `type_name` if it exists and is a member of this {UnionType}, (else `nil`) def get_possible_type(type_name, ctx) type = ctx.query.get_type(type_name) type if type && ctx.query.schema.possible_types(self, ctx).include?(type) end # Check if a type is a possible type of this {UnionType} # @param type [String, GraphQL::BaseType] Name of the type or a type definition # @param ctx [GraphQL::Query::Context] The context for the current query # @return [Boolean] True if the `type` exists and is a member of this {UnionType}, (else `nil`) def possible_type?(type, ctx) type_name = type.is_a?(String) ? type : type.graphql_name !get_possible_type(type_name, ctx).nil? end def resolve_type(value, ctx) ctx.query.resolve_type(self, value) end def resolve_type=(new_resolve_type_proc) @resolve_type_proc = new_resolve_type_proc end def type_memberships=(type_memberships) @type_memberships = type_memberships end private def possible_types_for_context(ctx) visible_types = [] @type_memberships.each do |type_membership| if type_membership.visible?(ctx) visible_types << BaseType.resolve_related_type(type_membership.object_type) end end visible_types end end end ruby-graphql-1.9.19/lib/graphql/unresolved_type_error.rb000066400000000000000000000030741362601351000234220ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Error raised when the value provided for a field # can't be resolved to one of the possible types for the field. class UnresolvedTypeError < GraphQL::RuntimeTypeError # @return [Object] The runtime value which couldn't be successfully resolved with `resolve_type` attr_reader :value # @return [GraphQL::Field] The field whose value couldn't be resolved (`field.type` is type which couldn't be resolved) attr_reader :field # @return [GraphQL::BaseType] The owner of `field` attr_reader :parent_type # @return [Object] The return of {Schema#resolve_type} for `value` attr_reader :resolved_type # @return [Array] The allowed options for resolving `value` to `field.type` attr_reader :possible_types def initialize(value, field, parent_type, resolved_type, possible_types) @value = value @field = field @parent_type = parent_type @resolved_type = resolved_type @possible_types = possible_types message = "The value from \"#{field.name}\" on \"#{parent_type}\" could not be resolved to \"#{field.type}\". " \ "(Received: `#{resolved_type.inspect}`, Expected: [#{possible_types.map(&:inspect).join(", ")}]) " \ "Make sure you have defined a `resolve_type` proc on your schema and that value `#{value.inspect}` " \ "gets resolved to a valid type. You may need to add your type to `orphan_types` if it implements an " \ "interface but isn't a return type of any other field." super(message) end end end ruby-graphql-1.9.19/lib/graphql/upgrader/000077500000000000000000000000001362601351000202425ustar00rootroot00000000000000ruby-graphql-1.9.19/lib/graphql/upgrader/member.rb000066400000000000000000001113061362601351000220400ustar00rootroot00000000000000# frozen_string_literal: true begin require 'parser/current' rescue LoadError raise LoadError, "GraphQL::Upgrader requires the 'parser' gem, please install it and/or add it to your Gemfile" end module GraphQL module Upgrader GRAPHQL_TYPES = '(Object|InputObject|Interface|Enum|Scalar|Union)' class Transform # @param input_text [String] Untransformed GraphQL-Ruby code # @return [String] The input text, with a transformation applied if necessary def apply(input_text) raise GraphQL::RequiredImplementationMissingError, "Return transformed text here" end # Recursively transform a `.define`-DSL-based type expression into a class-ready expression, for example: # # - `types[X]` -> `[X, null: true]` # - `types[X.to_non_null_type]` -> `[X]` # - `Int` -> `Integer` # - `X!` -> `X` # # Notice that `!` is removed sometimes, because it doesn't exist in the class API. # # @param type_expr [String] A `.define`-ready expression of a return type or input type # @return [String] A class-ready expression of the same type` def normalize_type_expression(type_expr, preserve_bang: false) case type_expr when /\A!/ # Handle the bang, normalize the inside "#{preserve_bang ? "!" : ""}#{normalize_type_expression(type_expr[1..-1], preserve_bang: preserve_bang)}" when /\Atypes\[.*\]\Z/ # Unwrap the brackets, normalize, then re-wrap inner_type = type_expr[6..-2] if inner_type.start_with?("!") nullable = false inner_type = inner_type[1..-1] elsif inner_type.end_with?(".to_non_null_type") nullable = false inner_type = inner_type[0...-17] else nullable = true end "[#{normalize_type_expression(inner_type, preserve_bang: preserve_bang)}#{nullable ? ", null: true" : ""}]" when /\Atypes\./ # Remove the prefix normalize_type_expression(type_expr[6..-1], preserve_bang: preserve_bang) when /\A->/ # Remove the proc wrapper, don't re-apply it # because stabby is not supported in class-based definition # (and shouldn't ever be necessary) unwrapped = type_expr .sub(/\A->\s?\{\s*/, "") .sub(/\s*\}/, "") normalize_type_expression(unwrapped, preserve_bang: preserve_bang) when "Int" "Integer" else type_expr end end def underscorize(str) str .gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2') # URLDecoder -> URL_Decoder .gsub(/([a-z\d])([A-Z])/,'\1_\2') # someThing -> some_Thing .downcase end def apply_processor(input_text, processor) ruby_ast = Parser::CurrentRuby.parse(input_text) processor.process(ruby_ast) processor rescue Parser::SyntaxError puts "Error text:" puts input_text raise end def reindent_lines(input_text, from_indent:, to_indent:) prev_indent = " " * from_indent next_indent = " " * to_indent # For each line, remove the previous indent, then add the new indent lines = input_text.split("\n").map do |line| line = line.sub(prev_indent, "") "#{next_indent}#{line}".rstrip end lines.join("\n") end # Remove trailing whitespace def trim_lines(input_text) input_text.gsub(/ +$/, "") end end # Turns `{X} = GraphQL::{Y}Type.define do` into `class {X} < Types::Base{Y}`. class TypeDefineToClassTransform < Transform # @param base_class_pattern [String] Replacement pattern for the base class name. Use this if your base classes have nonstandard names. def initialize(base_class_pattern: "Types::Base\\3") @find_pattern = /( *)([a-zA-Z_0-9:]*) = GraphQL::#{GRAPHQL_TYPES}Type\.define do/ @replace_pattern = "\\1class \\2 < #{base_class_pattern}" @interface_replace_pattern = "\\1module \\2\n\\1 include #{base_class_pattern}" end def apply(input_text) if input_text.include?("GraphQL::InterfaceType.define") input_text.sub(@find_pattern, @interface_replace_pattern) else input_text.sub(@find_pattern, @replace_pattern) end end end # Turns `{X} = GraphQL::Relay::Mutation.define do` into `class {X} < Mutations::BaseMutation` class MutationDefineToClassTransform < Transform # @param base_class_name [String] Replacement pattern for the base class name. Use this if your Mutation base class has a nonstandard name. def initialize(base_class_name: "Mutations::BaseMutation") @find_pattern = /([a-zA-Z_0-9:]*) = GraphQL::Relay::Mutation.define do/ @replace_pattern = "class \\1 < #{base_class_name}" end def apply(input_text) input_text.gsub(@find_pattern, @replace_pattern) end end # Remove `name "Something"` if it is redundant with the class name. # Or, if it is not redundant, move it to `graphql_name "Something"`. class NameTransform < Transform def apply(transformable) last_type_defn = transformable .split("\n") .select { |line| line.include?("class ") || line.include?("module ")} .last if last_type_defn && (matches = last_type_defn.match(/(class|module) (?[a-zA-Z_0-9:]*)( <|$)/)) type_name = matches[:type_name] # Get the name without any prefixes or suffixes type_name_without_the_type_part = type_name.split('::').last.gsub(/Type$/, '') # Find an overridden name value if matches = transformable.match(/ name ('|")(?.*)('|")/) name = matches[:overridden_name] if type_name_without_the_type_part != name # If the overridden name is still required, use `graphql_name` for it transformable = transformable.gsub(/ name (.*)/, ' graphql_name \1') else # Otherwise, remove it altogether transformable = transformable.gsub(/\s+name ('|").*('|")/, '') end end end transformable end end # Remove newlines -- normalize the text for processing class RemoveNewlinesTransform def apply(input_text) keep_looking = true while keep_looking do keep_looking = false # Find the `field` call (or other method), and an open paren, but not a close paren, or a comma between arguments input_text = input_text.gsub(/(?(?:field|input_field|return_field|connection|argument)(?:\([^)]*|.*,))\n\s*(?.+)/) do keep_looking = true field = $~[:field].chomp next_line = $~[:next_line] "#{field} #{next_line}" end end input_text end end # Remove parens from method call - normalize for processing class RemoveMethodParensTransform < Transform def apply(input_text) input_text.sub( /(field|input_field|return_field|connection|argument)\( *(.*?) *\)( *)/, '\1 \2\3' ) end end # Move `type X` to be the second positional argument to `field ...` class PositionalTypeArgTransform < Transform def apply(input_text) input_text.gsub( /(?(?:field|input_field|return_field|connection|argument) :(?:[a-zA-Z_0-9]*)) do(?.*?)[ ]*type (?.*?)\n/m ) do field = $~[:field] block_contents = $~[:block_contents] return_type = normalize_type_expression($~[:return_type], preserve_bang: true) "#{field}, #{return_type} do#{block_contents}" end end end # Find a configuration in the block and move it to a kwarg, # for example # ``` # do # property :thing # end # ``` # becomes: # ``` # property: thing # ``` class ConfigurationToKwargTransform < Transform def initialize(kwarg:) @kwarg = kwarg end def apply(input_text) input_text.gsub( /(?(?:field|return_field|input_field|connection|argument).*) do(?.*?)[ ]*#{@kwarg} (?.*?)\n/m ) do field = $~[:field] block_contents = $~[:block_contents] kwarg_value = $~[:kwarg_value].strip "#{field}, #{@kwarg}: #{kwarg_value} do#{block_contents}" end end end # Transform `property:` kwarg to `method:` kwarg class PropertyToMethodTransform < Transform def apply(input_text) input_text.gsub /property:/, 'method:' end end # Find a keyword whose value is a string or symbol, # and if the value is equivalent to the field name, # remove the keyword altogether. class RemoveRedundantKwargTransform < Transform def initialize(kwarg:) @kwarg = kwarg @finder_pattern = /(field|return_field|input_field|connection|argument) :(?[a-zA-Z_0-9]*).*#{@kwarg}: ['":](?[a-zA-Z_0-9?!]+)['"]?/ end def apply(input_text) if input_text =~ @finder_pattern field_name = $~[:name] kwarg_value = $~[:kwarg_value] if field_name == kwarg_value # It's redundant, remove it input_text = input_text.sub(/, #{@kwarg}: ['":]#{kwarg_value}['"]?/, "") end end input_text end end # Take camelized field names and convert them to underscore case. # (They'll be automatically camelized later.) class UnderscoreizeFieldNameTransform < Transform def apply(input_text) input_text.gsub /(?input_field|return_field|field|connection|argument) :(?[a-zA-Z_0-9_]*)/ do field_type = $~[:field_type] camelized_name = $~[:name] underscored_name = underscorize(camelized_name) "#{field_type} :#{underscored_name}" end end end class ProcToClassMethodTransform < Transform # @param proc_name [String] The name of the proc to be moved to `def self.#{proc_name}` def initialize(proc_name) @proc_name = proc_name # This will tell us whether to operate on the input or not @proc_check_pattern = /#{proc_name}\s?->/ end def apply(input_text) if input_text =~ @proc_check_pattern processor = apply_processor(input_text, NamedProcProcessor.new(@proc_name)) processor.proc_to_method_sections.reverse.each do |proc_to_method_section| proc_body = input_text[proc_to_method_section.proc_body_start..proc_to_method_section.proc_body_end] method_defn_indent = " " * proc_to_method_section.proc_defn_indent method_defn = "def self.#{@proc_name}(#{proc_to_method_section.proc_arg_names.join(", ")})\n#{method_defn_indent} #{proc_body}\n#{method_defn_indent}end\n" method_defn = trim_lines(method_defn) # replace the proc with the new method input_text[proc_to_method_section.proc_defn_start..proc_to_method_section.proc_defn_end] = method_defn end end input_text end class NamedProcProcessor < Parser::AST::Processor attr_reader :proc_to_method_sections def initialize(proc_name) @proc_name_sym = proc_name.to_sym @proc_to_method_sections = [] end class ProcToMethodSection attr_accessor :proc_arg_names, :proc_defn_start, :proc_defn_end, :proc_defn_indent, :proc_body_start, :proc_body_end, :inside_proc def initialize # @proc_name_sym = proc_name.to_sym @proc_arg_names = nil # Beginning of the `#{proc_name} -> {...}` call @proc_defn_start = nil # End of the last `end/}` @proc_defn_end = nil # Amount of whitespace to insert to the rewritten body @proc_defn_indent = nil # First statement of the proc @proc_body_start = nil # End of last statement in the proc @proc_body_end = nil # Used for identifying the proper block @inside_proc = false end end def on_send(node) receiver, method_name, _args = *node if method_name == @proc_name_sym && receiver.nil? proc_section = ProcToMethodSection.new source_exp = node.loc.expression proc_section.proc_defn_start = source_exp.begin.begin_pos proc_section.proc_defn_end = source_exp.end.end_pos proc_section.proc_defn_indent = source_exp.column proc_section.inside_proc = true @proc_to_method_sections << proc_section end res = super(node) @inside_proc = false res end def on_block(node) send_node, args_node, body_node = node.children _receiver, method_name, _send_args_node = *send_node if method_name == :lambda && !@proc_to_method_sections.empty? && @proc_to_method_sections[-1].inside_proc proc_to_method_section = @proc_to_method_sections[-1] source_exp = body_node.loc.expression proc_to_method_section.proc_arg_names = args_node.children.map { |arg_node| arg_node.children[0].to_s } proc_to_method_section.proc_body_start = source_exp.begin.begin_pos proc_to_method_section.proc_body_end = source_exp.end.end_pos proc_to_method_section.inside_proc = false end super(node) end end end class MutationResolveProcToMethodTransform < Transform # @param proc_name [String] The name of the proc to be moved to `def self.#{proc_name}` def initialize(proc_name: "resolve") @proc_name = proc_name end # TODO dedup with ResolveProcToMethodTransform def apply(input_text) if input_text =~ /GraphQL::Relay::Mutation\.define/ named_proc_processor = apply_processor(input_text, ProcToClassMethodTransform::NamedProcProcessor.new(@proc_name)) resolve_proc_processor = apply_processor(input_text, ResolveProcToMethodTransform::ResolveProcProcessor.new) named_proc_processor.proc_to_method_sections.zip(resolve_proc_processor.resolve_proc_sections).reverse.each do |pair| proc_to_method_section, resolve_proc_section = *pair proc_body = input_text[proc_to_method_section.proc_body_start..proc_to_method_section.proc_body_end] method_defn_indent = " " * proc_to_method_section.proc_defn_indent obj_arg_name, args_arg_name, ctx_arg_name = resolve_proc_section.proc_arg_names # This is not good, it will hit false positives # Should use AST to make this substitution if obj_arg_name != "_" proc_body.gsub!(/([^\w:.]|^)#{obj_arg_name}([^\w:]|$)/, '\1object\2') end if ctx_arg_name != "_" proc_body.gsub!(/([^\w:.]|^)#{ctx_arg_name}([^\w:]|$)/, '\1context\2') end method_defn = "def #{@proc_name}(**#{args_arg_name})\n#{method_defn_indent} #{proc_body}\n#{method_defn_indent}end\n" method_defn = trim_lines(method_defn) # Update usage of args keys method_defn = method_defn.gsub(/#{args_arg_name}(?\.key\?\(?|\[)["':](?[a-zA-Z0-9_]+)["']?(?\]|\))?/) do method_begin = $~[:method_begin] arg_name = underscorize($~[:arg_name]) method_end = $~[:method_end] "#{args_arg_name}#{method_begin}:#{arg_name}#{method_end}" end # replace the proc with the new method input_text[proc_to_method_section.proc_defn_start..proc_to_method_section.proc_defn_end] = method_defn end end input_text end end # Find hash literals which are returned from mutation resolves, # and convert their keys to underscores. This catches a lot of cases but misses # hashes which are initialized anywhere except in the return expression. class UnderscorizeMutationHashTransform < Transform def apply(input_text) if input_text =~ /def resolve\(\*\*/ processor = apply_processor(input_text, ReturnedHashLiteralProcessor.new) # Use reverse_each to avoid messing up positions processor.keys_to_upgrade.reverse_each do |key_data| underscored_key = underscorize(key_data[:key].to_s) if key_data[:operator] == ":" input_text[key_data[:start]...key_data[:end]] = underscored_key else input_text[key_data[:start]...key_data[:end]] = ":#{underscored_key}" end end end input_text end class ReturnedHashLiteralProcessor < Parser::AST::Processor attr_reader :keys_to_upgrade def initialize @keys_to_upgrade = [] end def on_def(node) method_name, _args, body = *node if method_name == :resolve possible_returned_hashes = find_returned_hashes(body, returning: false) possible_returned_hashes.each do |hash_node| pairs = *hash_node pairs.each do |pair_node| if pair_node.type == :pair # Skip over :kwsplat pair_k, _pair_v = *pair_node if pair_k.type == :sym && pair_k.children[0].to_s =~ /[a-z][A-Z]/ # Does it have any camelcase boundaries? source_exp = pair_k.loc.expression @keys_to_upgrade << { start: source_exp.begin.begin_pos, end: source_exp.end.end_pos, key: pair_k.children[0], operator: pair_node.loc.operator.source, } end end end end end end private # Look for hash nodes, starting from `node`. # Return hash nodes that are valid candiates for returning from this method. def find_returned_hashes(node, returning:) if node.is_a?(Array) *possible_returns, last_expression = *node return possible_returns.map { |c| find_returned_hashes(c, returning: false) }.flatten + # Check the last expression of a method body find_returned_hashes(last_expression, returning: returning) end case node.type when :hash if returning [node] else # This is some random hash literal [] end when :begin # Check the last expression of a method body find_returned_hashes(node.children, returning: true) when :resbody _condition, _assign, body = *node find_returned_hashes(body, returning: returning) when :kwbegin find_returned_hashes(node.children, returning: returning) when :rescue try_body, rescue_body, _ensure_body = *node find_returned_hashes(try_body, returning: returning) + find_returned_hashes(rescue_body, returning: returning) when :block # Check methods with blocks for possible returns method_call, _args, *body = *node if method_call.type == :send find_returned_hashes(body, returning: returning) end when :if # Check each branch of a conditional _condition, *branches = *node branches.compact.map { |b| find_returned_hashes(b, returning: returning) }.flatten when :return find_returned_hashes(node.children.first, returning: true) else [] end rescue p "--- UnderscorizeMutationHashTransform crashed on node: ---" p node raise end end end class ResolveProcToMethodTransform < Transform def apply(input_text) if input_text =~ /resolve\(? ?->/ # - Find the proc literal # - Get the three argument names (obj, arg, ctx) # - Get the proc body # - Find and replace: # - The ctx argument becomes `context` # - The obj argument becomes `object` # - Args is trickier: # - If it's not used, remove it # - If it's used, abandon ship and make it `**args` # - Convert string args access to symbol access, since it's a Ruby **splat # - Convert camelized arg names to underscored arg names # - (It would be nice to correctly become Ruby kwargs, but that might be too hard) # - Add a `# TODO` comment to the method source? # - Rebuild the method: # - use the field name as the method name # - handle args as described above # - put the modified proc body as the method body input_text.match(/(?input_field|field|connection|argument) :(?[a-zA-Z_0-9_]*)/) field_name = $~[:name] processor = apply_processor(input_text, ResolveProcProcessor.new) processor.resolve_proc_sections.reverse.each do |resolve_proc_section| proc_body = input_text[resolve_proc_section.proc_start..resolve_proc_section.proc_end] obj_arg_name, args_arg_name, ctx_arg_name = resolve_proc_section.proc_arg_names # This is not good, it will hit false positives # Should use AST to make this substitution if obj_arg_name != "_" proc_body.gsub!(/([^\w:.]|^)#{obj_arg_name}([^\w:]|$)/, '\1object\2') end if ctx_arg_name != "_" proc_body.gsub!(/([^\w:.]|^)#{ctx_arg_name}([^\w:]|$)/, '\1context\2') end method_def_indent = " " * (resolve_proc_section.resolve_indent - 2) # Turn the proc body into a method body method_body = reindent_lines(proc_body, from_indent: resolve_proc_section.resolve_indent + 2, to_indent: resolve_proc_section.resolve_indent) # Add `def... end` method_def = if input_text.include?("argument ") # This field has arguments "def #{field_name}(**#{args_arg_name})" else # No field arguments, so, no method arguments "def #{field_name}" end # Wrap the body in def ... end method_body = "\n#{method_def_indent}#{method_def}\n#{method_body}\n#{method_def_indent}end\n" # Update Argument access to be underscore and symbols # Update `args[...]` and `args.key?` method_body = method_body.gsub(/#{args_arg_name}(?\.key\?\(?|\[)["':](?[a-zA-Z0-9_]+)["']?(?\]|\))?/) do method_begin = $~[:method_begin] arg_name = underscorize($~[:arg_name]) method_end = $~[:method_end] "#{args_arg_name}#{method_begin}:#{arg_name}#{method_end}" end # Replace the resolve proc with the method input_text[resolve_proc_section.resolve_start..resolve_proc_section.resolve_end] = "" # The replacement above might have left some preceeding whitespace, # so remove it by deleting all whitespace chars before `resolve`: preceeding_whitespace = resolve_proc_section.resolve_start - 1 while input_text[preceeding_whitespace] == " " && preceeding_whitespace > 0 input_text[preceeding_whitespace] = "" preceeding_whitespace -= 1 end input_text += method_body input_text end end input_text end class ResolveProcProcessor < Parser::AST::Processor attr_reader :resolve_proc_sections def initialize @resolve_proc_sections = [] end class ResolveProcSection attr_accessor :proc_start, :proc_end, :proc_arg_names, :resolve_start, :resolve_end, :resolve_indent def initialize @proc_arg_names = nil @resolve_start = nil @resolve_end = nil @resolve_indent = nil @proc_start = nil @proc_end = nil end end def on_send(node) receiver, method_name, _args = *node if method_name == :resolve && receiver.nil? resolve_proc_section = ResolveProcSection.new source_exp = node.loc.expression resolve_proc_section.resolve_start = source_exp.begin.begin_pos resolve_proc_section.resolve_end = source_exp.end.end_pos resolve_proc_section.resolve_indent = source_exp.column @resolve_proc_sections << resolve_proc_section end super(node) end def on_block(node) send_node, args_node, body_node = node.children _receiver, method_name, _send_args_node = *send_node # Assume that the first three-argument proc we enter is the resolve if ( method_name == :lambda && args_node.children.size == 3 && !@resolve_proc_sections.empty? && @resolve_proc_sections[-1].proc_arg_names.nil? ) resolve_proc_section = @resolve_proc_sections[-1] source_exp = body_node.loc.expression resolve_proc_section.proc_arg_names = args_node.children.map { |arg_node| arg_node.children[0].to_s } resolve_proc_section.proc_start = source_exp.begin.begin_pos resolve_proc_section.proc_end = source_exp.end.end_pos end super(node) end end end # Transform `interfaces [A, B, C]` to `implements A\nimplements B\nimplements C\n` class InterfacesToImplementsTransform < Transform PATTERN = /(?\s*)(?:interfaces) \[\s*(?(?:[a-zA-Z_0-9:\.,\s]+))\]/m def apply(input_text) input_text.gsub(PATTERN) do indent = $~[:indent] interfaces = $~[:interfaces].split(',').map(&:strip).reject(&:empty?) # Preserve leading newlines before the `interfaces ...` # call, but don't re-insert them between `implements` calls. extra_leading_newlines = "\n" * (indent[/^\n*/].length - 1) indent = indent.sub(/^\n*/m, "") interfaces_calls = interfaces .map { |interface| "\n#{indent}implements #{interface}" } .join extra_leading_newlines + interfaces_calls end end end # Transform `possible_types [A, B, C]` to `possible_types(A, B, C)` class PossibleTypesTransform < Transform PATTERN = /(?\s*)(?:possible_types) \[\s*(?(?:[a-zA-Z_0-9:\.,\s]+))\]/m def apply(input_text) input_text.gsub(PATTERN) do indent = $~[:indent] possible_types = $~[:possible_types].split(',').map(&:strip).reject(&:empty?) extra_leading_newlines = indent[/^\n*/] method_indent = indent.sub(/^\n*/m, "") type_indent = " " + method_indent possible_types_call = "#{method_indent}possible_types(\n#{possible_types.map { |t| "#{type_indent}#{t},"}.join("\n")}\n#{method_indent})" extra_leading_newlines + trim_lines(possible_types_call) end end end class UpdateMethodSignatureTransform < Transform def apply(input_text) input_text.scan(/(?:input_field|field|return_field|connection|argument) .*$/).each do |field| matches = /(?input_field|return_field|field|connection|argument) :(?[a-zA-Z_0-9_]*)?(:?, +(?([A-Za-z\[\]\.\!_0-9\(\)]|::|-> ?\{ ?| ?\})+))?(?( |,|$).*)/.match(field) if matches name = matches[:name] return_type = matches[:return_type] remainder = matches[:remainder] field_type = matches[:field_type] with_block = remainder.gsub!(/\ do$/, '') remainder.gsub! /,$/, '' remainder.gsub! /^,/, '' remainder.chomp! if return_type non_nullable = return_type.sub! /(^|[^\[])!/, '\1' non_nullable ||= return_type.sub! /([^\[])\.to_non_null_type([^\]]|$)/, '\1' nullable = !non_nullable return_type = normalize_type_expression(return_type) else non_nullable = nil nullable = nil end input_text.sub!(field) do is_argument = ['argument', 'input_field'].include?(field_type) f = "#{is_argument ? 'argument' : 'field'} :#{name}" if return_type f += ", #{return_type}" end unless remainder.empty? f += ',' + remainder end if is_argument if nullable f += ', required: false' elsif non_nullable f += ', required: true' end else if nullable f += ', null: true' elsif non_nullable f += ', null: false' end end if field_type == 'connection' f += ', connection: true' end if with_block f += ' do' end f end end end input_text end end class RemoveEmptyBlocksTransform < Transform def apply(input_text) input_text.gsub(/\s*do\s*end/m, "") end end # Remove redundant newlines, which may have trailing spaces # Remove double newline after `do` # Remove double newline before `end` # Remove lines with whitespace only class RemoveExcessWhitespaceTransform < Transform def apply(input_text) input_text .gsub(/\n{3,}/m, "\n\n") .gsub(/do\n{2,}/m, "do\n") .gsub(/\n{2,}(\s*)end/m, "\n\\1end") .gsub(/\n +\n/m, "\n\n") end end # Skip this file if you see any `field` # helpers with `null: true` or `null: false` keywords # or `argument` helpers with `required:` keywords, # because it's already been transformed class SkipOnNullKeyword def skip?(input_text) input_text =~ /field.*null: (true|false)/ || input_text =~ /argument.*required: (true|false)/ end end class Member def initialize(member, skip: SkipOnNullKeyword, type_transforms: DEFAULT_TYPE_TRANSFORMS, field_transforms: DEFAULT_FIELD_TRANSFORMS, clean_up_transforms: DEFAULT_CLEAN_UP_TRANSFORMS) @member = member @skip = skip @type_transforms = type_transforms @field_transforms = field_transforms @clean_up_transforms = clean_up_transforms end DEFAULT_TYPE_TRANSFORMS = [ TypeDefineToClassTransform, MutationResolveProcToMethodTransform, # Do this before switching to class, so we can detect that its a mutation UnderscorizeMutationHashTransform, MutationDefineToClassTransform, NameTransform, InterfacesToImplementsTransform, PossibleTypesTransform, ProcToClassMethodTransform.new("coerce_input"), ProcToClassMethodTransform.new("coerce_result"), ProcToClassMethodTransform.new("resolve_type"), ] DEFAULT_FIELD_TRANSFORMS = [ RemoveNewlinesTransform, RemoveMethodParensTransform, PositionalTypeArgTransform, ConfigurationToKwargTransform.new(kwarg: "property"), ConfigurationToKwargTransform.new(kwarg: "description"), ConfigurationToKwargTransform.new(kwarg: "deprecation_reason"), ConfigurationToKwargTransform.new(kwarg: "hash_key"), PropertyToMethodTransform, UnderscoreizeFieldNameTransform, ResolveProcToMethodTransform, UpdateMethodSignatureTransform, RemoveRedundantKwargTransform.new(kwarg: "hash_key"), RemoveRedundantKwargTransform.new(kwarg: "method"), ] DEFAULT_CLEAN_UP_TRANSFORMS = [ RemoveExcessWhitespaceTransform, RemoveEmptyBlocksTransform, ] def upgrade type_source = @member.dup should_skip = @skip.new.skip?(type_source) # return the unmodified code if should_skip return type_source end # Transforms on type defn code: type_source = apply_transforms(type_source, @type_transforms) # Transforms on each field: field_sources = find_fields(type_source) field_sources.each do |field_source| transformed_field_source = apply_transforms(field_source.dup, @field_transforms) # Replace the original source code with the transformed source code: type_source = type_source.gsub(field_source, transformed_field_source) end # Clean-up: type_source = apply_transforms(type_source, @clean_up_transforms) # Return the transformed source: type_source end def upgradeable? return false if @member.include? '< GraphQL::Schema::' return false if @member =~ /< Types::Base#{GRAPHQL_TYPES}/ true end private def apply_transforms(source_code, transforms, idx: 0) next_transform = transforms[idx] case next_transform when nil # We got to the end of the list source_code when Class # Apply a class next_source_code = next_transform.new.apply(source_code) apply_transforms(next_source_code, transforms, idx: idx + 1) else # Apply an already-initialized object which responds to `apply` next_source_code = next_transform.apply(source_code) apply_transforms(next_source_code, transforms, idx: idx + 1) end end # Parse the type, find calls to `field` and `connection` # Return strings containing those calls def find_fields(type_source) type_ast = Parser::CurrentRuby.parse(type_source) finder = FieldFinder.new finder.process(type_ast) field_sources = [] # For each of the locations we found, extract the text for that definition. # The text will be transformed independently, # then the transformed text will replace the original text. FieldFinder::DEFINITION_METHODS.each do |def_method| finder.locations[def_method].each do |name, (starting_idx, ending_idx)| field_source = type_source[starting_idx..ending_idx] field_sources << field_source end end # Here's a crazy thing: the transformation is pure, # so definitions like `argument :id, types.ID` can be transformed once # then replaced everywhere. So: # - make a unique array here # - use `gsub` after performing the transformation. field_sources.uniq! field_sources rescue Parser::SyntaxError puts "Error Source:" puts type_source raise end class FieldFinder < Parser::AST::Processor # These methods are definition DSLs which may accept a block, # each of these definitions is passed for transformation in its own right. # `field` and `connection` take priority. In fact, they upgrade their # own arguments, so those upgrades turn out to be no-ops. DEFINITION_METHODS = [:field, :connection, :input_field, :return_field, :argument] attr_reader :locations def initialize # Pairs of `{ { method_name => { name => [start, end] } }`, # since fields/arguments are unique by name, within their category @locations = Hash.new { |h,k| h[k] = {} } end # @param send_node [node] The node which might be a `field` call, etc # @param source_node [node] The node whose source defines the bounds of the definition (eg, the surrounding block) def add_location(send_node:,source_node:) receiver_node, method_name, *arg_nodes = *send_node # Implicit self and one of the recognized methods if receiver_node.nil? && DEFINITION_METHODS.include?(method_name) name = arg_nodes[0] # This field may have already been added because # we find `(block ...)` nodes _before_ we find `(send ...)` nodes. if @locations[method_name][name].nil? starting_idx = source_node.loc.expression.begin.begin_pos ending_idx = source_node.loc.expression.end.end_pos @locations[method_name][name] = [starting_idx, ending_idx] end end end def on_block(node) send_node, _args_node, _body_node = *node add_location(send_node: send_node, source_node: node) super(node) end def on_send(node) add_location(send_node: node, source_node: node) super(node) end end end end end ruby-graphql-1.9.19/lib/graphql/upgrader/schema.rb000066400000000000000000000013641362601351000220330ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Upgrader class Schema def initialize(schema) @schema = schema end def upgrade transformable = schema.dup transformable.sub!( /([a-zA-Z_0-9]*) = GraphQL::Schema\.define do/, 'class \1 < GraphQL::Schema' ) transformable.sub!( /object_from_id ->\s?\((.*)\) do/, 'def self.object_from_id(\1)' ) transformable.sub!( /resolve_type ->\s?\((.*)\) do/, 'def self.resolve_type(\1)' ) transformable.sub!( /id_from_object ->\s?\((.*)\) do/, 'def self.id_from_object(\1)' ) transformable end private attr_reader :schema end end end ruby-graphql-1.9.19/lib/graphql/version.rb000066400000000000000000000001061362601351000204400ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL VERSION = "1.9.19" end ruby-graphql-1.9.19/readme.md000066400000000000000000000050721362601351000160100ustar00rootroot00000000000000# graphql graphql-ruby [![Build Status](https://travis-ci.org/rmosolgo/graphql-ruby.svg?branch=master)](https://travis-ci.org/rmosolgo/graphql-ruby) [![Gem Version](https://badge.fury.io/rb/graphql.svg)](https://rubygems.org/gems/graphql) [![Code Climate](https://codeclimate.com/github/rmosolgo/graphql-ruby/badges/gpa.svg)](https://codeclimate.com/github/rmosolgo/graphql-ruby) [![Test Coverage](https://codeclimate.com/github/rmosolgo/graphql-ruby/badges/coverage.svg)](https://codeclimate.com/github/rmosolgo/graphql-ruby) [![built with love](https://cloud.githubusercontent.com/assets/2231765/6766607/d07992c6-cfc9-11e4-813f-d9240714dd50.png)](https://rmosolgo.github.io/react-badges/) A Ruby implementation of [GraphQL](https://graphql.org/). - [Website](https://graphql-ruby.org/) - [API Documentation](https://www.rubydoc.info/gems/graphql) - [Newsletter](https://tinyletter.com/graphql-ruby) ## Installation Install from RubyGems by adding it to your `Gemfile`, then bundling. ```ruby # Gemfile gem 'graphql' ``` ``` $ bundle install ``` ## Getting Started ``` $ rails generate graphql:install ``` After this, you may need to run `bundle install` again, as by default graphiql-rails is added on installation. Or, see ["Getting Started"](https://graphql-ruby.org/getting_started.html). ## Upgrade I also sell [GraphQL::Pro](https://graphql.pro) which provides several features on top of the GraphQL runtime, including [Pundit authorization](https://graphql-ruby.org/authorization/pundit_integration), [CanCan authorization](https://graphql-ruby.org/authorization/can_can_integration), [Pusher-based subscriptions](https://graphql-ruby.org/subscriptions/pusher_implementation) and [persisted queries](https://graphql-ruby.org/operation_store/overview). Besides that, Pro customers get email support and an opportunity to support graphql-ruby's development! ## Goals - Implement the GraphQL spec & support a Relay front end - Provide idiomatic, plain-Ruby API with similarities to reference implementation where possible - Support Ruby on Rails and Relay ## Getting Involved - __Say hi & ask questions__ in the [#ruby channel on Slack](https://graphql-slack.herokuapp.com/) or [on Twitter](https://twitter.com/rmosolgo)! - __Report bugs__ by posting a description, full stack trace, and all relevant code in a [GitHub issue](https://github.com/rmosolgo/graphql-ruby/issues). - __Start hacking__ with the [Development guide](https://graphql-ruby.org/development).