graphql 2.3.14 → 2.3.15
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/generators/graphql/orm_mutations_base.rb +1 -1
- data/lib/generators/graphql/templates/base_resolver.erb +2 -0
- data/lib/generators/graphql/type_generator.rb +1 -1
- data/lib/graphql/analysis.rb +1 -1
- data/lib/graphql/execution/interpreter/resolve.rb +10 -6
- data/lib/graphql/language/comment.rb +18 -0
- data/lib/graphql/language/document_from_schema_definition.rb +38 -4
- data/lib/graphql/language/lexer.rb +15 -12
- data/lib/graphql/language/nodes.rb +22 -14
- data/lib/graphql/language/parser.rb +5 -0
- data/lib/graphql/language/printer.rb +23 -7
- data/lib/graphql/language.rb +6 -5
- data/lib/graphql/query.rb +1 -1
- data/lib/graphql/schema/argument.rb +13 -1
- data/lib/graphql/schema/enum.rb +1 -0
- data/lib/graphql/schema/enum_value.rb +9 -1
- data/lib/graphql/schema/field.rb +23 -3
- data/lib/graphql/schema/interface.rb +1 -0
- data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
- data/lib/graphql/schema/member/has_arguments.rb +2 -2
- data/lib/graphql/schema/member/has_fields.rb +2 -2
- data/lib/graphql/schema/resolver.rb +3 -4
- data/lib/graphql/schema/visibility/migration.rb +188 -0
- data/lib/graphql/schema/visibility/subset.rb +509 -0
- data/lib/graphql/schema/visibility.rb +30 -0
- data/lib/graphql/schema.rb +46 -41
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
- data/lib/graphql/testing/helpers.rb +1 -1
- data/lib/graphql/tracing/notifications_trace.rb +2 -2
- data/lib/graphql/version.rb +1 -1
- metadata +6 -4
- data/lib/graphql/schema/subset.rb +0 -509
- data/lib/graphql/schema/types_migration.rb +0 -187
data/lib/graphql/schema/field.rb
CHANGED
@@ -106,7 +106,7 @@ module GraphQL
|
|
106
106
|
# @param subscription [Class] A {GraphQL::Schema::Subscription} class to use for field configuration
|
107
107
|
# @return [GraphQL::Schema:Field] an instance of `self`
|
108
108
|
# @see {.initialize} for other options
|
109
|
-
def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
|
109
|
+
def self.from_options(name = nil, type = nil, desc = nil, comment: nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
|
110
110
|
if (resolver_class = resolver || mutation || subscription)
|
111
111
|
# Add a reference to that parent class
|
112
112
|
kwargs[:resolver_class] = resolver_class
|
@@ -116,6 +116,10 @@ module GraphQL
|
|
116
116
|
kwargs[:name] = name
|
117
117
|
end
|
118
118
|
|
119
|
+
if comment
|
120
|
+
kwargs[:comment] = comment
|
121
|
+
end
|
122
|
+
|
119
123
|
if !type.nil?
|
120
124
|
if desc
|
121
125
|
if kwargs[:description]
|
@@ -212,6 +216,7 @@ module GraphQL
|
|
212
216
|
# @param owner [Class] The type that this field belongs to
|
213
217
|
# @param null [Boolean] (defaults to `true`) `true` if this field may return `null`, `false` if it is never `null`
|
214
218
|
# @param description [String] Field description
|
219
|
+
# @param comment [String] Field comment
|
215
220
|
# @param deprecation_reason [String] If present, the field is marked "deprecated" with this message
|
216
221
|
# @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`)
|
217
222
|
# @param hash_key [String, Symbol] The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`)
|
@@ -236,7 +241,7 @@ module GraphQL
|
|
236
241
|
# @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
|
237
242
|
# @param validates [Array<Hash>] Configurations for validating this field
|
238
243
|
# @param fallback_value [Object] A fallback value if the method is not defined
|
239
|
-
def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
|
244
|
+
def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
|
240
245
|
if name.nil?
|
241
246
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
242
247
|
end
|
@@ -252,6 +257,7 @@ module GraphQL
|
|
252
257
|
@name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
|
253
258
|
|
254
259
|
@description = description
|
260
|
+
@comment = comment
|
255
261
|
@type = @owner_type = @own_validators = @own_directives = @own_arguments = @arguments_statically_coercible = nil # these will be prepared later if necessary
|
256
262
|
|
257
263
|
self.deprecation_reason = deprecation_reason
|
@@ -400,6 +406,20 @@ module GraphQL
|
|
400
406
|
end
|
401
407
|
end
|
402
408
|
|
409
|
+
# @param text [String]
|
410
|
+
# @return [String, nil]
|
411
|
+
def comment(text = nil)
|
412
|
+
if text
|
413
|
+
@comment = text
|
414
|
+
elsif !NOT_CONFIGURED.equal?(@comment)
|
415
|
+
@comment
|
416
|
+
elsif @resolver_class
|
417
|
+
@resolver_class.comment
|
418
|
+
else
|
419
|
+
nil
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
403
423
|
# Read extension instances from this field,
|
404
424
|
# or add new classes/options to be initialized on this field.
|
405
425
|
# Extensions are executed in the order they are added.
|
@@ -490,7 +510,7 @@ module GraphQL
|
|
490
510
|
if arguments[:last] && (max_possible_page_size.nil? || arguments[:last] > max_possible_page_size)
|
491
511
|
max_possible_page_size = arguments[:last]
|
492
512
|
end
|
493
|
-
elsif arguments.is_a?(GraphQL::UnauthorizedError)
|
513
|
+
elsif arguments.is_a?(GraphQL::ExecutionError) || arguments.is_a?(GraphQL::UnauthorizedError)
|
494
514
|
raise arguments
|
495
515
|
end
|
496
516
|
|
@@ -63,6 +63,7 @@ module GraphQL
|
|
63
63
|
|
64
64
|
child_class.introspection(introspection)
|
65
65
|
child_class.description(description)
|
66
|
+
child_class.comment(nil)
|
66
67
|
# If interfaces are mixed into each other, only define this class once
|
67
68
|
if !child_class.const_defined?(:UnresolvedTypeError, false)
|
68
69
|
add_unresolved_type_error(child_class)
|
@@ -50,12 +50,27 @@ module GraphQL
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
+
# Call this method to provide a new comment; OR
|
54
|
+
# call it without an argument to get the comment
|
55
|
+
# @param new_comment [String]
|
56
|
+
# @return [String, nil]
|
57
|
+
def comment(new_comment = NOT_CONFIGURED)
|
58
|
+
if !NOT_CONFIGURED.equal?(new_comment)
|
59
|
+
@comment = new_comment
|
60
|
+
elsif defined?(@comment)
|
61
|
+
@comment
|
62
|
+
else
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
53
67
|
# This pushes some configurations _down_ the inheritance tree,
|
54
68
|
# in order to prevent repetitive lookups at runtime.
|
55
69
|
module ConfigurationExtension
|
56
70
|
def inherited(child_class)
|
57
71
|
child_class.introspection(introspection)
|
58
72
|
child_class.description(description)
|
73
|
+
child_class.comment(nil)
|
59
74
|
child_class.default_graphql_name = nil
|
60
75
|
|
61
76
|
if defined?(@graphql_name) && @graphql_name && (self.name.nil? || graphql_name != default_graphql_name)
|
@@ -135,7 +135,7 @@ module GraphQL
|
|
135
135
|
|
136
136
|
def get_argument(argument_name, context = GraphQL::Query::NullContext.instance)
|
137
137
|
warden = Warden.from_context(context)
|
138
|
-
skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)
|
138
|
+
skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Subset)
|
139
139
|
for ancestor in ancestors
|
140
140
|
if ancestor.respond_to?(:own_arguments) &&
|
141
141
|
(a = ancestor.own_arguments[argument_name]) &&
|
@@ -210,7 +210,7 @@ module GraphQL
|
|
210
210
|
# @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
|
211
211
|
def get_argument(argument_name, context = GraphQL::Query::NullContext.instance)
|
212
212
|
warden = Warden.from_context(context)
|
213
|
-
if (arg_config = own_arguments[argument_name]) && ((context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)) || (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden)))
|
213
|
+
if (arg_config = own_arguments[argument_name]) && ((context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Subset)) || (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden)))
|
214
214
|
visible_arg || arg_config
|
215
215
|
elsif defined?(@resolver_class) && @resolver_class
|
216
216
|
@resolver_class.get_field_argument(argument_name, context)
|
@@ -99,7 +99,7 @@ module GraphQL
|
|
99
99
|
module InterfaceMethods
|
100
100
|
def get_field(field_name, context = GraphQL::Query::NullContext.instance)
|
101
101
|
warden = Warden.from_context(context)
|
102
|
-
skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)
|
102
|
+
skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Subset)
|
103
103
|
for ancestor in ancestors
|
104
104
|
if ancestor.respond_to?(:own_fields) &&
|
105
105
|
(f_entry = ancestor.own_fields[field_name]) &&
|
@@ -135,7 +135,7 @@ module GraphQL
|
|
135
135
|
# Objects need to check that the interface implementation is visible, too
|
136
136
|
warden = Warden.from_context(context)
|
137
137
|
ancs = ancestors
|
138
|
-
skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)
|
138
|
+
skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Subset)
|
139
139
|
i = 0
|
140
140
|
while (ancestor = ancs[i])
|
141
141
|
if ancestor.respond_to?(:own_fields) &&
|
@@ -8,6 +8,7 @@ module GraphQL
|
|
8
8
|
# - Arguments, via `.argument(...)` helper, which will be applied to the field.
|
9
9
|
# - Return type, via `.type(..., null: ...)`, which will be applied to the field.
|
10
10
|
# - Description, via `.description(...)`, which will be applied to the field
|
11
|
+
# - Comment, via `.comment(...)`, which will be applied to the field
|
11
12
|
# - Resolution, via `#resolve(**args)` method, which will be called to resolve the field.
|
12
13
|
# - `#object` and `#context` accessors for use during `#resolve`.
|
13
14
|
#
|
@@ -19,7 +20,7 @@ module GraphQL
|
|
19
20
|
# @see {GraphQL::Function} `Resolver` is a replacement for `GraphQL::Function`
|
20
21
|
class Resolver
|
21
22
|
include Schema::Member::GraphQLTypeNames
|
22
|
-
# Really we only need description from here, but:
|
23
|
+
# Really we only need description & comment from here, but:
|
23
24
|
extend Schema::Member::BaseDSLMethods
|
24
25
|
extend GraphQL::Schema::Member::HasArguments
|
25
26
|
extend GraphQL::Schema::Member::HasValidators
|
@@ -408,9 +409,7 @@ module GraphQL
|
|
408
409
|
|
409
410
|
private
|
410
411
|
|
411
|
-
|
412
|
-
@own_extensions
|
413
|
-
end
|
412
|
+
attr_reader :own_extensions
|
414
413
|
end
|
415
414
|
end
|
416
415
|
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
class Schema
|
4
|
+
class Visibility
|
5
|
+
# You can use this to see how {GraphQL::Schema::Warden} and {GraphQL::Schema::Visibility::Subset}
|
6
|
+
# handle `.visible?` differently in your schema.
|
7
|
+
#
|
8
|
+
# It runs the same method on both implementations and raises an error when the results diverge.
|
9
|
+
#
|
10
|
+
# To fix the error, modify your schema so that both implementations return the same thing.
|
11
|
+
# Or, open an issue on GitHub to discuss the difference.
|
12
|
+
#
|
13
|
+
# This plugin adds overhead to runtime and may cause unexpected crashes -- **don't** use it in production!
|
14
|
+
#
|
15
|
+
# This plugin adds two keys to `context` when running:
|
16
|
+
#
|
17
|
+
# - `visibility_migration_running: true`
|
18
|
+
# - For the {Warden} which it instantiates, it adds `visibility_migration_warden_running: true`.
|
19
|
+
#
|
20
|
+
# Use those keys to modify your `visible?` behavior as needed.
|
21
|
+
#
|
22
|
+
# Also, in a pinch, you can set `skip_visibility_migration_error: true` in context to turn off this behavior per-query.
|
23
|
+
# (In that case, it uses {Subset} directly.)
|
24
|
+
#
|
25
|
+
# @example Adding this plugin
|
26
|
+
#
|
27
|
+
# use GraphQL::Schema::Visibility::Migration
|
28
|
+
#
|
29
|
+
class Migration < GraphQL::Schema::Visibility::Subset
|
30
|
+
def self.use(schema)
|
31
|
+
schema.subset_class = self
|
32
|
+
end
|
33
|
+
|
34
|
+
class RuntimeTypesMismatchError < GraphQL::Error
|
35
|
+
def initialize(method_called, warden_result, subset_result, method_args)
|
36
|
+
super(<<~ERR)
|
37
|
+
Mismatch in types for `##{method_called}(#{method_args.map(&:inspect).join(", ")})`:
|
38
|
+
|
39
|
+
#{compare_results(warden_result, subset_result)}
|
40
|
+
|
41
|
+
Update your `.visible?` implementation to make these implementations return the same value.
|
42
|
+
|
43
|
+
See: https://graphql-ruby.org/authorization/visibility_migration.html
|
44
|
+
ERR
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def compare_results(warden_result, subset_result)
|
49
|
+
if warden_result.is_a?(Array) && subset_result.is_a?(Array)
|
50
|
+
all_results = warden_result | subset_result
|
51
|
+
all_results.sort_by!(&:graphql_name)
|
52
|
+
|
53
|
+
entries_text = all_results.map { |entry| "#{entry.graphql_name} (#{entry})"}
|
54
|
+
width = entries_text.map(&:size).max
|
55
|
+
yes = " ✔ "
|
56
|
+
no = " "
|
57
|
+
res = "".dup
|
58
|
+
res << "#{"Result".center(width)} Warden Subset \n"
|
59
|
+
all_results.each_with_index do |entry, idx|
|
60
|
+
res << "#{entries_text[idx].ljust(width)}#{warden_result.include?(entry) ? yes : no}#{subset_result.include?(entry) ? yes : no}\n"
|
61
|
+
end
|
62
|
+
res << "\n"
|
63
|
+
else
|
64
|
+
"- Warden returned: #{humanize(warden_result)}\n\n- Subset returned: #{humanize(subset_result)}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
def humanize(val)
|
68
|
+
case val
|
69
|
+
when Array
|
70
|
+
"#{val.size}: #{val.map { |v| humanize(v) }.sort.inspect}"
|
71
|
+
when Module
|
72
|
+
if val.respond_to?(:graphql_name)
|
73
|
+
"#{val.graphql_name} (#{val.inspect})"
|
74
|
+
else
|
75
|
+
val.inspect
|
76
|
+
end
|
77
|
+
else
|
78
|
+
val.inspect
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def initialize(context:, schema:)
|
84
|
+
@skip_error = context[:skip_visibility_migration_error]
|
85
|
+
context[:visibility_migration_running] = true
|
86
|
+
@subset_types = GraphQL::Schema::Visibility::Subset.new(context: context, schema: schema)
|
87
|
+
if !@skip_error
|
88
|
+
warden_ctx_vals = context.to_h.dup
|
89
|
+
warden_ctx_vals[:visibility_migration_warden_running] = true
|
90
|
+
if defined?(schema::WardenCompatSchema)
|
91
|
+
warden_schema = schema::WardenCompatSchema
|
92
|
+
else
|
93
|
+
warden_schema = Class.new(schema)
|
94
|
+
warden_schema.use_schema_visibility = false
|
95
|
+
# TODO public API
|
96
|
+
warden_schema.send(:add_type_and_traverse, [warden_schema.query, warden_schema.mutation, warden_schema.subscription].compact, root: true)
|
97
|
+
warden_schema.send(:add_type_and_traverse, warden_schema.directives.values + warden_schema.orphan_types, root: false)
|
98
|
+
end
|
99
|
+
warden_ctx = GraphQL::Query::Context.new(query: context.query, values: warden_ctx_vals)
|
100
|
+
example_warden = GraphQL::Schema::Warden.new(schema: warden_schema, context: warden_ctx)
|
101
|
+
@warden_types = example_warden.schema_subset
|
102
|
+
warden_ctx.warden = example_warden
|
103
|
+
warden_ctx.types = @warden_types
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def loaded_types
|
108
|
+
@subset_types.loaded_types
|
109
|
+
end
|
110
|
+
|
111
|
+
PUBLIC_SUBSET_METHODS = [
|
112
|
+
:enum_values,
|
113
|
+
:interfaces,
|
114
|
+
:all_types,
|
115
|
+
:fields,
|
116
|
+
:loadable?,
|
117
|
+
:type,
|
118
|
+
:arguments,
|
119
|
+
:argument,
|
120
|
+
:directive_exists?,
|
121
|
+
:directives,
|
122
|
+
:field,
|
123
|
+
:query_root,
|
124
|
+
:mutation_root,
|
125
|
+
:possible_types,
|
126
|
+
:subscription_root,
|
127
|
+
:reachable_type?
|
128
|
+
]
|
129
|
+
|
130
|
+
PUBLIC_SUBSET_METHODS.each do |subset_method|
|
131
|
+
define_method(subset_method) do |*args|
|
132
|
+
call_method_and_compare(subset_method, args)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def call_method_and_compare(method, args)
|
137
|
+
res_1 = @subset_types.public_send(method, *args)
|
138
|
+
if @skip_error
|
139
|
+
return res_1
|
140
|
+
end
|
141
|
+
|
142
|
+
res_2 = @warden_types.public_send(method, *args)
|
143
|
+
normalized_res_1 = res_1.is_a?(Array) ? Set.new(res_1) : res_1
|
144
|
+
normalized_res_2 = res_2.is_a?(Array) ? Set.new(res_2) : res_2
|
145
|
+
if !equivalent_schema_members?(normalized_res_1, normalized_res_2)
|
146
|
+
# Raise the errors with the orignally returned values:
|
147
|
+
err = RuntimeTypesMismatchError.new(method, res_2, res_1, args)
|
148
|
+
raise err
|
149
|
+
else
|
150
|
+
res_1
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def equivalent_schema_members?(member1, member2)
|
155
|
+
if member1.class != member2.class
|
156
|
+
return false
|
157
|
+
end
|
158
|
+
|
159
|
+
case member1
|
160
|
+
when Set
|
161
|
+
member1_array = member1.to_a.sort_by(&:graphql_name)
|
162
|
+
member2_array = member2.to_a.sort_by(&:graphql_name)
|
163
|
+
member1_array.each_with_index do |inner_member1, idx|
|
164
|
+
inner_member2 = member2_array[idx]
|
165
|
+
equivalent_schema_members?(inner_member1, inner_member2)
|
166
|
+
end
|
167
|
+
when GraphQL::Schema::Field
|
168
|
+
member1.ensure_loaded
|
169
|
+
member2.ensure_loaded
|
170
|
+
if member1.introspection? && member2.introspection?
|
171
|
+
member1.inspect == member2.inspect
|
172
|
+
else
|
173
|
+
member1 == member2
|
174
|
+
end
|
175
|
+
when Module
|
176
|
+
if member1.introspection? && member2.introspection?
|
177
|
+
member1.graphql_name == member2.graphql_name
|
178
|
+
else
|
179
|
+
member1 == member2
|
180
|
+
end
|
181
|
+
else
|
182
|
+
member1 == member2
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|