graphql 2.5.23 → 2.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/query_complexity.rb +29 -13
  3. data/lib/graphql/analysis.rb +20 -13
  4. data/lib/graphql/backtrace/table.rb +10 -1
  5. data/lib/graphql/current.rb +7 -1
  6. data/lib/graphql/dataloader.rb +1 -1
  7. data/lib/graphql/execution/directive_checks.rb +2 -0
  8. data/lib/graphql/execution/field_resolve_step.rb +744 -0
  9. data/lib/graphql/execution/finalize.rb +230 -0
  10. data/lib/graphql/execution/input_values.rb +333 -0
  11. data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -0
  12. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  13. data/lib/graphql/execution/interpreter/runtime.rb +36 -15
  14. data/lib/graphql/execution/load_argument_step.rb +102 -0
  15. data/lib/graphql/execution/next.rb +42 -16
  16. data/lib/graphql/execution/prepare_object_step.rb +147 -0
  17. data/lib/graphql/execution/resolve_type_step.rb +27 -0
  18. data/lib/graphql/execution/runner.rb +445 -0
  19. data/lib/graphql/execution/selections_step.rb +91 -0
  20. data/lib/graphql/execution.rb +10 -3
  21. data/lib/graphql/execution_error.rb +7 -13
  22. data/lib/graphql/introspection/entry_points.rb +2 -2
  23. data/lib/graphql/introspection/schema_type.rb +6 -2
  24. data/lib/graphql/language/lexer.rb +12 -8
  25. data/lib/graphql/language/parser.rb +1 -1
  26. data/lib/graphql/language.rb +8 -2
  27. data/lib/graphql/pagination/connections.rb +1 -3
  28. data/lib/graphql/query/context.rb +6 -0
  29. data/lib/graphql/query/partial.rb +18 -3
  30. data/lib/graphql/query.rb +12 -3
  31. data/lib/graphql/runtime_error.rb +6 -0
  32. data/lib/graphql/schema/argument.rb +3 -3
  33. data/lib/graphql/schema/build_from_definition.rb +10 -0
  34. data/lib/graphql/schema/directive/feature.rb +4 -0
  35. data/lib/graphql/schema/directive/transform.rb +20 -0
  36. data/lib/graphql/schema/directive.rb +23 -9
  37. data/lib/graphql/schema/field/connection_extension.rb +2 -15
  38. data/lib/graphql/schema/field/scope_extension.rb +0 -4
  39. data/lib/graphql/schema/field.rb +20 -20
  40. data/lib/graphql/schema/field_extension.rb +11 -41
  41. data/lib/graphql/schema/has_single_input_argument.rb +24 -13
  42. data/lib/graphql/schema/input_object.rb +4 -0
  43. data/lib/graphql/schema/interface.rb +26 -0
  44. data/lib/graphql/schema/introspection_system.rb +6 -21
  45. data/lib/graphql/schema/list.rb +4 -0
  46. data/lib/graphql/schema/member/base_dsl_methods.rb +0 -10
  47. data/lib/graphql/schema/printer.rb +1 -1
  48. data/lib/graphql/schema/ractor_shareable.rb +1 -0
  49. data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
  50. data/lib/graphql/schema/resolver.rb +30 -14
  51. data/lib/graphql/schema/subscription.rb +53 -8
  52. data/lib/graphql/schema/timeout.rb +2 -2
  53. data/lib/graphql/schema/validator/allow_blank_validator.rb +3 -3
  54. data/lib/graphql/schema/validator/allow_null_validator.rb +3 -3
  55. data/lib/graphql/schema/validator/exclusion_validator.rb +2 -2
  56. data/lib/graphql/schema/validator/format_validator.rb +3 -3
  57. data/lib/graphql/schema/validator/inclusion_validator.rb +2 -2
  58. data/lib/graphql/schema/validator/length_validator.rb +6 -6
  59. data/lib/graphql/schema/validator/numericality_validator.rb +19 -19
  60. data/lib/graphql/schema/validator/required_validator.rb +6 -4
  61. data/lib/graphql/schema/validator.rb +9 -0
  62. data/lib/graphql/schema/visibility/profile.rb +6 -4
  63. data/lib/graphql/schema/visibility/visit.rb +1 -1
  64. data/lib/graphql/schema/visibility.rb +30 -22
  65. data/lib/graphql/schema.rb +43 -20
  66. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +31 -25
  67. data/lib/graphql/subscriptions/event.rb +0 -1
  68. data/lib/graphql/subscriptions.rb +15 -0
  69. data/lib/graphql/tracing/perfetto_trace.rb +5 -3
  70. data/lib/graphql/tracing/trace.rb +6 -0
  71. data/lib/graphql/unauthorized_error.rb +1 -1
  72. data/lib/graphql/version.rb +1 -1
  73. data/lib/graphql.rb +1 -3
  74. metadata +11 -7
  75. data/lib/graphql/execution/next/field_resolve_step.rb +0 -743
  76. data/lib/graphql/execution/next/load_argument_step.rb +0 -64
  77. data/lib/graphql/execution/next/prepare_object_step.rb +0 -129
  78. data/lib/graphql/execution/next/runner.rb +0 -411
  79. data/lib/graphql/execution/next/selections_step.rb +0 -37
@@ -26,16 +26,6 @@ module GraphQL
26
26
  end
27
27
  end
28
28
 
29
- # Just a convenience method to point out that people should use graphql_name instead
30
- def name(new_name = nil)
31
- return super() if new_name.nil?
32
-
33
- fail(
34
- "The new name override method is `graphql_name`, not `name`. Usage: "\
35
- "graphql_name \"#{new_name}\""
36
- )
37
- end
38
-
39
29
  # Call this method to provide a new description; OR
40
30
  # call it without an argument to get the description
41
31
  # @param new_description [String]
@@ -58,8 +58,8 @@ module GraphQL
58
58
  end
59
59
  end
60
60
  schema = Class.new(GraphQL::Schema) {
61
- use GraphQL::Schema::Visibility
62
61
  query(query_root)
62
+ use GraphQL::Schema::Visibility
63
63
  def self.visible?(member, _ctx)
64
64
  member.graphql_name != "Root"
65
65
  end
@@ -72,6 +72,7 @@ module GraphQL
72
72
  # How to support it?
73
73
  def lazy?(_obj); false; end
74
74
  def sync_lazy(obj); obj; end
75
+ def resolves_lazies?; false; end
75
76
  end
76
77
  end
77
78
  end
@@ -25,7 +25,7 @@ module GraphQL
25
25
  argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
26
26
 
27
27
  # The payload should always include this field
28
- field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.")
28
+ field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.", hash_key: :client_mutation_id)
29
29
  # Relay classic default:
30
30
  null(true)
31
31
 
@@ -35,7 +35,6 @@ module GraphQL
35
35
  input = inputs[:input].to_kwargs
36
36
 
37
37
  if input
38
- # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are.
39
38
  input_kwargs = input.to_h
40
39
  client_mutation_id = input_kwargs.delete(:client_mutation_id)
41
40
  inputs[:input] = input_kwargs
@@ -51,6 +50,21 @@ module GraphQL
51
50
  return_hash
52
51
  end
53
52
  end
53
+
54
+ def call
55
+ input = @prepared_arguments[:input]&.to_kwargs
56
+
57
+ if input
58
+ client_mutation_id = input.delete(:client_mutation_id)
59
+ @prepared_arguments[:input] = input
60
+ end
61
+
62
+ super
63
+
64
+ if (return_value = exec_result[exec_index]).is_a?(Hash)
65
+ return_value[:client_mutation_id] = client_mutation_id
66
+ end
67
+ end
54
68
  end
55
69
  end
56
70
  end
@@ -60,29 +60,39 @@ module GraphQL
60
60
  attr_writer :prepared_arguments
61
61
 
62
62
  def call
63
- if self.class < Schema::HasSingleInputArgument
64
- @prepared_arguments = @prepared_arguments[:input]
65
- end
66
63
  q = context.query
67
64
  trace_objs = [object]
68
65
  q.current_trace.begin_execute_field(field, @prepared_arguments, trace_objs, q)
69
- begin
70
- is_authed, new_return_value = authorized?(**@prepared_arguments)
71
- rescue GraphQL::UnauthorizedError => err
72
- new_return_value = q.schema.unauthorized_object(err)
73
- is_authed = true # the error was handled
66
+ is_ready = ready?(**@prepared_arguments)
67
+ runner = @field_resolve_step.runner
68
+ if runner.resolves_lazies && runner.schema.lazy?(is_ready)
69
+ is_ready, new_return_value = runner.schema.sync_lazy(is_ready)
70
+ end
71
+
72
+ if is_ready.is_a?(Array)
73
+ is_ready, new_return_value = is_ready
74
+ if is_ready != false
75
+ raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{is_ready.inspect}, #{new_return_value.inspect}]"
76
+ else
77
+ new_return_value
78
+ end
74
79
  end
75
80
 
76
- if (runner = @field_resolve_step.runner).resolves_lazies && runner.schema.lazy?(is_authed)
81
+ if is_ready
82
+ begin
83
+ is_authed, new_return_value = authorized?(**@prepared_arguments)
84
+ rescue GraphQL::UnauthorizedError => err
85
+ new_return_value = q.schema.unauthorized_object(err)
86
+ is_authed = true # the error was handled
87
+ end
88
+ end
89
+
90
+ if runner.resolves_lazies && runner.schema.lazy?(is_authed)
77
91
  is_authed, new_return_value = runner.schema.sync_lazy(is_authed)
78
92
  end
79
93
 
80
94
  result = if is_authed
81
95
  Schema::Validator.validate!(self.class.validators, object, context, @prepared_arguments, as: @field)
82
- if q.subscription? && @field.owner == context.schema.subscription
83
- # This needs to use arguments without `loads:`
84
- @original_arguments = @field_resolve_step.coerce_arguments(@field, @field_resolve_step.ast_node.arguments, false)
85
- end
86
96
  call_resolve(@prepared_arguments)
87
97
  elsif new_return_value.nil?
88
98
  err = UnauthorizedFieldError.new(object: object, type: @field_resolve_step.parent_type, context: context, field: @field)
@@ -93,7 +103,13 @@ module GraphQL
93
103
  q = context.query
94
104
  q.current_trace.end_execute_field(field, @prepared_arguments, trace_objs, q, [result])
95
105
  exec_result[exec_index] = result
96
- rescue RuntimeError => err
106
+ rescue GraphQL::UnauthorizedError => auth_err
107
+ exec_result[exec_index] = begin
108
+ context.schema.unauthorized_object(auth_err)
109
+ rescue GraphQL::ExecutionError => exec_err
110
+ exec_err
111
+ end
112
+ rescue GraphQL::RuntimeError => err
97
113
  exec_result[exec_index] = err
98
114
  rescue StandardError => stderr
99
115
  exec_result[exec_index] = begin
@@ -30,14 +30,47 @@ module GraphQL
30
30
  end
31
31
  end
32
32
 
33
+ # @api private
34
+ def call_resolve(args_hash)
35
+ if @field_resolve_step.nil?
36
+ super
37
+ else
38
+ context.namespace(:subscriptions)[:update_event] = event
39
+ result = nil
40
+ unsubscribed = true
41
+ unsubscribed_result = nil
42
+ begin
43
+ result = super
44
+ unsubscribed = false
45
+ rescue EarlyUnsubscribe => err
46
+ unsubscribed_result = err.unsubscribed_result
47
+ end
48
+
49
+
50
+ if unsubscribed
51
+ if unsubscribed_result
52
+ context.namespace(:subscriptions)[:final_update] = true
53
+ unsubscribed_result
54
+ else
55
+ context.skip
56
+ end
57
+ else
58
+ result
59
+ end
60
+ end
61
+ end
62
+
33
63
  # @api private
34
64
  def resolve_with_support(**args)
35
65
  @original_arguments = args # before `loads:` have been run
36
66
  result = nil
37
67
  unsubscribed = true
38
- unsubscribed_result = catch :graphql_subscription_unsubscribed do
68
+ unsubscribed_result = nil
69
+ begin
39
70
  result = super
40
71
  unsubscribed = false
72
+ rescue EarlyUnsubscribe => err
73
+ unsubscribed_result = err.unsubscribed_result
41
74
  end
42
75
 
43
76
 
@@ -114,7 +147,13 @@ module GraphQL
114
147
  # @return [void]
115
148
  def unsubscribe(update_value = nil)
116
149
  context.namespace(:subscriptions)[:unsubscribed] = true
117
- throw :graphql_subscription_unsubscribed, update_value
150
+ err = EarlyUnsubscribe.new
151
+ err.unsubscribed_result = update_value
152
+ raise err
153
+ end
154
+
155
+ class EarlyUnsubscribe < GraphQL::RuntimeError
156
+ attr_accessor :unsubscribed_result
118
157
  end
119
158
 
120
159
  # Call this method to provide a new subscription_scope; OR
@@ -187,12 +226,18 @@ module GraphQL
187
226
 
188
227
  # @return [Subscriptions::Event] This object is used as a representation of this subscription for the backend
189
228
  def event
190
- @event ||= Subscriptions::Event.new(
191
- name: field.name,
192
- arguments: @original_arguments,
193
- context: context,
194
- field: field,
195
- )
229
+ @event ||= begin
230
+ if @original_arguments.nil? && @field_resolve_step
231
+ @original_arguments, _errors = @field_resolve_step.arguments_without_loads
232
+ end
233
+
234
+ Subscriptions::Event.new(
235
+ name: field.name,
236
+ arguments: @original_arguments,
237
+ context: context,
238
+ field: field,
239
+ )
240
+ end
196
241
  end
197
242
  end
198
243
  end
@@ -68,7 +68,7 @@ module GraphQL
68
68
  super
69
69
  end
70
70
 
71
- def execute_field(query:, field:, **_rest)
71
+ def begin_execute_field(field, _arguments, _objects, query)
72
72
  timeout_state = query.context.namespace(@timeout).fetch(:state)
73
73
  # If the `:state` is `false`, then `max_seconds(query)` opted out of timeout for this query.
74
74
  if timeout_state == false
@@ -84,7 +84,7 @@ module GraphQL
84
84
 
85
85
  # `handle_timeout` may have set this to be `false`
86
86
  if timeout_state != false
87
- error
87
+ raise error
88
88
  else
89
89
  super
90
90
  end
@@ -8,7 +8,7 @@ module GraphQL
8
8
  # @example Require a non-empty string for an argument
9
9
  # argument :name, String, required: true, validate: { allow_blank: false }
10
10
  class AllowBlankValidator < Validator
11
- def initialize(allow_blank_positional, allow_blank: nil, message: "%{validated} can't be blank", **default_options)
11
+ def initialize(allow_blank_positional = nil, allow_blank: nil, message: "%{validated} can't be blank", **default_options)
12
12
  @message = message
13
13
  super(**default_options)
14
14
  @allow_blank = allow_blank.nil? ? allow_blank_positional : allow_blank
@@ -16,10 +16,10 @@ module GraphQL
16
16
 
17
17
  def validate(_object, _context, value)
18
18
  if value.respond_to?(:blank?) && value.blank?
19
- if (value.nil? && @allow_null) || @allow_blank
19
+ if (value.nil? && validation_parameter(@allow_null)) || validation_parameter(@allow_blank)
20
20
  # pass
21
21
  else
22
- @message
22
+ validation_parameter(@message)
23
23
  end
24
24
  end
25
25
  end
@@ -9,15 +9,15 @@ module GraphQL
9
9
  # argument :name, String, required: false, validates: { allow_null: false }
10
10
  class AllowNullValidator < Validator
11
11
  MESSAGE = "%{validated} can't be null"
12
- def initialize(allow_null_positional, allow_null: nil, message: MESSAGE, **default_options)
12
+ def initialize(allow_null_positional = nil, allow_null: nil, message: MESSAGE, **default_options)
13
13
  @message = message
14
14
  super(**default_options)
15
15
  @allow_null = allow_null.nil? ? allow_null_positional : allow_null
16
16
  end
17
17
 
18
18
  def validate(_object, _context, value)
19
- if value.nil? && !@allow_null
20
- @message
19
+ if value.nil? && !validation_parameter(@allow_null)
20
+ validation_parameter(@message)
21
21
  end
22
22
  end
23
23
  end
@@ -23,8 +23,8 @@ module GraphQL
23
23
  def validate(_object, _context, value)
24
24
  if permitted_empty_value?(value)
25
25
  # pass
26
- elsif @in_list.include?(value)
27
- @message
26
+ elsif validation_parameter(@in_list).include?(value)
27
+ validation_parameter(@message)
28
28
  end
29
29
  end
30
30
  end
@@ -37,9 +37,9 @@ module GraphQL
37
37
  if permitted_empty_value?(value)
38
38
  # Do nothing
39
39
  elsif value.nil? ||
40
- (@with_pattern && !value.match?(@with_pattern)) ||
41
- (@without_pattern && value.match?(@without_pattern))
42
- @message
40
+ (@with_pattern && !value.match?(validation_parameter(@with_pattern))) ||
41
+ (@without_pattern && value.match?(validation_parameter(@without_pattern)))
42
+ validation_parameter(@message)
43
43
  end
44
44
  end
45
45
  end
@@ -25,8 +25,8 @@ module GraphQL
25
25
  def validate(_object, _context, value)
26
26
  if permitted_empty_value?(value)
27
27
  # pass
28
- elsif !@in_list.include?(value)
29
- @message
28
+ elsif !validation_parameter(@in_list).include?(value)
29
+ validation_parameter(@message)
30
30
  end
31
31
  end
32
32
  end
@@ -45,12 +45,12 @@ module GraphQL
45
45
  def validate(_object, _context, value)
46
46
  return if permitted_empty_value?(value) # pass in this case
47
47
  length = value.nil? ? 0 : value.length
48
- if @maximum && length > @maximum
49
- partial_format(@too_long, { count: @maximum })
50
- elsif @minimum && length < @minimum
51
- partial_format(@too_short, { count: @minimum })
52
- elsif @is && length != @is
53
- partial_format(@wrong_length, { count: @is })
48
+ if (current_max = validation_parameter(@maximum)) && length > current_max
49
+ partial_format(validation_parameter(@too_long), { count: current_max })
50
+ elsif (current_min = validation_parameter(@minimum)) && length < current_min
51
+ partial_format(validation_parameter(@too_short), { count: current_min })
52
+ elsif (current_is = validation_parameter(@is)) && length != current_is
53
+ partial_format(validation_parameter(@wrong_length), { count: current_is })
54
54
  end
55
55
  end
56
56
  end
@@ -55,25 +55,25 @@ module GraphQL
55
55
  if permitted_empty_value?(value)
56
56
  # pass in this case
57
57
  elsif value.nil? # @allow_null is handled in the parent class
58
- @null_message
59
- elsif @greater_than && value <= @greater_than
60
- partial_format(@message, { comparison: "greater than", target: @greater_than })
61
- elsif @greater_than_or_equal_to && value < @greater_than_or_equal_to
62
- partial_format(@message, { comparison: "greater than or equal to", target: @greater_than_or_equal_to })
63
- elsif @less_than && value >= @less_than
64
- partial_format(@message, { comparison: "less than", target: @less_than })
65
- elsif @less_than_or_equal_to && value > @less_than_or_equal_to
66
- partial_format(@message, { comparison: "less than or equal to", target: @less_than_or_equal_to })
67
- elsif @equal_to && value != @equal_to
68
- partial_format(@message, { comparison: "equal to", target: @equal_to })
69
- elsif @other_than && value == @other_than
70
- partial_format(@message, { comparison: "something other than", target: @other_than })
71
- elsif @even && !value.even?
72
- (partial_format(@message, { comparison: "even", target: "" })).strip
73
- elsif @odd && !value.odd?
74
- (partial_format(@message, { comparison: "odd", target: "" })).strip
75
- elsif @within && !@within.include?(value)
76
- partial_format(@message, { comparison: "within", target: @within })
58
+ validation_parameter(@null_message)
59
+ elsif (current_greater_than = validation_parameter(@greater_than)) && value <= current_greater_than
60
+ partial_format(validation_parameter(@message), { comparison: "greater than", target: current_greater_than })
61
+ elsif (current_greater_than_or_equal_to = validation_parameter(@greater_than_or_equal_to)) && value < current_greater_than_or_equal_to
62
+ partial_format(validation_parameter(@message), { comparison: "greater than or equal to", target: current_greater_than_or_equal_to })
63
+ elsif (current_less_than = validation_parameter(@less_than)) && value >= current_less_than
64
+ partial_format(validation_parameter(@message), { comparison: "less than", target: current_less_than })
65
+ elsif (current_less_than_or_equal_to = validation_parameter(@less_than_or_equal_to)) && value > current_less_than_or_equal_to
66
+ partial_format(validation_parameter(@message), { comparison: "less than or equal to", target: current_less_than_or_equal_to })
67
+ elsif (current_equal_to = validation_parameter(@equal_to)) && value != current_equal_to
68
+ partial_format(validation_parameter(@message), { comparison: "equal to", target: current_equal_to })
69
+ elsif (current_other_than = validation_parameter(@other_than)) && value == current_other_than
70
+ partial_format(validation_parameter(@message), { comparison: "something other than", target: current_other_than })
71
+ elsif validation_parameter(@even) && !value.even?
72
+ (partial_format(validation_parameter(@message), { comparison: "even", target: "" })).strip
73
+ elsif validation_parameter(@odd) && !value.odd?
74
+ (partial_format(validation_parameter(@message), { comparison: "odd", target: "" })).strip
75
+ elsif (current_within = validation_parameter(@within)) && !current_within.include?(value)
76
+ partial_format(validation_parameter(@message), { comparison: "within", target: current_within })
77
77
  end
78
78
  end
79
79
  end
@@ -67,7 +67,8 @@ module GraphQL
67
67
  no_visible_conditions = true
68
68
 
69
69
  if !value.nil?
70
- @one_of.each do |one_of_condition|
70
+ validation_parameter(@one_of).each do |one_of_condition|
71
+ one_of_condition = validation_parameter(one_of_condition)
71
72
  case one_of_condition
72
73
  when Symbol
73
74
  if no_visible_conditions && visible_keywords.include?(one_of_condition)
@@ -108,7 +109,7 @@ module GraphQL
108
109
  end
109
110
 
110
111
  if no_visible_conditions
111
- if @allow_all_hidden
112
+ if validation_parameter(@allow_all_hidden)
112
113
  return nil
113
114
  else
114
115
  raise GraphQL::Error, <<~ERR
@@ -122,7 +123,7 @@ module GraphQL
122
123
  if fully_matched_conditions == 1 && partially_matched_conditions == 0
123
124
  nil # OK
124
125
  else
125
- @message || build_message(context)
126
+ validation_parameter(@message) || build_message(context)
126
127
  end
127
128
  end
128
129
 
@@ -130,8 +131,9 @@ module GraphQL
130
131
  argument_definitions = context.types.arguments(@validated)
131
132
 
132
133
  required_names = @one_of.map do |arg_keyword|
134
+ arg_keyword = validation_parameter(arg_keyword)
133
135
  if arg_keyword.is_a?(Array)
134
- names = arg_keyword.map { |arg| arg_keyword_to_graphql_name(argument_definitions, arg) }
136
+ names = arg_keyword.map { |arg| arg_keyword_to_graphql_name(argument_definitions, validation_parameter(arg)) }
135
137
  names.compact! # hidden arguments are `nil`
136
138
  "(" + names.join(" and ") + ")"
137
139
  else
@@ -34,6 +34,15 @@ module GraphQL
34
34
  string
35
35
  end
36
36
 
37
+ # @return [Object] The current value to use for validation, based on `config_value` from configuration time. If a Proc is given, this calls it and returns it.
38
+ def validation_parameter(config_value)
39
+ if config_value.is_a?(Proc)
40
+ config_value.call
41
+ else
42
+ config_value
43
+ end
44
+ end
45
+
37
46
  # @return [Boolean] `true` if `value` is `nil` and this validator has `allow_null: true` or if value is `.blank?` and this validator has `allow_blank: true`
38
47
  def permitted_empty_value?(value)
39
48
  (value.nil? && @allow_null) ||
@@ -290,11 +290,13 @@ module GraphQL
290
290
  end
291
291
  # Lots more to do here
292
292
  end
293
- @schema.introspection_system.entry_points.each do |f|
294
- arguments(f).each do |arg|
295
- argument(f, arg.graphql_name)
293
+ if @schema.query
294
+ @schema.introspection_system.entry_points.each do |f|
295
+ arguments(f).each do |arg|
296
+ argument(f, arg.graphql_name)
297
+ end
298
+ field(@schema.query, f.graphql_name)
296
299
  end
297
- field(@schema.query, f.graphql_name)
298
300
  end
299
301
  @schema.introspection_system.dynamic_fields.each do |f|
300
302
  arguments(f).each do |arg|
@@ -102,7 +102,7 @@ module GraphQL
102
102
 
103
103
  missed_late_types_streak = 0
104
104
  while (owner, late_type = @late_bound_types.shift)
105
- if (late_type.is_a?(String) && (type = Member::BuildType.constantize(type))) ||
105
+ if (late_type.is_a?(String) && (type = Member::BuildType.constantize(late_type))) ||
106
106
  (late_type.is_a?(LateBoundType) && (type = @visited_types.find { |t| t.graphql_name == late_type.graphql_name }))
107
107
  missed_late_types_streak = 0 # might succeed next round
108
108
  update_type_owner(owner, type)
@@ -8,11 +8,17 @@ module GraphQL
8
8
  # Use this plugin to make some parts of your schema hidden from some viewers.
9
9
  #
10
10
  class Visibility
11
+ class TypeConfigurationError < GraphQL::Error
12
+ def initialize(config_message, config_str)
13
+ message = "GraphQL::Schema::Visibility already preloaded, but #{config_message} added to the schema. Move this `#{config_str}` configuration above `use(GraphQL::Schema::Visibility)"
14
+ super(message)
15
+ end
16
+ end
11
17
  # @param schema [Class<GraphQL::Schema>]
12
18
  # @param profiles [Hash<Symbol => Hash>] A hash of `name => context` pairs for preloading visibility profiles
13
19
  # @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?` and `Rails.env.staging?`)
14
20
  # @param migration_errors [Boolean] if `true`, raise an error when `Visibility` and `Warden` return different results
15
- def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails.env) ? (Rails.env.production? || Rails.env.staging?) : nil), migration_errors: false)
21
+ def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails.env) ? (Rails.env.production? || Rails.env.staging? || nil) : false), migration_errors: false)
16
22
  profiles&.each { |name, ctx|
17
23
  ctx[:visibility_profile] = name
18
24
  ctx.freeze
@@ -20,7 +26,7 @@ module GraphQL
20
26
  schema.visibility = self.new(schema, dynamic: dynamic, preload: preload, profiles: profiles, migration_errors: migration_errors)
21
27
  end
22
28
 
23
- def initialize(schema, dynamic:, preload:, profiles:, migration_errors:)
29
+ def initialize(schema, dynamic:, preload:, profiles:, migration_errors:, configuration_inherited: false)
24
30
  @schema = schema
25
31
  schema.use_visibility_profile = true
26
32
  schema.visibility_profile_class = if migration_errors
@@ -40,6 +46,7 @@ module GraphQL
40
46
  @types = nil
41
47
  @all_references = nil
42
48
  @loaded_all = false
49
+ @configuration_inherited = configuration_inherited
43
50
  if preload
44
51
  self.preload
45
52
  end
@@ -94,6 +101,7 @@ module GraphQL
94
101
  # Root types may have been nil:
95
102
  types_to_visit.compact!
96
103
  ensure_all_loaded(types_to_visit)
104
+ @cached_profiles.clear
97
105
  @profiles.each do |profile_name, example_ctx|
98
106
  prof = profile_for(example_ctx)
99
107
  prof.preload
@@ -102,41 +110,27 @@ module GraphQL
102
110
 
103
111
  # @api private
104
112
  def query_configured(query_type)
105
- if @preload
106
- ensure_all_loaded([query_type])
107
- end
113
+ require_if_preloaded("a query type was", "query(...)")
108
114
  end
109
115
 
110
116
  # @api private
111
117
  def mutation_configured(mutation_type)
112
- if @preload
113
- ensure_all_loaded([mutation_type])
114
- end
118
+ require_if_preloaded("a mutation type was", "mutation(...)")
115
119
  end
116
120
 
117
121
  # @api private
118
122
  def subscription_configured(subscription_type)
119
- if @preload
120
- ensure_all_loaded([subscription_type])
121
- end
123
+ require_if_preloaded("a mutation type was", "subscription(...)")
122
124
  end
123
125
 
124
126
  # @api private
125
127
  def orphan_types_configured(orphan_types)
126
- if @preload
127
- ensure_all_loaded(orphan_types)
128
- end
128
+ require_if_preloaded("orphan types were", "orphan_types(...)")
129
129
  end
130
130
 
131
131
  # @api private
132
132
  def introspection_system_configured(introspection_system)
133
- if @preload
134
- introspection_types = [
135
- *@schema.introspection_system.types.values,
136
- *@schema.introspection_system.entry_points.map { |ep| ep.type.unwrap },
137
- ]
138
- ensure_all_loaded(introspection_types)
139
- end
133
+ require_if_preloaded("custom introspection was", "introspection(...)")
140
134
  end
141
135
 
142
136
  # Make another Visibility for `schema` based on this one
@@ -148,7 +142,8 @@ module GraphQL
148
142
  dynamic: @dynamic,
149
143
  preload: @preload,
150
144
  profiles: @profiles,
151
- migration_errors: @migration_errors
145
+ migration_errors: @migration_errors,
146
+ configuration_inherited: true,
152
147
  )
153
148
  end
154
149
 
@@ -196,6 +191,19 @@ module GraphQL
196
191
 
197
192
  private
198
193
 
194
+ def require_if_preloaded(config_message, config_code)
195
+ case @preload
196
+ when false
197
+ # Rails.env wasn't defined, so this won't try to preload unless manually set to true
198
+ when true, nil
199
+ if @configuration_inherited
200
+ preload
201
+ else
202
+ raise TypeConfigurationError.new(config_message, config_code)
203
+ end
204
+ end
205
+ end
206
+
199
207
  def ensure_all_loaded(types_to_visit)
200
208
  while (type = types_to_visit.shift)
201
209
  if type.kind.fields? && @preloaded_types.add?(type)