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.
Files changed (235) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +5 -1
  3. data/lib/graphql/analysis/ast/analyzer.rb +1 -1
  4. data/lib/graphql/analysis/ast/visitor.rb +6 -1
  5. data/lib/graphql/backwards_compatibility.rb +1 -1
  6. data/lib/graphql/dig.rb +19 -0
  7. data/lib/graphql/directive.rb +13 -1
  8. data/lib/graphql/directive/include_directive.rb +1 -7
  9. data/lib/graphql/directive/skip_directive.rb +1 -8
  10. data/lib/graphql/execution/interpreter.rb +23 -13
  11. data/lib/graphql/execution/interpreter/resolve.rb +56 -0
  12. data/lib/graphql/execution/interpreter/runtime.rb +174 -74
  13. data/lib/graphql/execution/lazy.rb +7 -1
  14. data/lib/graphql/execution/lookahead.rb +71 -6
  15. data/lib/graphql/execution_error.rb +1 -1
  16. data/lib/graphql/introspection/entry_points.rb +5 -1
  17. data/lib/graphql/introspection/type_type.rb +4 -4
  18. data/lib/graphql/language.rb +0 -1
  19. data/lib/graphql/language/block_string.rb +37 -0
  20. data/lib/graphql/language/document_from_schema_definition.rb +1 -1
  21. data/lib/graphql/language/lexer.rb +55 -36
  22. data/lib/graphql/language/lexer.rl +8 -3
  23. data/lib/graphql/language/nodes.rb +27 -4
  24. data/lib/graphql/language/parser.rb +55 -55
  25. data/lib/graphql/language/parser.y +11 -11
  26. data/lib/graphql/language/printer.rb +1 -1
  27. data/lib/graphql/language/visitor.rb +22 -13
  28. data/lib/graphql/literal_validation_error.rb +6 -0
  29. data/lib/graphql/query.rb +13 -0
  30. data/lib/graphql/query/arguments.rb +2 -1
  31. data/lib/graphql/query/context.rb +3 -10
  32. data/lib/graphql/query/executor.rb +1 -1
  33. data/lib/graphql/query/validation_pipeline.rb +1 -1
  34. data/lib/graphql/relay/connection_resolve.rb +1 -1
  35. data/lib/graphql/relay/relation_connection.rb +1 -1
  36. data/lib/graphql/schema.rb +81 -11
  37. data/lib/graphql/schema/argument.rb +1 -1
  38. data/lib/graphql/schema/build_from_definition.rb +2 -4
  39. data/lib/graphql/schema/directive.rb +103 -0
  40. data/lib/graphql/schema/directive/feature.rb +66 -0
  41. data/lib/graphql/schema/directive/include.rb +25 -0
  42. data/lib/graphql/schema/directive/skip.rb +25 -0
  43. data/lib/graphql/schema/directive/transform.rb +48 -0
  44. data/lib/graphql/schema/enum_value.rb +2 -2
  45. data/lib/graphql/schema/field.rb +63 -17
  46. data/lib/graphql/schema/input_object.rb +1 -0
  47. data/lib/graphql/schema/member/base_dsl_methods.rb +4 -2
  48. data/lib/graphql/schema/member/build_type.rb +33 -1
  49. data/lib/graphql/schema/member/has_fields.rb +8 -73
  50. data/lib/graphql/schema/relay_classic_mutation.rb +6 -1
  51. data/lib/graphql/schema/resolver.rb +1 -1
  52. data/lib/graphql/static_validation.rb +2 -1
  53. data/lib/graphql/static_validation/all_rules.rb +1 -0
  54. data/lib/graphql/static_validation/base_visitor.rb +25 -10
  55. data/lib/graphql/static_validation/definition_dependencies.rb +3 -3
  56. data/lib/graphql/static_validation/{message.rb → error.rb} +11 -11
  57. data/lib/graphql/static_validation/interpreter_visitor.rb +14 -0
  58. data/lib/graphql/static_validation/literal_validator.rb +54 -11
  59. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +34 -5
  60. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +31 -0
  61. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +2 -2
  62. data/lib/graphql/static_validation/rules/argument_names_are_unique_error.rb +30 -0
  63. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +7 -1
  64. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +35 -0
  65. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -1
  66. data/lib/graphql/static_validation/rules/directives_are_defined_error.rb +29 -0
  67. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +11 -2
  68. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations_error.rb +31 -0
  69. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +11 -2
  70. data/lib/graphql/static_validation/rules/fields_are_defined_on_type_error.rb +32 -0
  71. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -2
  72. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections_error.rb +31 -0
  73. data/lib/graphql/static_validation/rules/fields_will_merge.rb +24 -6
  74. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +32 -0
  75. data/lib/graphql/static_validation/rules/fragment_names_are_unique.rb +5 -1
  76. data/lib/graphql/static_validation/rules/fragment_names_are_unique_error.rb +29 -0
  77. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +8 -1
  78. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible_error.rb +35 -0
  79. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +5 -1
  80. data/lib/graphql/static_validation/rules/fragment_types_exist_error.rb +29 -0
  81. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +6 -1
  82. data/lib/graphql/static_validation/rules/fragments_are_finite_error.rb +29 -0
  83. data/lib/graphql/static_validation/rules/fragments_are_named.rb +4 -1
  84. data/lib/graphql/static_validation/rules/fragments_are_named_error.rb +26 -0
  85. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +5 -1
  86. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types_error.rb +30 -0
  87. data/lib/graphql/static_validation/rules/fragments_are_used.rb +13 -3
  88. data/lib/graphql/static_validation/rules/fragments_are_used_error.rb +29 -0
  89. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +4 -1
  90. data/lib/graphql/static_validation/rules/mutation_root_exists_error.rb +26 -0
  91. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +2 -2
  92. data/lib/graphql/static_validation/rules/no_definitions_are_present_error.rb +25 -0
  93. data/lib/graphql/static_validation/rules/operation_names_are_valid.rb +9 -2
  94. data/lib/graphql/static_validation/rules/operation_names_are_valid_error.rb +28 -0
  95. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +7 -1
  96. data/lib/graphql/static_validation/rules/required_arguments_are_present_error.rb +35 -0
  97. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +47 -0
  98. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present_error.rb +35 -0
  99. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +4 -1
  100. data/lib/graphql/static_validation/rules/subscription_root_exists_error.rb +26 -0
  101. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +4 -3
  102. data/lib/graphql/static_validation/rules/unique_directives_per_location_error.rb +29 -0
  103. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +20 -6
  104. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed_error.rb +39 -0
  105. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +5 -1
  106. data/lib/graphql/static_validation/rules/variable_names_are_unique_error.rb +29 -0
  107. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +8 -1
  108. data/lib/graphql/static_validation/rules/variable_usages_are_allowed_error.rb +38 -0
  109. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -2
  110. data/lib/graphql/static_validation/rules/variables_are_input_types_error.rb +32 -0
  111. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +18 -2
  112. data/lib/graphql/static_validation/rules/variables_are_used_and_defined_error.rb +37 -0
  113. data/lib/graphql/static_validation/validator.rb +24 -14
  114. data/lib/graphql/tracing/new_relic_tracing.rb +2 -2
  115. data/lib/graphql/tracing/skylight_tracing.rb +2 -2
  116. data/lib/graphql/unauthorized_field_error.rb +23 -0
  117. data/lib/graphql/version.rb +1 -1
  118. data/spec/graphql/analysis/ast_spec.rb +40 -0
  119. data/spec/graphql/authorization_spec.rb +93 -20
  120. data/spec/graphql/base_type_spec.rb +3 -1
  121. data/spec/graphql/execution/interpreter_spec.rb +127 -4
  122. data/spec/graphql/execution/lazy_spec.rb +49 -0
  123. data/spec/graphql/execution/lookahead_spec.rb +113 -21
  124. data/spec/graphql/execution/multiplex_spec.rb +2 -1
  125. data/spec/graphql/introspection/type_type_spec.rb +1 -1
  126. data/spec/graphql/language/lexer_spec.rb +72 -3
  127. data/spec/graphql/language/printer_spec.rb +18 -6
  128. data/spec/graphql/query/arguments_spec.rb +21 -0
  129. data/spec/graphql/query/context_spec.rb +10 -0
  130. data/spec/graphql/schema/build_from_definition_spec.rb +144 -29
  131. data/spec/graphql/schema/directive/feature_spec.rb +81 -0
  132. data/spec/graphql/schema/directive/transform_spec.rb +39 -0
  133. data/spec/graphql/schema/enum_spec.rb +5 -3
  134. data/spec/graphql/schema/field_extension_spec.rb +3 -3
  135. data/spec/graphql/schema/field_spec.rb +19 -0
  136. data/spec/graphql/schema/input_object_spec.rb +81 -0
  137. data/spec/graphql/schema/member/build_type_spec.rb +46 -0
  138. data/spec/graphql/schema/member/scoped_spec.rb +3 -3
  139. data/spec/graphql/schema/printer_spec.rb +244 -96
  140. data/spec/graphql/schema/relay_classic_mutation_spec.rb +26 -0
  141. data/spec/graphql/schema/resolver_spec.rb +1 -1
  142. data/spec/graphql/schema/warden_spec.rb +35 -11
  143. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +212 -72
  144. data/spec/graphql/static_validation/rules/argument_names_are_unique_spec.rb +2 -2
  145. data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +72 -29
  146. data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +4 -2
  147. data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +4 -2
  148. data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +10 -5
  149. data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +10 -5
  150. data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +2 -1
  151. data/spec/graphql/static_validation/rules/fragment_names_are_unique_spec.rb +2 -1
  152. data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +6 -3
  153. data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +4 -2
  154. data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +4 -2
  155. data/spec/graphql/static_validation/rules/fragments_are_named_spec.rb +2 -1
  156. data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +6 -3
  157. data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +22 -2
  158. data/spec/graphql/static_validation/rules/mutation_root_exists_spec.rb +2 -1
  159. data/spec/graphql/static_validation/rules/operation_names_are_valid_spec.rb +6 -3
  160. data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +13 -4
  161. data/spec/graphql/static_validation/rules/required_input_object_attributes_are_present_spec.rb +58 -0
  162. data/spec/graphql/static_validation/rules/subscription_root_exists_spec.rb +2 -1
  163. data/spec/graphql/static_validation/rules/unique_directives_per_location_spec.rb +14 -7
  164. data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +14 -7
  165. data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +8 -4
  166. data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +8 -4
  167. data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +6 -3
  168. data/spec/graphql/static_validation/validator_spec.rb +6 -4
  169. data/spec/graphql/tracing/new_relic_tracing_spec.rb +10 -0
  170. data/spec/graphql/tracing/skylight_tracing_spec.rb +10 -0
  171. data/spec/graphql/types/iso_8601_date_time_spec.rb +1 -2
  172. data/spec/integration/mongoid/star_trek/schema.rb +5 -5
  173. data/spec/integration/rails/graphql/relay/relation_connection_spec.rb +37 -8
  174. data/spec/integration/rails/graphql/schema_spec.rb +2 -2
  175. data/spec/integration/rails/spec_helper.rb +10 -0
  176. data/spec/integration/tmp/app/graphql/types/bird_type.rb +7 -0
  177. data/spec/integration/tmp/dummy/Gemfile +45 -0
  178. data/spec/integration/tmp/dummy/README.rdoc +28 -0
  179. data/spec/integration/tmp/dummy/Rakefile +6 -0
  180. data/spec/integration/tmp/dummy/app/assets/javascripts/application.js +16 -0
  181. data/spec/integration/tmp/dummy/app/assets/stylesheets/application.css +15 -0
  182. data/spec/integration/tmp/dummy/app/controllers/application_controller.rb +5 -0
  183. data/spec/integration/tmp/dummy/app/controllers/graphql_controller.rb +43 -0
  184. data/spec/integration/tmp/dummy/app/graphql/dummy_schema.rb +34 -0
  185. data/spec/integration/tmp/dummy/app/graphql/types/base_enum.rb +4 -0
  186. data/spec/integration/tmp/dummy/app/graphql/types/base_input_object.rb +4 -0
  187. data/spec/integration/tmp/dummy/app/graphql/types/base_interface.rb +5 -0
  188. data/spec/integration/tmp/dummy/app/graphql/types/base_object.rb +4 -0
  189. data/spec/integration/tmp/dummy/app/graphql/types/base_scalar.rb +4 -0
  190. data/spec/integration/tmp/dummy/app/graphql/types/base_union.rb +4 -0
  191. data/spec/integration/tmp/dummy/app/graphql/types/mutation_type.rb +10 -0
  192. data/spec/integration/tmp/dummy/app/graphql/types/query_type.rb +15 -0
  193. data/spec/integration/tmp/dummy/app/helpers/application_helper.rb +2 -0
  194. data/spec/integration/tmp/dummy/app/views/layouts/application.html.erb +14 -0
  195. data/spec/integration/tmp/dummy/bin/bundle +3 -0
  196. data/spec/integration/tmp/dummy/bin/rails +4 -0
  197. data/spec/integration/tmp/dummy/bin/rake +4 -0
  198. data/spec/integration/tmp/dummy/bin/setup +29 -0
  199. data/spec/integration/tmp/dummy/config.ru +4 -0
  200. data/spec/integration/tmp/dummy/config/application.rb +32 -0
  201. data/spec/integration/tmp/dummy/config/boot.rb +3 -0
  202. data/spec/integration/tmp/dummy/config/environment.rb +5 -0
  203. data/spec/integration/tmp/dummy/config/environments/development.rb +38 -0
  204. data/spec/integration/tmp/dummy/config/environments/production.rb +76 -0
  205. data/spec/integration/tmp/dummy/config/environments/test.rb +42 -0
  206. data/spec/integration/tmp/dummy/config/initializers/assets.rb +11 -0
  207. data/spec/integration/tmp/dummy/config/initializers/backtrace_silencers.rb +7 -0
  208. data/spec/integration/tmp/dummy/config/initializers/cookies_serializer.rb +3 -0
  209. data/spec/integration/tmp/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  210. data/spec/integration/tmp/dummy/config/initializers/inflections.rb +16 -0
  211. data/spec/integration/tmp/dummy/config/initializers/mime_types.rb +4 -0
  212. data/spec/integration/tmp/dummy/config/initializers/session_store.rb +3 -0
  213. data/spec/integration/tmp/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
  214. data/spec/integration/tmp/dummy/config/initializers/wrap_parameters.rb +9 -0
  215. data/spec/integration/tmp/dummy/config/locales/en.yml +23 -0
  216. data/spec/integration/tmp/dummy/config/routes.rb +61 -0
  217. data/spec/integration/tmp/dummy/config/secrets.yml +22 -0
  218. data/spec/integration/tmp/dummy/db/seeds.rb +7 -0
  219. data/spec/integration/tmp/dummy/public/404.html +67 -0
  220. data/spec/integration/tmp/dummy/public/422.html +67 -0
  221. data/spec/integration/tmp/dummy/public/500.html +66 -0
  222. data/spec/integration/tmp/dummy/public/favicon.ico +0 -0
  223. data/spec/integration/tmp/dummy/public/robots.txt +5 -0
  224. data/spec/support/dummy/schema.rb +2 -2
  225. data/spec/support/error_bubbling_helpers.rb +23 -0
  226. data/spec/support/jazz.rb +53 -6
  227. data/spec/support/lazy_helpers.rb +26 -8
  228. data/spec/support/new_relic.rb +3 -0
  229. data/spec/support/skylight.rb +3 -0
  230. data/spec/support/star_wars/schema.rb +13 -9
  231. data/spec/support/static_validation_helpers.rb +3 -1
  232. metadata +145 -22
  233. data/lib/graphql/language/comments.rb +0 -45
  234. data/spec/graphql/schema/member/has_fields_spec.rb +0 -132
  235. 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 || @graphql_name
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
- if new_val
60
+ unless new_val.nil?
61
61
  @value = new_val
62
62
  end
63
63
  @value
@@ -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 [Object] The hash key to lookup to resolve this field (defaults to `name` or `name.to_s`)
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, connection: nil, max_page_size: nil, scope: nil, resolve: nil, introspection: false, hash_key: nil, 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)
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 || Member::BuildType.underscore(name.to_s)
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 && arguments.each_value.all? { |a| a.authorized?(object, context) }
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
- inner_obj = after_obj.object
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
- nil
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
- # Call the method with kwargs, if there are any
452
- if extended_args.any?
453
- field_receiver.public_send(method_sym, extended_args)
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.public_send(method_sym)
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}##{@method_sym}`, which did not exist
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 extended_args.any?
547
- extended_obj.public_send(@method_sym, **extended_args)
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.public_send(@method_sym)
595
+ resolve_field_method(extended_obj, extended_args, query_ctx)
550
596
  end
551
597
  end
552
598
  end
@@ -5,6 +5,7 @@ module GraphQL
5
5
  extend GraphQL::Schema::Member::AcceptsDefinition
6
6
  extend Forwardable
7
7
  extend GraphQL::Schema::Member::HasArguments
8
+ include GraphQL::Dig
8
9
 
9
10
  def initialize(values = nil, ruby_kwargs: nil, context:, defaults_used:)
10
11
  @context = context
@@ -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
- raise NotImplementedError, 'Anonymous class should declare a `graphql_name`' if name.nil?
86
+ @default_graphql_name ||= begin
87
+ raise NotImplementedError, 'Anonymous class should declare a `graphql_name`' if name.nil?
87
88
 
88
- name.split("::").last.sub(/Type\Z/, "")
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 = Object.const_get(type_expr)
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
- for ancestor in ancestors
71
- if ancestor.respond_to?(:own_fields) && f = ancestor.own_fields[field_name]
72
- return f
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