graphql 2.3.7 → 2.4.0

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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +46 -0
  3. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  4. data/lib/generators/graphql/templates/base_resolver.erb +2 -0
  5. data/lib/generators/graphql/type_generator.rb +1 -1
  6. data/lib/graphql/analysis/field_usage.rb +1 -1
  7. data/lib/graphql/analysis/query_complexity.rb +3 -3
  8. data/lib/graphql/analysis/visitor.rb +7 -6
  9. data/lib/graphql/analysis.rb +1 -1
  10. data/lib/graphql/current.rb +52 -0
  11. data/lib/graphql/dataloader/async_dataloader.rb +3 -2
  12. data/lib/graphql/dataloader/source.rb +6 -3
  13. data/lib/graphql/dataloader.rb +35 -11
  14. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  15. data/lib/graphql/execution/interpreter/resolve.rb +10 -6
  16. data/lib/graphql/execution/interpreter/runtime.rb +29 -25
  17. data/lib/graphql/execution/interpreter.rb +2 -0
  18. data/lib/graphql/execution/lookahead.rb +10 -10
  19. data/lib/graphql/introspection/directive_type.rb +1 -1
  20. data/lib/graphql/introspection/entry_points.rb +2 -2
  21. data/lib/graphql/introspection/field_type.rb +1 -1
  22. data/lib/graphql/introspection/schema_type.rb +6 -11
  23. data/lib/graphql/introspection/type_type.rb +5 -5
  24. data/lib/graphql/invalid_null_error.rb +1 -1
  25. data/lib/graphql/language/comment.rb +18 -0
  26. data/lib/graphql/language/document_from_schema_definition.rb +55 -28
  27. data/lib/graphql/language/lexer.rb +15 -15
  28. data/lib/graphql/language/nodes.rb +24 -16
  29. data/lib/graphql/language/parser.rb +14 -1
  30. data/lib/graphql/language/printer.rb +23 -7
  31. data/lib/graphql/language/sanitized_printer.rb +1 -1
  32. data/lib/graphql/language.rb +6 -6
  33. data/lib/graphql/query/context.rb +7 -1
  34. data/lib/graphql/query/null_context.rb +2 -2
  35. data/lib/graphql/query.rb +60 -8
  36. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  37. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  38. data/lib/graphql/rubocop.rb +2 -0
  39. data/lib/graphql/schema/addition.rb +1 -0
  40. data/lib/graphql/schema/always_visible.rb +6 -2
  41. data/lib/graphql/schema/argument.rb +14 -1
  42. data/lib/graphql/schema/build_from_definition.rb +9 -1
  43. data/lib/graphql/schema/directive/flagged.rb +1 -1
  44. data/lib/graphql/schema/enum.rb +54 -20
  45. data/lib/graphql/schema/enum_value.rb +9 -1
  46. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  47. data/lib/graphql/schema/field.rb +92 -37
  48. data/lib/graphql/schema/has_single_input_argument.rb +2 -1
  49. data/lib/graphql/schema/input_object.rb +27 -13
  50. data/lib/graphql/schema/interface.rb +21 -4
  51. data/lib/graphql/schema/introspection_system.rb +5 -16
  52. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  53. data/lib/graphql/schema/member/has_arguments.rb +14 -9
  54. data/lib/graphql/schema/member/has_fields.rb +8 -6
  55. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  56. data/lib/graphql/schema/printer.rb +1 -0
  57. data/lib/graphql/schema/resolver.rb +7 -9
  58. data/lib/graphql/schema/type_expression.rb +2 -2
  59. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  60. data/lib/graphql/schema/validator/required_validator.rb +28 -4
  61. data/lib/graphql/schema/validator.rb +2 -0
  62. data/lib/graphql/schema/visibility/migration.rb +186 -0
  63. data/lib/graphql/schema/visibility/profile.rb +523 -0
  64. data/lib/graphql/schema/visibility.rb +75 -0
  65. data/lib/graphql/schema/warden.rb +156 -10
  66. data/lib/graphql/schema.rb +283 -85
  67. data/lib/graphql/static_validation/base_visitor.rb +6 -5
  68. data/lib/graphql/static_validation/literal_validator.rb +4 -4
  69. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  70. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  71. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  72. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  73. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +3 -2
  74. data/lib/graphql/static_validation/rules/fields_will_merge.rb +8 -7
  75. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  76. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  77. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  78. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  79. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  80. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -3
  81. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
  82. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
  83. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  84. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  85. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  86. data/lib/graphql/static_validation/validation_context.rb +17 -2
  87. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
  88. data/lib/graphql/subscriptions/broadcast_analyzer.rb +10 -4
  89. data/lib/graphql/subscriptions/event.rb +1 -1
  90. data/lib/graphql/subscriptions.rb +5 -3
  91. data/lib/graphql/testing/helpers.rb +9 -5
  92. data/lib/graphql/tracing/notifications_trace.rb +2 -2
  93. data/lib/graphql/types/relay/connection_behaviors.rb +10 -0
  94. data/lib/graphql/types/relay/edge_behaviors.rb +10 -0
  95. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  96. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  97. data/lib/graphql/version.rb +1 -1
  98. data/lib/graphql.rb +3 -0
  99. metadata +29 -8
  100. data/lib/graphql/language/token.rb +0 -34
  101. data/lib/graphql/schema/invalid_type_error.rb +0 -7
@@ -255,14 +255,14 @@ module GraphQL
255
255
 
256
256
 
257
257
  def print_scalar_type_definition(scalar_type, extension: false)
258
- extension ? print_string("extend ") : print_description(scalar_type)
258
+ extension ? print_string("extend ") : print_description_and_comment(scalar_type)
259
259
  print_string("scalar ")
260
260
  print_string(scalar_type.name)
261
261
  print_directives(scalar_type.directives)
262
262
  end
263
263
 
264
264
  def print_object_type_definition(object_type, extension: false)
265
- extension ? print_string("extend ") : print_description(object_type)
265
+ extension ? print_string("extend ") : print_description_and_comment(object_type)
266
266
  print_string("type ")
267
267
  print_string(object_type.name)
268
268
  print_implements(object_type) unless object_type.interfaces.empty?
@@ -294,7 +294,7 @@ module GraphQL
294
294
  end
295
295
 
296
296
  def print_arguments(arguments, indent: "")
297
- if arguments.all? { |arg| !arg.description }
297
+ if arguments.all? { |arg| !arg.description && !arg.comment }
298
298
  print_string("(")
299
299
  arguments.each_with_index do |arg, i|
300
300
  print_input_value_definition(arg)
@@ -306,6 +306,7 @@ module GraphQL
306
306
 
307
307
  print_string("(\n")
308
308
  arguments.each_with_index do |arg, i|
309
+ print_comment(arg, indent: " " + indent, first_in_block: i == 0)
309
310
  print_description(arg, indent: " " + indent, first_in_block: i == 0)
310
311
  print_string(" ")
311
312
  print_string(indent)
@@ -328,7 +329,7 @@ module GraphQL
328
329
  end
329
330
 
330
331
  def print_interface_type_definition(interface_type, extension: false)
331
- extension ? print_string("extend ") : print_description(interface_type)
332
+ extension ? print_string("extend ") : print_description_and_comment(interface_type)
332
333
  print_string("interface ")
333
334
  print_string(interface_type.name)
334
335
  print_implements(interface_type) if interface_type.interfaces.any?
@@ -337,7 +338,7 @@ module GraphQL
337
338
  end
338
339
 
339
340
  def print_union_type_definition(union_type, extension: false)
340
- extension ? print_string("extend ") : print_description(union_type)
341
+ extension ? print_string("extend ") : print_description_and_comment(union_type)
341
342
  print_string("union ")
342
343
  print_string(union_type.name)
343
344
  print_directives(union_type.directives)
@@ -355,7 +356,7 @@ module GraphQL
355
356
  end
356
357
 
357
358
  def print_enum_type_definition(enum_type, extension: false)
358
- extension ? print_string("extend ") : print_description(enum_type)
359
+ extension ? print_string("extend ") : print_description_and_comment(enum_type)
359
360
  print_string("enum ")
360
361
  print_string(enum_type.name)
361
362
  print_directives(enum_type.directives)
@@ -363,6 +364,7 @@ module GraphQL
363
364
  print_string(" {\n")
364
365
  enum_type.values.each.with_index do |value, i|
365
366
  print_description(value, indent: " ", first_in_block: i == 0)
367
+ print_comment(value, indent: " ", first_in_block: i == 0)
366
368
  print_enum_value_definition(value)
367
369
  end
368
370
  print_string("}")
@@ -377,7 +379,7 @@ module GraphQL
377
379
  end
378
380
 
379
381
  def print_input_object_type_definition(input_object_type, extension: false)
380
- extension ? print_string("extend ") : print_description(input_object_type)
382
+ extension ? print_string("extend ") : print_description_and_comment(input_object_type)
381
383
  print_string("input ")
382
384
  print_string(input_object_type.name)
383
385
  print_directives(input_object_type.directives)
@@ -385,6 +387,7 @@ module GraphQL
385
387
  print_string(" {\n")
386
388
  input_object_type.fields.each.with_index do |field, i|
387
389
  print_description(field, indent: " ", first_in_block: i == 0)
390
+ print_comment(field, indent: " ", first_in_block: i == 0)
388
391
  print_string(" ")
389
392
  print_input_value_definition(field)
390
393
  print_string("\n")
@@ -424,6 +427,18 @@ module GraphQL
424
427
  print_string(GraphQL::Language::BlockString.print(node.description, indent: indent))
425
428
  end
426
429
 
430
+ def print_comment(node, indent: "", first_in_block: true)
431
+ return unless node.comment
432
+
433
+ print_string("\n") if indent != "" && !first_in_block
434
+ print_string(GraphQL::Language::Comment.print(node.comment, indent: indent))
435
+ end
436
+
437
+ def print_description_and_comment(node)
438
+ print_description(node)
439
+ print_comment(node)
440
+ end
441
+
427
442
  def print_field_definitions(fields)
428
443
  return if fields.empty?
429
444
 
@@ -431,6 +446,7 @@ module GraphQL
431
446
  i = 0
432
447
  fields.each do |field|
433
448
  print_description(field, indent: " ", first_in_block: i == 0)
449
+ print_comment(field, indent: " ", first_in_block: i == 0)
434
450
  print_string(" ")
435
451
  print_field_definition(field)
436
452
  print_string("\n")
@@ -113,7 +113,7 @@ module GraphQL
113
113
  end
114
114
 
115
115
  def print_field(field, indent: "")
116
- @current_field = query.get_field(@current_type, field.name)
116
+ @current_field = query.types.field(@current_type, field.name)
117
117
  old_type = @current_type
118
118
  @current_type = @current_field.type.unwrap
119
119
  super
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/language/block_string"
3
+ require "graphql/language/comment"
3
4
  require "graphql/language/printer"
4
5
  require "graphql/language/sanitized_printer"
5
6
  require "graphql/language/document_from_schema_definition"
@@ -9,7 +10,6 @@ require "graphql/language/nodes"
9
10
  require "graphql/language/cache"
10
11
  require "graphql/language/parser"
11
12
  require "graphql/language/static_visitor"
12
- require "graphql/language/token"
13
13
  require "graphql/language/visitor"
14
14
  require "graphql/language/definition_slice"
15
15
  require "strscan"
@@ -49,19 +49,19 @@ module GraphQL
49
49
  inside_single_quoted_string = false
50
50
  new_query_str = nil
51
51
  while !scanner.eos?
52
- if (match = scanner.scan(/(?:\\"|[^"\n\r]|""")+/m)) && new_query_str
53
- new_query_str << match
54
- elsif scanner.scan('"')
52
+ if scanner.skip(/(?:\\"|[^"\n\r]|""")+/m)
53
+ new_query_str && (new_query_str << scanner.matched)
54
+ elsif scanner.skip('"')
55
55
  new_query_str && (new_query_str << '"')
56
56
  inside_single_quoted_string = !inside_single_quoted_string
57
- elsif scanner.scan("\n")
57
+ elsif scanner.skip("\n")
58
58
  if inside_single_quoted_string
59
59
  new_query_str ||= query_str[0, scanner.pos - 1]
60
60
  new_query_str << '\\n'
61
61
  else
62
62
  new_query_str && (new_query_str << "\n")
63
63
  end
64
- elsif scanner.scan("\r")
64
+ elsif scanner.skip("\r")
65
65
  if inside_single_quoted_string
66
66
  new_query_str ||= query_str[0, scanner.pos - 1]
67
67
  new_query_str << '\\r'
@@ -82,7 +82,13 @@ module GraphQL
82
82
  @provided_values[key] = value
83
83
  end
84
84
 
85
- def_delegators :@query, :trace, :interpreter?
85
+ def_delegators :@query, :trace
86
+
87
+ def types
88
+ @types ||= @query.types
89
+ end
90
+
91
+ attr_writer :types
86
92
 
87
93
  RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path])
88
94
  # @!method []=(key, value)
@@ -27,8 +27,8 @@ module GraphQL
27
27
  @warden = Schema::Warden::NullWarden.new(context: self, schema: @schema)
28
28
  end
29
29
 
30
- def interpreter?
31
- true
30
+ def types
31
+ @types ||= Schema::Warden::VisibilityProfile.new(@warden)
32
32
  end
33
33
  end
34
34
  end
data/lib/graphql/query.rb CHANGED
@@ -95,12 +95,27 @@ module GraphQL
95
95
  # @param root_value [Object] the object used to resolve fields on the root type
96
96
  # @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value)
97
97
  # @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
98
- def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil)
98
+ # @param visibility_profile [Symbol]
99
+ def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, visibility_profile: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil, use_visibility_profile: nil)
99
100
  # Even if `variables: nil` is passed, use an empty hash for simpler logic
100
101
  variables ||= {}
101
102
  @schema = schema
102
103
  @context = schema.context_class.new(query: self, values: context)
103
- @warden = warden
104
+
105
+ if use_visibility_profile.nil?
106
+ use_visibility_profile = warden ? false : schema.use_visibility_profile?
107
+ end
108
+
109
+ @visibility_profile = visibility_profile
110
+
111
+ if use_visibility_profile
112
+ @visibility_profile = @schema.visibility.profile_for(@context, visibility_profile)
113
+ @warden = Schema::Warden::NullWarden.new(context: @context, schema: @schema)
114
+ else
115
+ @visibility_profile = nil
116
+ @warden = warden
117
+ end
118
+
104
119
  @subscription_topic = subscription_topic
105
120
  @root_value = root_value
106
121
  @fragments = nil
@@ -175,9 +190,8 @@ module GraphQL
175
190
  @query_string ||= (document ? document.to_query_string : nil)
176
191
  end
177
192
 
178
- def interpreter?
179
- true
180
- end
193
+ # @return [Symbol, nil]
194
+ attr_reader :visibility_profile
181
195
 
182
196
  attr_accessor :multiplex
183
197
 
@@ -195,8 +209,19 @@ module GraphQL
195
209
  def lookahead
196
210
  @lookahead ||= begin
197
211
  ast_node = selected_operation
198
- root_type = warden.root_type_for_operation(ast_node.operation_type || "query")
199
- GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [ast_node])
212
+ if ast_node.nil?
213
+ GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
214
+ else
215
+ root_type = case ast_node.operation_type
216
+ when nil, "query"
217
+ types.query_root # rubocop:disable Development/ContextIsPassedCop
218
+ when "mutation"
219
+ types.mutation_root # rubocop:disable Development/ContextIsPassedCop
220
+ when "subscription"
221
+ types.subscription_root # rubocop:disable Development/ContextIsPassedCop
222
+ end
223
+ GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [ast_node])
224
+ end
200
225
  end
201
226
  end
202
227
 
@@ -328,7 +353,34 @@ module GraphQL
328
353
  with_prepared_ast { @warden }
329
354
  end
330
355
 
331
- def_delegators :warden, :get_type, :get_field, :possible_types, :root_type_for_operation
356
+ def get_type(type_name)
357
+ types.type(type_name) # rubocop:disable Development/ContextIsPassedCop
358
+ end
359
+
360
+ def get_field(owner, field_name)
361
+ types.field(owner, field_name) # rubocop:disable Development/ContextIsPassedCop
362
+ end
363
+
364
+ def possible_types(type)
365
+ types.possible_types(type) # rubocop:disable Development/ContextIsPassedCop
366
+ end
367
+
368
+ def root_type_for_operation(op_type)
369
+ case op_type
370
+ when "query"
371
+ types.query_root # rubocop:disable Development/ContextIsPassedCop
372
+ when "mutation"
373
+ types.mutation_root # rubocop:disable Development/ContextIsPassedCop
374
+ when "subscription"
375
+ types.subscription_root # rubocop:disable Development/ContextIsPassedCop
376
+ else
377
+ raise ArgumentError, "unexpected root type name: #{op_type.inspect}; expected 'query', 'mutation', or 'subscription'"
378
+ end
379
+ end
380
+
381
+ def types
382
+ @visibility_profile || warden.visibility_profile
383
+ end
332
384
 
333
385
  # @param abstract_type [GraphQL::UnionType, GraphQL::InterfaceType]
334
386
  # @param value [Object] Any runtime value
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+ require_relative "./base_cop"
3
+
4
+ module GraphQL
5
+ module Rubocop
6
+ module GraphQL
7
+ # Identify (and auto-correct) any field whose type configuration isn't given
8
+ # in the configuration block.
9
+ #
10
+ # @example
11
+ # # bad, immediately causes Rails to load `app/graphql/types/thing.rb`
12
+ # field :thing, Types::Thing
13
+ #
14
+ # # good, defers loading until the file is needed
15
+ # field :thing do
16
+ # type(Types::Thing)
17
+ # end
18
+ #
19
+ class FieldTypeInBlock < BaseCop
20
+ MSG = "type configuration can be moved to a block to defer loading the type's file"
21
+
22
+ BUILT_IN_SCALAR_NAMES = ["Float", "Int", "Integer", "String", "ID", "Boolean"]
23
+ def_node_matcher :field_config_with_inline_type, <<-Pattern
24
+ (
25
+ send {nil? _} :field sym ${const array} ...
26
+ )
27
+ Pattern
28
+
29
+ def_node_matcher :field_config_with_inline_type_and_block, <<-Pattern
30
+ (
31
+ block
32
+ (send {nil? _} :field sym ${const array} ...) ...
33
+ (args)
34
+ _
35
+
36
+ )
37
+ Pattern
38
+
39
+ def on_block(node)
40
+ ignore_node(node)
41
+ field_config_with_inline_type_and_block(node) do |type_const|
42
+ type_const_str = get_type_argument_str(node, type_const)
43
+ if ignore_inline_type_str?(type_const_str)
44
+ # Do nothing ...
45
+ else
46
+ add_offense(type_const) do |corrector|
47
+ cleaned_node_source = delete_type_argument(node, type_const)
48
+ field_indent = determine_field_indent(node)
49
+ cleaned_node_source.sub!(/(\{|do)/, "\\1\n#{field_indent} type #{type_const_str}")
50
+ corrector.replace(node, cleaned_node_source)
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ def on_send(node)
57
+ return if part_of_ignored_node?(node)
58
+ field_config_with_inline_type(node) do |type_const|
59
+ type_const_str = get_type_argument_str(node, type_const)
60
+ if ignore_inline_type_str?(type_const_str)
61
+ # Do nothing -- not loading from another file
62
+ else
63
+ add_offense(type_const) do |corrector|
64
+ cleaned_node_source = delete_type_argument(node, type_const)
65
+ field_indent = determine_field_indent(node)
66
+ cleaned_node_source += " do\n#{field_indent} type #{type_const_str}\n#{field_indent}end"
67
+ corrector.replace(node, cleaned_node_source)
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+
74
+ private
75
+
76
+ def ignore_inline_type_str?(type_str)
77
+ if BUILT_IN_SCALAR_NAMES.include?(type_str)
78
+ true
79
+ elsif (inner_type_str = type_str.sub(/\[([A-Za-z]+)(, null: (true|false))?\]/, '\1')) && BUILT_IN_SCALAR_NAMES.include?(inner_type_str)
80
+ true
81
+ else
82
+ false
83
+ end
84
+ end
85
+
86
+ def get_type_argument_str(send_node, type_const)
87
+ first_pos = type_const.location.expression.begin_pos
88
+ end_pos = type_const.location.expression.end_pos
89
+ node_source = send_node.source_range.source
90
+ node_first_pos = send_node.location.expression.begin_pos
91
+
92
+ relative_first_pos = first_pos - node_first_pos
93
+ end_removal_pos = end_pos - node_first_pos
94
+
95
+ node_source[relative_first_pos...end_removal_pos]
96
+ end
97
+
98
+ def delete_type_argument(send_node, type_const)
99
+ first_pos = type_const.location.expression.begin_pos
100
+ end_pos = type_const.location.expression.end_pos
101
+ node_source = send_node.source_range.source
102
+ node_first_pos = send_node.location.expression.begin_pos
103
+
104
+ relative_first_pos = first_pos - node_first_pos
105
+ end_removal_pos = end_pos - node_first_pos
106
+
107
+ begin_removal_pos = relative_first_pos
108
+ while node_source[begin_removal_pos] != ","
109
+ begin_removal_pos -= 1
110
+ if begin_removal_pos < 1
111
+ raise "Invariant: somehow backtracked to beginning of node looking for a comma (node source: #{node_source.inspect})"
112
+ end
113
+ end
114
+
115
+ node_source[0...begin_removal_pos] + node_source[end_removal_pos..-1]
116
+ end
117
+
118
+ def determine_field_indent(send_node)
119
+ type_defn_node = send_node
120
+
121
+ while (type_defn_node && !(type_defn_node.class_definition? || type_defn_node.module_definition?))
122
+ type_defn_node = type_defn_node.parent
123
+ end
124
+
125
+ if type_defn_node.nil?
126
+ raise "Invariant: Something went wrong in GraphQL-Ruby, couldn't find surrounding class definition for field (#{send_node}).\n\nPlease report this error on GitHub."
127
+ end
128
+
129
+ type_defn_source = type_defn_node.source
130
+ indent_test_idx = send_node.location.expression.begin_pos - type_defn_node.source_range.begin_pos - 1
131
+ field_indent = "".dup
132
+ while type_defn_source[indent_test_idx] == " "
133
+ field_indent << " "
134
+ indent_test_idx -= 1
135
+ if indent_test_idx == 0
136
+ raise "Invariant: somehow backtracted to beginning of class when looking for field indent (source: #{node_source.inspect})"
137
+ end
138
+ end
139
+ field_indent
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ require_relative "./base_cop"
3
+
4
+ module GraphQL
5
+ module Rubocop
6
+ module GraphQL
7
+ # Identify (and auto-correct) any root types in your schema file.
8
+ #
9
+ # @example
10
+ # # bad, immediately causes Rails to load `app/graphql/types/query.rb`
11
+ # query Types::Query
12
+ #
13
+ # # good, defers loading until the file is needed
14
+ # query { Types::Query }
15
+ #
16
+ class RootTypesInBlock < BaseCop
17
+ MSG = "type configuration can be moved to a block to defer loading the type's file"
18
+
19
+ def_node_matcher :root_type_config_without_block, <<-Pattern
20
+ (
21
+ send nil? {:query :mutation :subscription} const
22
+ )
23
+ Pattern
24
+
25
+ def on_send(node)
26
+ root_type_config_without_block(node) do
27
+ add_offense(node) do |corrector|
28
+ new_node_source = node.source_range.source
29
+ new_node_source.sub!(/(query|mutation|subscription)/, '\1 {')
30
+ new_node_source << " }"
31
+ corrector.replace(node, new_node_source)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -2,3 +2,5 @@
2
2
 
3
3
  require "graphql/rubocop/graphql/default_null_true"
4
4
  require "graphql/rubocop/graphql/default_required_true"
5
+ require "graphql/rubocop/graphql/field_type_in_block"
6
+ require "graphql/rubocop/graphql/root_types_in_block"
@@ -189,6 +189,7 @@ module GraphQL
189
189
  add_directives_from(type)
190
190
  if type.kind.fields?
191
191
  type.all_field_definitions.each do |field|
192
+ field.ensure_loaded
192
193
  name = field.graphql_name
193
194
  field_type = field.type.unwrap
194
195
  if !field_type.is_a?(GraphQL::Schema::LateBoundType)
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  class Schema
4
- class AlwaysVisible
4
+ module AlwaysVisible
5
5
  def self.use(schema, **opts)
6
- schema.warden_class = GraphQL::Schema::Warden::NullWarden
6
+ schema.extend(self)
7
+ end
8
+
9
+ def visible?(_member, _context)
10
+ true
7
11
  end
8
12
  end
9
13
  end
@@ -50,11 +50,12 @@ module GraphQL
50
50
  # @param deprecation_reason [String]
51
51
  # @param validates [Hash, nil] Options for building validators, if any should be applied
52
52
  # @param replace_null_with_default [Boolean] if `true`, incoming values of `null` will be replaced with the configured `default_value`
53
- def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NOT_CONFIGURED, as: nil, from_resolver: false, camelize: true, prepare: nil, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block)
53
+ def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, comment: nil, ast_node: nil, default_value: NOT_CONFIGURED, as: nil, from_resolver: false, camelize: true, prepare: nil, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block)
54
54
  arg_name ||= name
55
55
  @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
56
56
  @type_expr = type_expr || type
57
57
  @description = desc || description
58
+ @comment = comment
58
59
  @null = required != true
59
60
  @default_value = default_value
60
61
  if replace_null_with_default
@@ -129,6 +130,17 @@ module GraphQL
129
130
  end
130
131
  end
131
132
 
133
+ attr_writer :comment
134
+
135
+ # @return [String] Comment for this argument
136
+ def comment(text = nil)
137
+ if text
138
+ @comment = text
139
+ else
140
+ @comment
141
+ end
142
+ end
143
+
132
144
  # @return [String] Deprecation reason for this argument
133
145
  def deprecation_reason(text = nil)
134
146
  if text
@@ -352,6 +364,7 @@ module GraphQL
352
364
 
353
365
  # @api private
354
366
  def validate_default_value
367
+ return unless default_value?
355
368
  coerced_default_value = begin
356
369
  # This is weird, but we should accept single-item default values for list-type arguments.
357
370
  # If we used `coerce_isolated_input` below, it would do this for us, but it's not really
@@ -127,11 +127,12 @@ module GraphQL
127
127
  builder = self
128
128
 
129
129
  found_types = types.values
130
+ object_types = found_types.select { |t| t.respond_to?(:kind) && t.kind.object? }
130
131
  schema_class = Class.new(schema_superclass) do
131
132
  begin
132
133
  # Add these first so that there's some chance of resolving late-bound types
133
134
  add_type_and_traverse(found_types, root: false)
134
- orphan_types(found_types.select { |t| t.respond_to?(:kind) && t.kind.object? })
135
+ orphan_types(object_types)
135
136
  query query_root_type
136
137
  mutation mutation_root_type
137
138
  subscription subscription_root_type
@@ -141,6 +142,12 @@ module GraphQL
141
142
  raise InvalidDocumentError, "Type \"#{type_name}\" not found in document.", err_backtrace
142
143
  end
143
144
 
145
+ object_types.each do |t|
146
+ t.interfaces.each do |int_t|
147
+ int_t.orphan_types(t)
148
+ end
149
+ end
150
+
144
151
  if default_resolve.respond_to?(:resolve_type)
145
152
  def self.resolve_type(*args)
146
153
  self.definition_default_resolve.resolve_type(*args)
@@ -181,6 +188,7 @@ module GraphQL
181
188
 
182
189
  def self.inherited(child_class)
183
190
  child_class.definition_default_resolve = self.definition_default_resolve
191
+ super
184
192
  end
185
193
  end
186
194
 
@@ -7,7 +7,7 @@ module GraphQL
7
7
  # In this case, the server hides types and fields _entirely_, unless the current context has certain `:flags` present.
8
8
  class Flagged < GraphQL::Schema::Directive
9
9
  def initialize(target, **options)
10
- if target.is_a?(Module) && !target.ancestors.include?(VisibleByFlag)
10
+ if target.is_a?(Module)
11
11
  # This is type class of some kind, `include` will put this module
12
12
  # in between the type class itself and its super class, so `super` will work fine
13
13
  target.include(VisibleByFlag)