graphql 2.3.7 → 2.4.7

Sign up to get free protection for your applications and to get access to all the features.
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}` "