graphql 1.11.10 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +5 -5
  3. data/lib/generators/graphql/relay_generator.rb +63 -0
  4. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  5. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  6. data/lib/generators/graphql/templates/node_type.erb +9 -0
  7. data/lib/generators/graphql/templates/object.erb +1 -1
  8. data/lib/generators/graphql/templates/query_type.erb +1 -3
  9. data/lib/generators/graphql/templates/schema.erb +8 -35
  10. data/lib/graphql/analysis/analyze_query.rb +7 -0
  11. data/lib/graphql/analysis/ast/visitor.rb +9 -1
  12. data/lib/graphql/analysis/ast.rb +11 -2
  13. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  14. data/lib/graphql/backtrace/table.rb +22 -2
  15. data/lib/graphql/backtrace/tracer.rb +40 -9
  16. data/lib/graphql/backtrace.rb +28 -19
  17. data/lib/graphql/backwards_compatibility.rb +1 -0
  18. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  19. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  20. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  21. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  22. data/lib/graphql/dataloader/null_dataloader.rb +21 -0
  23. data/lib/graphql/dataloader/request.rb +24 -0
  24. data/lib/graphql/dataloader/request_all.rb +22 -0
  25. data/lib/graphql/dataloader/source.rb +93 -0
  26. data/lib/graphql/dataloader.rb +197 -0
  27. data/lib/graphql/define/assign_global_id_field.rb +1 -1
  28. data/lib/graphql/define/instance_definable.rb +32 -2
  29. data/lib/graphql/define/type_definer.rb +5 -5
  30. data/lib/graphql/deprecated_dsl.rb +5 -0
  31. data/lib/graphql/enum_type.rb +2 -0
  32. data/lib/graphql/execution/errors.rb +4 -0
  33. data/lib/graphql/execution/execute.rb +7 -0
  34. data/lib/graphql/execution/interpreter/arguments.rb +51 -14
  35. data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
  36. data/lib/graphql/execution/interpreter/runtime.rb +210 -124
  37. data/lib/graphql/execution/interpreter.rb +10 -6
  38. data/lib/graphql/execution/multiplex.rb +20 -6
  39. data/lib/graphql/function.rb +4 -0
  40. data/lib/graphql/input_object_type.rb +2 -0
  41. data/lib/graphql/interface_type.rb +3 -1
  42. data/lib/graphql/language/document_from_schema_definition.rb +50 -23
  43. data/lib/graphql/object_type.rb +2 -0
  44. data/lib/graphql/pagination/connection.rb +5 -1
  45. data/lib/graphql/pagination/connections.rb +6 -16
  46. data/lib/graphql/query/context.rb +4 -0
  47. data/lib/graphql/query/serial_execution.rb +1 -0
  48. data/lib/graphql/query/validation_pipeline.rb +1 -1
  49. data/lib/graphql/query.rb +2 -0
  50. data/lib/graphql/relay/base_connection.rb +7 -0
  51. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  52. data/lib/graphql/relay/connection_type.rb +1 -1
  53. data/lib/graphql/relay/mutation.rb +1 -0
  54. data/lib/graphql/relay/node.rb +3 -0
  55. data/lib/graphql/relay/type_extensions.rb +2 -0
  56. data/lib/graphql/scalar_type.rb +2 -0
  57. data/lib/graphql/schema/argument.rb +25 -7
  58. data/lib/graphql/schema/build_from_definition.rb +139 -51
  59. data/lib/graphql/schema/directive/flagged.rb +57 -0
  60. data/lib/graphql/schema/directive.rb +76 -0
  61. data/lib/graphql/schema/enum.rb +3 -0
  62. data/lib/graphql/schema/enum_value.rb +12 -6
  63. data/lib/graphql/schema/field/connection_extension.rb +3 -2
  64. data/lib/graphql/schema/field.rb +28 -9
  65. data/lib/graphql/schema/input_object.rb +33 -22
  66. data/lib/graphql/schema/interface.rb +1 -0
  67. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
  68. data/lib/graphql/schema/member/build_type.rb +3 -3
  69. data/lib/graphql/schema/member/has_arguments.rb +24 -6
  70. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  71. data/lib/graphql/schema/member/has_directives.rb +98 -0
  72. data/lib/graphql/schema/member/has_validators.rb +31 -0
  73. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  74. data/lib/graphql/schema/member.rb +4 -0
  75. data/lib/graphql/schema/object.rb +11 -0
  76. data/lib/graphql/schema/printer.rb +5 -4
  77. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
  78. data/lib/graphql/schema/resolver.rb +7 -0
  79. data/lib/graphql/schema/subscription.rb +19 -1
  80. data/lib/graphql/schema/timeout_middleware.rb +2 -0
  81. data/lib/graphql/schema/validation.rb +2 -0
  82. data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
  83. data/lib/graphql/schema/validator/format_validator.rb +49 -0
  84. data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
  85. data/lib/graphql/schema/validator/length_validator.rb +57 -0
  86. data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
  87. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  88. data/lib/graphql/schema/validator.rb +163 -0
  89. data/lib/graphql/schema.rb +72 -49
  90. data/lib/graphql/static_validation/base_visitor.rb +0 -3
  91. data/lib/graphql/static_validation/rules/fields_will_merge.rb +4 -4
  92. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  93. data/lib/graphql/static_validation/validation_context.rb +1 -6
  94. data/lib/graphql/static_validation/validator.rb +12 -14
  95. data/lib/graphql/subscriptions.rb +17 -20
  96. data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
  97. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  98. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  99. data/lib/graphql/tracing.rb +2 -2
  100. data/lib/graphql/types/relay/base_connection.rb +2 -92
  101. data/lib/graphql/types/relay/base_edge.rb +2 -35
  102. data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
  103. data/lib/graphql/types/relay/default_relay.rb +27 -0
  104. data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
  105. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  106. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  107. data/lib/graphql/types/relay/node.rb +2 -4
  108. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  109. data/lib/graphql/types/relay/node_field.rb +1 -19
  110. data/lib/graphql/types/relay/nodes_field.rb +1 -19
  111. data/lib/graphql/types/relay/page_info.rb +2 -14
  112. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  113. data/lib/graphql/types/relay.rb +11 -3
  114. data/lib/graphql/union_type.rb +2 -0
  115. data/lib/graphql/upgrader/member.rb +1 -0
  116. data/lib/graphql/upgrader/schema.rb +1 -0
  117. data/lib/graphql/version.rb +1 -1
  118. data/lib/graphql.rb +38 -4
  119. metadata +31 -6
  120. data/lib/graphql/types/relay/base_field.rb +0 -22
  121. data/lib/graphql/types/relay/base_interface.rb +0 -29
  122. 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)
@@ -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
@@ -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
@@ -37,6 +37,9 @@ module GraphQL
37
37
  def value(*args, **kwargs, &block)
38
38
  kwargs[:owner] = self
39
39
  value = enum_value_class.new(*args, **kwargs, &block)
40
+ if own_values.key?(value.graphql_name)
41
+ raise ArgumentError, "#{value.graphql_name} is already defined for #{self.graphql_name}, please remove one of the definitions."
42
+ end
40
43
  own_values[value.graphql_name] = value
41
44
  nil
42
45
  end
@@ -30,23 +30,29 @@ module GraphQL
30
30
  include GraphQL::Schema::Member::AcceptsDefinition
31
31
  include GraphQL::Schema::Member::HasPath
32
32
  include GraphQL::Schema::Member::HasAstNode
33
+ include GraphQL::Schema::Member::HasDirectives
34
+ include GraphQL::Schema::Member::HasDeprecationReason
33
35
 
34
36
  attr_reader :graphql_name
35
37
 
36
38
  # @return [Class] The enum type that owns this value
37
39
  attr_reader :owner
38
40
 
39
- # @return [String] Explains why this value was deprecated (if present, this will be marked deprecated in introspection)
40
- attr_accessor :deprecation_reason
41
-
42
- def initialize(graphql_name, desc = nil, owner:, ast_node: nil, description: nil, value: nil, deprecation_reason: nil, &block)
41
+ def initialize(graphql_name, desc = nil, owner:, ast_node: nil, directives: nil, description: nil, value: nil, deprecation_reason: nil, &block)
43
42
  @graphql_name = graphql_name.to_s
44
43
  GraphQL::NameValidator.validate!(@graphql_name)
45
44
  @description = desc || description
46
45
  @value = value.nil? ? @graphql_name : value
47
- @deprecation_reason = deprecation_reason
46
+ if deprecation_reason
47
+ self.deprecation_reason = deprecation_reason
48
+ end
48
49
  @owner = owner
49
50
  @ast_node = ast_node
51
+ if directives
52
+ directives.each do |dir_class, dir_options|
53
+ directive(dir_class, **dir_options)
54
+ end
55
+ end
50
56
 
51
57
  if block_given?
52
58
  instance_eval(&block)
@@ -73,7 +79,7 @@ module GraphQL
73
79
  enum_value.name = @graphql_name
74
80
  enum_value.description = @description
75
81
  enum_value.value = @value
76
- enum_value.deprecation_reason = @deprecation_reason
82
+ enum_value.deprecation_reason = self.deprecation_reason
77
83
  enum_value.metadata[:type_class] = self
78
84
  enum_value.ast_node = ast_node
79
85
  enum_value
@@ -42,6 +42,7 @@ module GraphQL
42
42
  value.after_value ||= original_arguments[:after]
43
43
  value.last_value ||= original_arguments[:last]
44
44
  value.before_value ||= original_arguments[:before]
45
+ value.field ||= field
45
46
  if field.has_max_page_size? && !value.has_max_page_size_override?
46
47
  value.max_page_size = field.max_page_size
47
48
  end
@@ -50,8 +51,8 @@ module GraphQL
50
51
  end
51
52
  value
52
53
  elsif context.schema.new_connections?
53
- wrappers = context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers
54
- context.schema.connections.wrap(field, object.object, value, original_arguments, context, wrappers: wrappers)
54
+ context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers
55
+ context.schema.connections.wrap(field, object.object, value, original_arguments, context)
55
56
  else
56
57
  if object.is_a?(GraphQL::Schema::Object)
57
58
  object = object.object
@@ -15,8 +15,11 @@ module GraphQL
15
15
  include GraphQL::Schema::Member::HasArguments
16
16
  include GraphQL::Schema::Member::HasAstNode
17
17
  include GraphQL::Schema::Member::HasPath
18
+ include GraphQL::Schema::Member::HasValidators
18
19
  extend GraphQL::Schema::FindInheritedValue
19
20
  include GraphQL::Schema::FindInheritedValue::EmptyObjects
21
+ include GraphQL::Schema::Member::HasDirectives
22
+ include GraphQL::Schema::Member::HasDeprecationReason
20
23
 
21
24
  # @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided
22
25
  attr_reader :name
@@ -24,9 +27,6 @@ module GraphQL
24
27
 
25
28
  attr_writer :description
26
29
 
27
- # @return [String, nil] If present, the field is marked as deprecated with this documentation
28
- attr_accessor :deprecation_reason
29
-
30
30
  # @return [Symbol] Method or hash key on the underlying object to look up
31
31
  attr_reader :method_sym
32
32
 
@@ -82,10 +82,10 @@ module GraphQL
82
82
  # @see {.initialize} for other options
83
83
  def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
84
84
  if kwargs[:field]
85
- if kwargs[:field] == GraphQL::Relay::Node.field
85
+ if kwargs[:field].is_a?(GraphQL::Field) && kwargs[:field] == GraphQL::Types::Relay::NodeField.graphql_definition
86
86
  warn("Legacy-style `GraphQL::Relay::Node.field` is being added to a class-based type. See `GraphQL::Types::Relay::NodeField` for a replacement.")
87
87
  return GraphQL::Types::Relay::NodeField
88
- elsif kwargs[:field] == GraphQL::Relay::Node.plural_field
88
+ elsif kwargs[:field].is_a?(GraphQL::Field) && kwargs[:field] == GraphQL::Types::Relay::NodesField.graphql_definition
89
89
  warn("Legacy-style `GraphQL::Relay::Node.plural_field` is being added to a class-based type. See `GraphQL::Types::Relay::NodesField` for a replacement.")
90
90
  return GraphQL::Types::Relay::NodesField
91
91
  end
@@ -199,11 +199,14 @@ module GraphQL
199
199
  # @param scope [Boolean] If true, the return type's `.scope_items` method will be called on the return value
200
200
  # @param subscription_scope [Symbol, String] A key in `context` which will be used to scope subscription payloads
201
201
  # @param extensions [Array<Class, Hash<Class => Object>>] Named extensions to apply to this field (see also {#extension})
202
+ # @param directives [Hash{Class => Hash}] Directives to apply to this field
202
203
  # @param trace [Boolean] If true, a {GraphQL::Tracing} tracer will measure this scalar field
203
204
  # @param broadcastable [Boolean] Whether or not this field can be distributed in subscription broadcasts
204
205
  # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
205
206
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
206
- 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: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, &definition_block)
207
+ # @param validates [Array<Hash>] Configurations for validating this field
208
+ # @param legacy_edge_class [Class, nil] (DEPRECATED) If present, pass this along to the legacy field definition
209
+ 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: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, legacy_edge_class: nil, &definition_block)
207
210
  if name.nil?
208
211
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
209
212
  end
@@ -230,7 +233,7 @@ module GraphQL
230
233
  end
231
234
  @function = function
232
235
  @resolve = resolve
233
- @deprecation_reason = deprecation_reason
236
+ self.deprecation_reason = deprecation_reason
234
237
 
235
238
  if method && hash_key
236
239
  raise ArgumentError, "Provide `method:` _or_ `hash_key:`, not both. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}`)"
@@ -269,6 +272,7 @@ module GraphQL
269
272
  @relay_nodes_field = relay_nodes_field
270
273
  @ast_node = ast_node
271
274
  @method_conflict_warning = method_conflict_warning
275
+ @legacy_edge_class = legacy_edge_class
272
276
 
273
277
  arguments.each do |name, arg|
274
278
  if arg.is_a?(Hash)
@@ -297,6 +301,14 @@ module GraphQL
297
301
  self.extension(connection_extension)
298
302
  end
299
303
 
304
+ if directives.any?
305
+ directives.each do |(dir_class, options)|
306
+ self.directive(dir_class, **options)
307
+ end
308
+ end
309
+
310
+ self.validates(validates)
311
+
300
312
  if definition_block
301
313
  if definition_block.arity == 1
302
314
  yield self
@@ -438,8 +450,8 @@ module GraphQL
438
450
  field_defn.description = @description
439
451
  end
440
452
 
441
- if @deprecation_reason
442
- field_defn.deprecation_reason = @deprecation_reason
453
+ if self.deprecation_reason
454
+ field_defn.deprecation_reason = self.deprecation_reason
443
455
  end
444
456
 
445
457
  if @resolver_class
@@ -461,6 +473,10 @@ module GraphQL
461
473
  field_defn.relay_nodes_field = @relay_nodes_field
462
474
  end
463
475
 
476
+ if @legacy_edge_class
477
+ field_defn.edge_class = @legacy_edge_class
478
+ end
479
+
464
480
  field_defn.resolve = self.method(:resolve_field)
465
481
  field_defn.connection = connection?
466
482
  field_defn.connection_max_page_size = max_page_size
@@ -580,6 +596,9 @@ module GraphQL
580
596
  begin
581
597
  # Unwrap the GraphQL object to get the application object.
582
598
  application_object = object.object
599
+
600
+ Schema::Validator.validate!(validators, application_object, ctx, args)
601
+
583
602
  ctx.schema.after_lazy(self.authorized?(application_object, args, ctx)) do |is_authorized|
584
603
  if is_authorized
585
604
  public_send_field(object, args, ctx)