graphql 1.11.6 → 1.12.3

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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +5 -5
  3. data/lib/generators/graphql/object_generator.rb +2 -0
  4. data/lib/generators/graphql/relay_generator.rb +63 -0
  5. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  6. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  7. data/lib/generators/graphql/templates/node_type.erb +9 -0
  8. data/lib/generators/graphql/templates/object.erb +1 -1
  9. data/lib/generators/graphql/templates/query_type.erb +1 -3
  10. data/lib/generators/graphql/templates/schema.erb +8 -35
  11. data/lib/graphql.rb +39 -4
  12. data/lib/graphql/analysis/analyze_query.rb +7 -0
  13. data/lib/graphql/analysis/ast.rb +11 -2
  14. data/lib/graphql/analysis/ast/visitor.rb +9 -1
  15. data/lib/graphql/backtrace.rb +28 -19
  16. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  17. data/lib/graphql/backtrace/table.rb +22 -2
  18. data/lib/graphql/backtrace/tracer.rb +40 -9
  19. data/lib/graphql/backwards_compatibility.rb +2 -1
  20. data/lib/graphql/base_type.rb +1 -1
  21. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  22. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  23. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  24. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  25. data/lib/graphql/dataloader.rb +198 -0
  26. data/lib/graphql/dataloader/null_dataloader.rb +21 -0
  27. data/lib/graphql/dataloader/request.rb +24 -0
  28. data/lib/graphql/dataloader/request_all.rb +22 -0
  29. data/lib/graphql/dataloader/source.rb +93 -0
  30. data/lib/graphql/define/assign_global_id_field.rb +1 -1
  31. data/lib/graphql/define/instance_definable.rb +32 -2
  32. data/lib/graphql/define/type_definer.rb +5 -5
  33. data/lib/graphql/deprecated_dsl.rb +7 -2
  34. data/lib/graphql/deprecation.rb +13 -0
  35. data/lib/graphql/enum_type.rb +2 -0
  36. data/lib/graphql/execution/errors.rb +4 -0
  37. data/lib/graphql/execution/execute.rb +7 -0
  38. data/lib/graphql/execution/interpreter.rb +10 -6
  39. data/lib/graphql/execution/interpreter/arguments.rb +57 -5
  40. data/lib/graphql/execution/interpreter/arguments_cache.rb +8 -0
  41. data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
  42. data/lib/graphql/execution/interpreter/runtime.rb +219 -117
  43. data/lib/graphql/execution/multiplex.rb +20 -6
  44. data/lib/graphql/function.rb +4 -0
  45. data/lib/graphql/input_object_type.rb +2 -0
  46. data/lib/graphql/integer_decoding_error.rb +17 -0
  47. data/lib/graphql/interface_type.rb +3 -1
  48. data/lib/graphql/internal_representation/document.rb +2 -2
  49. data/lib/graphql/internal_representation/rewrite.rb +1 -1
  50. data/lib/graphql/invalid_null_error.rb +1 -1
  51. data/lib/graphql/language/document_from_schema_definition.rb +50 -23
  52. data/lib/graphql/object_type.rb +2 -0
  53. data/lib/graphql/pagination/connection.rb +5 -1
  54. data/lib/graphql/pagination/connections.rb +6 -16
  55. data/lib/graphql/query.rb +6 -1
  56. data/lib/graphql/query/arguments.rb +1 -1
  57. data/lib/graphql/query/context.rb +8 -1
  58. data/lib/graphql/query/serial_execution.rb +1 -0
  59. data/lib/graphql/query/validation_pipeline.rb +1 -1
  60. data/lib/graphql/relay/array_connection.rb +2 -2
  61. data/lib/graphql/relay/base_connection.rb +7 -0
  62. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  63. data/lib/graphql/relay/connection_type.rb +1 -1
  64. data/lib/graphql/relay/mutation.rb +1 -0
  65. data/lib/graphql/relay/node.rb +3 -0
  66. data/lib/graphql/relay/type_extensions.rb +2 -0
  67. data/lib/graphql/scalar_type.rb +2 -0
  68. data/lib/graphql/schema.rb +80 -29
  69. data/lib/graphql/schema/argument.rb +25 -7
  70. data/lib/graphql/schema/build_from_definition.rb +139 -51
  71. data/lib/graphql/schema/default_type_error.rb +2 -0
  72. data/lib/graphql/schema/directive.rb +76 -0
  73. data/lib/graphql/schema/directive/flagged.rb +57 -0
  74. data/lib/graphql/schema/enum.rb +3 -0
  75. data/lib/graphql/schema/enum_value.rb +12 -6
  76. data/lib/graphql/schema/field.rb +50 -22
  77. data/lib/graphql/schema/field/connection_extension.rb +3 -2
  78. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  79. data/lib/graphql/schema/input_object.rb +33 -22
  80. data/lib/graphql/schema/interface.rb +1 -0
  81. data/lib/graphql/schema/member.rb +4 -0
  82. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
  83. data/lib/graphql/schema/member/build_type.rb +3 -3
  84. data/lib/graphql/schema/member/has_arguments.rb +67 -50
  85. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  86. data/lib/graphql/schema/member/has_directives.rb +98 -0
  87. data/lib/graphql/schema/member/has_validators.rb +31 -0
  88. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  89. data/lib/graphql/schema/middleware_chain.rb +1 -1
  90. data/lib/graphql/schema/object.rb +11 -0
  91. data/lib/graphql/schema/printer.rb +5 -4
  92. data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
  93. data/lib/graphql/schema/resolver.rb +7 -0
  94. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
  95. data/lib/graphql/schema/subscription.rb +19 -1
  96. data/lib/graphql/schema/timeout_middleware.rb +3 -1
  97. data/lib/graphql/schema/validation.rb +4 -2
  98. data/lib/graphql/schema/validator.rb +163 -0
  99. data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
  100. data/lib/graphql/schema/validator/format_validator.rb +49 -0
  101. data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
  102. data/lib/graphql/schema/validator/length_validator.rb +57 -0
  103. data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
  104. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  105. data/lib/graphql/static_validation.rb +1 -0
  106. data/lib/graphql/static_validation/all_rules.rb +1 -0
  107. data/lib/graphql/static_validation/rules/fields_will_merge.rb +25 -17
  108. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  109. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  110. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  111. data/lib/graphql/static_validation/validator.rb +32 -9
  112. data/lib/graphql/subscriptions.rb +17 -20
  113. data/lib/graphql/subscriptions/subscription_root.rb +1 -1
  114. data/lib/graphql/tracing.rb +2 -2
  115. data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
  116. data/lib/graphql/tracing/platform_tracing.rb +4 -2
  117. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  118. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  119. data/lib/graphql/types/int.rb +9 -2
  120. data/lib/graphql/types/relay.rb +11 -3
  121. data/lib/graphql/types/relay/base_connection.rb +2 -91
  122. data/lib/graphql/types/relay/base_edge.rb +2 -34
  123. data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
  124. data/lib/graphql/types/relay/default_relay.rb +27 -0
  125. data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
  126. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  127. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  128. data/lib/graphql/types/relay/node.rb +2 -4
  129. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  130. data/lib/graphql/types/relay/node_field.rb +1 -19
  131. data/lib/graphql/types/relay/nodes_field.rb +1 -19
  132. data/lib/graphql/types/relay/page_info.rb +2 -14
  133. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  134. data/lib/graphql/types/string.rb +7 -1
  135. data/lib/graphql/union_type.rb +2 -0
  136. data/lib/graphql/upgrader/member.rb +1 -0
  137. data/lib/graphql/upgrader/schema.rb +1 -0
  138. data/lib/graphql/version.rb +1 -1
  139. data/readme.md +1 -1
  140. metadata +53 -9
  141. data/lib/graphql/types/relay/base_field.rb +0 -22
  142. data/lib/graphql/types/relay/base_interface.rb +0 -29
  143. data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -3,6 +3,7 @@ require "graphql/schema/build_from_definition/resolve_map"
3
3
 
4
4
  module GraphQL
5
5
  class Schema
6
+ # TODO Populate `.directive(...)` from here
6
7
  module BuildFromDefinition
7
8
  if !String.method_defined?(:-@)
8
9
  using GraphQL::StringDedupBackport
@@ -18,8 +19,8 @@ module GraphQL
18
19
  from_document(parser.parse_file(definition_path), **kwargs)
19
20
  end
20
21
 
21
- def from_document(document, default_resolve:, using: {}, relay: false, interpreter: true)
22
- Builder.build(document, default_resolve: default_resolve || {}, relay: relay, using: using, interpreter: interpreter)
22
+ def from_document(document, default_resolve:, using: {}, relay: false)
23
+ Builder.build(document, default_resolve: default_resolve || {}, relay: relay, using: using)
23
24
  end
24
25
  end
25
26
 
@@ -27,7 +28,7 @@ module GraphQL
27
28
  module Builder
28
29
  extend self
29
30
 
30
- def build(document, default_resolve:, using: {}, interpreter: true, relay:)
31
+ def build(document, default_resolve:, using: {}, relay:)
31
32
  raise InvalidDocumentError.new('Must provide a document ast.') if !document || !document.is_a?(GraphQL::Language::Nodes::Document)
32
33
 
33
34
  if default_resolve.is_a?(Hash)
@@ -41,45 +42,43 @@ module GraphQL
41
42
  schema_definition = schema_defns.first
42
43
  types = {}
43
44
  directives = {}
44
- type_resolver = ->(type) { resolve_type(types, type) }
45
+ type_resolver = build_resolve_type(types, directives, ->(type_name) { types[type_name] ||= Schema::LateBoundType.new(type_name)})
46
+ # Make a different type resolver because we need to coerce directive arguments
47
+ # _while_ building the schema.
48
+ # It will dig for a type if it encounters a custom type. This could be a problem if there are cycles.
49
+ directive_type_resolver = nil
50
+ directive_type_resolver = build_resolve_type(GraphQL::Schema::BUILT_IN_TYPES, directives, ->(type_name) {
51
+ types[type_name] ||= begin
52
+ defn = document.definitions.find { |d| d.respond_to?(:name) && d.name == type_name }
53
+ build_definition_from_node(defn, directive_type_resolver, default_resolve)
54
+ end
55
+ })
45
56
 
46
57
  document.definitions.each do |definition|
47
- case definition
48
- when GraphQL::Language::Nodes::SchemaDefinition
49
- nil # already handled
50
- when GraphQL::Language::Nodes::EnumTypeDefinition
51
- types[definition.name] = build_enum_type(definition, type_resolver)
52
- when GraphQL::Language::Nodes::ObjectTypeDefinition
53
- types[definition.name] = build_object_type(definition, type_resolver)
54
- when GraphQL::Language::Nodes::InterfaceTypeDefinition
55
- types[definition.name] = build_interface_type(definition, type_resolver)
56
- when GraphQL::Language::Nodes::UnionTypeDefinition
57
- types[definition.name] = build_union_type(definition, type_resolver)
58
- when GraphQL::Language::Nodes::ScalarTypeDefinition
59
- types[definition.name] = build_scalar_type(definition, type_resolver, default_resolve: default_resolve)
60
- when GraphQL::Language::Nodes::InputObjectTypeDefinition
61
- types[definition.name] = build_input_object_type(definition, type_resolver)
62
- when GraphQL::Language::Nodes::DirectiveDefinition
63
- directives[definition.name] = build_directive(definition, type_resolver)
58
+ if definition.is_a?(GraphQL::Language::Nodes::DirectiveDefinition)
59
+ directives[definition.name] = build_directive(definition, directive_type_resolver)
64
60
  end
65
61
  end
66
62
 
67
- # At this point, if types named by the built in types are _late-bound_ types,
68
- # that means they were referenced in the schema but not defined in the schema.
69
- # That's supported for built-in types. (Eg, you can use `String` without defining it.)
70
- # In that case, insert the concrete type definition now.
71
- #
72
- # However, if the type in `types` is a _concrete_ type definition, that means that
73
- # the document contained an explicit definition of the scalar type.
74
- # Don't override it in this case.
75
- GraphQL::Schema::BUILT_IN_TYPES.each do |scalar_name, built_in_scalar|
76
- existing_type = types[scalar_name]
77
- if existing_type.is_a?(GraphQL::Schema::LateBoundType)
78
- types[scalar_name] = built_in_scalar
63
+ directives = GraphQL::Schema.default_directives.merge(directives)
64
+
65
+ # In case any directives referenced built-in types for their arguments:
66
+ replace_late_bound_types_with_built_in(types)
67
+
68
+ document.definitions.each do |definition|
69
+ case definition
70
+ when GraphQL::Language::Nodes::SchemaDefinition, GraphQL::Language::Nodes::DirectiveDefinition
71
+ nil # already handled
72
+ else
73
+ # It's possible that this was already loaded by the directives
74
+ prev_type = types[definition.name]
75
+ if prev_type.nil? || prev_type.is_a?(Schema::LateBoundType)
76
+ types[definition.name] = build_definition_from_node(definition, type_resolver, default_resolve)
77
+ end
79
78
  end
80
79
  end
81
80
 
82
- directives = GraphQL::Schema.default_directives.merge(directives)
81
+ replace_late_bound_types_with_built_in(types)
83
82
 
84
83
  if schema_definition
85
84
  if schema_definition.query
@@ -133,11 +132,6 @@ module GraphQL
133
132
  ast_node(schema_definition)
134
133
  end
135
134
 
136
- if interpreter
137
- use GraphQL::Execution::Interpreter
138
- use GraphQL::Analysis::AST
139
- end
140
-
141
135
  using.each do |plugin, options|
142
136
  if options
143
137
  use(plugin, **options)
@@ -169,10 +163,85 @@ module GraphQL
169
163
  raise(GraphQL::RequiredImplementationMissingError, "Generated Schema cannot use Interface or Union types for execution. Implement resolve_type on your resolver.")
170
164
  }
171
165
 
166
+ def build_definition_from_node(definition, type_resolver, default_resolve)
167
+ case definition
168
+ when GraphQL::Language::Nodes::EnumTypeDefinition
169
+ build_enum_type(definition, type_resolver)
170
+ when GraphQL::Language::Nodes::ObjectTypeDefinition
171
+ build_object_type(definition, type_resolver)
172
+ when GraphQL::Language::Nodes::InterfaceTypeDefinition
173
+ build_interface_type(definition, type_resolver)
174
+ when GraphQL::Language::Nodes::UnionTypeDefinition
175
+ build_union_type(definition, type_resolver)
176
+ when GraphQL::Language::Nodes::ScalarTypeDefinition
177
+ build_scalar_type(definition, type_resolver, default_resolve: default_resolve)
178
+ when GraphQL::Language::Nodes::InputObjectTypeDefinition
179
+ build_input_object_type(definition, type_resolver)
180
+ end
181
+ end
182
+
183
+ # Modify `types`, replacing any late-bound references to built-in types
184
+ # with their actual definitions.
185
+ #
186
+ # (Schema definitions are allowed to reference those built-ins without redefining them.)
187
+ # @return void
188
+ def replace_late_bound_types_with_built_in(types)
189
+ GraphQL::Schema::BUILT_IN_TYPES.each do |scalar_name, built_in_scalar|
190
+ existing_type = types[scalar_name]
191
+ if existing_type.is_a?(GraphQL::Schema::LateBoundType)
192
+ types[scalar_name] = built_in_scalar
193
+ end
194
+ end
195
+ end
196
+
197
+ def build_directives(definition, ast_node, type_resolver)
198
+ dirs = prepare_directives(ast_node, type_resolver)
199
+ dirs.each do |dir_class, options|
200
+ definition.directive(dir_class, **options)
201
+ end
202
+ end
203
+
204
+ def prepare_directives(ast_node, type_resolver)
205
+ dirs = {}
206
+ ast_node.directives.each do |dir_node|
207
+ if dir_node.name == "deprecated"
208
+ # This is handled using `deprecation_reason`
209
+ next
210
+ else
211
+ dir_class = type_resolver.call(dir_node.name)
212
+ if dir_class.nil?
213
+ raise ArgumentError, "No definition for @#{dir_node.name} on #{ast_node.name} at #{ast_node.line}:#{ast_node.col}"
214
+ end
215
+ options = args_to_kwargs(dir_class, dir_node)
216
+ dirs[dir_class] = options
217
+ end
218
+ end
219
+ dirs
220
+ end
221
+
222
+ def args_to_kwargs(arg_owner, node)
223
+ if node.respond_to?(:arguments)
224
+ kwargs = {}
225
+ node.arguments.each do |arg_node|
226
+ arg_defn = arg_owner.get_argument(arg_node.name)
227
+ kwargs[arg_defn.keyword] = args_to_kwargs(arg_defn.type.unwrap, arg_node.value)
228
+ end
229
+ kwargs
230
+ elsif node.is_a?(Array)
231
+ node.map { |n| args_to_kwargs(arg_owner, n) }
232
+ elsif node.is_a?(Language::Nodes::Enum)
233
+ node.name
234
+ else
235
+ # scalar
236
+ node
237
+ end
238
+ end
239
+
172
240
  def build_enum_type(enum_type_definition, type_resolver)
173
241
  builder = self
174
242
  Class.new(GraphQL::Schema::Enum) do
175
243
  graphql_name(enum_type_definition.name)
244
+ builder.build_directives(self, enum_type_definition, type_resolver)
176
245
  description(enum_type_definition.description)
177
246
  ast_node(enum_type_definition)
178
247
  enum_type_definition.values.each do |enum_value_definition|
@@ -180,6 +249,7 @@ module GraphQL
180
249
  value: enum_value_definition.name,
181
250
  deprecation_reason: builder.build_deprecation_reason(enum_value_definition.directives),
182
251
  description: enum_value_definition.description,
252
+ directives: builder.prepare_directives(enum_value_definition, type_resolver),
183
253
  ast_node: enum_value_definition,
184
254
  )
185
255
  end
@@ -202,6 +272,7 @@ module GraphQL
202
272
  graphql_name(scalar_type_definition.name)
203
273
  description(scalar_type_definition.description)
204
274
  ast_node(scalar_type_definition)
275
+ builder.build_directives(self, scalar_type_definition, type_resolver)
205
276
 
206
277
  if default_resolve.respond_to?(:coerce_input)
207
278
  # Put these method definitions in another method to avoid retaining `type_resolve`
@@ -219,11 +290,13 @@ module GraphQL
219
290
  end
220
291
 
221
292
  def build_union_type(union_type_definition, type_resolver)
293
+ builder = self
222
294
  Class.new(GraphQL::Schema::Union) do
223
295
  graphql_name(union_type_definition.name)
224
296
  description(union_type_definition.description)
225
297
  possible_types(*union_type_definition.types.map { |type_name| type_resolver.call(type_name) })
226
298
  ast_node(union_type_definition)
299
+ builder.build_directives(self, union_type_definition, type_resolver)
227
300
  end
228
301
  end
229
302
 
@@ -234,6 +307,7 @@ module GraphQL
234
307
  graphql_name(object_type_definition.name)
235
308
  description(object_type_definition.description)
236
309
  ast_node(object_type_definition)
310
+ builder.build_directives(self, object_type_definition, type_resolver)
237
311
 
238
312
  object_type_definition.interfaces.each do |interface_name|
239
313
  interface_defn = type_resolver.call(interface_name)
@@ -250,6 +324,7 @@ module GraphQL
250
324
  graphql_name(input_object_type_definition.name)
251
325
  description(input_object_type_definition.description)
252
326
  ast_node(input_object_type_definition)
327
+ builder.build_directives(self, input_object_type_definition, type_resolver)
253
328
  builder.build_arguments(self, input_object_type_definition.fields, type_resolver)
254
329
  end
255
330
  end
@@ -290,6 +365,7 @@ module GraphQL
290
365
  ast_node: argument_defn,
291
366
  camelize: false,
292
367
  method_access: false,
368
+ directives: prepare_directives(argument_defn, type_resolver),
293
369
  **default_value_kwargs
294
370
  )
295
371
  end
@@ -313,6 +389,7 @@ module GraphQL
313
389
  graphql_name(interface_type_definition.name)
314
390
  description(interface_type_definition.description)
315
391
  ast_node(interface_type_definition)
392
+ builder.build_directives(self, interface_type_definition, type_resolver)
316
393
 
317
394
  builder.build_fields(self, interface_type_definition.fields, type_resolver, default_resolve: nil)
318
395
  end
@@ -335,6 +412,7 @@ module GraphQL
335
412
  ast_node: field_definition,
336
413
  method_conflict_warning: false,
337
414
  camelize: false,
415
+ directives: prepare_directives(field_definition, type_resolver),
338
416
  resolver_method: resolve_method_name,
339
417
  )
340
418
 
@@ -353,18 +431,28 @@ module GraphQL
353
431
  end
354
432
  end
355
433
 
356
- def resolve_type(types, ast_node)
357
- case ast_node
358
- when GraphQL::Language::Nodes::TypeName
359
- type_name = ast_node.name
360
- types[type_name] ||= GraphQL::Schema::LateBoundType.new(type_name)
361
- when GraphQL::Language::Nodes::NonNullType
362
- resolve_type(types, ast_node.of_type).to_non_null_type
363
- when GraphQL::Language::Nodes::ListType
364
- resolve_type(types, ast_node.of_type).to_list_type
365
- else
366
- raise "Unexpected ast_node: #{ast_node.inspect}"
367
- end
434
+ def build_resolve_type(lookup_hash, directives, missing_type_handler)
435
+ resolve_type_proc = nil
436
+ resolve_type_proc = ->(ast_node) {
437
+ case ast_node
438
+ when GraphQL::Language::Nodes::TypeName
439
+ type_name = ast_node.name
440
+ if lookup_hash.key?(type_name)
441
+ lookup_hash[type_name]
442
+ else
443
+ missing_type_handler.call(type_name)
444
+ end
445
+ when GraphQL::Language::Nodes::NonNullType
446
+ resolve_type_proc.call(ast_node.of_type).to_non_null_type
447
+ when GraphQL::Language::Nodes::ListType
448
+ resolve_type_proc.call(ast_node.of_type).to_list_type
449
+ when String
450
+ directives[ast_node]
451
+ else
452
+ raise "Unexpected ast_node: #{ast_node.inspect}"
453
+ end
454
+ }
455
+ resolve_type_proc
368
456
  end
369
457
 
370
458
  def resolve_type_name(type)
@@ -8,6 +8,8 @@ module GraphQL
8
8
  ctx.errors << type_error
9
9
  when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
10
10
  raise type_error
11
+ when GraphQL::IntegerDecodingError
12
+ nil
11
13
  end
12
14
  end
13
15
  end
@@ -9,6 +9,13 @@ module GraphQL
9
9
  class Directive < GraphQL::Schema::Member
10
10
  extend GraphQL::Schema::Member::HasArguments
11
11
  class << self
12
+ # Directives aren't types, they don't have kinds.
13
+ undef_method :kind
14
+
15
+ def path
16
+ "@#{super}"
17
+ end
18
+
12
19
  # Return a name based on the class name,
13
20
  # but downcase the first letter.
14
21
  def default_graphql_name
@@ -21,6 +28,11 @@ module GraphQL
21
28
 
22
29
  def locations(*new_locations)
23
30
  if new_locations.any?
31
+ new_locations.each do |new_loc|
32
+ if !LOCATIONS.include?(new_loc.to_sym)
33
+ raise ArgumentError, "#{self} (#{self.graphql_name}) has an invalid directive location: `locations #{new_loc}` "
34
+ end
35
+ end
24
36
  @locations = new_locations
25
37
  else
26
38
  @locations ||= (superclass.respond_to?(:locations) ? superclass.locations : [])
@@ -87,6 +99,23 @@ module GraphQL
87
99
  end
88
100
  end
89
101
 
102
+ # @return [GraphQL::Schema::Field, GraphQL::Schema::Argument, Class, Module]
103
+ attr_reader :owner
104
+
105
+ # @return [GraphQL::Interpreter::Arguments]
106
+ attr_reader :arguments
107
+
108
+ def initialize(owner, **arguments)
109
+ @owner = owner
110
+ assert_valid_owner
111
+ # It's be nice if we had the real context here, but we don't. What we _would_ get is:
112
+ # - error handling
113
+ # - lazy resolution
114
+ # Probably, those won't be needed here, since these are configuration arguments,
115
+ # not runtime arguments.
116
+ @arguments = self.class.coerce_arguments(nil, arguments, Query::NullContext)
117
+ end
118
+
90
119
  LOCATIONS = [
91
120
  QUERY = :QUERY,
92
121
  MUTATION = :MUTATION,
@@ -129,6 +158,53 @@ module GraphQL
129
158
  INPUT_OBJECT: 'Location adjacent to an input object type definition.',
130
159
  INPUT_FIELD_DEFINITION: 'Location adjacent to an input object field definition.',
131
160
  }
161
+
162
+ private
163
+
164
+ def assert_valid_owner
165
+ case @owner
166
+ when Class
167
+ if @owner < GraphQL::Schema::Object
168
+ assert_has_location(OBJECT)
169
+ elsif @owner < GraphQL::Schema::Union
170
+ assert_has_location(UNION)
171
+ elsif @owner < GraphQL::Schema::Enum
172
+ assert_has_location(ENUM)
173
+ elsif @owner < GraphQL::Schema::InputObject
174
+ assert_has_location(INPUT_OBJECT)
175
+ elsif @owner < GraphQL::Schema::Scalar
176
+ assert_has_location(SCALAR)
177
+ elsif @owner < GraphQL::Schema
178
+ assert_has_location(SCHEMA)
179
+ else
180
+ raise "Unexpected directive owner class: #{@owner}"
181
+ end
182
+ when Module
183
+ assert_has_location(INTERFACE)
184
+ when GraphQL::Schema::Argument
185
+ if @owner.owner.is_a?(GraphQL::Schema::Field)
186
+ assert_has_location(ARGUMENT_DEFINITION)
187
+ else
188
+ assert_has_location(INPUT_FIELD_DEFINITION)
189
+ end
190
+ when GraphQL::Schema::Field
191
+ assert_has_location(FIELD_DEFINITION)
192
+ when GraphQL::Schema::EnumValue
193
+ assert_has_location(ENUM_VALUE)
194
+ else
195
+ raise "Unexpected directive owner: #{@owner.inspect}"
196
+ end
197
+ end
198
+
199
+ def assert_has_location(location)
200
+ if !self.class.locations.include?(location)
201
+ raise ArgumentError, <<-MD
202
+ Directive `@#{self.class.graphql_name}` can't be attached to #{@owner.graphql_name} because #{location} isn't included in its locations (#{self.class.locations.join(", ")}).
203
+
204
+ Use `locations(#{location})` to update this directive's definition, or remove it from #{@owner.graphql_name}.
205
+ MD
206
+ end
207
+ end
132
208
  end
133
209
  end
134
210
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ class Directive < GraphQL::Schema::Member
5
+ # This is _similar_ to {Directive::Feature}, except it's prescribed by the server, not the client.
6
+ #
7
+ # In this case, the server hides types and fields _entirely_, unless the current context has certain `:flags` present.
8
+ class Flagged < GraphQL::Schema::Directive
9
+ def initialize(target, **options)
10
+ if target.is_a?(Module) && !target.ancestors.include?(VisibleByFlag)
11
+ # This is type class of some kind, `include` will put this module
12
+ # in between the type class itself and its super class, so `super` will work fine
13
+ target.include(VisibleByFlag)
14
+ elsif !target.is_a?(VisibleByFlag)
15
+ # This is an instance of a base class. `include` won't put this in front of the
16
+ # base class implementation, so we need to `.prepend`.
17
+ # `#visible?` could probably be moved to a module and then this could use `include` instead.
18
+ target.class.prepend(VisibleByFlag)
19
+ end
20
+ super
21
+ end
22
+
23
+ description "Hides this part of the schema unless the named flag is present in context[:flags]"
24
+
25
+ locations(
26
+ GraphQL::Schema::Directive::FIELD_DEFINITION,
27
+ GraphQL::Schema::Directive::OBJECT,
28
+ GraphQL::Schema::Directive::SCALAR,
29
+ GraphQL::Schema::Directive::ENUM,
30
+ GraphQL::Schema::Directive::UNION,
31
+ GraphQL::Schema::Directive::INTERFACE,
32
+ GraphQL::Schema::Directive::INPUT_OBJECT,
33
+ GraphQL::Schema::Directive::ENUM_VALUE,
34
+ GraphQL::Schema::Directive::ARGUMENT_DEFINITION,
35
+ GraphQL::Schema::Directive::INPUT_FIELD_DEFINITION,
36
+ )
37
+
38
+ argument :by, [String], "Flags to check for this schema member", required: true
39
+
40
+ module VisibleByFlag
41
+ def self.included(schema_class)
42
+ schema_class.extend(self)
43
+ end
44
+
45
+ def visible?(context)
46
+ if dir = self.directives.find { |d| d.is_a?(Flagged) }
47
+ relevant_flags = (f = context[:flags]) && dir.arguments[:by] & f
48
+ relevant_flags && relevant_flags.any? && super
49
+ else
50
+ super
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end