graphql 2.3.7 → 2.4.7

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 (126) 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 +8 -7
  9. data/lib/graphql/analysis.rb +4 -4
  10. data/lib/graphql/autoload.rb +38 -0
  11. data/lib/graphql/current.rb +52 -0
  12. data/lib/graphql/dataloader/async_dataloader.rb +7 -6
  13. data/lib/graphql/dataloader/source.rb +7 -4
  14. data/lib/graphql/dataloader.rb +40 -19
  15. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  16. data/lib/graphql/execution/interpreter/resolve.rb +13 -9
  17. data/lib/graphql/execution/interpreter/runtime.rb +35 -31
  18. data/lib/graphql/execution/interpreter.rb +6 -4
  19. data/lib/graphql/execution/lookahead.rb +18 -11
  20. data/lib/graphql/introspection/directive_type.rb +1 -1
  21. data/lib/graphql/introspection/entry_points.rb +2 -2
  22. data/lib/graphql/introspection/field_type.rb +1 -1
  23. data/lib/graphql/introspection/schema_type.rb +6 -11
  24. data/lib/graphql/introspection/type_type.rb +5 -5
  25. data/lib/graphql/invalid_null_error.rb +1 -1
  26. data/lib/graphql/language/cache.rb +13 -0
  27. data/lib/graphql/language/comment.rb +18 -0
  28. data/lib/graphql/language/document_from_schema_definition.rb +62 -34
  29. data/lib/graphql/language/lexer.rb +18 -15
  30. data/lib/graphql/language/nodes.rb +24 -16
  31. data/lib/graphql/language/parser.rb +14 -1
  32. data/lib/graphql/language/printer.rb +31 -15
  33. data/lib/graphql/language/sanitized_printer.rb +1 -1
  34. data/lib/graphql/language.rb +6 -6
  35. data/lib/graphql/pagination/connection.rb +1 -1
  36. data/lib/graphql/query/context/scoped_context.rb +1 -1
  37. data/lib/graphql/query/context.rb +13 -6
  38. data/lib/graphql/query/null_context.rb +3 -5
  39. data/lib/graphql/query/variable_validation_error.rb +1 -1
  40. data/lib/graphql/query.rb +72 -18
  41. data/lib/graphql/railtie.rb +7 -0
  42. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  43. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  44. data/lib/graphql/rubocop.rb +2 -0
  45. data/lib/graphql/schema/addition.rb +2 -1
  46. data/lib/graphql/schema/always_visible.rb +6 -2
  47. data/lib/graphql/schema/argument.rb +14 -1
  48. data/lib/graphql/schema/build_from_definition.rb +9 -1
  49. data/lib/graphql/schema/directive/flagged.rb +2 -2
  50. data/lib/graphql/schema/directive.rb +1 -1
  51. data/lib/graphql/schema/enum.rb +71 -23
  52. data/lib/graphql/schema/enum_value.rb +10 -2
  53. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  54. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  55. data/lib/graphql/schema/field.rb +102 -47
  56. data/lib/graphql/schema/field_extension.rb +1 -1
  57. data/lib/graphql/schema/has_single_input_argument.rb +5 -2
  58. data/lib/graphql/schema/input_object.rb +90 -39
  59. data/lib/graphql/schema/interface.rb +22 -5
  60. data/lib/graphql/schema/introspection_system.rb +5 -16
  61. data/lib/graphql/schema/loader.rb +1 -1
  62. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  63. data/lib/graphql/schema/member/has_arguments.rb +36 -23
  64. data/lib/graphql/schema/member/has_directives.rb +3 -3
  65. data/lib/graphql/schema/member/has_fields.rb +26 -6
  66. data/lib/graphql/schema/member/has_interfaces.rb +4 -4
  67. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  68. data/lib/graphql/schema/member/has_validators.rb +1 -1
  69. data/lib/graphql/schema/object.rb +8 -0
  70. data/lib/graphql/schema/printer.rb +1 -0
  71. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  72. data/lib/graphql/schema/resolver.rb +12 -14
  73. data/lib/graphql/schema/subscription.rb +2 -2
  74. data/lib/graphql/schema/type_expression.rb +2 -2
  75. data/lib/graphql/schema/union.rb +1 -1
  76. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  77. data/lib/graphql/schema/validator/required_validator.rb +28 -4
  78. data/lib/graphql/schema/validator.rb +3 -1
  79. data/lib/graphql/schema/visibility/migration.rb +188 -0
  80. data/lib/graphql/schema/visibility/profile.rb +359 -0
  81. data/lib/graphql/schema/visibility/visit.rb +190 -0
  82. data/lib/graphql/schema/visibility.rb +294 -0
  83. data/lib/graphql/schema/warden.rb +179 -16
  84. data/lib/graphql/schema.rb +348 -94
  85. data/lib/graphql/static_validation/base_visitor.rb +6 -5
  86. data/lib/graphql/static_validation/literal_validator.rb +4 -4
  87. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  88. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  89. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  90. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  91. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  92. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  93. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +2 -2
  94. data/lib/graphql/static_validation/rules/fields_will_merge.rb +8 -7
  95. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  96. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  97. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  98. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  99. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  100. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  101. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
  102. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
  103. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
  104. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  105. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  106. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  107. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  108. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  109. data/lib/graphql/static_validation/validation_context.rb +18 -2
  110. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +3 -2
  111. data/lib/graphql/subscriptions/broadcast_analyzer.rb +10 -4
  112. data/lib/graphql/subscriptions/event.rb +1 -1
  113. data/lib/graphql/subscriptions/serialize.rb +2 -0
  114. data/lib/graphql/subscriptions.rb +6 -4
  115. data/lib/graphql/testing/helpers.rb +10 -6
  116. data/lib/graphql/tracing/notifications_trace.rb +2 -2
  117. data/lib/graphql/types/relay/connection_behaviors.rb +12 -2
  118. data/lib/graphql/types/relay/edge_behaviors.rb +11 -1
  119. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  120. data/lib/graphql/types.rb +18 -11
  121. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  122. data/lib/graphql/version.rb +1 -1
  123. data/lib/graphql.rb +53 -45
  124. metadata +31 -8
  125. data/lib/graphql/language/token.rb +0 -34
  126. data/lib/graphql/schema/invalid_type_error.rb +0 -7
@@ -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'
@@ -223,7 +223,7 @@ module GraphQL
223
223
 
224
224
  def detect_was_authorized_by_scope_items
225
225
  if @context &&
226
- (current_runtime_state = Thread.current[:__graphql_runtime_info]) &&
226
+ (current_runtime_state = Fiber[:__graphql_runtime_info]) &&
227
227
  (query_runtime_state = current_runtime_state[@context.query])
228
228
  query_runtime_state.was_authorized_by_scope_items
229
229
  else
@@ -60,7 +60,7 @@ module GraphQL
60
60
  each_present_path_ctx do |path_ctx|
61
61
  if path_ctx.key?(key)
62
62
  found_value = path_ctx[key]
63
- if other_keys.any?
63
+ if !other_keys.empty?
64
64
  return found_value.dig(*other_keys)
65
65
  else
66
66
  return found_value
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- require "graphql/query/context/scoped_context"
3
2
 
4
3
  module GraphQL
5
4
  class Query
@@ -82,7 +81,13 @@ module GraphQL
82
81
  @provided_values[key] = value
83
82
  end
84
83
 
85
- def_delegators :@query, :trace, :interpreter?
84
+ def_delegators :@query, :trace
85
+
86
+ def types
87
+ @types ||= @query.types
88
+ end
89
+
90
+ attr_writer :types
86
91
 
87
92
  RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path])
88
93
  # @!method []=(key, value)
@@ -98,7 +103,7 @@ module GraphQL
98
103
  if key == :current_path
99
104
  current_path
100
105
  else
101
- (current_runtime_state = Thread.current[:__graphql_runtime_info]) &&
106
+ (current_runtime_state = Fiber[:__graphql_runtime_info]) &&
102
107
  (query_runtime_state = current_runtime_state[@query]) &&
103
108
  (query_runtime_state.public_send(key))
104
109
  end
@@ -138,7 +143,7 @@ module GraphQL
138
143
  end
139
144
 
140
145
  def current_path
141
- current_runtime_state = Thread.current[:__graphql_runtime_info]
146
+ current_runtime_state = Fiber[:__graphql_runtime_info]
142
147
  query_runtime_state = current_runtime_state && current_runtime_state[@query]
143
148
 
144
149
  path = query_runtime_state &&
@@ -163,7 +168,7 @@ module GraphQL
163
168
 
164
169
  def fetch(key, default = UNSPECIFIED_FETCH_DEFAULT)
165
170
  if RUNTIME_METADATA_KEYS.include?(key)
166
- (runtime = Thread.current[:__graphql_runtime_info]) &&
171
+ (runtime = Fiber[:__graphql_runtime_info]) &&
167
172
  (query_runtime_state = runtime[@query]) &&
168
173
  (query_runtime_state.public_send(key))
169
174
  elsif @scoped_context.key?(key)
@@ -181,7 +186,7 @@ module GraphQL
181
186
 
182
187
  def dig(key, *other_keys)
183
188
  if RUNTIME_METADATA_KEYS.include?(key)
184
- (current_runtime_state = Thread.current[:__graphql_runtime_info]) &&
189
+ (current_runtime_state = Fiber[:__graphql_runtime_info]) &&
185
190
  (query_runtime_state = current_runtime_state[@query]) &&
186
191
  (obj = query_runtime_state.public_send(key)) &&
187
192
  if other_keys.empty?
@@ -283,3 +288,5 @@ module GraphQL
283
288
  end
284
289
  end
285
290
  end
291
+
292
+ require "graphql/query/context/scoped_context"
@@ -18,17 +18,15 @@ module GraphQL
18
18
  extend Forwardable
19
19
 
20
20
  attr_reader :schema, :query, :warden, :dataloader
21
- def_delegators GraphQL::EmptyObjects::EMPTY_HASH, :[], :fetch, :dig, :key?
21
+ def_delegators GraphQL::EmptyObjects::EMPTY_HASH, :[], :fetch, :dig, :key?, :to_h
22
22
 
23
23
  def initialize
24
24
  @query = NullQuery.new
25
25
  @dataloader = GraphQL::Dataloader::NullDataloader.new
26
26
  @schema = NullSchema
27
27
  @warden = Schema::Warden::NullWarden.new(context: self, schema: @schema)
28
- end
29
-
30
- def interpreter?
31
- true
28
+ @types = @warden.visibility_profile
29
+ freeze
32
30
  end
33
31
  end
34
32
  end
@@ -10,7 +10,7 @@ module GraphQL
10
10
 
11
11
  msg ||= "Variable $#{variable_ast.name} of type #{type.to_type_signature} was provided invalid value"
12
12
 
13
- if problem_fields.any?
13
+ if !problem_fields.empty?
14
14
  msg += " for #{problem_fields.join(", ")}"
15
15
  end
16
16
 
data/lib/graphql/query.rb CHANGED
@@ -1,19 +1,21 @@
1
1
  # frozen_string_literal: true
2
- require "graphql/query/context"
3
- require "graphql/query/fingerprint"
4
- require "graphql/query/null_context"
5
- require "graphql/query/result"
6
- require "graphql/query/variables"
7
- require "graphql/query/input_validation_result"
8
- require "graphql/query/variable_validation_error"
9
- require "graphql/query/validation_pipeline"
10
2
 
11
3
  module GraphQL
12
4
  # A combination of query string and {Schema} instance which can be reduced to a {#result}.
13
5
  class Query
6
+ extend Autoload
14
7
  include Tracing::Traceable
15
8
  extend Forwardable
16
9
 
10
+ autoload :Context, "graphql/query/context"
11
+ autoload :Fingerprint, "graphql/query/fingerprint"
12
+ autoload :NullContext, "graphql/query/null_context"
13
+ autoload :Result, "graphql/query/result"
14
+ autoload :Variables, "graphql/query/variables"
15
+ autoload :InputValidationResult, "graphql/query/input_validation_result"
16
+ autoload :VariableValidationError, "graphql/query/variable_validation_error"
17
+ autoload :ValidationPipeline, "graphql/query/validation_pipeline"
18
+
17
19
  class OperationNameMissingError < GraphQL::ExecutionError
18
20
  def initialize(name)
19
21
  msg = if name.nil?
@@ -95,12 +97,27 @@ module GraphQL
95
97
  # @param root_value [Object] the object used to resolve fields on the root type
96
98
  # @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value)
97
99
  # @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)
100
+ # @param visibility_profile [Symbol]
101
+ 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
102
  # Even if `variables: nil` is passed, use an empty hash for simpler logic
100
103
  variables ||= {}
101
104
  @schema = schema
102
105
  @context = schema.context_class.new(query: self, values: context)
103
- @warden = warden
106
+
107
+ if use_visibility_profile.nil?
108
+ use_visibility_profile = warden ? false : schema.use_visibility_profile?
109
+ end
110
+
111
+ @visibility_profile = visibility_profile
112
+
113
+ if use_visibility_profile
114
+ @visibility_profile = @schema.visibility.profile_for(@context, visibility_profile)
115
+ @warden = Schema::Warden::NullWarden.new(context: @context, schema: @schema)
116
+ else
117
+ @visibility_profile = nil
118
+ @warden = warden
119
+ end
120
+
104
121
  @subscription_topic = subscription_topic
105
122
  @root_value = root_value
106
123
  @fragments = nil
@@ -118,7 +135,7 @@ module GraphQL
118
135
  end
119
136
  end
120
137
 
121
- if context_tracers.any? && !(schema.trace_class <= GraphQL::Tracing::CallLegacyTracers)
138
+ if !context_tracers.empty? && !(schema.trace_class <= GraphQL::Tracing::CallLegacyTracers)
122
139
  raise ArgumentError, "context[:tracers] are not supported without `trace_with(GraphQL::Tracing::CallLegacyTracers)` in the schema configuration, please add it."
123
140
  end
124
141
 
@@ -175,9 +192,8 @@ module GraphQL
175
192
  @query_string ||= (document ? document.to_query_string : nil)
176
193
  end
177
194
 
178
- def interpreter?
179
- true
180
- end
195
+ # @return [Symbol, nil]
196
+ attr_reader :visibility_profile
181
197
 
182
198
  attr_accessor :multiplex
183
199
 
@@ -195,8 +211,19 @@ module GraphQL
195
211
  def lookahead
196
212
  @lookahead ||= begin
197
213
  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])
214
+ if ast_node.nil?
215
+ GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
216
+ else
217
+ root_type = case ast_node.operation_type
218
+ when nil, "query"
219
+ types.query_root # rubocop:disable Development/ContextIsPassedCop
220
+ when "mutation"
221
+ types.mutation_root # rubocop:disable Development/ContextIsPassedCop
222
+ when "subscription"
223
+ types.subscription_root # rubocop:disable Development/ContextIsPassedCop
224
+ end
225
+ GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [ast_node])
226
+ end
200
227
  end
201
228
  end
202
229
 
@@ -328,7 +355,34 @@ module GraphQL
328
355
  with_prepared_ast { @warden }
329
356
  end
330
357
 
331
- def_delegators :warden, :get_type, :get_field, :possible_types, :root_type_for_operation
358
+ def get_type(type_name)
359
+ types.type(type_name) # rubocop:disable Development/ContextIsPassedCop
360
+ end
361
+
362
+ def get_field(owner, field_name)
363
+ types.field(owner, field_name) # rubocop:disable Development/ContextIsPassedCop
364
+ end
365
+
366
+ def possible_types(type)
367
+ types.possible_types(type) # rubocop:disable Development/ContextIsPassedCop
368
+ end
369
+
370
+ def root_type_for_operation(op_type)
371
+ case op_type
372
+ when "query"
373
+ types.query_root # rubocop:disable Development/ContextIsPassedCop
374
+ when "mutation"
375
+ types.mutation_root # rubocop:disable Development/ContextIsPassedCop
376
+ when "subscription"
377
+ types.subscription_root # rubocop:disable Development/ContextIsPassedCop
378
+ else
379
+ raise ArgumentError, "unexpected root type name: #{op_type.inspect}; expected 'query', 'mutation', or 'subscription'"
380
+ end
381
+ end
382
+
383
+ def types
384
+ @visibility_profile || warden.visibility_profile
385
+ end
332
386
 
333
387
  # @param abstract_type [GraphQL::UnionType, GraphQL::InterfaceType]
334
388
  # @param value [Object] Any runtime value
@@ -427,7 +481,7 @@ module GraphQL
427
481
  @mutation = false
428
482
  @subscription = false
429
483
  operation_name_error = nil
430
- if @operations.any?
484
+ if !@operations.empty?
431
485
  @selected_operation = find_operation(@operations, @operation_name)
432
486
  if @selected_operation.nil?
433
487
  operation_name_error = GraphQL::Query::OperationNameMissingError.new(@operation_name)
@@ -1,9 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GraphQL
4
+ # Support {GraphQL::Parser::Cache} and {GraphQL.eager_load!}
5
+ #
6
+ # @example Enable the parser cache with default directory
7
+ #
8
+ # config.graphql.parser_cache = true
9
+ #
4
10
  class Railtie < Rails::Railtie
5
11
  config.graphql = ActiveSupport::OrderedOptions.new
6
12
  config.graphql.parser_cache = false
13
+ config.eager_load_namespaces << GraphQL
7
14
 
8
15
  initializer("graphql.cache") do |app|
9
16
  if config.graphql.parser_cache
@@ -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"
@@ -40,7 +40,7 @@ module GraphQL
40
40
  end
41
41
 
42
42
  def add_directives_from(owner)
43
- if (dir_instances = owner.directives).any?
43
+ if !(dir_instances = owner.directives).empty?
44
44
  dirs = dir_instances.map(&:class)
45
45
  @directives.merge(dirs)
46
46
  add_type_and_traverse(dirs)
@@ -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)
@@ -45,7 +45,7 @@ module GraphQL
45
45
  def visible?(context)
46
46
  if dir = self.directives.find { |d| d.is_a?(Flagged) }
47
47
  relevant_flags = (f = context[:flags]) && dir.arguments[:by] & f # rubocop:disable Development/ContextIsPassedCop -- definition-related
48
- relevant_flags && relevant_flags.any? && super
48
+ relevant_flags && !relevant_flags.empty? && super
49
49
  else
50
50
  super
51
51
  end
@@ -29,7 +29,7 @@ module GraphQL
29
29
  end
30
30
 
31
31
  def locations(*new_locations)
32
- if new_locations.any?
32
+ if !new_locations.empty?
33
33
  new_locations.each do |new_loc|
34
34
  if !LOCATIONS.include?(new_loc.to_sym)
35
35
  raise ArgumentError, "#{self} (#{self.graphql_name}) has an invalid directive location: `locations #{new_loc}` "