graphql 1.11.10 → 1.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/generators/graphql/install_generator.rb +5 -5
- data/lib/generators/graphql/relay_generator.rb +63 -0
- data/lib/generators/graphql/templates/base_connection.erb +8 -0
- data/lib/generators/graphql/templates/base_edge.erb +8 -0
- data/lib/generators/graphql/templates/node_type.erb +9 -0
- data/lib/generators/graphql/templates/object.erb +1 -1
- data/lib/generators/graphql/templates/query_type.erb +1 -3
- data/lib/generators/graphql/templates/schema.erb +8 -35
- data/lib/graphql/analysis/analyze_query.rb +7 -0
- data/lib/graphql/analysis/ast/visitor.rb +9 -1
- data/lib/graphql/analysis/ast.rb +11 -2
- data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
- data/lib/graphql/backtrace/table.rb +22 -2
- data/lib/graphql/backtrace/tracer.rb +40 -9
- data/lib/graphql/backtrace.rb +28 -19
- data/lib/graphql/backwards_compatibility.rb +1 -0
- data/lib/graphql/compatibility/execution_specification.rb +1 -0
- data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
- data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
- data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
- data/lib/graphql/dataloader/null_dataloader.rb +21 -0
- data/lib/graphql/dataloader/request.rb +24 -0
- data/lib/graphql/dataloader/request_all.rb +22 -0
- data/lib/graphql/dataloader/source.rb +93 -0
- data/lib/graphql/dataloader.rb +197 -0
- data/lib/graphql/define/assign_global_id_field.rb +1 -1
- data/lib/graphql/define/instance_definable.rb +32 -2
- data/lib/graphql/define/type_definer.rb +5 -5
- data/lib/graphql/deprecated_dsl.rb +5 -0
- data/lib/graphql/enum_type.rb +2 -0
- data/lib/graphql/execution/errors.rb +4 -0
- data/lib/graphql/execution/execute.rb +7 -0
- data/lib/graphql/execution/interpreter/arguments.rb +51 -14
- data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
- data/lib/graphql/execution/interpreter/runtime.rb +210 -124
- data/lib/graphql/execution/interpreter.rb +10 -6
- data/lib/graphql/execution/multiplex.rb +20 -6
- data/lib/graphql/function.rb +4 -0
- data/lib/graphql/input_object_type.rb +2 -0
- data/lib/graphql/interface_type.rb +3 -1
- data/lib/graphql/language/document_from_schema_definition.rb +50 -23
- data/lib/graphql/object_type.rb +2 -0
- data/lib/graphql/pagination/connection.rb +5 -1
- data/lib/graphql/pagination/connections.rb +6 -16
- data/lib/graphql/query/context.rb +4 -0
- data/lib/graphql/query/serial_execution.rb +1 -0
- data/lib/graphql/query/validation_pipeline.rb +1 -1
- data/lib/graphql/query.rb +2 -0
- data/lib/graphql/relay/base_connection.rb +7 -0
- data/lib/graphql/relay/connection_instrumentation.rb +4 -4
- data/lib/graphql/relay/connection_type.rb +1 -1
- data/lib/graphql/relay/mutation.rb +1 -0
- data/lib/graphql/relay/node.rb +3 -0
- data/lib/graphql/relay/type_extensions.rb +2 -0
- data/lib/graphql/scalar_type.rb +2 -0
- data/lib/graphql/schema/argument.rb +25 -7
- data/lib/graphql/schema/build_from_definition.rb +139 -51
- data/lib/graphql/schema/directive/flagged.rb +57 -0
- data/lib/graphql/schema/directive.rb +76 -0
- data/lib/graphql/schema/enum.rb +3 -0
- data/lib/graphql/schema/enum_value.rb +12 -6
- data/lib/graphql/schema/field/connection_extension.rb +3 -2
- data/lib/graphql/schema/field.rb +28 -9
- data/lib/graphql/schema/input_object.rb +33 -22
- data/lib/graphql/schema/interface.rb +1 -0
- data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
- data/lib/graphql/schema/member/build_type.rb +3 -3
- data/lib/graphql/schema/member/has_arguments.rb +24 -6
- data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
- data/lib/graphql/schema/member/has_directives.rb +98 -0
- data/lib/graphql/schema/member/has_validators.rb +31 -0
- data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
- data/lib/graphql/schema/member.rb +4 -0
- data/lib/graphql/schema/object.rb +11 -0
- data/lib/graphql/schema/printer.rb +5 -4
- data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
- data/lib/graphql/schema/resolver.rb +7 -0
- data/lib/graphql/schema/subscription.rb +19 -1
- data/lib/graphql/schema/timeout_middleware.rb +2 -0
- data/lib/graphql/schema/validation.rb +2 -0
- data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
- data/lib/graphql/schema/validator/format_validator.rb +49 -0
- data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
- data/lib/graphql/schema/validator/length_validator.rb +57 -0
- data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
- data/lib/graphql/schema/validator/required_validator.rb +68 -0
- data/lib/graphql/schema/validator.rb +163 -0
- data/lib/graphql/schema.rb +72 -49
- data/lib/graphql/static_validation/base_visitor.rb +0 -3
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +4 -4
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
- data/lib/graphql/static_validation/validation_context.rb +1 -6
- data/lib/graphql/static_validation/validator.rb +12 -14
- data/lib/graphql/subscriptions.rb +17 -20
- data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
- data/lib/graphql/tracing/platform_tracing.rb +3 -1
- data/lib/graphql/tracing/skylight_tracing.rb +1 -1
- data/lib/graphql/tracing.rb +2 -2
- data/lib/graphql/types/relay/base_connection.rb +2 -92
- data/lib/graphql/types/relay/base_edge.rb +2 -35
- data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
- data/lib/graphql/types/relay/default_relay.rb +27 -0
- data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
- data/lib/graphql/types/relay/has_node_field.rb +41 -0
- data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
- data/lib/graphql/types/relay/node.rb +2 -4
- data/lib/graphql/types/relay/node_behaviors.rb +15 -0
- data/lib/graphql/types/relay/node_field.rb +1 -19
- data/lib/graphql/types/relay/nodes_field.rb +1 -19
- data/lib/graphql/types/relay/page_info.rb +2 -14
- data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
- data/lib/graphql/types/relay.rb +11 -3
- data/lib/graphql/union_type.rb +2 -0
- data/lib/graphql/upgrader/member.rb +1 -0
- data/lib/graphql/upgrader/schema.rb +1 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +38 -4
- metadata +31 -6
- data/lib/graphql/types/relay/base_field.rb +0 -22
- data/lib/graphql/types/relay/base_interface.rb +0 -29
- data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -7,6 +7,7 @@ module GraphQL
|
|
7
7
|
extend GraphQL::Schema::Member::HasArguments
|
8
8
|
extend GraphQL::Schema::Member::HasArguments::ArgumentObjectLoader
|
9
9
|
extend GraphQL::Schema::Member::ValidatesInput
|
10
|
+
extend GraphQL::Schema::Member::HasValidators
|
10
11
|
|
11
12
|
include GraphQL::Dig
|
12
13
|
|
@@ -37,14 +38,15 @@ module GraphQL
|
|
37
38
|
load_application_object(arg_defn, loads, value, context)
|
38
39
|
end
|
39
40
|
maybe_lazies << context.schema.after_lazy(loaded_value) do |loaded_value|
|
40
|
-
|
41
|
+
overwrite_argument(ruby_kwargs_key, loaded_value)
|
41
42
|
end
|
42
43
|
end
|
43
44
|
|
44
45
|
# Weirdly, procs are applied during coercion, but not methods.
|
45
46
|
# Probably because these methods require a `self`.
|
46
47
|
if arg_defn.prepare.is_a?(Symbol) || context.nil? || !context.interpreter?
|
47
|
-
|
48
|
+
prepared_value = arg_defn.prepare_value(self, @ruby_style_hash[ruby_kwargs_key])
|
49
|
+
overwrite_argument(ruby_kwargs_key, prepared_value)
|
48
50
|
end
|
49
51
|
end
|
50
52
|
end
|
@@ -74,6 +76,9 @@ module GraphQL
|
|
74
76
|
def prepare
|
75
77
|
if context
|
76
78
|
context.schema.after_any_lazies(@maybe_lazies) do
|
79
|
+
object = context[:current_object]
|
80
|
+
# Pass this object's class with `as` so that messages are rendered correctly from inherited validators
|
81
|
+
Schema::Validator.validate!(self.class.validators, object, context, @ruby_style_hash, as: self.class)
|
77
82
|
self
|
78
83
|
end
|
79
84
|
else
|
@@ -168,17 +173,10 @@ module GraphQL
|
|
168
173
|
return result
|
169
174
|
end
|
170
175
|
|
171
|
-
input
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
# Handle ActionController::Parameters:
|
176
|
-
input.to_unsafe_h
|
177
|
-
rescue
|
178
|
-
# We're not sure it'll act like a hash, so reject it:
|
179
|
-
result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
|
180
|
-
return result
|
181
|
-
end
|
176
|
+
if !(input.respond_to?(:to_h) || input.respond_to?(:to_unsafe_h))
|
177
|
+
# We're not sure it'll act like a hash, so reject it:
|
178
|
+
result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
|
179
|
+
return result
|
182
180
|
end
|
183
181
|
|
184
182
|
# Inject missing required arguments
|
@@ -190,16 +188,19 @@ module GraphQL
|
|
190
188
|
m
|
191
189
|
end
|
192
190
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
191
|
+
|
192
|
+
[input, missing_required_inputs].each do |args_to_validate|
|
193
|
+
args_to_validate.each do |argument_name, value|
|
194
|
+
argument = warden.get_argument(self, argument_name)
|
195
|
+
# Items in the input that are unexpected
|
196
|
+
unless argument
|
197
|
+
result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name])
|
198
|
+
next
|
199
|
+
end
|
200
|
+
# Items in the input that are expected, but have invalid values
|
201
|
+
argument_result = argument.type.validate_input(value, ctx)
|
202
|
+
result.merge_result!(argument_name, argument_result) unless argument_result.valid?
|
199
203
|
end
|
200
|
-
# Items in the input that are expected, but have invalid values
|
201
|
-
argument_result = argument.type.validate_input(value, ctx)
|
202
|
-
result.merge_result!(argument_name, argument_result) unless argument_result.valid?
|
203
204
|
end
|
204
205
|
|
205
206
|
result
|
@@ -240,6 +241,16 @@ module GraphQL
|
|
240
241
|
result
|
241
242
|
end
|
242
243
|
end
|
244
|
+
|
245
|
+
private
|
246
|
+
|
247
|
+
def overwrite_argument(key, value)
|
248
|
+
# Argument keywords come in frozen from the interpreter, dup them before modifying them.
|
249
|
+
if @ruby_style_hash.frozen?
|
250
|
+
@ruby_style_hash = @ruby_style_hash.dup
|
251
|
+
end
|
252
|
+
@ruby_style_hash[key] = value
|
253
|
+
end
|
243
254
|
end
|
244
255
|
end
|
245
256
|
end
|
@@ -15,6 +15,7 @@ module GraphQL
|
|
15
15
|
include GraphQL::Schema::Member::Scoped
|
16
16
|
include GraphQL::Schema::Member::HasAstNode
|
17
17
|
include GraphQL::Schema::Member::HasUnresolvedTypeError
|
18
|
+
include GraphQL::Schema::Member::HasDirectives
|
18
19
|
|
19
20
|
# Methods defined in this block will be:
|
20
21
|
# - Added as class methods to this interface
|
@@ -60,11 +60,11 @@ module GraphQL
|
|
60
60
|
parse_type(type_expr.first, null: false)
|
61
61
|
when 2
|
62
62
|
inner_type, nullable_option = type_expr
|
63
|
-
if nullable_option.keys != [:null] || nullable_option
|
63
|
+
if nullable_option.keys != [:null] || (nullable_option[:null] != true && nullable_option[:null] != false)
|
64
64
|
raise ArgumentError, LIST_TYPE_ERROR
|
65
65
|
end
|
66
66
|
list_type = true
|
67
|
-
parse_type(inner_type, null:
|
67
|
+
parse_type(inner_type, null: nullable_option[:null])
|
68
68
|
else
|
69
69
|
raise ArgumentError, LIST_TYPE_ERROR
|
70
70
|
end
|
@@ -75,7 +75,7 @@ module GraphQL
|
|
75
75
|
if type_expr.respond_to?(:graphql_definition)
|
76
76
|
type_expr
|
77
77
|
else
|
78
|
-
# Eg `String` => GraphQL::
|
78
|
+
# Eg `String` => GraphQL::Types::String
|
79
79
|
parse_type(type_expr.name, null: true)
|
80
80
|
end
|
81
81
|
when Proc
|
@@ -89,7 +89,7 @@ module GraphQL
|
|
89
89
|
arg_defns = self.arguments
|
90
90
|
|
91
91
|
if arg_defns.empty?
|
92
|
-
GraphQL::Execution::Interpreter::Arguments
|
92
|
+
GraphQL::Execution::Interpreter::Arguments::EMPTY
|
93
93
|
else
|
94
94
|
argument_values = {}
|
95
95
|
arg_lazies = arg_defns.map do |arg_name, arg_defn|
|
@@ -111,24 +111,28 @@ module GraphQL
|
|
111
111
|
if has_value
|
112
112
|
loads = arg_defn.loads
|
113
113
|
loaded_value = nil
|
114
|
+
coerced_value = context.schema.error_handler.with_error_handling(context) do
|
115
|
+
arg_defn.type.coerce_input(value, context)
|
116
|
+
end
|
117
|
+
|
118
|
+
# TODO this should probably be inside after_lazy
|
114
119
|
if loads && !arg_defn.from_resolver?
|
115
120
|
loaded_value = if arg_defn.type.list?
|
116
|
-
loaded_values =
|
121
|
+
loaded_values = coerced_value.map { |val| load_application_object(arg_defn, loads, val, context) }
|
117
122
|
context.schema.after_any_lazies(loaded_values) { |result| result }
|
118
123
|
else
|
119
|
-
load_application_object(arg_defn, loads,
|
124
|
+
load_application_object(arg_defn, loads, coerced_value, context)
|
120
125
|
end
|
121
126
|
end
|
122
127
|
|
123
128
|
coerced_value = if loaded_value
|
124
129
|
loaded_value
|
125
130
|
else
|
126
|
-
|
127
|
-
arg_defn.type.coerce_input(value, context)
|
128
|
-
end
|
131
|
+
coerced_value
|
129
132
|
end
|
130
133
|
|
131
134
|
context.schema.after_lazy(coerced_value) do |coerced_value|
|
135
|
+
validate_directive_argument(arg_defn, coerced_value)
|
132
136
|
prepared_value = context.schema.error_handler.with_error_handling(context) do
|
133
137
|
arg_defn.prepare_value(parent_object, coerced_value, context: context)
|
134
138
|
end
|
@@ -140,6 +144,9 @@ module GraphQL
|
|
140
144
|
default_used: default_used,
|
141
145
|
)
|
142
146
|
end
|
147
|
+
else
|
148
|
+
# has_value is false
|
149
|
+
validate_directive_argument(arg_defn, nil)
|
143
150
|
end
|
144
151
|
end
|
145
152
|
|
@@ -151,6 +158,17 @@ module GraphQL
|
|
151
158
|
end
|
152
159
|
end
|
153
160
|
|
161
|
+
# Usually, this is validated statically by RequiredArgumentsArePresent,
|
162
|
+
# but not for directives.
|
163
|
+
# TODO apply static validations on schema definitions?
|
164
|
+
def validate_directive_argument(arg_defn, value)
|
165
|
+
if arg_defn.owner.is_a?(Class) && arg_defn.owner < GraphQL::Schema::Directive
|
166
|
+
if value.nil? && arg_defn.type.non_null?
|
167
|
+
raise ArgumentError, "#{arg_defn.path} is required, but no value was given"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
154
172
|
def arguments_statically_coercible?
|
155
173
|
return @arguments_statically_coercible if defined?(@arguments_statically_coercible)
|
156
174
|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Member
|
6
|
+
module HasDeprecationReason
|
7
|
+
# @return [String, nil] Explains why this member was deprecated (if present, this will be marked deprecated in introspection)
|
8
|
+
def deprecation_reason
|
9
|
+
dir = self.directives.find { |d| d.is_a?(GraphQL::Schema::Directive::Deprecated) }
|
10
|
+
dir && dir.arguments[:reason]
|
11
|
+
end
|
12
|
+
|
13
|
+
# Set the deprecation reason for this member, or remove it by assigning `nil`
|
14
|
+
# @param text [String, nil]
|
15
|
+
def deprecation_reason=(text)
|
16
|
+
if text.nil?
|
17
|
+
remove_directive(GraphQL::Schema::Directive::Deprecated)
|
18
|
+
else
|
19
|
+
directive(GraphQL::Schema::Directive::Deprecated, reason: text)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Member
|
6
|
+
module HasDirectives
|
7
|
+
# Create an instance of `dir_class` for `self`, using `options`.
|
8
|
+
#
|
9
|
+
# It removes a previously-attached instance of `dir_class`, if there is one.
|
10
|
+
#
|
11
|
+
# @return [void]
|
12
|
+
def directive(dir_class, **options)
|
13
|
+
@own_directives ||= []
|
14
|
+
remove_directive(dir_class)
|
15
|
+
@own_directives << dir_class.new(self, **options)
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
# Remove an attached instance of `dir_class`, if there is one
|
20
|
+
# @param dir_class [Class<GraphQL::Schema::Directive>]
|
21
|
+
# @return [viod]
|
22
|
+
def remove_directive(dir_class)
|
23
|
+
@own_directives && @own_directives.reject! { |d| d.is_a?(dir_class) }
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
NO_DIRECTIVES = [].freeze
|
28
|
+
|
29
|
+
def directives
|
30
|
+
case self
|
31
|
+
when Class
|
32
|
+
inherited_directives = if superclass.respond_to?(:directives)
|
33
|
+
superclass.directives
|
34
|
+
else
|
35
|
+
NO_DIRECTIVES
|
36
|
+
end
|
37
|
+
if inherited_directives.any? && @own_directives
|
38
|
+
dirs = []
|
39
|
+
merge_directives(dirs, inherited_directives)
|
40
|
+
merge_directives(dirs, @own_directives)
|
41
|
+
dirs
|
42
|
+
elsif @own_directives
|
43
|
+
@own_directives
|
44
|
+
elsif inherited_directives.any?
|
45
|
+
inherited_directives
|
46
|
+
else
|
47
|
+
NO_DIRECTIVES
|
48
|
+
end
|
49
|
+
when Module
|
50
|
+
dirs = nil
|
51
|
+
self.ancestors.reverse_each do |ancestor|
|
52
|
+
if ancestor.respond_to?(:own_directives) &&
|
53
|
+
(anc_dirs = ancestor.own_directives).any?
|
54
|
+
dirs ||= []
|
55
|
+
merge_directives(dirs, anc_dirs)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
if own_directives
|
59
|
+
dirs ||= []
|
60
|
+
merge_directives(dirs, own_directives)
|
61
|
+
end
|
62
|
+
dirs || NO_DIRECTIVES
|
63
|
+
when HasDirectives
|
64
|
+
@own_directives || NO_DIRECTIVES
|
65
|
+
else
|
66
|
+
raise "Invariant: how could #{self} not be a Class, Module, or instance of HasDirectives?"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
def own_directives
|
73
|
+
@own_directives
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# Modify `target` by adding items from `dirs` such that:
|
79
|
+
# - Any name conflict is overriden by the incoming member of `dirs`
|
80
|
+
# - Any other member of `dirs` is appended
|
81
|
+
# @param target [Array<GraphQL::Schema::Directive>]
|
82
|
+
# @param dirs [Array<GraphQL::Schema::Directive>]
|
83
|
+
# @return [void]
|
84
|
+
def merge_directives(target, dirs)
|
85
|
+
dirs.each do |dir|
|
86
|
+
if (idx = target.find_index { |d| d.graphql_name == dir.graphql_name })
|
87
|
+
target.slice!(idx)
|
88
|
+
target.insert(idx, dir)
|
89
|
+
else
|
90
|
+
target << dir
|
91
|
+
end
|
92
|
+
end
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
class Schema
|
4
|
+
class Member
|
5
|
+
module HasValidators
|
6
|
+
include Schema::FindInheritedValue::EmptyObjects
|
7
|
+
|
8
|
+
# Build {GraphQL::Schema::Validator}s based on the given configuration
|
9
|
+
# and use them for this schema member
|
10
|
+
# @param validation_config [Hash{Symbol => Hash}]
|
11
|
+
# @return [void]
|
12
|
+
def validates(validation_config)
|
13
|
+
new_validators = GraphQL::Schema::Validator.from_config(self, validation_config)
|
14
|
+
@own_validators ||= []
|
15
|
+
@own_validators.concat(new_validators)
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Array<GraphQL::Schema::Validator>]
|
20
|
+
def validators
|
21
|
+
own_validators = @own_validators || EMPTY_ARRAY
|
22
|
+
if self.is_a?(Class) && superclass.respond_to?(:validators) && (inherited_validators = superclass.validators).any?
|
23
|
+
inherited_validators + own_validators
|
24
|
+
else
|
25
|
+
own_validators
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -4,8 +4,11 @@ require 'graphql/schema/member/base_dsl_methods'
|
|
4
4
|
require 'graphql/schema/member/cached_graphql_definition'
|
5
5
|
require 'graphql/schema/member/graphql_type_names'
|
6
6
|
require 'graphql/schema/member/has_ast_node'
|
7
|
+
require 'graphql/schema/member/has_directives'
|
8
|
+
require 'graphql/schema/member/has_deprecation_reason'
|
7
9
|
require 'graphql/schema/member/has_path'
|
8
10
|
require 'graphql/schema/member/has_unresolved_type_error'
|
11
|
+
require 'graphql/schema/member/has_validators'
|
9
12
|
require 'graphql/schema/member/relay_shortcuts'
|
10
13
|
require 'graphql/schema/member/scoped'
|
11
14
|
require 'graphql/schema/member/type_system_helpers'
|
@@ -30,6 +33,7 @@ module GraphQL
|
|
30
33
|
extend RelayShortcuts
|
31
34
|
extend HasPath
|
32
35
|
extend HasAstNode
|
36
|
+
extend HasDirectives
|
33
37
|
end
|
34
38
|
end
|
35
39
|
end
|
@@ -14,6 +14,17 @@ module GraphQL
|
|
14
14
|
# @return [GraphQL::Query::Context] the context instance for this query
|
15
15
|
attr_reader :context
|
16
16
|
|
17
|
+
# @return [GraphQL::Dataloader]
|
18
|
+
def dataloader
|
19
|
+
context.dataloader
|
20
|
+
end
|
21
|
+
|
22
|
+
# Call this in a field method to return a value that should be returned to the client
|
23
|
+
# without any further handling by GraphQL.
|
24
|
+
def raw_value(obj)
|
25
|
+
GraphQL::Execution::Interpreter::RawValue.new(obj)
|
26
|
+
end
|
27
|
+
|
17
28
|
class << self
|
18
29
|
# This is protected so that we can be sure callers use the public method, {.authorized_new}
|
19
30
|
# @see authorized_new to make instances
|
@@ -59,14 +59,15 @@ module GraphQL
|
|
59
59
|
|
60
60
|
# Return the GraphQL schema string for the introspection type system
|
61
61
|
def self.print_introspection_schema
|
62
|
-
query_root =
|
63
|
-
|
62
|
+
query_root = Class.new(GraphQL::Schema::Object) do
|
63
|
+
graphql_name "Root"
|
64
|
+
field :throwaway_field, String, null: true
|
64
65
|
end
|
65
|
-
schema = GraphQL::Schema
|
66
|
+
schema = Class.new(GraphQL::Schema) { query(query_root) }
|
66
67
|
|
67
68
|
introspection_schema_ast = GraphQL::Language::DocumentFromSchemaDefinition.new(
|
68
69
|
schema,
|
69
|
-
except: ->(member, _) { member.
|
70
|
+
except: ->(member, _) { member.graphql_name == "Root" },
|
70
71
|
include_introspection_types: true,
|
71
72
|
include_built_in_directives: true,
|
72
73
|
).document
|
@@ -24,6 +24,7 @@ module GraphQL
|
|
24
24
|
# Really we only need description from here, but:
|
25
25
|
extend Schema::Member::BaseDSLMethods
|
26
26
|
extend GraphQL::Schema::Member::HasArguments
|
27
|
+
extend GraphQL::Schema::Member::HasValidators
|
27
28
|
include Schema::Member::HasPath
|
28
29
|
extend Schema::Member::HasPath
|
29
30
|
|
@@ -49,6 +50,11 @@ module GraphQL
|
|
49
50
|
# @return [GraphQL::Query::Context]
|
50
51
|
attr_reader :context
|
51
52
|
|
53
|
+
# @return [GraphQL::Dataloader]
|
54
|
+
def dataloader
|
55
|
+
context.dataloader
|
56
|
+
end
|
57
|
+
|
52
58
|
# @return [GraphQL::Schema::Field]
|
53
59
|
attr_reader :field
|
54
60
|
|
@@ -80,6 +86,7 @@ module GraphQL
|
|
80
86
|
load_arguments_val = load_arguments(args)
|
81
87
|
context.schema.after_lazy(load_arguments_val) do |loaded_args|
|
82
88
|
@prepared_arguments = loaded_args
|
89
|
+
Schema::Validator.validate!(self.class.validators, object, context, loaded_args, as: @field)
|
83
90
|
# Then call `authorized?`, which may raise or may return a lazy object
|
84
91
|
authorized_val = if loaded_args.any?
|
85
92
|
authorized?(**loaded_args)
|
@@ -25,6 +25,22 @@ module GraphQL
|
|
25
25
|
@mode = context.query.subscription_update? ? :update : :subscribe
|
26
26
|
end
|
27
27
|
|
28
|
+
def resolve_with_support(**args)
|
29
|
+
result = nil
|
30
|
+
unsubscribed = true
|
31
|
+
catch :graphql_subscription_unsubscribed do
|
32
|
+
result = super
|
33
|
+
unsubscribed = false
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
if unsubscribed
|
38
|
+
context.skip
|
39
|
+
else
|
40
|
+
result
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
28
44
|
# Implement the {Resolve} API
|
29
45
|
def resolve(**args)
|
30
46
|
# Dispatch based on `@mode`, which will raise a `NoMethodError` if we ever
|
@@ -55,7 +71,8 @@ module GraphQL
|
|
55
71
|
def resolve_update(**args)
|
56
72
|
ret_val = args.any? ? update(**args) : update
|
57
73
|
if ret_val == :no_update
|
58
|
-
|
74
|
+
context.namespace(:subscriptions)[:no_update] = true
|
75
|
+
context.skip
|
59
76
|
else
|
60
77
|
ret_val
|
61
78
|
end
|
@@ -80,6 +97,7 @@ module GraphQL
|
|
80
97
|
|
81
98
|
# Call this to halt execution and remove this subscription from the system
|
82
99
|
def unsubscribe
|
100
|
+
context.namespace(:subscriptions)[:unsubscribed] = true
|
83
101
|
throw :graphql_subscription_unsubscribed
|
84
102
|
end
|
85
103
|
|
@@ -23,6 +23,8 @@ module GraphQL
|
|
23
23
|
# Bugsnag.notify(timeout_error, {query_string: query_ctx.query.query_string})
|
24
24
|
# end
|
25
25
|
#
|
26
|
+
# @api deprecated
|
27
|
+
# @see Schema::Timeout
|
26
28
|
class TimeoutMiddleware
|
27
29
|
# @param max_seconds [Numeric] how many seconds the query should be allowed to resolve new fields
|
28
30
|
def initialize(max_seconds:, context_key: nil, &block)
|
@@ -6,6 +6,8 @@ module GraphQL
|
|
6
6
|
# Its {RULES} contain objects that respond to `#call(type)`. Rules are
|
7
7
|
# looked up for given types (by class ancestry), then applied to
|
8
8
|
# the object until an error is returned.
|
9
|
+
#
|
10
|
+
# Remove this in GraphQL-Ruby 2.0 when schema instances are removed.
|
9
11
|
class Validation
|
10
12
|
# Lookup the rules for `object` based on its class,
|
11
13
|
# Then returns an error message or `nil`
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Validator
|
6
|
+
# Use this to specifically reject values from an argument.
|
7
|
+
#
|
8
|
+
# @example disallow certain values
|
9
|
+
#
|
10
|
+
# argument :favorite_non_prime, Integer, required: true,
|
11
|
+
# validates: { exclusion: { in: [2, 3, 5, 7, ... ]} }
|
12
|
+
#
|
13
|
+
class ExclusionValidator < Validator
|
14
|
+
# @param message [String]
|
15
|
+
# @param in [Array] The values to reject
|
16
|
+
def initialize(message: "%{validated} is reserved", in:, **default_options)
|
17
|
+
# `in` is a reserved word, so work around that
|
18
|
+
@in_list = binding.local_variable_get(:in)
|
19
|
+
@message = message
|
20
|
+
super(**default_options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def validate(_object, _context, value)
|
24
|
+
if @in_list.include?(value)
|
25
|
+
@message
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Validator
|
6
|
+
# Use this to assert that string values match (or don't match) the given RegExp.
|
7
|
+
#
|
8
|
+
# @example requiring input to match a pattern
|
9
|
+
#
|
10
|
+
# argument :handle, String, required: true,
|
11
|
+
# validates: { format: { with: /\A[a-z0-9_]+\Z/ } }
|
12
|
+
#
|
13
|
+
# @example reject inputs that match a pattern
|
14
|
+
#
|
15
|
+
# argument :word_that_doesnt_begin_with_a_vowel, String, required: true,
|
16
|
+
# validates: { format: { without: /\A[aeiou]/ } }
|
17
|
+
#
|
18
|
+
# # It's pretty hard to come up with a legitimate use case for `without:`
|
19
|
+
#
|
20
|
+
class FormatValidator < Validator
|
21
|
+
if !String.method_defined?(:match?)
|
22
|
+
using GraphQL::StringMatchBackport
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param with [RegExp, nil]
|
26
|
+
# @param without [Regexp, nil]
|
27
|
+
# @param message [String]
|
28
|
+
def initialize(
|
29
|
+
with: nil,
|
30
|
+
without: nil,
|
31
|
+
message: "%{validated} is invalid",
|
32
|
+
**default_options
|
33
|
+
)
|
34
|
+
@with_pattern = with
|
35
|
+
@without_pattern = without
|
36
|
+
@message = message
|
37
|
+
super(**default_options)
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate(_object, _context, value)
|
41
|
+
if (@with_pattern && !value.match?(@with_pattern)) ||
|
42
|
+
(@without_pattern && value.match?(@without_pattern))
|
43
|
+
@message
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Validator
|
6
|
+
# You can use this to allow certain values for an argument.
|
7
|
+
#
|
8
|
+
# Usually, a {GraphQL::Schema::Enum} is better for this, because it's self-documenting.
|
9
|
+
#
|
10
|
+
# @example only allow certain values for an argument
|
11
|
+
#
|
12
|
+
# argument :favorite_prime, Integer, required: true,
|
13
|
+
# validates: { inclusion: { in: [2, 3, 5, 7, 11, ... ] } }
|
14
|
+
#
|
15
|
+
class InclusionValidator < Validator
|
16
|
+
# @param message [String]
|
17
|
+
# @param in [Array] The values to allow
|
18
|
+
def initialize(in:, message: "%{validated} is not included in the list", **default_options)
|
19
|
+
# `in` is a reserved word, so work around that
|
20
|
+
@in_list = binding.local_variable_get(:in)
|
21
|
+
@message = message
|
22
|
+
super(**default_options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate(_object, _context, value)
|
26
|
+
if !@in_list.include?(value)
|
27
|
+
@message
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|