graphql 1.9.0.pre1 → 1.9.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|