graphql 2.3.6 → 2.3.8
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/analysis/field_usage.rb +1 -1
- data/lib/graphql/analysis/query_complexity.rb +3 -3
- data/lib/graphql/analysis/visitor.rb +7 -6
- data/lib/graphql/execution/interpreter/runtime.rb +6 -6
- data/lib/graphql/execution/lookahead.rb +10 -10
- data/lib/graphql/introspection/directive_type.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/introspection/field_type.rb +1 -1
- data/lib/graphql/introspection/schema_type.rb +13 -3
- data/lib/graphql/introspection/type_type.rb +5 -5
- data/lib/graphql/language/document_from_schema_definition.rb +19 -26
- data/lib/graphql/language/lexer.rb +0 -3
- data/lib/graphql/language/sanitized_printer.rb +1 -1
- data/lib/graphql/language.rb +0 -1
- data/lib/graphql/query/context.rb +4 -0
- data/lib/graphql/query/null_context.rb +4 -0
- data/lib/graphql/query.rb +26 -3
- data/lib/graphql/schema/always_visible.rb +1 -0
- data/lib/graphql/schema/enum.rb +4 -4
- data/lib/graphql/schema/field.rb +7 -3
- data/lib/graphql/schema/has_single_input_argument.rb +2 -1
- data/lib/graphql/schema/input_object.rb +8 -7
- data/lib/graphql/schema/introspection_system.rb +2 -14
- data/lib/graphql/schema/member/has_arguments.rb +7 -6
- data/lib/graphql/schema/member/has_fields.rb +6 -4
- data/lib/graphql/schema/resolver.rb +4 -5
- data/lib/graphql/schema/subset.rb +397 -0
- data/lib/graphql/schema/type_expression.rb +2 -2
- data/lib/graphql/schema/validator/all_validator.rb +60 -0
- data/lib/graphql/schema/validator.rb +2 -0
- data/lib/graphql/schema/warden.rb +88 -1
- data/lib/graphql/schema.rb +44 -15
- data/lib/graphql/static_validation/base_visitor.rb +6 -5
- data/lib/graphql/static_validation/literal_validator.rb +4 -4
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -2
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +7 -7
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
- data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -3
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
- data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
- data/lib/graphql/static_validation/validation_context.rb +2 -2
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +10 -4
- data/lib/graphql/subscriptions/event.rb +1 -1
- data/lib/graphql/subscriptions.rb +2 -2
- data/lib/graphql/testing/helpers.rb +2 -2
- data/lib/graphql/types/relay/connection_behaviors.rb +10 -0
- data/lib/graphql/types/relay/edge_behaviors.rb +10 -0
- data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
- data/lib/graphql/version.rb +1 -1
- metadata +4 -4
- data/lib/graphql/language/token.rb +0 -34
- data/lib/graphql/schema/invalid_type_error.rb +0 -7
@@ -23,7 +23,8 @@ module GraphQL
|
|
23
23
|
@ruby_style_hash = ruby_kwargs
|
24
24
|
@arguments = arguments
|
25
25
|
# Apply prepares, not great to have it duplicated here.
|
26
|
-
self.class.arguments(context).each_value
|
26
|
+
arg_defns = context ? context.types.arguments(self.class) : self.class.arguments(context).each_value
|
27
|
+
arg_defns.each do |arg_defn|
|
27
28
|
ruby_kwargs_key = arg_defn.keyword
|
28
29
|
if @ruby_style_hash.key?(ruby_kwargs_key)
|
29
30
|
# Weirdly, procs are applied during coercion, but not methods.
|
@@ -58,7 +59,7 @@ module GraphQL
|
|
58
59
|
def self.authorized?(obj, value, ctx)
|
59
60
|
# Authorize each argument (but this doesn't apply if `prepare` is implemented):
|
60
61
|
if value.respond_to?(:key?)
|
61
|
-
arguments(
|
62
|
+
ctx.types.arguments(self).each do |input_obj_arg|
|
62
63
|
if value.key?(input_obj_arg.keyword) &&
|
63
64
|
!input_obj_arg.authorized?(obj, value[input_obj_arg.keyword], ctx)
|
64
65
|
return false
|
@@ -149,7 +150,7 @@ module GraphQL
|
|
149
150
|
INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object."
|
150
151
|
|
151
152
|
def validate_non_null_input(input, ctx, max_errors: nil)
|
152
|
-
|
153
|
+
types = ctx.types
|
153
154
|
|
154
155
|
if input.is_a?(Array)
|
155
156
|
return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
|
@@ -161,9 +162,9 @@ module GraphQL
|
|
161
162
|
end
|
162
163
|
|
163
164
|
# Inject missing required arguments
|
164
|
-
missing_required_inputs =
|
165
|
-
if !input.key?(
|
166
|
-
m[
|
165
|
+
missing_required_inputs = ctx.types.arguments(self).reduce({}) do |m, (argument)|
|
166
|
+
if !input.key?(argument.graphql_name) && argument.type.non_null? && types.argument(self, argument.graphql_name)
|
167
|
+
m[argument.graphql_name] = nil
|
167
168
|
end
|
168
169
|
|
169
170
|
m
|
@@ -172,7 +173,7 @@ module GraphQL
|
|
172
173
|
result = nil
|
173
174
|
[input, missing_required_inputs].each do |args_to_validate|
|
174
175
|
args_to_validate.each do |argument_name, value|
|
175
|
-
argument =
|
176
|
+
argument = types.argument(self, argument_name)
|
176
177
|
# Items in the input that are unexpected
|
177
178
|
if argument.nil?
|
178
179
|
result ||= Query::InputValidationResult.new
|
@@ -69,7 +69,7 @@ module GraphQL
|
|
69
69
|
def resolve_late_bindings
|
70
70
|
@types.each do |name, t|
|
71
71
|
if t.kind.fields?
|
72
|
-
t.
|
72
|
+
t.all_field_definitions.each do |field_defn|
|
73
73
|
field_defn.type = resolve_late_binding(field_defn.type)
|
74
74
|
end
|
75
75
|
end
|
@@ -113,19 +113,7 @@ module GraphQL
|
|
113
113
|
|
114
114
|
def get_fields_from_class(class_sym:)
|
115
115
|
object_type_defn = load_constant(class_sym)
|
116
|
-
|
117
|
-
if object_type_defn.is_a?(Module)
|
118
|
-
object_type_defn.fields
|
119
|
-
else
|
120
|
-
extracted_field_defns = {}
|
121
|
-
object_class = object_type_defn.metadata[:type_class]
|
122
|
-
object_type_defn.all_fields.each do |field_defn|
|
123
|
-
inner_resolve = field_defn.resolve_proc
|
124
|
-
resolve_with_instantiate = PerFieldProxyResolve.new(object_class: object_class, inner_resolve: inner_resolve)
|
125
|
-
extracted_field_defns[field_defn.name] = field_defn.redefine(resolve: resolve_with_instantiate)
|
126
|
-
end
|
127
|
-
extracted_field_defns
|
128
|
-
end
|
116
|
+
object_type_defn.fields
|
129
117
|
end
|
130
118
|
|
131
119
|
# This is probably not 100% robust -- but it has to be good enough to avoid modifying the built-in introspection types
|
@@ -135,10 +135,11 @@ 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
139
|
for ancestor in ancestors
|
139
140
|
if ancestor.respond_to?(:own_arguments) &&
|
140
141
|
(a = ancestor.own_arguments[argument_name]) &&
|
141
|
-
(a = Warden.visible_entry?(:visible_argument?, a, context, warden))
|
142
|
+
(skip_visible || (a = Warden.visible_entry?(:visible_argument?, a, context, warden)))
|
142
143
|
return a
|
143
144
|
end
|
144
145
|
end
|
@@ -205,8 +206,8 @@ module GraphQL
|
|
205
206
|
# @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
|
206
207
|
def get_argument(argument_name, context = GraphQL::Query::NullContext.instance)
|
207
208
|
warden = Warden.from_context(context)
|
208
|
-
if (arg_config = own_arguments[argument_name]) && (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden))
|
209
|
-
visible_arg
|
209
|
+
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)))
|
210
|
+
visible_arg || arg_config
|
210
211
|
elsif defined?(@resolver_class) && @resolver_class
|
211
212
|
@resolver_class.get_field_argument(argument_name, context)
|
212
213
|
else
|
@@ -230,7 +231,7 @@ module GraphQL
|
|
230
231
|
# @return [Interpreter::Arguments, Execution::Lazy<Interpreter::Arguments>]
|
231
232
|
def coerce_arguments(parent_object, values, context, &block)
|
232
233
|
# Cache this hash to avoid re-merging it
|
233
|
-
arg_defns = context.
|
234
|
+
arg_defns = context.types.arguments(self)
|
234
235
|
total_args_count = arg_defns.size
|
235
236
|
|
236
237
|
finished_args = nil
|
@@ -364,8 +365,8 @@ module GraphQL
|
|
364
365
|
end
|
365
366
|
|
366
367
|
if !(
|
367
|
-
context.
|
368
|
-
context.
|
368
|
+
context.types.possible_types(argument.loads).include?(application_object_type) ||
|
369
|
+
context.types.loadable?(argument.loads, context)
|
369
370
|
)
|
370
371
|
err = GraphQL::LoadApplicationObjectFailedError.new(context: context, argument: argument, id: id, object: application_object)
|
371
372
|
application_object = load_application_object_failed(err)
|
@@ -99,11 +99,12 @@ 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
103
|
for ancestor in ancestors
|
103
104
|
if ancestor.respond_to?(:own_fields) &&
|
104
105
|
(f_entry = ancestor.own_fields[field_name]) &&
|
105
|
-
(
|
106
|
-
return
|
106
|
+
(skip_visible || (f_entry = Warden.visible_entry?(:visible_field?, f_entry, context, warden)))
|
107
|
+
return f_entry
|
107
108
|
end
|
108
109
|
end
|
109
110
|
nil
|
@@ -134,13 +135,14 @@ module GraphQL
|
|
134
135
|
# Objects need to check that the interface implementation is visible, too
|
135
136
|
warden = Warden.from_context(context)
|
136
137
|
ancs = ancestors
|
138
|
+
skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)
|
137
139
|
i = 0
|
138
140
|
while (ancestor = ancs[i])
|
139
141
|
if ancestor.respond_to?(:own_fields) &&
|
140
142
|
visible_interface_implementation?(ancestor, context, warden) &&
|
141
143
|
(f_entry = ancestor.own_fields[field_name]) &&
|
142
|
-
(
|
143
|
-
return
|
144
|
+
(skip_visible || (f_entry = Warden.visible_entry?(:visible_field?, f_entry, context, warden)))
|
145
|
+
return f_entry
|
144
146
|
end
|
145
147
|
i += 1
|
146
148
|
end
|
@@ -36,7 +36,7 @@ module GraphQL
|
|
36
36
|
@field = field
|
37
37
|
# Since this hash is constantly rebuilt, cache it for this call
|
38
38
|
@arguments_by_keyword = {}
|
39
|
-
|
39
|
+
context.types.arguments(self.class).each do |arg|
|
40
40
|
@arguments_by_keyword[arg.keyword] = arg
|
41
41
|
end
|
42
42
|
@prepared_arguments = nil
|
@@ -152,7 +152,7 @@ module GraphQL
|
|
152
152
|
# @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.)
|
153
153
|
def authorized?(**inputs)
|
154
154
|
arg_owner = @field # || self.class
|
155
|
-
args =
|
155
|
+
args = context.types.arguments(arg_owner)
|
156
156
|
authorize_arguments(args, inputs)
|
157
157
|
end
|
158
158
|
|
@@ -169,7 +169,7 @@ module GraphQL
|
|
169
169
|
private
|
170
170
|
|
171
171
|
def authorize_arguments(args, inputs)
|
172
|
-
args.
|
172
|
+
args.each do |argument|
|
173
173
|
arg_keyword = argument.keyword
|
174
174
|
if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
|
175
175
|
auth_result = argument.authorized?(self, arg_value, context)
|
@@ -182,10 +182,9 @@ module GraphQL
|
|
182
182
|
elsif auth_result == false
|
183
183
|
return auth_result
|
184
184
|
end
|
185
|
-
else
|
186
|
-
true
|
187
185
|
end
|
188
186
|
end
|
187
|
+
true
|
189
188
|
end
|
190
189
|
|
191
190
|
def load_arguments(args)
|
@@ -0,0 +1,397 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Subset
|
6
|
+
def initialize(query)
|
7
|
+
@query = query
|
8
|
+
@context = query.context
|
9
|
+
@schema = query.schema
|
10
|
+
@all_types = {}
|
11
|
+
@all_types_loaded = false
|
12
|
+
@unvisited_types = []
|
13
|
+
@referenced_types = Hash.new { |h, type_defn| h[type_defn] = [] }.compare_by_identity
|
14
|
+
@cached_possible_types = nil
|
15
|
+
@cached_visible = Hash.new { |h, member|
|
16
|
+
h[member] = @schema.visible?(member, @context)
|
17
|
+
}.compare_by_identity
|
18
|
+
|
19
|
+
@cached_visible_fields = Hash.new { |h, owner|
|
20
|
+
h[owner] = Hash.new do |h2, field|
|
21
|
+
h2[field] = if @cached_visible[field] &&
|
22
|
+
(ret_type = field.type.unwrap) &&
|
23
|
+
@cached_visible[ret_type] &&
|
24
|
+
reachable_type?(ret_type.graphql_name) &&
|
25
|
+
(owner == field.owner || (!owner.kind.object?) || field_on_visible_interface?(field, owner))
|
26
|
+
|
27
|
+
if !field.introspection?
|
28
|
+
# The problem is that some introspection fields may have references
|
29
|
+
# to non-custom introspection types.
|
30
|
+
# If those were added here, they'd cause a DuplicateNamesError.
|
31
|
+
# This is basically a bug -- those fields _should_ reference the custom types.
|
32
|
+
add_type(ret_type, field)
|
33
|
+
end
|
34
|
+
true
|
35
|
+
else
|
36
|
+
false
|
37
|
+
end
|
38
|
+
end.compare_by_identity
|
39
|
+
}.compare_by_identity
|
40
|
+
|
41
|
+
@cached_visible_arguments = Hash.new do |h, arg|
|
42
|
+
h[arg] = if @cached_visible[arg] && (arg_type = arg.type.unwrap) && @cached_visible[arg_type]
|
43
|
+
add_type(arg_type, arg)
|
44
|
+
true
|
45
|
+
else
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end.compare_by_identity
|
49
|
+
|
50
|
+
@unfiltered_pt = Hash.new do |hash, type|
|
51
|
+
hash[type] = @schema.possible_types(type)
|
52
|
+
end.compare_by_identity
|
53
|
+
end
|
54
|
+
|
55
|
+
def field_on_visible_interface?(field, owner)
|
56
|
+
ints = owner.interface_type_memberships.map(&:abstract_type)
|
57
|
+
field_name = field.graphql_name
|
58
|
+
filtered_ints = interfaces(owner)
|
59
|
+
any_interface_has_field = false
|
60
|
+
any_interface_has_visible_field = false
|
61
|
+
ints.each do |int_t|
|
62
|
+
if (_int_f_defn = int_t.get_field(field_name, @context))
|
63
|
+
any_interface_has_field = true
|
64
|
+
|
65
|
+
if filtered_ints.include?(int_t) # TODO cycles, or maybe not necessary since previously checked? && @cached_visible_fields[owner][field]
|
66
|
+
any_interface_has_visible_field = true
|
67
|
+
break
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
if any_interface_has_field
|
73
|
+
any_interface_has_visible_field
|
74
|
+
else
|
75
|
+
true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def type(type_name)
|
80
|
+
t = if (loaded_t = @all_types[type_name])
|
81
|
+
loaded_t
|
82
|
+
elsif !@all_types_loaded
|
83
|
+
load_all_types
|
84
|
+
@all_types[type_name]
|
85
|
+
end
|
86
|
+
|
87
|
+
if t
|
88
|
+
if t.is_a?(Array)
|
89
|
+
vis_t = nil
|
90
|
+
t.each do |t_defn|
|
91
|
+
if @cached_visible[t_defn]
|
92
|
+
if vis_t.nil?
|
93
|
+
vis_t = t_defn
|
94
|
+
else
|
95
|
+
raise_duplicate_definition(vis_t, t_defn)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
vis_t
|
100
|
+
else
|
101
|
+
if t && @cached_visible[t]
|
102
|
+
t
|
103
|
+
else
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def field(owner, field_name)
|
111
|
+
f = if owner.kind.fields? && (field = owner.get_field(field_name, @context))
|
112
|
+
field
|
113
|
+
elsif owner == query_root && (entry_point_field = @schema.introspection_system.entry_point(name: field_name))
|
114
|
+
entry_point_field
|
115
|
+
elsif (dynamic_field = @schema.introspection_system.dynamic_field(name: field_name))
|
116
|
+
dynamic_field
|
117
|
+
else
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
if f.is_a?(Array)
|
121
|
+
visible_f = nil
|
122
|
+
f.each do |f_defn|
|
123
|
+
if @cached_visible_fields[owner][f_defn]
|
124
|
+
|
125
|
+
if visible_f.nil?
|
126
|
+
visible_f = f_defn
|
127
|
+
else
|
128
|
+
raise_duplicate_definition(visible_f, f_defn)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
visible_f
|
133
|
+
else
|
134
|
+
if f && @cached_visible_fields[owner][f]
|
135
|
+
f
|
136
|
+
else
|
137
|
+
nil
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def fields(owner)
|
143
|
+
non_duplicate_items(owner.all_field_definitions, @cached_visible_fields[owner])
|
144
|
+
end
|
145
|
+
|
146
|
+
def arguments(owner)
|
147
|
+
non_duplicate_items(owner.all_argument_definitions, @cached_visible_arguments)
|
148
|
+
end
|
149
|
+
|
150
|
+
def argument(owner, arg_name)
|
151
|
+
# TODO this makes a Warden.visible_entry call down the stack
|
152
|
+
# I need a non-Warden implementation
|
153
|
+
arg = owner.get_argument(arg_name, @context)
|
154
|
+
if arg.is_a?(Array)
|
155
|
+
visible_arg = nil
|
156
|
+
arg.each do |arg_defn|
|
157
|
+
if @cached_visible_arguments[arg_defn]
|
158
|
+
if arg_defn&.loads
|
159
|
+
add_type(arg_defn.loads, arg_defn)
|
160
|
+
end
|
161
|
+
if visible_arg.nil?
|
162
|
+
visible_arg = arg_defn
|
163
|
+
else
|
164
|
+
raise_duplicate_definition(visible_arg, arg_defn)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
visible_arg
|
169
|
+
else
|
170
|
+
if arg && @cached_visible_arguments[arg]
|
171
|
+
if arg&.loads
|
172
|
+
add_type(arg.loads, arg)
|
173
|
+
end
|
174
|
+
arg
|
175
|
+
else
|
176
|
+
nil
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def possible_types(type)
|
182
|
+
@cached_possible_types ||= Hash.new do |h, type|
|
183
|
+
pt = case type.kind.name
|
184
|
+
when "INTERFACE"
|
185
|
+
# TODO this requires the global map
|
186
|
+
@unfiltered_pt[type]
|
187
|
+
when "UNION"
|
188
|
+
type.type_memberships.select { |tm| @cached_visible[tm] && @cached_visible[tm.object_type] }.map!(&:object_type)
|
189
|
+
else
|
190
|
+
[type]
|
191
|
+
end
|
192
|
+
|
193
|
+
# TODO use `select!` when possible, skip it for `[type]`
|
194
|
+
h[type] = pt.select { |t|
|
195
|
+
@cached_visible[t] && referenced?(t)
|
196
|
+
}
|
197
|
+
end.compare_by_identity
|
198
|
+
@cached_possible_types[type]
|
199
|
+
end
|
200
|
+
|
201
|
+
def interfaces(obj_or_int_type)
|
202
|
+
ints = obj_or_int_type.interface_type_memberships
|
203
|
+
.select { |itm| @cached_visible[itm] && @cached_visible[itm.abstract_type] }
|
204
|
+
.map!(&:abstract_type)
|
205
|
+
ints.uniq! # Remove any duplicate interfaces implemented via other interfaces
|
206
|
+
ints
|
207
|
+
end
|
208
|
+
|
209
|
+
def query_root
|
210
|
+
add_if_visible(@schema.query)
|
211
|
+
end
|
212
|
+
|
213
|
+
def mutation_root
|
214
|
+
add_if_visible(@schema.mutation)
|
215
|
+
end
|
216
|
+
|
217
|
+
def subscription_root
|
218
|
+
add_if_visible(@schema.subscription)
|
219
|
+
end
|
220
|
+
|
221
|
+
def all_types
|
222
|
+
@all_types_filtered ||= begin
|
223
|
+
load_all_types
|
224
|
+
at = []
|
225
|
+
@all_types.each do |_name, type_defn|
|
226
|
+
if possible_types(type_defn).any? || referenced?(type_defn)
|
227
|
+
at << type_defn
|
228
|
+
end
|
229
|
+
end
|
230
|
+
at
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def enum_values(owner)
|
235
|
+
values = non_duplicate_items(owner.all_enum_value_definitions, @cached_visible)
|
236
|
+
if values.size == 0
|
237
|
+
raise GraphQL::Schema::Enum::MissingValuesError.new(owner)
|
238
|
+
end
|
239
|
+
values
|
240
|
+
end
|
241
|
+
|
242
|
+
def directive_exists?(dir_name)
|
243
|
+
dir = @schema.directives[dir_name]
|
244
|
+
dir && @cached_visible[dir]
|
245
|
+
end
|
246
|
+
|
247
|
+
def directives
|
248
|
+
@schema.directives.each_value.select { |d| @cached_visible[d] }
|
249
|
+
end
|
250
|
+
|
251
|
+
def loadable?(t, _ctx)
|
252
|
+
!@all_types[t.graphql_name] # TODO make sure t is not reachable but t is visible
|
253
|
+
end
|
254
|
+
|
255
|
+
# TODO rename this to indicate that it is called with a typename
|
256
|
+
def reachable_type?(type_name)
|
257
|
+
load_all_types
|
258
|
+
!!((t = @all_types[type_name]) && referenced?(t))
|
259
|
+
end
|
260
|
+
|
261
|
+
def loaded_types
|
262
|
+
@all_types.values
|
263
|
+
end
|
264
|
+
|
265
|
+
private
|
266
|
+
|
267
|
+
def add_if_visible(t)
|
268
|
+
(t && @cached_visible[t]) ? (add_type(t, true); t) : nil
|
269
|
+
end
|
270
|
+
|
271
|
+
def add_type(t, by_member)
|
272
|
+
if t && @cached_visible[t]
|
273
|
+
n = t.graphql_name
|
274
|
+
if (prev_t = @all_types[n])
|
275
|
+
if !prev_t.equal?(t)
|
276
|
+
raise_duplicate_definition(prev_t, t)
|
277
|
+
end
|
278
|
+
false
|
279
|
+
else
|
280
|
+
@referenced_types[t] << by_member
|
281
|
+
@all_types[n] = t
|
282
|
+
@unvisited_types << t
|
283
|
+
true
|
284
|
+
end
|
285
|
+
else
|
286
|
+
false
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def non_duplicate_items(definitions, visibility_cache)
|
291
|
+
non_dups = []
|
292
|
+
definitions.each do |defn|
|
293
|
+
if visibility_cache[defn]
|
294
|
+
if (dup_defn = non_dups.find { |d| d.graphql_name == defn.graphql_name })
|
295
|
+
raise_duplicate_definition(dup_defn, defn)
|
296
|
+
end
|
297
|
+
non_dups << defn
|
298
|
+
end
|
299
|
+
end
|
300
|
+
non_dups
|
301
|
+
end
|
302
|
+
|
303
|
+
def raise_duplicate_definition(first_defn, second_defn)
|
304
|
+
raise DuplicateNamesError.new(duplicated_name: first_defn.path, duplicated_definition_1: first_defn.inspect, duplicated_definition_2: second_defn.inspect)
|
305
|
+
end
|
306
|
+
|
307
|
+
def referenced?(t)
|
308
|
+
load_all_types
|
309
|
+
res = if @referenced_types[t].any? { |member| (member == true) || @cached_visible[member] }
|
310
|
+
if t.kind.abstract?
|
311
|
+
possible_types(t).any?
|
312
|
+
else
|
313
|
+
true
|
314
|
+
end
|
315
|
+
end
|
316
|
+
res
|
317
|
+
end
|
318
|
+
|
319
|
+
def load_all_types
|
320
|
+
return if @all_types_loaded
|
321
|
+
@all_types_loaded = true
|
322
|
+
schema_types = [
|
323
|
+
query_root,
|
324
|
+
mutation_root,
|
325
|
+
subscription_root,
|
326
|
+
*@schema.introspection_system.types.values,
|
327
|
+
]
|
328
|
+
|
329
|
+
# Don't include any orphan_types whose interfaces aren't visible.
|
330
|
+
@schema.orphan_types.each do |orphan_type|
|
331
|
+
if @cached_visible[orphan_type] &&
|
332
|
+
orphan_type.interface_type_memberships.any? { |tm| @cached_visible[tm] && @cached_visible[tm.abstract_type] }
|
333
|
+
schema_types << orphan_type
|
334
|
+
end
|
335
|
+
end
|
336
|
+
schema_types.compact! # TODO why is this necessary?!
|
337
|
+
schema_types.flatten! # handle multiple defns
|
338
|
+
schema_types.each { |t| add_type(t, true) }
|
339
|
+
|
340
|
+
while t = @unvisited_types.pop
|
341
|
+
# These have already been checked for `.visible?`
|
342
|
+
visit_type(t)
|
343
|
+
end
|
344
|
+
|
345
|
+
@all_types.delete_if { |type_name, type_defn| !referenced?(type_defn) }
|
346
|
+
nil
|
347
|
+
end
|
348
|
+
|
349
|
+
def visit_type(type)
|
350
|
+
if type.kind.input_object?
|
351
|
+
# recurse into visible arguments
|
352
|
+
arguments(type).each do |argument|
|
353
|
+
add_type(argument.type.unwrap, argument)
|
354
|
+
end
|
355
|
+
elsif type.kind.union?
|
356
|
+
# recurse into visible possible types
|
357
|
+
type.type_memberships.each do |tm|
|
358
|
+
if @cached_visible[tm] && @cached_visible[tm.object_type]
|
359
|
+
add_type(tm.object_type, tm)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
elsif type.kind.fields?
|
363
|
+
if type.kind.object?
|
364
|
+
# recurse into visible implemented interfaces
|
365
|
+
interfaces(type).each do |interface|
|
366
|
+
add_type(interface, type)
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
# recurse into visible fields
|
371
|
+
t_f = type.all_field_definitions
|
372
|
+
t_f.each do |field|
|
373
|
+
if @cached_visible[field]
|
374
|
+
field_type = field.type.unwrap
|
375
|
+
if field_type.kind.interface?
|
376
|
+
pt = @unfiltered_pt[field_type]
|
377
|
+
pt.each do |obj_type|
|
378
|
+
if @cached_visible[obj_type] &&
|
379
|
+
(tm = obj_type.interface_type_memberships.find { |tm| tm.abstract_type == field_type }) &&
|
380
|
+
@cached_visible[tm]
|
381
|
+
add_type(obj_type, tm)
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
add_type(field_type, field)
|
386
|
+
|
387
|
+
# recurse into visible arguments
|
388
|
+
arguments(field).each do |argument|
|
389
|
+
add_type(argument.type.unwrap, argument)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
@@ -5,13 +5,13 @@ module GraphQL
|
|
5
5
|
module TypeExpression
|
6
6
|
# Fetch a type from a type map by its AST specification.
|
7
7
|
# Return `nil` if not found.
|
8
|
-
# @param type_owner [#
|
8
|
+
# @param type_owner [#type] A thing for looking up types by name
|
9
9
|
# @param ast_node [GraphQL::Language::Nodes::AbstractNode]
|
10
10
|
# @return [Class, GraphQL::Schema::NonNull, GraphQL::Schema:List]
|
11
11
|
def self.build_type(type_owner, ast_node)
|
12
12
|
case ast_node
|
13
13
|
when GraphQL::Language::Nodes::TypeName
|
14
|
-
type_owner.
|
14
|
+
type_owner.type(ast_node.name) # rubocop:disable Development/ContextIsPassedCop -- this is a `context` or `warden`, it's already query-aware
|
15
15
|
when GraphQL::Language::Nodes::NonNullType
|
16
16
|
ast_inner_type = ast_node.of_type
|
17
17
|
inner_type = build_type(type_owner, ast_inner_type)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Validator
|
6
|
+
# Use this to validate each member of an array value.
|
7
|
+
#
|
8
|
+
# @example validate format of all strings in an array
|
9
|
+
#
|
10
|
+
# argument :handles, [String],
|
11
|
+
# validates: { all: { format: { with: /\A[a-z0-9_]+\Z/ } } }
|
12
|
+
#
|
13
|
+
# @example multiple validators can be combined
|
14
|
+
#
|
15
|
+
# argument :handles, [String],
|
16
|
+
# validates: { all: { format: { with: /\A[a-z0-9_]+\Z/ }, length: { maximum: 32 } } }
|
17
|
+
#
|
18
|
+
# @example any type can be used
|
19
|
+
#
|
20
|
+
# argument :choices, [Integer],
|
21
|
+
# validates: { all: { inclusion: { in: 1..12 } } }
|
22
|
+
#
|
23
|
+
class AllValidator < Validator
|
24
|
+
def initialize(validated:, allow_blank: false, allow_null: false, **validators)
|
25
|
+
super(validated: validated, allow_blank: allow_blank, allow_null: allow_null)
|
26
|
+
|
27
|
+
@validators = Validator.from_config(validated, validators)
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate(object, context, value)
|
31
|
+
all_errors = EMPTY_ARRAY
|
32
|
+
|
33
|
+
value.each do |subvalue|
|
34
|
+
@validators.each do |validator|
|
35
|
+
errors = validator.validate(object, context, subvalue)
|
36
|
+
if errors &&
|
37
|
+
(errors.is_a?(Array) && errors != EMPTY_ARRAY) ||
|
38
|
+
(errors.is_a?(String))
|
39
|
+
if all_errors.frozen? # It's empty
|
40
|
+
all_errors = []
|
41
|
+
end
|
42
|
+
if errors.is_a?(String)
|
43
|
+
all_errors << errors
|
44
|
+
else
|
45
|
+
all_errors.concat(errors)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
unless all_errors.frozen?
|
52
|
+
all_errors.uniq!
|
53
|
+
end
|
54
|
+
|
55
|
+
all_errors
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -169,3 +169,5 @@ require "graphql/schema/validator/allow_null_validator"
|
|
169
169
|
GraphQL::Schema::Validator.install(:allow_null, GraphQL::Schema::Validator::AllowNullValidator)
|
170
170
|
require "graphql/schema/validator/allow_blank_validator"
|
171
171
|
GraphQL::Schema::Validator.install(:allow_blank, GraphQL::Schema::Validator::AllowBlankValidator)
|
172
|
+
require "graphql/schema/validator/all_validator"
|
173
|
+
GraphQL::Schema::Validator.install(:all, GraphQL::Schema::Validator::AllValidator)
|