graphql 2.3.20 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 271275308827280721c8a7d93c7a06cdb086c21a3d3ddb1443c27e20567ceba1
4
- data.tar.gz: 4a6e8749d13c45572b86cee56eb8078afc6e70a62caff64d3abcf57d42b0fbfe
3
+ metadata.gz: 12a25d94ae9348527390bad889be7c918390c0a1b133735612ac5a80ca0c6633
4
+ data.tar.gz: de4bb61a31dc643d359157dff498410f70a5a7bd18d6c254463a2a5294706949
5
5
  SHA512:
6
- metadata.gz: e438382adc6974ff9cc323a076d94675422d8ec5ad5ca0d869421e5b278c9ffa55eb86f07074fa9479552ea17c50c4fc7fac27d630ad99f72977caad5b3adf39
7
- data.tar.gz: d035be86b6070cee2aa2559a2567660a4b0847c93f1ab714691deec6f0a8d0eb468ebe1e89eefe33bb64d86ed2ad9fe769ef5b9e7d5fb1f2c9120f1047769fbe
6
+ metadata.gz: f85c5a5e4ab0083862c990fe6cd3f0ed99d36a414721d4c3d0a7a3e5c59042cdc5611a6b74d10d0e18525d9e51a32dc6aa1a2349c789b04dae75a835f4bb322a
7
+ data.tar.gz: e0304dded75753771417672c704054a1daec13ab428ed92f97dbdcee2771f21874bfb6aedf9bd0054806ed97b4483b98376f03526b129d96628735c1933f0d35
@@ -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 types
31
- @types ||= Schema::Warden::VisibilityProfile.new(@warden)
28
+ @types = @warden.visibility_profile
29
+ freeze
32
30
  end
33
31
  end
34
32
  end
@@ -86,10 +86,24 @@ module GraphQL
86
86
  def enum_values(context = GraphQL::Query::NullContext.instance)
87
87
  inherited_values = superclass.respond_to?(:enum_values) ? superclass.enum_values(context) : nil
88
88
  visible_values = []
89
- warden = Warden.from_context(context)
89
+ types = Warden.types_from_context(context)
90
90
  own_values.each do |key, values_entry|
91
- if (v = Warden.visible_entry?(:visible_enum_value?, values_entry, context, warden))
92
- visible_values << v
91
+ visible_value = nil
92
+ if values_entry.is_a?(Array)
93
+ values_entry.each do |v|
94
+ if types.visible_enum_value?(v, context)
95
+ if visible_value.nil?
96
+ visible_value = v
97
+ visible_values << v
98
+ else
99
+ raise DuplicateNamesError.new(
100
+ duplicated_name: v.path, duplicated_definition_1: visible_value.inspect, duplicated_definition_2: v.inspect
101
+ )
102
+ end
103
+ end
104
+ end
105
+ elsif types.visible_enum_value?(values_entry, context)
106
+ visible_values << values_entry
93
107
  end
94
108
  end
95
109
 
@@ -74,7 +74,7 @@ module GraphQL
74
74
  end
75
75
 
76
76
  def inspect
77
- "#<#{self.class} #{path} @value=#{@value.inspect}#{description ? " @description=#{description.inspect}" : ""}>"
77
+ "#<#{self.class} #{path} @value=#{@value.inspect}#{description ? " @description=#{description.inspect}" : ""}#{deprecation_reason ? " @deprecation_reason=#{deprecation_reason.inspect}" : ""}>"
78
78
  end
79
79
 
80
80
  def visible?(_ctx); true; end
@@ -58,6 +58,7 @@ module GraphQL
58
58
  end
59
59
  end
60
60
  schema = Class.new(GraphQL::Schema) {
61
+ use GraphQL::Schema::Visibility
61
62
  query(query_root)
62
63
  def self.visible?(member, _ctx)
63
64
  member.graphql_name != "Root"
@@ -96,6 +96,7 @@ module GraphQL
96
96
  end
97
97
  warden_ctx = GraphQL::Query::Context.new(query: context.query, values: warden_ctx_vals)
98
98
  warden_ctx.warden = GraphQL::Schema::Warden.new(schema: warden_schema, context: warden_ctx)
99
+ warden_ctx.warden.skip_warning = true
99
100
  warden_ctx.types = @warden_types = warden_ctx.warden.visibility_profile
100
101
  end
101
102
  end
@@ -121,7 +122,8 @@ module GraphQL
121
122
  :mutation_root,
122
123
  :possible_types,
123
124
  :subscription_root,
124
- :reachable_type?
125
+ :reachable_type?,
126
+ :visible_enum_value?,
125
127
  ]
126
128
 
127
129
  PUBLIC_PROFILE_METHODS.each do |profile_method|
@@ -22,9 +22,9 @@ module GraphQL
22
22
  end
23
23
  end
24
24
 
25
- def self.pass_thru(context:, schema:)
26
- profile = self.new(context: context, schema: schema)
27
- profile.instance_variable_set(:@cached_visible, Hash.new { |h,k| h[k] = true })
25
+ def self.null_profile(context:, schema:)
26
+ profile = self.new(name: "NullProfile", context: context, schema: schema)
27
+ profile.instance_variable_set(:@cached_visible, Hash.new { |k, v| k[v] = true }.compare_by_identity)
28
28
  profile
29
29
  end
30
30
 
@@ -123,7 +123,7 @@ module GraphQL
123
123
  end.compare_by_identity
124
124
 
125
125
  @cached_enum_values = Hash.new do |h, enum_t|
126
- values = non_duplicate_items(enum_t.all_enum_value_definitions, @cached_visible)
126
+ values = non_duplicate_items(enum_t.enum_values(@context), @cached_visible)
127
127
  if values.size == 0
128
128
  raise GraphQL::Schema::Enum::MissingValuesError.new(enum_t)
129
129
  end
@@ -327,6 +327,10 @@ module GraphQL
327
327
  !!@all_types[name]
328
328
  end
329
329
 
330
+ def visible_enum_value?(enum_value, _ctx = nil)
331
+ @cached_visible[enum_value]
332
+ end
333
+
330
334
  private
331
335
 
332
336
  def add_if_visible(t)
@@ -19,6 +19,17 @@ module GraphQL
19
19
  PassThruWarden
20
20
  end
21
21
 
22
+ def self.types_from_context(context)
23
+ context.types || PassThruWarden
24
+ rescue NoMethodError
25
+ # this might be a hash which won't respond to #warden
26
+ PassThruWarden
27
+ end
28
+
29
+ def self.use(schema)
30
+ # no-op
31
+ end
32
+
22
33
  # @param visibility_method [Symbol] a Warden method to call for this entry
23
34
  # @param entry [Object, Array<Object>] One or more definitions for a given name in a GraphQL Schema
24
35
  # @param context [GraphQL::Query::Context]
@@ -73,24 +84,20 @@ module GraphQL
73
84
  @visibility_profile = Warden::VisibilityProfile.new(self)
74
85
  end
75
86
 
76
- # @api private
77
- module NullVisibilityProfile
78
- def self.new(context:, schema:)
79
- NullWarden.new(context: context, schema: schema).visibility_profile
80
- end
81
- end
87
+ # No-op, but for compatibility:
88
+ attr_writer :skip_warning
82
89
 
83
90
  attr_reader :visibility_profile
84
91
 
85
92
  def visible_field?(field_defn, _ctx = nil, owner = nil); true; end
86
93
  def visible_argument?(arg_defn, _ctx = nil); true; end
87
94
  def visible_type?(type_defn, _ctx = nil); true; end
88
- def visible_enum_value?(enum_value, _ctx = nil); true; end
95
+ def visible_enum_value?(enum_value, _ctx = nil); enum_value.visible?(Query::NullContext.instance); end
89
96
  def visible_type_membership?(type_membership, _ctx = nil); true; end
90
97
  def interface_type_memberships(obj_type, _ctx = nil); obj_type.interface_type_memberships; end
91
98
  def get_type(type_name); @schema.get_type(type_name, Query::NullContext.instance, false); end # rubocop:disable Development/ContextIsPassedCop
92
99
  def arguments(argument_owner, ctx = nil); argument_owner.all_argument_definitions; end
93
- def enum_values(enum_defn); enum_defn.enum_values; end # rubocop:disable Development/ContextIsPassedCop
100
+ def enum_values(enum_defn); enum_defn.enum_values(Query::NullContext.instance); end # rubocop:disable Development/ContextIsPassedCop
94
101
  def get_argument(parent_type, argument_name); parent_type.get_argument(argument_name); end # rubocop:disable Development/ContextIsPassedCop
95
102
  def types; @schema.types; end # rubocop:disable Development/ContextIsPassedCop
96
103
  def root_type_for_operation(op_name); @schema.root_type_for_operation(op_name); end
@@ -176,6 +183,10 @@ module GraphQL
176
183
  def reachable_type?(type_name)
177
184
  !!@warden.reachable_type?(type_name)
178
185
  end
186
+
187
+ def visible_enum_value?(enum_value, ctx = nil)
188
+ @warden.visible_enum_value?(enum_value, ctx)
189
+ end
179
190
  end
180
191
 
181
192
  # @param context [GraphQL::Query::Context]
@@ -187,7 +198,7 @@ module GraphQL
187
198
  @mutation = @schema.mutation
188
199
  @subscription = @schema.subscription
189
200
  @context = context
190
- @visibility_cache = read_through { |m| schema.visible?(m, context) }
201
+ @visibility_cache = read_through { |m| check_visible(schema, m) }
191
202
  # Initialize all ivars to improve object shape consistency:
192
203
  @types = @visible_types = @reachable_types = @visible_parent_fields =
193
204
  @visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays =
@@ -195,8 +206,11 @@ module GraphQL
195
206
  @visible_and_reachable_type = @unions = @unfiltered_interfaces =
196
207
  @reachable_type_set = @visibility_profile =
197
208
  nil
209
+ @skip_warning = schema.plugins.any? { |(plugin, _opts)| plugin == GraphQL::Schema::Warden }
198
210
  end
199
211
 
212
+ attr_writer :skip_warning
213
+
200
214
  # @return [Hash<String, GraphQL::BaseType>] Visible types in the schema
201
215
  def types
202
216
  @types ||= begin
@@ -465,6 +479,58 @@ module GraphQL
465
479
  Hash.new { |h, k| h[k] = yield(k) }.compare_by_identity
466
480
  end
467
481
 
482
+ def check_visible(schema, member)
483
+ if schema.visible?(member, @context)
484
+ true
485
+ elsif @skip_warning
486
+ false
487
+ else
488
+ member_s = member.respond_to?(:path) ? member.path : member.inspect
489
+ member_type = case member
490
+ when Module
491
+ if member.respond_to?(:kind)
492
+ member.kind.name.downcase
493
+ else
494
+ ""
495
+ end
496
+ when GraphQL::Schema::Field
497
+ "field"
498
+ when GraphQL::Schema::EnumValue
499
+ "enum value"
500
+ when GraphQL::Schema::Argument
501
+ "argument"
502
+ else
503
+ ""
504
+ end
505
+
506
+ schema_s = schema.name ? "#{schema.name}'s" : ""
507
+ schema_name = schema.name ? "#{schema.name}" : "your schema"
508
+ warn(ADD_WARDEN_WARNING % { schema_s: schema_s, schema_name: schema_name, member: member_s, member_type: member_type })
509
+ @skip_warning = true # only warn once per query
510
+ # If there's no schema name, add the backtrace for additional context:
511
+ if schema_s == ""
512
+ puts caller.map { |l| " #{l}"}
513
+ end
514
+ false
515
+ end
516
+ end
517
+
518
+ ADD_WARDEN_WARNING = <<~WARNING
519
+ DEPRECATION: %{schema_s} "%{member}" %{member_type} returned `false` for `.visible?` but `GraphQL::Schema::Visibility` isn't configured yet.
520
+
521
+ Address this warning by adding:
522
+
523
+ use GraphQL::Schema::Visibility
524
+
525
+ to the definition for %{schema_name}. (Future GraphQL-Ruby versions won't check `.visible?` methods by default.)
526
+
527
+ Alternatively, for legacy behavior, add:
528
+
529
+ use GraphQL::Schema::Warden # legacy visibility behavior
530
+
531
+ For more information see: https://graphql-ruby.org/authorization/visibility.html
532
+ WARNING
533
+
468
534
  def reachable_type_set
469
535
  return @reachable_type_set if @reachable_type_set
470
536
 
@@ -1162,7 +1162,7 @@ module GraphQL
1162
1162
  # @return [Object, nil] The application which `object_id` references, or `nil` if there is no object or the current operation shouldn't have access to the object
1163
1163
  # @see id_from_object which produces these IDs
1164
1164
  def object_from_id(object_id, context)
1165
- raise GraphQL::RequiredImplementationMissingError, "#{self.name}.object_from_id(object_id, context) must be implemented to load by ID (tried to load from id `#{node_id}`)"
1165
+ raise GraphQL::RequiredImplementationMissingError, "#{self.name}.object_from_id(object_id, context) must be implemented to load by ID (tried to load from id `#{object_id}`)"
1166
1166
  end
1167
1167
 
1168
1168
  # Return a stable ID string for `object` so that it can be refetched later, using {.object_from_id}.
@@ -1575,6 +1575,20 @@ module GraphQL
1575
1575
  end
1576
1576
  end
1577
1577
 
1578
+ # Returns `DidYouMean` if it's defined.
1579
+ # Override this to return `nil` if you don't want to use `DidYouMean`
1580
+ def did_you_mean(new_dym = NOT_CONFIGURED)
1581
+ if NOT_CONFIGURED.equal?(new_dym)
1582
+ if defined?(@did_you_mean)
1583
+ @did_you_mean
1584
+ else
1585
+ find_inherited_value(:did_you_mean, defined?(DidYouMean) ? DidYouMean : nil)
1586
+ end
1587
+ else
1588
+ @did_you_mean = new_dym
1589
+ end
1590
+ end
1591
+
1578
1592
  private
1579
1593
 
1580
1594
  def add_trace_options_for(mode, new_options)
@@ -10,8 +10,9 @@ module GraphQL
10
10
  elsif parent_defn
11
11
  kind_of_node = node_type(parent)
12
12
  error_arg_name = parent_name(parent, parent_defn)
13
+ arg_names = context.types.arguments(parent_defn).map(&:graphql_name)
13
14
  add_error(GraphQL::StaticValidation::ArgumentsAreDefinedError.new(
14
- "#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'",
15
+ "#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'#{context.did_you_mean_suggestion(node.name, arg_names)}",
15
16
  nodes: node,
16
17
  name: error_arg_name,
17
18
  type: kind_of_node,
@@ -10,8 +10,9 @@ module GraphQL
10
10
  if !@types.directive_exists?(node.name)
11
11
  @directives_are_defined_errors_by_name ||= {}
12
12
  error = @directives_are_defined_errors_by_name[node.name] ||= begin
13
+ @directive_names ||= @types.directives.map(&:graphql_name)
13
14
  err = GraphQL::StaticValidation::DirectivesAreDefinedError.new(
14
- "Directive @#{node.name} is not defined",
15
+ "Directive @#{node.name} is not defined#{context.did_you_mean_suggestion(node.name, @directive_names)}",
15
16
  nodes: [],
16
17
  directive: node.name
17
18
  )
@@ -14,8 +14,9 @@ module GraphQL
14
14
  node_name: parent_type.graphql_name
15
15
  ))
16
16
  else
17
+ message = "Field '#{node.name}' doesn't exist on type '#{parent_type.graphql_name}'#{context.did_you_mean_suggestion(node.name, context.types.fields(parent_type).map(&:graphql_name))}"
17
18
  add_error(GraphQL::StaticValidation::FieldsAreDefinedOnTypeError.new(
18
- "Field '#{node.name}' doesn't exist on type '#{parent_type.graphql_name}'",
19
+ message,
19
20
  nodes: node,
20
21
  field: node.name,
21
22
  type: parent_type.graphql_name
@@ -23,8 +23,18 @@ module GraphQL
23
23
  type_name = fragment_node.type.name
24
24
  type = @types.type(type_name)
25
25
  if type.nil?
26
+ @all_possible_fragment_type_names ||= begin
27
+ names = []
28
+ context.types.all_types.each do |type|
29
+ if type.kind.fields?
30
+ names << type.graphql_name
31
+ end
32
+ end
33
+ names
34
+ end
35
+
26
36
  add_error(GraphQL::StaticValidation::FragmentTypesExistError.new(
27
- "No such type #{type_name}, so it can't be a fragment condition",
37
+ "No such type #{type_name}, so it can't be a fragment condition#{context.did_you_mean_suggestion(type_name, @all_possible_fragment_type_names)}",
28
38
  nodes: fragment_node,
29
39
  type: type_name
30
40
  ))
@@ -7,8 +7,17 @@ module GraphQL
7
7
  type = context.query.types.type(type_name)
8
8
 
9
9
  if type.nil?
10
+ @all_possible_input_type_names ||= begin
11
+ names = []
12
+ context.types.all_types.each { |(t)|
13
+ if t.kind.input?
14
+ names << t.graphql_name
15
+ end
16
+ }
17
+ names
18
+ end
10
19
  add_error(GraphQL::StaticValidation::VariablesAreInputTypesError.new(
11
- "#{type_name} isn't a defined input type (on $#{node.name})",
20
+ "#{type_name} isn't a defined input type (on $#{node.name})#{context.did_you_mean_suggestion(type_name, @all_possible_input_type_names)}",
12
21
  nodes: node,
13
22
  name: node.name,
14
23
  type: type_name
@@ -48,6 +48,21 @@ module GraphQL
48
48
  def schema_directives
49
49
  @schema_directives ||= schema.directives
50
50
  end
51
+
52
+ def did_you_mean_suggestion(name, options)
53
+ if did_you_mean = schema.did_you_mean
54
+ suggestions = did_you_mean::SpellChecker.new(dictionary: options).correct(name)
55
+ case suggestions.size
56
+ when 0
57
+ ""
58
+ when 1
59
+ " (Did you mean `#{suggestions.first}`?)"
60
+ else
61
+ last_sugg = suggestions.pop
62
+ " (Did you mean #{suggestions.map {|s| "`#{s}`"}.join(", ")} or `#{last_sugg}`?)"
63
+ end
64
+ end
65
+ end
51
66
  end
52
67
  end
53
68
  end
@@ -92,7 +92,7 @@ module GraphQL
92
92
  end
93
93
  graphql_result
94
94
  else
95
- unfiltered_type = Schema::Visibility::Profile.pass_thru(schema: schema, context: context).type(type_name)
95
+ unfiltered_type = Schema::Visibility::Profile.null_profile(schema: schema, context: context).type(type_name)
96
96
  if unfiltered_type
97
97
  raise TypeNotVisibleError.new(type_name: type_name)
98
98
  else
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.3.20"
3
+ VERSION = "2.4.1"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.20
4
+ version: 2.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-31 00:00:00.000000000 Z
11
+ date: 2024-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64