graphql 2.6.2 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a0c0b5ff2af84e07dab381443b1b62137074324bb3063c8387bf6dc777d51d00
4
- data.tar.gz: 4e5383e45c9d62ea5a04782867a743d01d6db0d0bc853deadc7a229f26d440e4
3
+ metadata.gz: 16abc9c2a5eda0da251dbe7e14433241bc7d038d2527926832ceb29336fb857a
4
+ data.tar.gz: c89bd2a4b340ca30bfa7b823f51e4671972355d54bd56fdaed598c13a415c3b3
5
5
  SHA512:
6
- metadata.gz: 86019205d2eb2dabd464dc615af28ab078c1a48e43c0701a1852dddfd13eded8954e42e91b7176c685eed10679b15bf4bcef2c86c48d9f9ad4b870cdc78bb19a
7
- data.tar.gz: b95880ff00058b167fe7574ca5e267814fe7aa9be65df0d2691d658344a796dd1fdafedad6cc35b58c3299c5401b39a964353cb5ab0e3974296ca4c26f9ab504
6
+ metadata.gz: 6452d2a517c502b4a8934582d060885a5f6462de244a1f331433f1076dc57a879cdd081e147f70925064a99607e0794fcf6eadad7a3f4a9111a5ab979c552554
7
+ data.tar.gz: b6a0219606d905fc885ab2192596d1acb1d92bce407defc751bfafb1a7199e9c32275c1d3202f3246251e72b80554698b1f54920f76178f1ddc81b41d628bacd
@@ -9,6 +9,8 @@ module GraphQL
9
9
  super
10
10
  @skip_introspection_fields = !query.schema.max_complexity_count_introspection_fields
11
11
  @complexities_on_type_by_query = {}
12
+ @intersect_cache = Hash.new { |h, k| h[k] = {}.compare_by_identity }.compare_by_identity
13
+ @possible_types_cache = {}.compare_by_identity
12
14
  end
13
15
 
14
16
  # Override this method to use the complexity result
@@ -159,8 +161,22 @@ module GraphQL
159
161
 
160
162
  def types_intersect?(query, a, b)
161
163
  return true if a == b
162
- a_types = query.types.possible_types(a)
163
- query.types.possible_types(b).any? { |t| a_types.include?(t) }
164
+
165
+ if a.object_id < b.object_id
166
+ first_cache = @intersect_cache[a]
167
+ second_key = b
168
+ else
169
+ first_cache = @intersect_cache[b]
170
+ second_key = a
171
+ end
172
+
173
+ if first_cache.key?(second_key)
174
+ first_cache[second_key]
175
+ else
176
+ a_types = @possible_types_cache[a] ||= query.types.possible_types(a).to_set
177
+ b_types = @possible_types_cache[b] ||= query.types.possible_types(b).to_set
178
+ first_cache[second_key] = a_types.intersect?(b_types)
179
+ end
164
180
  end
165
181
 
166
182
  # A hook which is called whenever a field's max complexity is calculated.
@@ -175,18 +191,16 @@ module GraphQL
175
191
  # @param inner_selections [Array<Hash<String, ScopedTypeComplexity>>] Field selections for a scope
176
192
  # @return [Integer] Total complexity value for all these selections in the parent scope
177
193
  def merged_max_complexity(query, inner_selections)
178
- # Aggregate a set of all unique field selection keys across all scopes.
179
- # Use a hash, but ignore the values; it's just a fast way to work with the keys.
180
- unique_field_keys = inner_selections.each_with_object({}) do |inner_selection, memo|
181
- memo.merge!(inner_selection)
194
+ child_scopes_by_key = {}
195
+ inner_selections.each do |inner_selection|
196
+ inner_selection.each do |k, v|
197
+ scopes = child_scopes_by_key[k] ||= []
198
+ scopes << v
199
+ end
182
200
  end
183
-
184
201
  # Add up the total cost for each unique field name's coalesced selections
185
- unique_field_keys.each_key.reduce(0) do |total, field_key|
186
- # Collect all child scopes for this field key;
187
- # all keys come with at least one scope.
188
- child_scopes = inner_selections.filter_map { _1[field_key] }
189
-
202
+ total = 0
203
+ child_scopes_by_key.each do |field_key, child_scopes|
190
204
  # Compute maximum possible cost of child selections;
191
205
  # composites merge their maximums, while leaf scopes are always zero.
192
206
  # FieldsWillMerge validation assures all scopes are uniformly composite or leaf.
@@ -214,8 +228,10 @@ module GraphQL
214
228
  child_complexity: maximum_children_cost,
215
229
  )
216
230
 
217
- total + maximum_cost
231
+ total += maximum_cost
218
232
  end
233
+
234
+ total
219
235
  end
220
236
 
221
237
  def legacy_merged_max_complexity(query, inner_selections)
@@ -90,7 +90,16 @@ module GraphQL
90
90
 
91
91
  if ast_node
92
92
  field_defn = query.get_field(result.graphql_result_type, ast_node.name)
93
- args = query.arguments_for(ast_node, field_defn).to_h
93
+ args = begin
94
+ if (cached_args = query.arguments_cache.cached_arguments_for(ast_node, field_defn))
95
+ cached_args.to_h
96
+ else
97
+ EmptyObjects::EMPTY_HASH
98
+ end
99
+ rescue StandardError => err
100
+ "Failed to load arguments, #{err.class}: #{err.message}"
101
+ end
102
+
94
103
  field_path = field_defn.path
95
104
  if ast_node.alias
96
105
  field_path += " as #{ast_node.alias}"
@@ -41,7 +41,13 @@ module GraphQL
41
41
  # @see GraphQL::Field#path for a string identifying this field
42
42
  # @return [GraphQL::Field, nil] The currently-running field, if there is one.
43
43
  def self.field
44
- Fiber[:__graphql_runtime_info]&.values&.first&.current_field
44
+ if (interpreter_info = Fiber[:__graphql_runtime_info])
45
+ interpreter_info.values&.first&.current_field
46
+ elsif (field = Fiber[:__graphql_current_field])
47
+ field
48
+ else
49
+ nil
50
+ end
45
51
  end
46
52
 
47
53
  # @return [Class, nil] The currently-running {Dataloader::Source} class, if there is one.
@@ -18,11 +18,13 @@ module GraphQL
18
18
  case name
19
19
  when SKIP
20
20
  args = query.arguments_for(directive_ast_node, directive_defn)
21
+ next if args.is_a?(GraphQL::ExecutionError)
21
22
  if args[:if] == true
22
23
  return false
23
24
  end
24
25
  when INCLUDE
25
26
  args = query.arguments_for(directive_ast_node, directive_defn)
27
+ next if args.is_a?(GraphQL::ExecutionError)
26
28
  if args[:if] == false
27
29
  return false
28
30
  end
@@ -50,11 +50,14 @@ module GraphQL
50
50
 
51
51
  def value
52
52
  query = @selections_step.query
53
+ set_current_field
53
54
  query.current_trace.begin_execute_field(@field_definition, @arguments, @field_results, query)
54
55
  sync(@field_results)
55
56
  query.current_trace.end_execute_field(@field_definition, @arguments, @field_results, query, @field_results)
56
57
  @runner.add_step(self)
57
58
  true
59
+ ensure
60
+ set_current_field(nil)
58
61
  end
59
62
 
60
63
  def sync(lazy)
@@ -76,6 +79,7 @@ module GraphQL
76
79
  end
77
80
 
78
81
  def call
82
+ set_current_field if @field_definition
79
83
  if @enqueued_authorization
80
84
  enqueue_next_steps
81
85
  elsif @finish_extension_idx
@@ -94,6 +98,8 @@ module GraphQL
94
98
  else
95
99
  raise
96
100
  end
101
+ ensure
102
+ set_current_field(nil)
97
103
  end
98
104
 
99
105
  def add_graphql_error(err)
@@ -121,6 +127,7 @@ module GraphQL
121
127
  query = @selections_step.query
122
128
  field_name = @ast_node.name
123
129
  @field_definition = query.types.field(@parent_type, field_name) || raise(GraphQL::Error, "No field definition found for #{@parent_type.to_type_signature}.#{ast_node.name} (at #{@ast_node.position})")
130
+ set_current_field
124
131
  @arguments, errors = @runner.input_values[query].argument_values(@field_definition, @ast_node.arguments, self) # rubocop:disable Development/ContextIsPassedCop
125
132
  if errors
126
133
  build_errors_result(errors, nil)
@@ -131,6 +138,8 @@ module GraphQL
131
138
  @field_results.nil? # Make sure the arguments flow didn't already call through
132
139
  execute_field
133
140
  end
141
+ ensure
142
+ set_current_field(nil)
134
143
  end
135
144
 
136
145
  # Used for compatibility in Schema::Subscription
@@ -552,6 +561,10 @@ module GraphQL
552
561
  @runner.schema.type_error(err, @selections_step.query.context)
553
562
  end
554
563
 
564
+ def set_current_field(new_value = @field_definition)
565
+ Fiber[:__graphql_current_field] = new_value
566
+ end
567
+
555
568
  private
556
569
 
557
570
  def build_graphql_result(graphql_result, key, field_result, return_type, is_nn, is_list, is_from_array) # rubocop:disable Metrics/ParameterLists
@@ -117,7 +117,7 @@ module GraphQL
117
117
  @current_exec_path << key
118
118
  @current_result_path << key
119
119
 
120
- field_defn = @query.context.types.field(parent_type, ast_selection.name) || raise("Invariant: No field found for #{static_type.to_type_signature}.#{ast_selection.name}")
120
+ field_defn = @query.context.types.field(parent_type, ast_selection.name) || raise("Invariant: No field found for #{parent_type.to_type_signature}.#{ast_selection.name}")
121
121
  result_type = field_defn.type
122
122
  if (result_type_non_null = result_type.non_null?)
123
123
  result_type = result_type.of_type
@@ -154,12 +154,14 @@ module GraphQL
154
154
  @runner.type_condition_applies?(@query.context, static_type_at_result, t.name)
155
155
  )
156
156
  result_h = check_object_result(result_h, parent_type, ast_selection.selections)
157
+ return nil if result_h.nil?
157
158
  end
158
159
  when Language::Nodes::FragmentSpread
159
160
  fragment_defn = @query.document.definitions.find { |defn| defn.is_a?(Language::Nodes::FragmentDefinition) && defn.name == ast_selection.name }
160
161
  static_type_at_result = @static_type_at[result_h]
161
162
  if static_type_at_result && @runner.type_condition_applies?(@query.context, static_type_at_result, fragment_defn.type.name)
162
163
  result_h = check_object_result(result_h, parent_type, fragment_defn.selections)
164
+ return nil if result_h.nil?
163
165
  end
164
166
  end
165
167
  end
@@ -30,7 +30,10 @@ module GraphQL
30
30
  @storage[argument_owner][parent_object][ast_node] = resolved_args
31
31
  end
32
32
  end
33
+ end
33
34
 
35
+ def cached_arguments_for(ast_node, argument_owner)
36
+ @storage[argument_owner][nil][ast_node]
34
37
  end
35
38
 
36
39
  # @yield [Interpreter::Arguments, Lazy<Interpreter::Arguments>] The finally-loaded arguments
@@ -14,6 +14,7 @@ module GraphQL
14
14
  end
15
15
 
16
16
  def value
17
+ @field_resolve_step.set_current_field
17
18
  schema = @field_resolve_step.runner.schema
18
19
  @loaded_value = schema.sync_lazy(@loaded_value)
19
20
  assign_value
@@ -34,9 +35,12 @@ module GraphQL
34
35
  @loaded_value = ex_err
35
36
  end
36
37
  assign_value
38
+ ensure
39
+ @field_resolve_step.set_current_field(nil)
37
40
  end
38
41
 
39
42
  def call
43
+ @field_resolve_step.set_current_field
40
44
  context = @field_resolve_step.selections_step.query.context
41
45
  @loaded_value = begin
42
46
  @load_receiver.load_and_authorize_application_object(@argument_definition, @argument_value, context)
@@ -64,6 +68,8 @@ module GraphQL
64
68
  ex_err
65
69
  end
66
70
  assign_value
71
+ ensure
72
+ @field_resolve_step.set_current_field(nil)
67
73
  end
68
74
 
69
75
  private
@@ -19,6 +19,7 @@ module GraphQL
19
19
  end
20
20
 
21
21
  def value
22
+ @field_resolve_step.set_current_field
22
23
  if @authorized_value
23
24
  query = @field_resolve_step.selections_step.query
24
25
  query.current_trace.begin_authorized(@resolved_type, @object, query.context)
@@ -36,9 +37,12 @@ module GraphQL
36
37
  ctx.query.current_trace.end_resolve_type(st, @object, ctx, @resolved_type)
37
38
  end
38
39
  @runner.add_step(self)
40
+ ensure
41
+ @field_resolve_step.set_current_field(nil)
39
42
  end
40
43
 
41
44
  def call
45
+ @field_resolve_step.set_current_field
42
46
  case @next_step
43
47
  when :resolve_type
44
48
  static_type = @field_resolve_step.static_type
@@ -65,6 +69,8 @@ module GraphQL
65
69
  else
66
70
  raise ArgumentError, "This is a bug, unknown step: #{@next_step.inspect}"
67
71
  end
72
+ ensure
73
+ @field_resolve_step.set_current_field(nil)
68
74
  end
69
75
 
70
76
  def authorize
@@ -116,7 +116,7 @@ module GraphQL
116
116
  query.context.add_error(err)
117
117
  end
118
118
 
119
- trace.execute_query_lazy(query: nil, multiplex: @multiplex) do
119
+ trace.execute_query_lazy(query: @multiplex.queries.size == 1 ? @multiplex.queries.first : nil, multiplex: @multiplex) do
120
120
  while (next_isolated_steps = isolated_steps.shift)
121
121
  next_isolated_steps.each do |step|
122
122
  add_step(step)
@@ -47,13 +47,19 @@ module GraphQL
47
47
  def self.escape_single_quoted_newlines(query_str)
48
48
  scanner = StringScanner.new(query_str)
49
49
  inside_single_quoted_string = false
50
+ inside_triple_quoted_string = false
50
51
  new_query_str = nil
51
52
  while !scanner.eos?
52
- if scanner.skip(/(?:\\"|[^"\n\r]|""")+/m)
53
+ if scanner.skip('"""')
54
+ inside_triple_quoted_string = !inside_triple_quoted_string
55
+ new_query_str && (new_query_str << scanner.matched)
56
+ elsif scanner.skip(/(?:\\"|[^"\n\r])+/m)
53
57
  new_query_str && (new_query_str << scanner.matched)
54
58
  elsif scanner.skip('"')
55
59
  new_query_str && (new_query_str << '"')
56
- inside_single_quoted_string = !inside_single_quoted_string
60
+ if !inside_triple_quoted_string
61
+ inside_single_quoted_string = !inside_single_quoted_string
62
+ end
57
63
  elsif scanner.skip("\n")
58
64
  if inside_single_quoted_string
59
65
  new_query_str ||= query_str[0, scanner.pos - 1]
@@ -94,7 +94,7 @@ module GraphQL
94
94
  end
95
95
 
96
96
  if required == :nullable
97
- self.owner.validates(required: { argument: arg_name })
97
+ self.owner.validates(required: { argument: @keyword })
98
98
  end
99
99
 
100
100
  if definition_block
@@ -101,7 +101,7 @@ module GraphQL
101
101
  end
102
102
 
103
103
  child_class.ancestors.reverse_each do |ancestor|
104
- if ancestor.const_defined?(:ResolverMethods)
104
+ if ancestor != child_class && ancestor <= GraphQL::Schema::Interface && ancestor.const_defined?(:ResolverMethods, false)
105
105
  child_class.extend(ancestor::ResolverMethods)
106
106
  end
107
107
  end
@@ -105,11 +105,13 @@ module GraphQL
105
105
  end
106
106
 
107
107
  def load_constant(class_name)
108
- const = @custom_namespace.const_get(class_name)
108
+ const = begin
109
+ @custom_namespace.const_get(class_name)
110
+ rescue NameError
111
+ # Dup the built-in so that the cached fields aren't shared
112
+ @built_in_namespace.const_get(class_name)
113
+ end
109
114
  dup_type_class(const)
110
- rescue NameError
111
- # Dup the built-in so that the cached fields aren't shared
112
- dup_type_class(@built_in_namespace.const_get(class_name))
113
115
  end
114
116
 
115
117
  def get_fields_from_class(class_sym:)
@@ -133,23 +135,6 @@ module GraphQL
133
135
  end
134
136
  end
135
137
  end
136
-
137
- class PerFieldProxyResolve
138
- def initialize(object_class:, inner_resolve:)
139
- @object_class = object_class
140
- @inner_resolve = inner_resolve
141
- end
142
-
143
- def call(obj, args, ctx)
144
- query_ctx = ctx.query.context
145
- # Remove the QueryType wrapper
146
- if obj.is_a?(GraphQL::Schema::Object)
147
- obj = obj.object
148
- end
149
- wrapped_object = @object_class.wrap(obj, query_ctx)
150
- @inner_resolve.call(wrapped_object, args, ctx)
151
- end
152
- end
153
138
  end
154
139
  end
155
140
  end
@@ -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
@@ -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|
@@ -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)
@@ -757,6 +757,7 @@ module GraphQL
757
757
  # reset this cached value:
758
758
  @introspection_system = nil
759
759
  introspection_system
760
+ self.visibility&.introspection_system_configured(introspection_system)
760
761
  @introspection
761
762
  else
762
763
  @introspection || find_inherited_value(:introspection)
@@ -768,7 +769,6 @@ module GraphQL
768
769
  if !@introspection_system
769
770
  @introspection_system = Schema::IntrospectionSystem.new(self)
770
771
  @introspection_system.resolve_late_bindings
771
- self.visibility&.introspection_system_configured(@introspection_system)
772
772
  end
773
773
  @introspection_system
774
774
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.6.2"
3
+ VERSION = "2.6.3"
4
4
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.2
4
+ version: 2.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-05-12 00:00:00.000000000 Z
10
+ date: 2026-05-26 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64