graphql 2.3.20 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/graphql/query/null_context.rb +3 -5
- data/lib/graphql/schema/enum.rb +17 -3
- data/lib/graphql/schema/enum_value.rb +1 -1
- data/lib/graphql/schema/printer.rb +1 -0
- data/lib/graphql/schema/visibility/migration.rb +3 -1
- data/lib/graphql/schema/visibility/profile.rb +8 -4
- data/lib/graphql/schema/warden.rb +75 -9
- data/lib/graphql/schema.rb +15 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +2 -1
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +2 -1
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +11 -1
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +10 -1
- data/lib/graphql/static_validation/validation_context.rb +15 -0
- data/lib/graphql/testing/helpers.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 12a25d94ae9348527390bad889be7c918390c0a1b133735612ac5a80ca0c6633
|
4
|
+
data.tar.gz: de4bb61a31dc643d359157dff498410f70a5a7bd18d6c254463a2a5294706949
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
data/lib/graphql/schema/enum.rb
CHANGED
@@ -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
|
-
|
89
|
+
types = Warden.types_from_context(context)
|
90
90
|
own_values.each do |key, values_entry|
|
91
|
-
|
92
|
-
|
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
|
@@ -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.
|
26
|
-
profile = self.new(context: context, schema: schema)
|
27
|
-
profile.instance_variable_set(:@cached_visible, Hash.new { |
|
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.
|
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
|
-
#
|
77
|
-
|
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);
|
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
|
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
|
|
data/lib/graphql/schema.rb
CHANGED
@@ -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 `#{
|
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
|
-
|
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.
|
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
|
data/lib/graphql/version.rb
CHANGED
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.
|
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-
|
11
|
+
date: 2024-11-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: base64
|