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
@@ -17,8 +17,8 @@ module GraphQL
17
17
  # @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
18
18
  # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
19
19
  # It can also be specified per-query with `context[:set_new_relic_transaction_name]`.
20
- def initialize(set_transaction_name: false)
21
- @set_transaction_name = set_transaction_name
20
+ def initialize(options = {})
21
+ @set_transaction_name = options.fetch(:set_transaction_name, false)
22
22
  super
23
23
  end
24
24
 
@@ -17,8 +17,8 @@ module GraphQL
17
17
  # @param set_endpoint_name [Boolean] If true, the GraphQL operation name will be used as the endpoint name.
18
18
  # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
19
19
  # It can also be specified per-query with `context[:set_skylight_endpoint_name]`.
20
- def initialize(set_endpoint_name: false)
21
- @set_endpoint_name = set_endpoint_name
20
+ def initialize(options = {})
21
+ @set_endpoint_name = options.fetch(:set_endpoint_name, false)
22
22
  super
23
23
  end
24
24
 
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class UnauthorizedFieldError < GraphQL::UnauthorizedError
4
+ # @return [Field] the field that failed the authorization check
5
+ attr_reader :field
6
+
7
+ def initialize(message = nil, object: nil, type: nil, context: nil, field: nil)
8
+ if message.nil? && [field, type].any?(&:nil?)
9
+ raise ArgumentError, "#{self.class.name} requires either a message or keywords"
10
+ end
11
+
12
+ @field = field
13
+ message ||= begin
14
+ if object
15
+ "An instance of #{object.class} failed #{type.name}'s authorization check on field #{field.name}"
16
+ else
17
+ "Failed #{type.name}'s authorization check on field #{field.name}"
18
+ end
19
+ end
20
+ super(message, object: object, type: type, context: context)
21
+ end
22
+ end
23
+ end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.9.0.pre1"
3
+ VERSION = "1.9.0.pre2"
4
4
  end
@@ -61,6 +61,16 @@ describe GraphQL::Analysis::AST do
61
61
  end
62
62
  end
63
63
 
64
+ class AstPreviousField < GraphQL::Analysis::AST::Analyzer
65
+ def on_enter_field(node, parent, visitor)
66
+ @previous_field = visitor.previous_field_definition
67
+ end
68
+
69
+ def result
70
+ @previous_field
71
+ end
72
+ end
73
+
64
74
  describe "using the AST analysis engine" do
65
75
  let(:schema) do
66
76
  query_type = Class.new(GraphQL::Schema::Object) do
@@ -77,6 +87,7 @@ describe GraphQL::Analysis::AST do
77
87
  query query_type
78
88
  use GraphQL::Analysis::AST
79
89
  query_analyzer AstErrorAnalyzer
90
+ use GraphQL::Execution::Interpreter
80
91
  end
81
92
  end
82
93
 
@@ -89,7 +100,26 @@ describe GraphQL::Analysis::AST do
89
100
  let(:query) { GraphQL::Query.new(schema, query_string, variables: {}) }
90
101
 
91
102
  it "runs the AST analyzers correctly" do
103
+ res = query.result
104
+ refute res.key?("data")
105
+ assert_equal ["An Error!"], res["errors"].map { |e| e["message"] }
106
+ end
107
+
108
+ it "skips rewrite" do
109
+ # Try running the query:
92
110
  query.result
111
+ # But the validation step doesn't build an irep_node tree
112
+ assert_nil query.irep_selection
113
+ end
114
+
115
+ describe "when validate: false" do
116
+ let(:query) { GraphQL::Query.new(schema, query_string, validate: false) }
117
+ it "Skips rewrite" do
118
+ # Try running the query:
119
+ query.result
120
+ # But the validation step doesn't build an irep_node tree
121
+ assert_nil query.irep_selection
122
+ end
93
123
  end
94
124
  end
95
125
 
@@ -127,6 +157,16 @@ describe GraphQL::Analysis::AST do
127
157
  assert_equal 2, reduce_result.size
128
158
  end
129
159
  end
160
+
161
+ describe "Visitor#previous_field_definition" do
162
+ let(:analyzers) { [AstPreviousField] }
163
+ let(:query) { GraphQL::Query.new(Dummy::Schema, "{ __schema { types { name } } }") }
164
+
165
+ it "it runs the analyzer" do
166
+ prev_field = reduce_result.first
167
+ assert_equal "__Schema.types", prev_field.metadata[:type_class].path
168
+ end
169
+ end
130
170
  end
131
171
 
132
172
  it "calls the defined analyzers" do
@@ -48,7 +48,7 @@ describe GraphQL::Authorization do
48
48
  end
49
49
 
50
50
  def authorized?(object, context)
51
- super && object != :hide
51
+ super && object != :hide && object != :replace
52
52
  end
53
53
  end
54
54
 
@@ -159,7 +159,7 @@ describe GraphQL::Authorization do
159
159
  super && value != "a"
160
160
  end
161
161
 
162
- field :value, String, null: false, method: :object
162
+ field :value, String, null: false, method: :itself
163
163
  end
164
164
 
165
165
  module UnauthorizedInterface
@@ -186,7 +186,7 @@ describe GraphQL::Authorization do
186
186
  Box.new(value: Box.new(value: Box.new(value: Box.new(value: is_authed))))
187
187
  end
188
188
 
189
- field :value, String, null: false, method: :object
189
+ field :value, String, null: false, method: :itself
190
190
  end
191
191
 
192
192
  class IntegerObject < BaseObject
@@ -197,7 +197,7 @@ describe GraphQL::Authorization do
197
197
  is_allowed = !(ctx[:unauthorized_relay] || obj == ctx[:exclude_integer])
198
198
  Box.new(value: Box.new(value: is_allowed))
199
199
  end
200
- field :value, Integer, null: false, method: :object
200
+ field :value, Integer, null: false, method: :itself
201
201
  end
202
202
 
203
203
  class IntegerObjectEdge < GraphQL::Types::Relay::BaseEdge
@@ -237,7 +237,7 @@ describe GraphQL::Authorization do
237
237
 
238
238
  class Query < BaseObject
239
239
  field :hidden, Integer, null: false
240
- field :unauthorized, Integer, null: true, method: :object
240
+ field :unauthorized, Integer, null: true, method: :itself
241
241
  field :int2, Integer, null: true do
242
242
  argument :int, Integer, required: false
243
243
  argument :hidden, Integer, required: false
@@ -268,22 +268,22 @@ describe GraphQL::Authorization do
268
268
  end
269
269
 
270
270
  def empty_array; []; end
271
- field :hidden_object, HiddenObject, null: false, method: :itself
272
- field :hidden_interface, HiddenInterface, null: false, method: :itself
273
- field :hidden_default_interface, HiddenDefaultInterface, null: false, method: :itself
274
- field :hidden_connection, RelayObject.connection_type, null: :false, method: :empty_array
275
- field :hidden_edge, RelayObject.edge_type, null: :false, method: :edge_object
271
+ field :hidden_object, HiddenObject, null: false, resolver_method: :itself
272
+ field :hidden_interface, HiddenInterface, null: false, resolver_method: :itself
273
+ field :hidden_default_interface, HiddenDefaultInterface, null: false, resolver_method: :itself
274
+ field :hidden_connection, RelayObject.connection_type, null: :false, resolver_method: :empty_array
275
+ field :hidden_edge, RelayObject.edge_type, null: :false, resolver_method: :edge_object
276
276
 
277
277
  field :inaccessible, Integer, null: false, method: :object_id
278
- field :inaccessible_object, InaccessibleObject, null: false, method: :itself
279
- field :inaccessible_interface, InaccessibleInterface, null: false, method: :itself
280
- field :inaccessible_default_interface, InaccessibleDefaultInterface, null: false, method: :itself
281
- field :inaccessible_connection, RelayObject.connection_type, null: :false, method: :empty_array
282
- field :inaccessible_edge, RelayObject.edge_type, null: :false, method: :edge_object
278
+ field :inaccessible_object, InaccessibleObject, null: false, resolver_method: :itself
279
+ field :inaccessible_interface, InaccessibleInterface, null: false, resolver_method: :itself
280
+ field :inaccessible_default_interface, InaccessibleDefaultInterface, null: false, resolver_method: :itself
281
+ field :inaccessible_connection, RelayObject.connection_type, null: :false, resolver_method: :empty_array
282
+ field :inaccessible_edge, RelayObject.edge_type, null: :false, resolver_method: :edge_object
283
283
 
284
- field :unauthorized_object, UnauthorizedObject, null: true, method: :itself
285
- field :unauthorized_connection, RelayObject.connection_type, null: false, method: :array_with_item
286
- field :unauthorized_edge, RelayObject.edge_type, null: false, method: :edge_object
284
+ field :unauthorized_object, UnauthorizedObject, null: true, resolver_method: :itself
285
+ field :unauthorized_connection, RelayObject.connection_type, null: false, resolver_method: :array_with_item
286
+ field :unauthorized_edge, RelayObject.edge_type, null: false, resolver_method: :edge_object
287
287
 
288
288
  def edge_object
289
289
  OpenStruct.new(node: 100)
@@ -305,11 +305,11 @@ describe GraphQL::Authorization do
305
305
  [self, self]
306
306
  end
307
307
 
308
- field :unauthorized_lazy_check_box, UnauthorizedCheckBox, null: true, method: :unauthorized_lazy_box do
308
+ field :unauthorized_lazy_check_box, UnauthorizedCheckBox, null: true, resolver_method: :unauthorized_lazy_box do
309
309
  argument :value, String, required: true
310
310
  end
311
311
 
312
- field :unauthorized_interface, UnauthorizedInterface, null: true, method: :unauthorized_lazy_box do
312
+ field :unauthorized_interface, UnauthorizedInterface, null: true, resolver_method: :unauthorized_lazy_box do
313
313
  argument :value, String, required: true
314
314
  end
315
315
 
@@ -383,6 +383,8 @@ describe GraphQL::Authorization do
383
383
  def self.unauthorized_object(err)
384
384
  if err.object.respond_to?(:replacement)
385
385
  err.object.replacement
386
+ elsif err.object == :replace
387
+ 33
386
388
  else
387
389
  raise GraphQL::ExecutionError, "Unauthorized #{err.type.graphql_name}: #{err.object}"
388
390
  end
@@ -390,6 +392,18 @@ describe GraphQL::Authorization do
390
392
 
391
393
  # use GraphQL::Backtrace
392
394
  end
395
+
396
+ class SchemaWithFieldHook < GraphQL::Schema
397
+ query(Query)
398
+
399
+ def self.unauthorized_field(err)
400
+ if err.object == :replace
401
+ 42
402
+ else
403
+ raise GraphQL::ExecutionError, "Unauthorized field #{err.field.graphql_name} on #{err.type.graphql_name}: #{err.object}"
404
+ end
405
+ end
406
+ end
393
407
  end
394
408
 
395
409
  def auth_execute(*args)
@@ -621,6 +635,65 @@ describe GraphQL::Authorization do
621
635
  end
622
636
  end
623
637
 
638
+ describe "field level authorization" do
639
+ describe "unauthorized field" do
640
+ describe "with an unauthorized field hook configured" do
641
+ describe "when the hook returns a value" do
642
+ it "replaces the response with the return value of the unauthorized field hook" do
643
+ query = "{ unauthorized }"
644
+ response = AuthTest::SchemaWithFieldHook.execute(query, root_value: :replace)
645
+ assert_equal 42, response["data"].fetch("unauthorized")
646
+ end
647
+ end
648
+
649
+ describe "when the field hook raises an error" do
650
+ it "returns nil" do
651
+ query = "{ unauthorized }"
652
+ response = AuthTest::SchemaWithFieldHook.execute(query, root_value: :hide)
653
+ assert_nil response["data"].fetch("unauthorized")
654
+ end
655
+
656
+ it "adds the error to the errors key" do
657
+ query = "{ unauthorized }"
658
+ response = AuthTest::SchemaWithFieldHook.execute(query, root_value: :hide)
659
+ assert_equal ["Unauthorized field unauthorized on Query: hide"], response["errors"].map { |e| e["message"] }
660
+ end
661
+ end
662
+ end
663
+
664
+ describe "with an unauthorized field hook not configured" do
665
+ describe "When the object hook replaces the field" do
666
+ it "delegates to the unauthorized object hook, which replaces the object" do
667
+ query = "{ unauthorized }"
668
+ response = AuthTest::Schema.execute(query, root_value: :replace)
669
+ assert_equal 33, response["data"].fetch("unauthorized")
670
+ end
671
+ end
672
+ describe "When the object hook raises an error" do
673
+ it "returns nil" do
674
+ query = "{ unauthorized }"
675
+ response = AuthTest::Schema.execute(query, root_value: :hide)
676
+ assert_nil response["data"].fetch("unauthorized")
677
+ end
678
+
679
+ it "adds the error to the errors key" do
680
+ query = "{ unauthorized }"
681
+ response = AuthTest::Schema.execute(query, root_value: :hide)
682
+ assert_equal ["Unauthorized Query: hide"], response["errors"].map { |e| e["message"] }
683
+ end
684
+ end
685
+ end
686
+ end
687
+
688
+ describe "authorized field" do
689
+ it "returns the field data" do
690
+ query = "{ unauthorized }"
691
+ response = AuthTest::SchemaWithFieldHook.execute(query, root_value: 1)
692
+ assert_equal 1, response["data"].fetch("unauthorized")
693
+ end
694
+ end
695
+ end
696
+
624
697
  it "halts on unauthorized fields, using the parent object" do
625
698
  query = "{ unauthorized }"
626
699
  hidden_response = auth_execute(query, root_value: :hide)
@@ -112,7 +112,9 @@ describe GraphQL::BaseType do
112
112
  schema = GraphQL::Schema.define(query: query_root)
113
113
 
114
114
  expected = <<TYPE
115
- # A blog post
115
+ """
116
+ A blog post
117
+ """
116
118
  type Post {
117
119
  body: String!
118
120
  id: ID!
@@ -4,10 +4,17 @@ require "spec_helper"
4
4
  describe GraphQL::Execution::Interpreter do
5
5
  module InterpreterTest
6
6
  class Box
7
- attr_reader :value
8
-
9
- def initialize(value:)
7
+ def initialize(value: nil, &block)
10
8
  @value = value
9
+ @block = block
10
+ end
11
+
12
+ def value
13
+ if @block
14
+ @value = @block.call
15
+ @block = nil
16
+ end
17
+ @value
11
18
  end
12
19
  end
13
20
 
@@ -17,12 +24,25 @@ describe GraphQL::Execution::Interpreter do
17
24
  field :name, String, null: false
18
25
  field :cards, ["InterpreterTest::Card"], null: false
19
26
 
27
+ def self.authorized?(expansion, ctx)
28
+ if expansion.sym == "NOPE"
29
+ false
30
+ else
31
+ true
32
+ end
33
+ end
34
+
20
35
  def cards
21
36
  Query::CARDS.select { |c| c.expansion_sym == @object.sym }
22
37
  end
23
38
 
24
39
  def lazy_sym
25
- Box.new(value: sym)
40
+ Box.new(value: object.sym)
41
+ end
42
+
43
+ field :null_union_field_test, Integer, null: false
44
+ def null_union_field_test
45
+ 1
26
46
  end
27
47
  end
28
48
 
@@ -34,6 +54,11 @@ describe GraphQL::Execution::Interpreter do
34
54
  def expansion
35
55
  Query::EXPANSIONS.find { |e| e.sym == @object.expansion_sym }
36
56
  end
57
+
58
+ field :null_union_field_test, Integer, null: true
59
+ def null_union_field_test
60
+ nil
61
+ end
37
62
  end
38
63
 
39
64
  class Color < GraphQL::Schema::Enum
@@ -59,6 +84,7 @@ describe GraphQL::Execution::Interpreter do
59
84
  field :calls, Integer, null: false do
60
85
  argument :expected, Integer, required: true
61
86
  end
87
+
62
88
  def calls(expected:)
63
89
  c = context[:calls] += 1
64
90
  if c != expected
@@ -67,9 +93,26 @@ describe GraphQL::Execution::Interpreter do
67
93
  c
68
94
  end
69
95
  end
96
+
97
+ field :runtime_info, String, null: false
98
+ def runtime_info
99
+ "#{context.namespace(:interpreter)[:current_path]} -> #{context.namespace(:interpreter)[:current_field].path}"
100
+ end
101
+
102
+ field :lazy_runtime_info, String, null: false
103
+ def lazy_runtime_info
104
+ Box.new {
105
+ "#{context.namespace(:interpreter)[:current_path]} -> #{context.namespace(:interpreter)[:current_field].path}"
106
+ }
107
+ end
70
108
  end
71
109
 
72
110
  class Query < GraphQL::Schema::Object
111
+ # Try a root-level authorized hook that returns a lazy value
112
+ def self.authorized?(obj, ctx)
113
+ Box.new(value: true)
114
+ end
115
+
73
116
  field :card, Card, null: true do
74
117
  argument :name, String, required: true
75
118
  end
@@ -99,6 +142,8 @@ describe GraphQL::Execution::Interpreter do
99
142
  OpenStruct.new(name: "Ravnica, City of Guilds", sym: "RAV"),
100
143
  # This data has an error, for testing null propagation
101
144
  OpenStruct.new(name: nil, sym: "XYZ"),
145
+ # This is not allowed by .authorized?,
146
+ OpenStruct.new(name: nil, sym: "NOPE"),
102
147
  ]
103
148
 
104
149
  field :find, [Entity], null: false do
@@ -112,12 +157,21 @@ describe GraphQL::Execution::Interpreter do
112
157
  end
113
158
  end
114
159
 
160
+ field :findMany, [Entity, null: true], null: false do
161
+ argument :ids, [ID], required: true
162
+ end
163
+
164
+ def find_many(ids:)
165
+ find(id: ids).map { |e| Box.new(value: e) }
166
+ end
167
+
115
168
  field :field_counter, FieldCounter, null: false
116
169
  def field_counter; :field_counter; end
117
170
  end
118
171
 
119
172
  class Schema < GraphQL::Schema
120
173
  use GraphQL::Execution::Interpreter
174
+ use GraphQL::Analysis::AST
121
175
  query(Query)
122
176
  lazy_resolve(Box, :value)
123
177
  end
@@ -201,6 +255,26 @@ describe GraphQL::Execution::Interpreter do
201
255
  end
202
256
  end
203
257
 
258
+ describe "runtime info in context" do
259
+ it "is available" do
260
+ res = InterpreterTest::Schema.execute <<-GRAPHQL
261
+ {
262
+ fieldCounter {
263
+ runtimeInfo
264
+ fieldCounter {
265
+ runtimeInfo
266
+ lazyRuntimeInfo
267
+ }
268
+ }
269
+ }
270
+ GRAPHQL
271
+
272
+ assert_equal '["fieldCounter", "runtimeInfo"] -> FieldCounter.runtimeInfo', res["data"]["fieldCounter"]["runtimeInfo"]
273
+ assert_equal '["fieldCounter", "fieldCounter", "runtimeInfo"] -> FieldCounter.runtimeInfo', res["data"]["fieldCounter"]["fieldCounter"]["runtimeInfo"]
274
+ assert_equal '["fieldCounter", "fieldCounter", "lazyRuntimeInfo"] -> FieldCounter.lazyRuntimeInfo', res["data"]["fieldCounter"]["fieldCounter"]["lazyRuntimeInfo"]
275
+ end
276
+ end
277
+
204
278
  describe "CI setup" do
205
279
  it "sets interpreter based on a constant" do
206
280
  if TESTING_INTERPRETER
@@ -228,6 +302,7 @@ describe GraphQL::Execution::Interpreter do
228
302
  # Although the expansion was found, its name of `nil`
229
303
  # propagated to here
230
304
  assert_nil res["data"].fetch("expansion")
305
+ assert_equal ["Cannot return null for non-nullable field Expansion.name"], res["errors"].map { |e| e["message"] }
231
306
  end
232
307
 
233
308
  it "propagates nulls in lists" do
@@ -245,6 +320,54 @@ describe GraphQL::Execution::Interpreter do
245
320
  # A null in one of the list items removed the whole list
246
321
  assert_nil(res["data"])
247
322
  end
323
+
324
+ it "works with unions that fail .authorized?" do
325
+ res = InterpreterTest::Schema.execute <<-GRAPHQL
326
+ {
327
+ find(id: "NOPE") {
328
+ ... on Expansion {
329
+ sym
330
+ }
331
+ }
332
+ }
333
+ GRAPHQL
334
+ assert_equal ["Cannot return null for non-nullable field Query.find"], res["errors"].map { |e| e["message"] }
335
+ end
336
+
337
+ it "works with lists of unions" do
338
+ res = InterpreterTest::Schema.execute <<-GRAPHQL
339
+ {
340
+ findMany(ids: ["RAV", "NOPE", "BOGUS"]) {
341
+ ... on Expansion {
342
+ sym
343
+ }
344
+ }
345
+ }
346
+ GRAPHQL
347
+
348
+ assert_equal 3, res["data"]["findMany"].size
349
+ assert_equal "RAV", res["data"]["findMany"][0]["sym"]
350
+ assert_equal nil, res["data"]["findMany"][1]
351
+ assert_equal nil, res["data"]["findMany"][2]
352
+ assert_equal false, res.key?("errors")
353
+ end
354
+
355
+ it "works with union lists that have members of different kinds, with different nullabilities" do
356
+ res = InterpreterTest::Schema.execute <<-GRAPHQL
357
+ {
358
+ findMany(ids: ["RAV", "Dark Confidant"]) {
359
+ ... on Expansion {
360
+ nullUnionFieldTest
361
+ }
362
+ ... on Card {
363
+ nullUnionFieldTest
364
+ }
365
+ }
366
+ }
367
+ GRAPHQL
368
+
369
+ assert_equal [1, nil], res["data"]["findMany"].map { |f| f["nullUnionFieldTest"] }
370
+ end
248
371
  end
249
372
 
250
373
  describe "duplicated fields" do