graphql 1.9.0.pre1 → 1.9.0.pre2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/graphql.rb +5 -1
- data/lib/graphql/analysis/ast/analyzer.rb +1 -1
- data/lib/graphql/analysis/ast/visitor.rb +6 -1
- data/lib/graphql/backwards_compatibility.rb +1 -1
- data/lib/graphql/dig.rb +19 -0
- data/lib/graphql/directive.rb +13 -1
- data/lib/graphql/directive/include_directive.rb +1 -7
- data/lib/graphql/directive/skip_directive.rb +1 -8
- data/lib/graphql/execution/interpreter.rb +23 -13
- data/lib/graphql/execution/interpreter/resolve.rb +56 -0
- data/lib/graphql/execution/interpreter/runtime.rb +174 -74
- data/lib/graphql/execution/lazy.rb +7 -1
- data/lib/graphql/execution/lookahead.rb +71 -6
- data/lib/graphql/execution_error.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +5 -1
- data/lib/graphql/introspection/type_type.rb +4 -4
- data/lib/graphql/language.rb +0 -1
- data/lib/graphql/language/block_string.rb +37 -0
- data/lib/graphql/language/document_from_schema_definition.rb +1 -1
- data/lib/graphql/language/lexer.rb +55 -36
- data/lib/graphql/language/lexer.rl +8 -3
- data/lib/graphql/language/nodes.rb +27 -4
- data/lib/graphql/language/parser.rb +55 -55
- data/lib/graphql/language/parser.y +11 -11
- data/lib/graphql/language/printer.rb +1 -1
- data/lib/graphql/language/visitor.rb +22 -13
- data/lib/graphql/literal_validation_error.rb +6 -0
- data/lib/graphql/query.rb +13 -0
- data/lib/graphql/query/arguments.rb +2 -1
- data/lib/graphql/query/context.rb +3 -10
- data/lib/graphql/query/executor.rb +1 -1
- data/lib/graphql/query/validation_pipeline.rb +1 -1
- data/lib/graphql/relay/connection_resolve.rb +1 -1
- data/lib/graphql/relay/relation_connection.rb +1 -1
- data/lib/graphql/schema.rb +81 -11
- data/lib/graphql/schema/argument.rb +1 -1
- data/lib/graphql/schema/build_from_definition.rb +2 -4
- data/lib/graphql/schema/directive.rb +103 -0
- data/lib/graphql/schema/directive/feature.rb +66 -0
- data/lib/graphql/schema/directive/include.rb +25 -0
- data/lib/graphql/schema/directive/skip.rb +25 -0
- data/lib/graphql/schema/directive/transform.rb +48 -0
- data/lib/graphql/schema/enum_value.rb +2 -2
- data/lib/graphql/schema/field.rb +63 -17
- data/lib/graphql/schema/input_object.rb +1 -0
- data/lib/graphql/schema/member/base_dsl_methods.rb +4 -2
- data/lib/graphql/schema/member/build_type.rb +33 -1
- data/lib/graphql/schema/member/has_fields.rb +8 -73
- data/lib/graphql/schema/relay_classic_mutation.rb +6 -1
- data/lib/graphql/schema/resolver.rb +1 -1
- data/lib/graphql/static_validation.rb +2 -1
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/base_visitor.rb +25 -10
- data/lib/graphql/static_validation/definition_dependencies.rb +3 -3
- data/lib/graphql/static_validation/{message.rb → error.rb} +11 -11
- data/lib/graphql/static_validation/interpreter_visitor.rb +14 -0
- data/lib/graphql/static_validation/literal_validator.rb +54 -11
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +34 -5
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +31 -0
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +2 -2
- data/lib/graphql/static_validation/rules/argument_names_are_unique_error.rb +30 -0
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +7 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +35 -0
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -1
- data/lib/graphql/static_validation/rules/directives_are_defined_error.rb +29 -0
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +11 -2
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations_error.rb +31 -0
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +11 -2
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type_error.rb +32 -0
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -2
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections_error.rb +31 -0
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +24 -6
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +32 -0
- data/lib/graphql/static_validation/rules/fragment_names_are_unique.rb +5 -1
- data/lib/graphql/static_validation/rules/fragment_names_are_unique_error.rb +29 -0
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +8 -1
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible_error.rb +35 -0
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +5 -1
- data/lib/graphql/static_validation/rules/fragment_types_exist_error.rb +29 -0
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +6 -1
- data/lib/graphql/static_validation/rules/fragments_are_finite_error.rb +29 -0
- data/lib/graphql/static_validation/rules/fragments_are_named.rb +4 -1
- data/lib/graphql/static_validation/rules/fragments_are_named_error.rb +26 -0
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +5 -1
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types_error.rb +30 -0
- data/lib/graphql/static_validation/rules/fragments_are_used.rb +13 -3
- data/lib/graphql/static_validation/rules/fragments_are_used_error.rb +29 -0
- data/lib/graphql/static_validation/rules/mutation_root_exists.rb +4 -1
- data/lib/graphql/static_validation/rules/mutation_root_exists_error.rb +26 -0
- data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +2 -2
- data/lib/graphql/static_validation/rules/no_definitions_are_present_error.rb +25 -0
- data/lib/graphql/static_validation/rules/operation_names_are_valid.rb +9 -2
- data/lib/graphql/static_validation/rules/operation_names_are_valid_error.rb +28 -0
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +7 -1
- data/lib/graphql/static_validation/rules/required_arguments_are_present_error.rb +35 -0
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +47 -0
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present_error.rb +35 -0
- data/lib/graphql/static_validation/rules/subscription_root_exists.rb +4 -1
- data/lib/graphql/static_validation/rules/subscription_root_exists_error.rb +26 -0
- data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +4 -3
- data/lib/graphql/static_validation/rules/unique_directives_per_location_error.rb +29 -0
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +20 -6
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed_error.rb +39 -0
- data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +5 -1
- data/lib/graphql/static_validation/rules/variable_names_are_unique_error.rb +29 -0
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +8 -1
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed_error.rb +38 -0
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -2
- data/lib/graphql/static_validation/rules/variables_are_input_types_error.rb +32 -0
- data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +18 -2
- data/lib/graphql/static_validation/rules/variables_are_used_and_defined_error.rb +37 -0
- data/lib/graphql/static_validation/validator.rb +24 -14
- data/lib/graphql/tracing/new_relic_tracing.rb +2 -2
- data/lib/graphql/tracing/skylight_tracing.rb +2 -2
- data/lib/graphql/unauthorized_field_error.rb +23 -0
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/analysis/ast_spec.rb +40 -0
- data/spec/graphql/authorization_spec.rb +93 -20
- data/spec/graphql/base_type_spec.rb +3 -1
- data/spec/graphql/execution/interpreter_spec.rb +127 -4
- data/spec/graphql/execution/lazy_spec.rb +49 -0
- data/spec/graphql/execution/lookahead_spec.rb +113 -21
- data/spec/graphql/execution/multiplex_spec.rb +2 -1
- data/spec/graphql/introspection/type_type_spec.rb +1 -1
- data/spec/graphql/language/lexer_spec.rb +72 -3
- data/spec/graphql/language/printer_spec.rb +18 -6
- data/spec/graphql/query/arguments_spec.rb +21 -0
- data/spec/graphql/query/context_spec.rb +10 -0
- data/spec/graphql/schema/build_from_definition_spec.rb +144 -29
- data/spec/graphql/schema/directive/feature_spec.rb +81 -0
- data/spec/graphql/schema/directive/transform_spec.rb +39 -0
- data/spec/graphql/schema/enum_spec.rb +5 -3
- data/spec/graphql/schema/field_extension_spec.rb +3 -3
- data/spec/graphql/schema/field_spec.rb +19 -0
- data/spec/graphql/schema/input_object_spec.rb +81 -0
- data/spec/graphql/schema/member/build_type_spec.rb +46 -0
- data/spec/graphql/schema/member/scoped_spec.rb +3 -3
- data/spec/graphql/schema/printer_spec.rb +244 -96
- data/spec/graphql/schema/relay_classic_mutation_spec.rb +26 -0
- data/spec/graphql/schema/resolver_spec.rb +1 -1
- data/spec/graphql/schema/warden_spec.rb +35 -11
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +212 -72
- data/spec/graphql/static_validation/rules/argument_names_are_unique_spec.rb +2 -2
- data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +72 -29
- data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +10 -5
- data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +10 -5
- data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +2 -1
- data/spec/graphql/static_validation/rules/fragment_names_are_unique_spec.rb +2 -1
- data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +6 -3
- data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/fragments_are_named_spec.rb +2 -1
- data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +6 -3
- data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +22 -2
- data/spec/graphql/static_validation/rules/mutation_root_exists_spec.rb +2 -1
- data/spec/graphql/static_validation/rules/operation_names_are_valid_spec.rb +6 -3
- data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +13 -4
- data/spec/graphql/static_validation/rules/required_input_object_attributes_are_present_spec.rb +58 -0
- data/spec/graphql/static_validation/rules/subscription_root_exists_spec.rb +2 -1
- data/spec/graphql/static_validation/rules/unique_directives_per_location_spec.rb +14 -7
- data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +14 -7
- data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +8 -4
- data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +8 -4
- data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +6 -3
- data/spec/graphql/static_validation/validator_spec.rb +6 -4
- data/spec/graphql/tracing/new_relic_tracing_spec.rb +10 -0
- data/spec/graphql/tracing/skylight_tracing_spec.rb +10 -0
- data/spec/graphql/types/iso_8601_date_time_spec.rb +1 -2
- data/spec/integration/mongoid/star_trek/schema.rb +5 -5
- data/spec/integration/rails/graphql/relay/relation_connection_spec.rb +37 -8
- data/spec/integration/rails/graphql/schema_spec.rb +2 -2
- data/spec/integration/rails/spec_helper.rb +10 -0
- data/spec/integration/tmp/app/graphql/types/bird_type.rb +7 -0
- data/spec/integration/tmp/dummy/Gemfile +45 -0
- data/spec/integration/tmp/dummy/README.rdoc +28 -0
- data/spec/integration/tmp/dummy/Rakefile +6 -0
- data/spec/integration/tmp/dummy/app/assets/javascripts/application.js +16 -0
- data/spec/integration/tmp/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/integration/tmp/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/integration/tmp/dummy/app/controllers/graphql_controller.rb +43 -0
- data/spec/integration/tmp/dummy/app/graphql/dummy_schema.rb +34 -0
- data/spec/integration/tmp/dummy/app/graphql/types/base_enum.rb +4 -0
- data/spec/integration/tmp/dummy/app/graphql/types/base_input_object.rb +4 -0
- data/spec/integration/tmp/dummy/app/graphql/types/base_interface.rb +5 -0
- data/spec/integration/tmp/dummy/app/graphql/types/base_object.rb +4 -0
- data/spec/integration/tmp/dummy/app/graphql/types/base_scalar.rb +4 -0
- data/spec/integration/tmp/dummy/app/graphql/types/base_union.rb +4 -0
- data/spec/integration/tmp/dummy/app/graphql/types/mutation_type.rb +10 -0
- data/spec/integration/tmp/dummy/app/graphql/types/query_type.rb +15 -0
- data/spec/integration/tmp/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/integration/tmp/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/integration/tmp/dummy/bin/bundle +3 -0
- data/spec/integration/tmp/dummy/bin/rails +4 -0
- data/spec/integration/tmp/dummy/bin/rake +4 -0
- data/spec/integration/tmp/dummy/bin/setup +29 -0
- data/spec/integration/tmp/dummy/config.ru +4 -0
- data/spec/integration/tmp/dummy/config/application.rb +32 -0
- data/spec/integration/tmp/dummy/config/boot.rb +3 -0
- data/spec/integration/tmp/dummy/config/environment.rb +5 -0
- data/spec/integration/tmp/dummy/config/environments/development.rb +38 -0
- data/spec/integration/tmp/dummy/config/environments/production.rb +76 -0
- data/spec/integration/tmp/dummy/config/environments/test.rb +42 -0
- data/spec/integration/tmp/dummy/config/initializers/assets.rb +11 -0
- data/spec/integration/tmp/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/integration/tmp/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/integration/tmp/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/integration/tmp/dummy/config/initializers/inflections.rb +16 -0
- data/spec/integration/tmp/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/integration/tmp/dummy/config/initializers/session_store.rb +3 -0
- data/spec/integration/tmp/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
- data/spec/integration/tmp/dummy/config/initializers/wrap_parameters.rb +9 -0
- data/spec/integration/tmp/dummy/config/locales/en.yml +23 -0
- data/spec/integration/tmp/dummy/config/routes.rb +61 -0
- data/spec/integration/tmp/dummy/config/secrets.yml +22 -0
- data/spec/integration/tmp/dummy/db/seeds.rb +7 -0
- data/spec/integration/tmp/dummy/public/404.html +67 -0
- data/spec/integration/tmp/dummy/public/422.html +67 -0
- data/spec/integration/tmp/dummy/public/500.html +66 -0
- data/spec/integration/tmp/dummy/public/favicon.ico +0 -0
- data/spec/integration/tmp/dummy/public/robots.txt +5 -0
- data/spec/support/dummy/schema.rb +2 -2
- data/spec/support/error_bubbling_helpers.rb +23 -0
- data/spec/support/jazz.rb +53 -6
- data/spec/support/lazy_helpers.rb +26 -8
- data/spec/support/new_relic.rb +3 -0
- data/spec/support/skylight.rb +3 -0
- data/spec/support/star_wars/schema.rb +13 -9
- data/spec/support/static_validation_helpers.rb +3 -1
- metadata +145 -22
- data/lib/graphql/language/comments.rb +0 -45
- data/spec/graphql/schema/member/has_fields_spec.rb +0 -132
- data/spec/integration/tmp/app/graphql/types/family_type.rb +0 -9
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
class Schema
|
4
|
+
class Directive < GraphQL::Schema::Member
|
5
|
+
# An example directive to show how you might interact with the runtime.
|
6
|
+
#
|
7
|
+
# This directive might be used along with a server-side feature flag system like Flipper.
|
8
|
+
#
|
9
|
+
# With that system, you could use this directive to exclude parts of a query
|
10
|
+
# if the current viewer doesn't have certain flags enabled.
|
11
|
+
# (So, this flag would be for internal clients, like your iOS app, not third-party API clients.)
|
12
|
+
#
|
13
|
+
# To use it, you have to implement `.enabled?`, for example:
|
14
|
+
#
|
15
|
+
# @example Implementing the Feature directive
|
16
|
+
# # app/graphql/directives/feature.rb
|
17
|
+
# class Directives::Feature < GraphQL::Schema::Directive::Feature
|
18
|
+
# def self.enabled?(flag_name, _obj, context)
|
19
|
+
# # Translate some GraphQL data for Ruby:
|
20
|
+
# flag_key = flag_name.underscore
|
21
|
+
# current_user = context[:viewer]
|
22
|
+
# # Check the feature flag however your app does it:
|
23
|
+
# MyFeatureFlags.enabled?(current_user, flag_key)
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# @example Flagging a part of the query
|
28
|
+
# viewer {
|
29
|
+
# # This field only runs if `.enabled?("recommendationEngine", obj, context)`
|
30
|
+
# # returns true. Otherwise, it's treated as if it didn't exist.
|
31
|
+
# recommendations @feature(flag: "recommendationEngine") {
|
32
|
+
# name
|
33
|
+
# rating
|
34
|
+
# }
|
35
|
+
# }
|
36
|
+
class Feature < Schema::Directive
|
37
|
+
description "Directs the executor to run this only if a certain server-side feature is enabled."
|
38
|
+
|
39
|
+
locations(
|
40
|
+
GraphQL::Schema::Directive::FIELD,
|
41
|
+
GraphQL::Schema::Directive::FRAGMENT_SPREAD,
|
42
|
+
GraphQL::Schema::Directive::INLINE_FRAGMENT
|
43
|
+
)
|
44
|
+
|
45
|
+
argument :flag, String, required: true,
|
46
|
+
description: "The name of the feature to check before continuing"
|
47
|
+
|
48
|
+
# Implement the Directive API
|
49
|
+
def self.include?(object, arguments, context)
|
50
|
+
flag_name = arguments[:flag]
|
51
|
+
self.enabled?(flag_name, object, context)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Override this method in your app's subclass of this directive.
|
55
|
+
#
|
56
|
+
# @param flag_name [String] The client-provided string of a feature to check
|
57
|
+
# @param object [GraphQL::Schema::Objct] The currently-evaluated GraphQL object instance
|
58
|
+
# @param context [GraphQL::Query::Context]
|
59
|
+
# @return [Boolean] If truthy, execution will continue
|
60
|
+
def self.enabled?(flag_name, object, context)
|
61
|
+
raise NotImplementedError, "Implement `.enabled?(flag_name, object, context)` to return true or false for the feature flag (#{flag_name.inspect})"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
class Schema
|
4
|
+
class Directive < GraphQL::Schema::Member
|
5
|
+
class Include < GraphQL::Schema::Directive
|
6
|
+
description "Directs the executor to include this field or fragment only when the `if` argument is true."
|
7
|
+
|
8
|
+
locations(
|
9
|
+
GraphQL::Schema::Directive::FIELD,
|
10
|
+
GraphQL::Schema::Directive::FRAGMENT_SPREAD,
|
11
|
+
GraphQL::Schema::Directive::INLINE_FRAGMENT
|
12
|
+
)
|
13
|
+
|
14
|
+
argument :if, Boolean, required: true,
|
15
|
+
description: "Included when true."
|
16
|
+
|
17
|
+
default_directive true
|
18
|
+
|
19
|
+
def self.include?(obj, args, ctx)
|
20
|
+
!!args[:if]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
class Schema
|
4
|
+
class Directive < GraphQL::Schema::Member
|
5
|
+
class Skip < Schema::Directive
|
6
|
+
description "Directs the executor to skip this field or fragment when the `if` argument is true."
|
7
|
+
|
8
|
+
locations(
|
9
|
+
GraphQL::Schema::Directive::FIELD,
|
10
|
+
GraphQL::Schema::Directive::FRAGMENT_SPREAD,
|
11
|
+
GraphQL::Schema::Directive::INLINE_FRAGMENT
|
12
|
+
)
|
13
|
+
|
14
|
+
argument :if, Boolean, required: true,
|
15
|
+
description: "Skipped when true."
|
16
|
+
|
17
|
+
default_directive true
|
18
|
+
|
19
|
+
def self.include?(obj, args, ctx)
|
20
|
+
!args[:if]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
class Schema
|
4
|
+
class Directive < GraphQL::Schema::Member
|
5
|
+
# An example directive to show how you might interact with the runtime.
|
6
|
+
#
|
7
|
+
# This directive takes the return value of the tagged part of the query,
|
8
|
+
# and if the named transform is whitelisted and applies to the return value,
|
9
|
+
# it's applied by calling a method with that name.
|
10
|
+
#
|
11
|
+
# @example Installing the directive
|
12
|
+
# class MySchema < GraphQL::Schema
|
13
|
+
# directive(GraphQL::Schema::Directive::Transform)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# @example Transforming strings
|
17
|
+
# viewer {
|
18
|
+
# username @transform(by: "upcase")
|
19
|
+
# }
|
20
|
+
class Transform < Schema::Directive
|
21
|
+
description "Directs the executor to run named transform on the return value."
|
22
|
+
|
23
|
+
locations(
|
24
|
+
GraphQL::Schema::Directive::FIELD,
|
25
|
+
)
|
26
|
+
|
27
|
+
argument :by, String, required: true,
|
28
|
+
description: "The name of the transform to run if applicable"
|
29
|
+
|
30
|
+
TRANSFORMS = [
|
31
|
+
"upcase",
|
32
|
+
"downcase",
|
33
|
+
# ??
|
34
|
+
]
|
35
|
+
# Implement the Directive API
|
36
|
+
def self.resolve(object, arguments, context)
|
37
|
+
path = context.namespace(:interpreter)[:current_path]
|
38
|
+
return_value = yield
|
39
|
+
transform_name = arguments[:by]
|
40
|
+
if TRANSFORMS.include?(transform_name) && return_value.respond_to?(transform_name)
|
41
|
+
return_value = return_value.public_send(transform_name)
|
42
|
+
context.namespace(:interpreter)[:runtime].write_in_response(path, return_value)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -40,7 +40,7 @@ module GraphQL
|
|
40
40
|
def initialize(graphql_name, desc = nil, owner:, description: nil, value: nil, deprecation_reason: nil, &block)
|
41
41
|
@graphql_name = graphql_name.to_s
|
42
42
|
@description = desc || description
|
43
|
-
@value = value
|
43
|
+
@value = value.nil? ? @graphql_name : value
|
44
44
|
@deprecation_reason = deprecation_reason
|
45
45
|
@owner = owner
|
46
46
|
|
@@ -57,7 +57,7 @@ module GraphQL
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def value(new_val = nil)
|
60
|
-
|
60
|
+
unless new_val.nil?
|
61
61
|
@value = new_val
|
62
62
|
end
|
63
63
|
@value
|
data/lib/graphql/schema/field.rb
CHANGED
@@ -20,15 +20,21 @@ module GraphQL
|
|
20
20
|
# @return [String, nil] If present, the field is marked as deprecated with this documentation
|
21
21
|
attr_accessor :deprecation_reason
|
22
22
|
|
23
|
-
# @return [Symbol] Method or hash key to look up
|
23
|
+
# @return [Symbol] Method or hash key on the underlying object to look up
|
24
24
|
attr_reader :method_sym
|
25
25
|
|
26
|
-
# @return [String] Method or hash key to look up
|
26
|
+
# @return [String] Method or hash key on the underlying object to look up
|
27
27
|
attr_reader :method_str
|
28
28
|
|
29
|
+
# @return [Symbol] The method on the type to look up
|
30
|
+
attr_reader :resolver_method
|
31
|
+
|
29
32
|
# @return [Class] The type that this field belongs to
|
30
33
|
attr_reader :owner
|
31
34
|
|
35
|
+
# @return [Symobol] the original name of the field, passed in by the user
|
36
|
+
attr_reader :original_name
|
37
|
+
|
32
38
|
# @return [Class, nil] The {Schema::Resolver} this field was derived from, if there is one
|
33
39
|
def resolver
|
34
40
|
@resolver_class
|
@@ -123,8 +129,9 @@ module GraphQL
|
|
123
129
|
# @param null [Boolean] `true` if this field may return `null`, `false` if it is never `null`
|
124
130
|
# @param description [String] Field description
|
125
131
|
# @param deprecation_reason [String] If present, the field is marked "deprecated" with this message
|
126
|
-
# @param method [Symbol] The method to call to resolve this field (defaults to `name`)
|
127
|
-
# @param hash_key [
|
132
|
+
# @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`)
|
133
|
+
# @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`)
|
134
|
+
# @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`)
|
128
135
|
# @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
|
129
136
|
# @param max_page_size [Integer] For connections, the maximum number of items to return from this field
|
130
137
|
# @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
|
@@ -139,7 +146,7 @@ module GraphQL
|
|
139
146
|
# @param subscription_scope [Symbol, String] A key in `context` which will be used to scope subscription payloads
|
140
147
|
# @param extensions [Array<Class>] Named extensions to apply to this field (see also {#extension})
|
141
148
|
# @param trace [Boolean] If true, a {GraphQL::Tracing} tracer will measure this scalar field
|
142
|
-
def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil,
|
149
|
+
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)
|
143
150
|
if name.nil?
|
144
151
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
145
152
|
end
|
@@ -154,6 +161,8 @@ module GraphQL
|
|
154
161
|
if (field || function || resolve) && extras.any?
|
155
162
|
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:`"
|
156
163
|
end
|
164
|
+
@original_name = name
|
165
|
+
@underscored_name = Member::BuildType.underscore(name.to_s)
|
157
166
|
@name = camelize ? Member::BuildType.camelize(name.to_s) : name.to_s
|
158
167
|
@description = description
|
159
168
|
if field.is_a?(GraphQL::Schema::Field)
|
@@ -164,15 +173,28 @@ module GraphQL
|
|
164
173
|
@function = function
|
165
174
|
@resolve = resolve
|
166
175
|
@deprecation_reason = deprecation_reason
|
176
|
+
|
167
177
|
if method && hash_key
|
168
178
|
raise ArgumentError, "Provide `method:` _or_ `hash_key:`, not both. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}`)"
|
169
179
|
end
|
170
180
|
|
181
|
+
if resolver_method
|
182
|
+
if method
|
183
|
+
raise ArgumentError, "Provide `method:` _or_ `resolver_method:`, not both. (called with: `method: #{method.inspect}, resolver_method: #{resolver_method.inspect}`)"
|
184
|
+
end
|
185
|
+
|
186
|
+
if hash_key
|
187
|
+
raise ArgumentError, "Provide `hash_key:` _or_ `resolver_method:`, not both. (called with: `hash_key: #{hash_key.inspect}, resolver_method: #{resolver_method.inspect}`)"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
171
191
|
# TODO: I think non-string/symbol hash keys are wrongly normalized (eg `1` will not work)
|
172
|
-
method_name = method || hash_key ||
|
192
|
+
method_name = method || hash_key || @underscored_name
|
193
|
+
resolver_method ||= @underscored_name
|
173
194
|
|
174
195
|
@method_str = method_name.to_s
|
175
196
|
@method_sym = method_name.to_sym
|
197
|
+
@resolver_method = resolver_method
|
176
198
|
@complexity = complexity
|
177
199
|
@return_type_expr = type
|
178
200
|
@return_type_null = null
|
@@ -396,7 +418,17 @@ MSG
|
|
396
418
|
true
|
397
419
|
end
|
398
420
|
|
399
|
-
self_auth
|
421
|
+
if self_auth
|
422
|
+
# Faster than `.any?`
|
423
|
+
arguments.each_value do |arg|
|
424
|
+
if !arg.authorized?(object, context)
|
425
|
+
return false
|
426
|
+
end
|
427
|
+
end
|
428
|
+
true
|
429
|
+
else
|
430
|
+
false
|
431
|
+
end
|
400
432
|
end
|
401
433
|
|
402
434
|
# Implement {GraphQL::Field}'s resolve API.
|
@@ -407,7 +439,9 @@ MSG
|
|
407
439
|
ctx.schema.after_lazy(obj) do |after_obj|
|
408
440
|
# First, apply auth ...
|
409
441
|
query_ctx = ctx.query.context
|
410
|
-
|
442
|
+
# Some legacy fields can have `nil` here, not exactly sure why.
|
443
|
+
# @see https://github.com/rmosolgo/graphql-ruby/issues/1990 before removing
|
444
|
+
inner_obj = after_obj && after_obj.object
|
411
445
|
if authorized?(inner_obj, query_ctx)
|
412
446
|
# Then if it passed, resolve the field
|
413
447
|
if @resolve_proc
|
@@ -417,7 +451,8 @@ MSG
|
|
417
451
|
public_send_field(after_obj, args, ctx)
|
418
452
|
end
|
419
453
|
else
|
420
|
-
|
454
|
+
err = GraphQL::UnauthorizedFieldError.new(object: inner_obj, type: obj.class, context: ctx, field: self)
|
455
|
+
query_ctx.schema.unauthorized_field(err)
|
421
456
|
end
|
422
457
|
end
|
423
458
|
end
|
@@ -448,13 +483,20 @@ MSG
|
|
448
483
|
extended_obj
|
449
484
|
end
|
450
485
|
|
451
|
-
|
452
|
-
|
453
|
-
|
486
|
+
if field_receiver.respond_to?(@resolver_method)
|
487
|
+
# Call the method with kwargs, if there are any
|
488
|
+
if extended_args.any?
|
489
|
+
field_receiver.public_send(@resolver_method, **extended_args)
|
490
|
+
else
|
491
|
+
field_receiver.public_send(@resolver_method)
|
492
|
+
end
|
454
493
|
else
|
455
|
-
field_receiver
|
494
|
+
resolve_field_method(field_receiver, extended_args, ctx)
|
456
495
|
end
|
457
496
|
end
|
497
|
+
else
|
498
|
+
err = GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
|
499
|
+
ctx.schema.unauthorized_field(err)
|
458
500
|
end
|
459
501
|
rescue GraphQL::UnauthorizedError => err
|
460
502
|
ctx.schema.unauthorized_object(err)
|
@@ -491,7 +533,7 @@ MSG
|
|
491
533
|
raise <<-ERR
|
492
534
|
Failed to implement #{@owner.graphql_name}.#{@name}, tried:
|
493
535
|
|
494
|
-
- `#{obj.class}##{@
|
536
|
+
- `#{obj.class}##{@resolver_method}`, which did not exist
|
495
537
|
- `#{obj.object.class}##{@method_sym}`, which did not exist
|
496
538
|
- Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
|
497
539
|
|
@@ -543,10 +585,14 @@ MSG
|
|
543
585
|
extended_obj = @resolver_class.new(object: extended_obj, context: query_ctx)
|
544
586
|
end
|
545
587
|
|
546
|
-
if
|
547
|
-
|
588
|
+
if extended_obj.respond_to?(@resolver_method)
|
589
|
+
if extended_args.any?
|
590
|
+
extended_obj.public_send(@resolver_method, **extended_args)
|
591
|
+
else
|
592
|
+
extended_obj.public_send(@resolver_method)
|
593
|
+
end
|
548
594
|
else
|
549
|
-
extended_obj
|
595
|
+
resolve_field_method(extended_obj, extended_args, query_ctx)
|
550
596
|
end
|
551
597
|
end
|
552
598
|
end
|
@@ -83,9 +83,11 @@ module GraphQL
|
|
83
83
|
# The default name is the Ruby constant name,
|
84
84
|
# without any namespaces and with any `-Type` suffix removed
|
85
85
|
def default_graphql_name
|
86
|
-
|
86
|
+
@default_graphql_name ||= begin
|
87
|
+
raise NotImplementedError, 'Anonymous class should declare a `graphql_name`' if name.nil?
|
87
88
|
|
88
|
-
|
89
|
+
name.split("::").last.sub(/Type\Z/, "")
|
90
|
+
end
|
89
91
|
end
|
90
92
|
|
91
93
|
def visible?(context)
|
@@ -33,7 +33,7 @@ module GraphQL
|
|
33
33
|
null = false
|
34
34
|
parse_type(type_expr[0..-2], null: true)
|
35
35
|
else
|
36
|
-
maybe_type =
|
36
|
+
maybe_type = constantize(type_expr)
|
37
37
|
case maybe_type
|
38
38
|
when GraphQL::BaseType
|
39
39
|
maybe_type
|
@@ -125,6 +125,38 @@ module GraphQL
|
|
125
125
|
camelized
|
126
126
|
end
|
127
127
|
|
128
|
+
# Resolves constant from string (based on Rails `ActiveSupport::Inflector.constantize`)
|
129
|
+
def constantize(string)
|
130
|
+
names = string.split('::')
|
131
|
+
|
132
|
+
# Trigger a built-in NameError exception including the ill-formed constant in the message.
|
133
|
+
Object.const_get(string) if names.empty?
|
134
|
+
|
135
|
+
# Remove the first blank element in case of '::ClassName' notation.
|
136
|
+
names.shift if names.size > 1 && names.first.empty?
|
137
|
+
|
138
|
+
names.inject(Object) do |constant, name|
|
139
|
+
if constant == Object
|
140
|
+
constant.const_get(name)
|
141
|
+
else
|
142
|
+
candidate = constant.const_get(name)
|
143
|
+
next candidate if constant.const_defined?(name, false)
|
144
|
+
next candidate unless Object.const_defined?(name)
|
145
|
+
|
146
|
+
# Go down the ancestors to check if it is owned directly. The check
|
147
|
+
# stops when we reach Object or the end of ancestors tree.
|
148
|
+
constant = constant.ancestors.inject do |const, ancestor|
|
149
|
+
break const if ancestor == Object
|
150
|
+
break ancestor if ancestor.const_defined?(name, false)
|
151
|
+
const
|
152
|
+
end
|
153
|
+
|
154
|
+
# Owner is in Object, so raise.
|
155
|
+
constant.const_get(name, false)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
128
160
|
def underscore(string)
|
129
161
|
string
|
130
162
|
.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2') # URLDecoder -> URL_Decoder
|
@@ -4,47 +4,6 @@ module GraphQL
|
|
4
4
|
class Member
|
5
5
|
# Shared code for Object and Interface
|
6
6
|
module HasFields
|
7
|
-
class << self
|
8
|
-
# When this module is added to a class,
|
9
|
-
# add a place for that class's default behaviors
|
10
|
-
def self.extended(child_class)
|
11
|
-
add_default_resolve_module(child_class)
|
12
|
-
super
|
13
|
-
end
|
14
|
-
|
15
|
-
# Create a module which will have instance methods for implementing fields.
|
16
|
-
# These will be `super` methods for fields in interfaces, objects and mutations.
|
17
|
-
# Use an instance variable on the class instead of a constant
|
18
|
-
# so that module namespaces won't be an issue. (If we used constants,
|
19
|
-
# `child_class::DefaultResolve` might find a constant from an included module.)
|
20
|
-
def add_default_resolve_module(child_class)
|
21
|
-
if child_class.instance_variable_get(:@_default_resolve)
|
22
|
-
# This can happen when an object implements an interface,
|
23
|
-
# since that interface has the `included` hook above.
|
24
|
-
return
|
25
|
-
end
|
26
|
-
|
27
|
-
default_resolve_module = Module.new
|
28
|
-
child_class.instance_variable_set(:@_default_resolve, default_resolve_module)
|
29
|
-
child_class.include(default_resolve_module)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
# When this is included into interfaces,
|
34
|
-
# add a place for default field behaviors
|
35
|
-
def included(child_class)
|
36
|
-
HasFields.add_default_resolve_module(child_class)
|
37
|
-
# Also, prepare a place for default field implementations
|
38
|
-
super
|
39
|
-
end
|
40
|
-
|
41
|
-
# When a subclass of objects are created,
|
42
|
-
# add a place for that subclass's default field behaviors
|
43
|
-
def inherited(child_class)
|
44
|
-
HasFields.add_default_resolve_module(child_class)
|
45
|
-
super
|
46
|
-
end
|
47
|
-
|
48
7
|
# Add a field to this object or interface with the given definition
|
49
8
|
# @see {GraphQL::Schema::Field#initialize} for method signature
|
50
9
|
# @return [void]
|
@@ -67,24 +26,23 @@ module GraphQL
|
|
67
26
|
end
|
68
27
|
|
69
28
|
def get_field(field_name)
|
70
|
-
|
71
|
-
|
72
|
-
|
29
|
+
if (f = own_fields[field_name])
|
30
|
+
f
|
31
|
+
else
|
32
|
+
for ancestor in ancestors
|
33
|
+
if ancestor.respond_to?(:own_fields) && f = ancestor.own_fields[field_name]
|
34
|
+
return f
|
35
|
+
end
|
73
36
|
end
|
37
|
+
nil
|
74
38
|
end
|
75
|
-
nil
|
76
39
|
end
|
77
40
|
|
78
41
|
# Register this field with the class, overriding a previous one if needed.
|
79
|
-
# Also, add a parent method for resolving this field.
|
80
42
|
# @param field_defn [GraphQL::Schema::Field]
|
81
43
|
# @return [void]
|
82
44
|
def add_field(field_defn)
|
83
45
|
own_fields[field_defn.name] = field_defn
|
84
|
-
if !method_defined?(field_defn.method_sym)
|
85
|
-
# Only add the super method if there isn't one already.
|
86
|
-
add_super_method(field_defn.name.inspect, field_defn.method_sym)
|
87
|
-
end
|
88
46
|
nil
|
89
47
|
end
|
90
48
|
|
@@ -114,29 +72,6 @@ module GraphQL
|
|
114
72
|
def own_fields
|
115
73
|
@own_fields ||= {}
|
116
74
|
end
|
117
|
-
|
118
|
-
private
|
119
|
-
# Find the magic module for holding super methods,
|
120
|
-
# and add a field named `method_name` for implementing the field
|
121
|
-
# called `field_name`.
|
122
|
-
# It will be the `super` method if the method is overwritten in the class definition.
|
123
|
-
def add_super_method(field_key, method_name)
|
124
|
-
default_resolve_module = @_default_resolve
|
125
|
-
if default_resolve_module.nil?
|
126
|
-
# This should have been set up in one of the inherited or included hooks above,
|
127
|
-
# if it wasn't, it's because those hooks weren't called because `super` wasn't present.
|
128
|
-
raise <<-ERR
|
129
|
-
Uh oh! #{self} doesn't have a default resolve module. This probably means that an `inherited` hook didn't call super.
|
130
|
-
Check `inherited` on #{self}'s superclasses.
|
131
|
-
ERR
|
132
|
-
end
|
133
|
-
default_resolve_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
134
|
-
def #{method_name}(**args)
|
135
|
-
field_inst = self.class.get_field(#{field_key}) || raise(%|Failed to find field #{field_key} for \#{self.class} among \#{self.class.fields.keys}|)
|
136
|
-
field_inst.resolve_field_method(self, args, context)
|
137
|
-
end
|
138
|
-
RUBY
|
139
|
-
end
|
140
75
|
end
|
141
76
|
end
|
142
77
|
end
|