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.
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