graphql 1.12.18 → 1.12.24
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/object_generator.rb +2 -1
- data/lib/generators/graphql/relay.rb +19 -11
- data/lib/generators/graphql/templates/schema.erb +14 -2
- data/lib/graphql/analysis/ast/field_usage.rb +6 -2
- data/lib/graphql/dataloader/source.rb +30 -2
- data/lib/graphql/deprecation.rb +1 -5
- data/lib/graphql/integer_encoding_error.rb +18 -2
- data/lib/graphql/language/nodes.rb +10 -1
- data/lib/graphql/pagination/connections.rb +35 -16
- data/lib/graphql/query/validation_pipeline.rb +1 -1
- data/lib/graphql/schema/directive.rb +4 -0
- data/lib/graphql/schema/member/has_arguments.rb +7 -1
- data/lib/graphql/schema/resolver.rb +10 -0
- data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
- data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
- data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
- data/lib/graphql/schema/validator/format_validator.rb +4 -1
- data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
- data/lib/graphql/schema/validator/length_validator.rb +5 -3
- data/lib/graphql/schema/validator/numericality_validator.rb +7 -1
- data/lib/graphql/schema/validator.rb +36 -25
- data/lib/graphql/static_validation/error.rb +3 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +51 -25
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +2 -0
- data/lib/graphql/static_validation/validation_context.rb +2 -1
- data/lib/graphql/string_encoding_error.rb +13 -3
- data/lib/graphql/subscriptions/event.rb +46 -2
- data/lib/graphql/types/int.rb +1 -1
- data/lib/graphql/types/string.rb +1 -1
- data/lib/graphql/unauthorized_error.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fee883bb45ad2391215f85a31ceec2f69b19c07c138708f002643c2fdf4c8a19
|
4
|
+
data.tar.gz: 925fb0e6f0acbb8ceddb79ad2eec4e8cfcd73bdf5cb51ad708dd1d7ebb491e2d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 480aba657ba456d83bc543132f730b56444068a8a14c054c5f7f98a3d7c87179d23d843cc862b4ba8388bdf020fcb3f231c4513a93c824cc7ce15e61a9160c67
|
7
|
+
data.tar.gz: 5744a84a14e922b2b26984986351cab87e41da464b985b1b28d53c7c42a921b5dd53cadca09c8a1679a2af1f7ee40dd42894e32bee373b88c8c2fe6fa917a1e9
|
@@ -12,7 +12,8 @@ module Graphql
|
|
12
12
|
#
|
13
13
|
# Add the Node interface with `--node`.
|
14
14
|
class ObjectGenerator < TypeGeneratorBase
|
15
|
-
desc "Create a GraphQL::ObjectType with the given name and fields"
|
15
|
+
desc "Create a GraphQL::ObjectType with the given name and fields." \
|
16
|
+
"If the given type name matches an existing ActiveRecord model, the generated type will automatically include fields for the models database columns."
|
16
17
|
source_root File.expand_path('../templates', __FILE__)
|
17
18
|
|
18
19
|
argument :custom_fields,
|
@@ -32,20 +32,28 @@ module Graphql
|
|
32
32
|
|
33
33
|
# Return a string UUID for `object`
|
34
34
|
def self.id_from_object(object, type_definition, query_ctx)
|
35
|
-
#
|
36
|
-
|
37
|
-
#
|
38
|
-
|
35
|
+
# For example, use Rails' GlobalID library (https://github.com/rails/globalid):
|
36
|
+
object_id = object.to_global_id.to_s
|
37
|
+
# Remove this redundant prefix to make IDs shorter:
|
38
|
+
object_id = object_id.sub("gid://\#{GlobalID.app}/", "")
|
39
|
+
encoded_id = Base64.urlsafe_encode64(object_id)
|
40
|
+
# Remove the "=" padding
|
41
|
+
encoded_id = encoded_id.sub(/=+/, "")
|
42
|
+
# Add a type hint
|
43
|
+
type_hint = type_definition.graphql_name.first
|
44
|
+
"\#{type_hint}_\#{encoded_id}"
|
39
45
|
end
|
40
46
|
|
41
47
|
# Given a string UUID, find the object
|
42
|
-
def self.object_from_id(
|
43
|
-
# For example,
|
44
|
-
#
|
45
|
-
|
46
|
-
#
|
47
|
-
|
48
|
-
#
|
48
|
+
def self.object_from_id(encoded_id_with_hint, query_ctx)
|
49
|
+
# For example, use Rails' GlobalID library (https://github.com/rails/globalid):
|
50
|
+
# Split off the type hint
|
51
|
+
_type_hint, encoded_id = encoded_id_with_hint.split("_", 2)
|
52
|
+
# Decode the ID
|
53
|
+
id = Base64.urlsafe_decode64(encoded_id)
|
54
|
+
# Rebuild it for Rails then find the object:
|
55
|
+
full_global_id = "gid://\#{GlobalID.app}/\#{id}"
|
56
|
+
GlobalID::Locator.locate(full_global_id)
|
49
57
|
end
|
50
58
|
RUBY
|
51
59
|
inject_into_file schema_file_path, schema_code, before: /^end\n/m, force: false
|
@@ -4,11 +4,23 @@ class <%= schema_name %> < GraphQL::Schema
|
|
4
4
|
<% if options[:batch] %>
|
5
5
|
# GraphQL::Batch setup:
|
6
6
|
use GraphQL::Batch
|
7
|
+
<% else %>
|
8
|
+
# For batch-loading (see https://graphql-ruby.org/dataloader/overview.html)
|
9
|
+
use GraphQL::Dataloader
|
7
10
|
<% end %>
|
11
|
+
# GraphQL-Ruby calls this when something goes wrong while running a query:
|
12
|
+
def self.type_error(err, context)
|
13
|
+
# if err.is_a?(GraphQL::InvalidNullError)
|
14
|
+
# # report to your bug tracker here
|
15
|
+
# return nil
|
16
|
+
# end
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
8
20
|
# Union and Interface Resolution
|
9
21
|
def self.resolve_type(abstract_type, obj, ctx)
|
10
|
-
# TODO: Implement this
|
11
|
-
# to return the correct object type for `obj`
|
22
|
+
# TODO: Implement this method
|
23
|
+
# to return the correct GraphQL object type for `obj`
|
12
24
|
raise(GraphQL::RequiredImplementationMissingError)
|
13
25
|
end
|
14
26
|
end
|
@@ -15,8 +15,12 @@ module GraphQL
|
|
15
15
|
field = "#{visitor.parent_type_definition.graphql_name}.#{field_defn.graphql_name}"
|
16
16
|
@used_fields << field
|
17
17
|
@used_deprecated_fields << field if field_defn.deprecation_reason
|
18
|
-
|
19
|
-
|
18
|
+
arguments = visitor.query.arguments_for(node, visitor.field_definition)
|
19
|
+
# If there was an error when preparing this argument object,
|
20
|
+
# then this might be an error or something:
|
21
|
+
if arguments.respond_to?(:argument_values)
|
22
|
+
extract_deprecated_arguments(arguments.argument_values)
|
23
|
+
end
|
20
24
|
end
|
21
25
|
|
22
26
|
def result
|
@@ -6,7 +6,11 @@ module GraphQL
|
|
6
6
|
# Called by {Dataloader} to prepare the {Source}'s internal state
|
7
7
|
# @api private
|
8
8
|
def setup(dataloader)
|
9
|
+
# These keys have been requested but haven't been fetched yet
|
9
10
|
@pending_keys = []
|
11
|
+
# These keys have been passed to `fetch` but haven't been finished yet
|
12
|
+
@fetching_keys = []
|
13
|
+
# { key => result }
|
10
14
|
@results = {}
|
11
15
|
@dataloader = dataloader
|
12
16
|
end
|
@@ -64,29 +68,46 @@ module GraphQL
|
|
64
68
|
# Then run the batch and update the cache.
|
65
69
|
# @return [void]
|
66
70
|
def sync
|
71
|
+
pending_keys = @pending_keys.dup
|
67
72
|
@dataloader.yield
|
73
|
+
iterations = 0
|
74
|
+
while pending_keys.any? { |k| !@results.key?(k) }
|
75
|
+
iterations += 1
|
76
|
+
if iterations > 1000
|
77
|
+
raise "#{self.class}#sync tried 1000 times to load pending keys (#{pending_keys}), but they still weren't loaded. There is likely a circular dependency."
|
78
|
+
end
|
79
|
+
@dataloader.yield
|
80
|
+
end
|
81
|
+
nil
|
68
82
|
end
|
69
83
|
|
70
84
|
# @return [Boolean] True if this source has any pending requests for data.
|
71
85
|
def pending?
|
72
|
-
|
86
|
+
!@pending_keys.empty?
|
73
87
|
end
|
74
88
|
|
75
89
|
# Called by {GraphQL::Dataloader} to resolve and pending requests to this source.
|
76
90
|
# @api private
|
77
91
|
# @return [void]
|
78
92
|
def run_pending_keys
|
93
|
+
if !@fetching_keys.empty?
|
94
|
+
@pending_keys -= @fetching_keys
|
95
|
+
end
|
79
96
|
return if @pending_keys.empty?
|
80
97
|
fetch_keys = @pending_keys.uniq
|
98
|
+
@fetching_keys.concat(fetch_keys)
|
81
99
|
@pending_keys = []
|
82
100
|
results = fetch(fetch_keys)
|
83
101
|
fetch_keys.each_with_index do |key, idx|
|
84
102
|
@results[key] = results[idx]
|
85
103
|
end
|
104
|
+
nil
|
86
105
|
rescue StandardError => error
|
87
106
|
fetch_keys.each { |key| @results[key] = error }
|
88
107
|
ensure
|
89
|
-
|
108
|
+
if fetch_keys
|
109
|
+
@fetching_keys -= fetch_keys
|
110
|
+
end
|
90
111
|
end
|
91
112
|
|
92
113
|
# These arguments are given to `dataloader.with(source_class, ...)`. The object
|
@@ -116,6 +137,13 @@ module GraphQL
|
|
116
137
|
# @return [Object] The result from {#fetch} for `key`.
|
117
138
|
# @api private
|
118
139
|
def result_for(key)
|
140
|
+
if !@results.key?(key)
|
141
|
+
raise <<-ERR
|
142
|
+
Invariant: fetching result for a key on #{self.class} that hasn't been loaded yet (#{key.inspect}, loaded: #{@results.keys})
|
143
|
+
|
144
|
+
This key should have been loaded already. This is a bug in GraphQL::Dataloader, please report it on GitHub: https://github.com/rmosolgo/graphql-ruby/issues/new.
|
145
|
+
ERR
|
146
|
+
end
|
119
147
|
result = @results[key]
|
120
148
|
|
121
149
|
raise result if result.class <= StandardError
|
data/lib/graphql/deprecation.rb
CHANGED
@@ -12,9 +12,25 @@ module GraphQL
|
|
12
12
|
# The value which couldn't be encoded
|
13
13
|
attr_reader :integer_value
|
14
14
|
|
15
|
-
|
15
|
+
# @return [GraphQL::Schema::Field] The field that returned a too-big integer
|
16
|
+
attr_reader :field
|
17
|
+
|
18
|
+
# @return [Array<String, Integer>] Where the field appeared in the GraphQL response
|
19
|
+
attr_reader :path
|
20
|
+
|
21
|
+
def initialize(value, context:)
|
16
22
|
@integer_value = value
|
17
|
-
|
23
|
+
@field = context[:current_field]
|
24
|
+
@path = context[:current_path]
|
25
|
+
message = "Integer out of bounds: #{value}".dup
|
26
|
+
if @path
|
27
|
+
message << " @ #{@path.join(".")}"
|
28
|
+
end
|
29
|
+
if @field
|
30
|
+
message << " (#{@field.path})"
|
31
|
+
end
|
32
|
+
message << ". Consider using ID or GraphQL::Types::BigInt instead."
|
33
|
+
super(message)
|
18
34
|
end
|
19
35
|
end
|
20
36
|
end
|
@@ -197,7 +197,16 @@ module GraphQL
|
|
197
197
|
else
|
198
198
|
module_eval <<-RUBY, __FILE__, __LINE__
|
199
199
|
def children
|
200
|
-
@children ||=
|
200
|
+
@children ||= begin
|
201
|
+
if #{children_of_type.keys.map { |k| "@#{k}.any?" }.join(" || ")}
|
202
|
+
new_children = []
|
203
|
+
#{children_of_type.keys.map { |k| "new_children.concat(@#{k})" }.join("; ")}
|
204
|
+
new_children.freeze
|
205
|
+
new_children
|
206
|
+
else
|
207
|
+
NO_CHILDREN
|
208
|
+
end
|
209
|
+
end
|
201
210
|
end
|
202
211
|
RUBY
|
203
212
|
end
|
@@ -70,23 +70,42 @@ module GraphQL
|
|
70
70
|
wrappers = context ? context.namespace(:connections)[:all_wrappers] : all_wrappers
|
71
71
|
impl = wrapper_for(items, wrappers: wrappers)
|
72
72
|
|
73
|
-
if impl
|
74
|
-
|
73
|
+
if impl
|
74
|
+
impl.new(
|
75
|
+
items,
|
76
|
+
context: context,
|
77
|
+
parent: parent,
|
78
|
+
field: field,
|
79
|
+
max_page_size: field.has_max_page_size? ? field.max_page_size : context.schema.default_max_page_size,
|
80
|
+
first: arguments[:first],
|
81
|
+
after: arguments[:after],
|
82
|
+
last: arguments[:last],
|
83
|
+
before: arguments[:before],
|
84
|
+
arguments: arguments,
|
85
|
+
edge_class: edge_class_for_field(field),
|
86
|
+
)
|
87
|
+
else
|
88
|
+
begin
|
89
|
+
connection_class = GraphQL::Relay::BaseConnection.connection_for_nodes(items)
|
90
|
+
if parent.is_a?(GraphQL::Schema::Object)
|
91
|
+
parent = parent.object
|
92
|
+
end
|
93
|
+
connection_class.new(
|
94
|
+
items,
|
95
|
+
arguments,
|
96
|
+
field: field,
|
97
|
+
max_page_size: field.max_page_size,
|
98
|
+
parent: parent,
|
99
|
+
context: context,
|
100
|
+
)
|
101
|
+
rescue RuntimeError => err
|
102
|
+
if err.message.include?("No connection implementation to wrap")
|
103
|
+
raise ImplementationMissingError, "Couldn't find a connection wrapper for #{items.class} during #{field.path} (#{items.inspect})"
|
104
|
+
else
|
105
|
+
raise err
|
106
|
+
end
|
107
|
+
end
|
75
108
|
end
|
76
|
-
|
77
|
-
impl.new(
|
78
|
-
items,
|
79
|
-
context: context,
|
80
|
-
parent: parent,
|
81
|
-
field: field,
|
82
|
-
max_page_size: field.has_max_page_size? ? field.max_page_size : context.schema.default_max_page_size,
|
83
|
-
first: arguments[:first],
|
84
|
-
after: arguments[:after],
|
85
|
-
last: arguments[:last],
|
86
|
-
before: arguments[:before],
|
87
|
-
arguments: arguments,
|
88
|
-
edge_class: edge_class_for_field(field),
|
89
|
-
)
|
90
109
|
end
|
91
110
|
|
92
111
|
# use an override if there is one
|
@@ -72,7 +72,7 @@ module GraphQL
|
|
72
72
|
elsif @operation_name_error
|
73
73
|
@validation_errors << @operation_name_error
|
74
74
|
else
|
75
|
-
validation_result = @schema.static_validator.validate(@query, validate: @validate, timeout: @schema.validate_timeout)
|
75
|
+
validation_result = @schema.static_validator.validate(@query, validate: @validate, timeout: @schema.validate_timeout, max_errors: @schema.validate_max_errors)
|
76
76
|
@validation_errors.concat(validation_result[:errors])
|
77
77
|
@internal_representation = validation_result[:irep]
|
78
78
|
|
@@ -254,11 +254,17 @@ module GraphQL
|
|
254
254
|
if authed
|
255
255
|
application_object
|
256
256
|
else
|
257
|
-
|
257
|
+
err = GraphQL::UnauthorizedError.new(
|
258
258
|
object: application_object,
|
259
259
|
type: class_based_type,
|
260
260
|
context: context,
|
261
261
|
)
|
262
|
+
if self.respond_to?(:unauthorized_object)
|
263
|
+
err.set_backtrace(caller)
|
264
|
+
unauthorized_object(err)
|
265
|
+
else
|
266
|
+
raise err
|
267
|
+
end
|
262
268
|
end
|
263
269
|
end
|
264
270
|
else
|
@@ -160,6 +160,16 @@ module GraphQL
|
|
160
160
|
end
|
161
161
|
end
|
162
162
|
|
163
|
+
# Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type.
|
164
|
+
#
|
165
|
+
# By default, the error is re-raised and passed along to {{Schema.unauthorized_object}}.
|
166
|
+
#
|
167
|
+
# Any value returned here will be used _instead of_ of the loaded object.
|
168
|
+
# @param err [GraphQL::UnauthorizedError]
|
169
|
+
def unauthorized_object(err)
|
170
|
+
raise err
|
171
|
+
end
|
172
|
+
|
163
173
|
private
|
164
174
|
|
165
175
|
def load_arguments(args)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Validator
|
6
|
+
# Use this to specifically reject values that respond to `.blank?` and respond truthy for that method.
|
7
|
+
#
|
8
|
+
# @example Require a non-empty string for an argument
|
9
|
+
# argument :name, String, required: true, validate: { allow_blank: false }
|
10
|
+
class AllowBlankValidator < Validator
|
11
|
+
def initialize(allow_blank_positional, allow_blank: nil, message: "%{validated} can't be blank", **default_options)
|
12
|
+
@message = message
|
13
|
+
super(**default_options)
|
14
|
+
@allow_blank = allow_blank.nil? ? allow_blank_positional : allow_blank
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate(_object, _context, value)
|
18
|
+
if value.respond_to?(:blank?) && value.blank?
|
19
|
+
if (value.nil? && @allow_null) || @allow_blank
|
20
|
+
# pass
|
21
|
+
else
|
22
|
+
@message
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Validator
|
6
|
+
# Use this to specifically reject or permit `nil` values (given as `null` from GraphQL).
|
7
|
+
#
|
8
|
+
# @example require a non-null value for an argument if it is provided
|
9
|
+
# argument :name, String, required: false, validates: { allow_null: false }
|
10
|
+
class AllowNullValidator < Validator
|
11
|
+
MESSAGE = "%{validated} can't be null"
|
12
|
+
def initialize(allow_null_positional, allow_null: nil, message: MESSAGE, **default_options)
|
13
|
+
@message = message
|
14
|
+
super(**default_options)
|
15
|
+
@allow_null = allow_null.nil? ? allow_null_positional : allow_null
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate(_object, _context, value)
|
19
|
+
if value.nil? && !@allow_null
|
20
|
+
@message
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -38,7 +38,10 @@ module GraphQL
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def validate(_object, _context, value)
|
41
|
-
if (
|
41
|
+
if permitted_empty_value?(value)
|
42
|
+
# Do nothing
|
43
|
+
elsif value.nil? ||
|
44
|
+
(@with_pattern && !value.match?(@with_pattern)) ||
|
42
45
|
(@without_pattern && value.match?(@without_pattern))
|
43
46
|
@message
|
44
47
|
end
|
@@ -43,11 +43,13 @@ module GraphQL
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def validate(_object, _context, value)
|
46
|
-
if
|
46
|
+
return if permitted_empty_value?(value) # pass in this case
|
47
|
+
length = value.nil? ? 0 : value.length
|
48
|
+
if @maximum && length > @maximum
|
47
49
|
partial_format(@too_long, { count: @maximum })
|
48
|
-
elsif @minimum &&
|
50
|
+
elsif @minimum && length < @minimum
|
49
51
|
partial_format(@too_short, { count: @minimum })
|
50
|
-
elsif @is &&
|
52
|
+
elsif @is && length != @is
|
51
53
|
partial_format(@wrong_length, { count: @is })
|
52
54
|
end
|
53
55
|
end
|
@@ -32,6 +32,7 @@ module GraphQL
|
|
32
32
|
equal_to: nil, other_than: nil,
|
33
33
|
odd: nil, even: nil, within: nil,
|
34
34
|
message: "%{validated} must be %{comparison} %{target}",
|
35
|
+
null_message: Validator::AllowNullValidator::MESSAGE,
|
35
36
|
**default_options
|
36
37
|
)
|
37
38
|
|
@@ -45,11 +46,16 @@ module GraphQL
|
|
45
46
|
@even = even
|
46
47
|
@within = within
|
47
48
|
@message = message
|
49
|
+
@null_message = null_message
|
48
50
|
super(**default_options)
|
49
51
|
end
|
50
52
|
|
51
53
|
def validate(object, context, value)
|
52
|
-
if
|
54
|
+
if permitted_empty_value?(value)
|
55
|
+
# pass in this case
|
56
|
+
elsif value.nil? # @allow_null is handled in the parent class
|
57
|
+
@null_message
|
58
|
+
elsif @greater_than && value <= @greater_than
|
53
59
|
partial_format(@message, { comparison: "greater than", target: @greater_than })
|
54
60
|
elsif @greater_than_or_equal_to && value < @greater_than_or_equal_to
|
55
61
|
partial_format(@message, { comparison: "greater than or equal to", target: @greater_than_or_equal_to })
|
@@ -7,7 +7,6 @@ module GraphQL
|
|
7
7
|
# @return [GraphQL::Schema::Argument, GraphQL::Schema::Field, GraphQL::Schema::Resolver, Class<GraphQL::Schema::InputObject>]
|
8
8
|
attr_reader :validated
|
9
9
|
|
10
|
-
# TODO should this implement `if:` and `unless:` ?
|
11
10
|
# @param validated [GraphQL::Schema::Argument, GraphQL::Schema::Field, GraphQL::Schema::Resolver, Class<GraphQL::Schema::InputObject>] The argument or argument owner this validator is attached to
|
12
11
|
# @param allow_blank [Boolean] if `true`, then objects that respond to `.blank?` and return true for `.blank?` will skip this validation
|
13
12
|
# @param allow_null [Boolean] if `true`, then incoming `null`s will skip this validation
|
@@ -25,26 +24,6 @@ module GraphQL
|
|
25
24
|
raise GraphQL::RequiredImplementationMissingError, "Validator classes should implement #validate"
|
26
25
|
end
|
27
26
|
|
28
|
-
# This is called by the validation system and eventually calls {#validate}.
|
29
|
-
# @api private
|
30
|
-
def apply(object, context, value)
|
31
|
-
if value.nil?
|
32
|
-
if @allow_null
|
33
|
-
nil # skip this
|
34
|
-
else
|
35
|
-
"%{validated} can't be null"
|
36
|
-
end
|
37
|
-
elsif value.respond_to?(:blank?) && value.blank?
|
38
|
-
if @allow_blank
|
39
|
-
nil # skip this
|
40
|
-
else
|
41
|
-
"%{validated} can't be blank"
|
42
|
-
end
|
43
|
-
else
|
44
|
-
validate(object, context, value)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
27
|
# This is like `String#%`, but it supports the case that only some of `string`'s
|
49
28
|
# values are present in `substitutions`
|
50
29
|
def partial_format(string, substitutions)
|
@@ -55,6 +34,12 @@ module GraphQL
|
|
55
34
|
string
|
56
35
|
end
|
57
36
|
|
37
|
+
# @return [Boolean] `true` if `value` is `nil` and this validator has `allow_null: true` or if value is `.blank?` and this validator has `allow_blank: true`
|
38
|
+
def permitted_empty_value?(value)
|
39
|
+
(value.nil? && @allow_null) ||
|
40
|
+
(@allow_blank && value.respond_to?(:blank?) && value.blank?)
|
41
|
+
end
|
42
|
+
|
58
43
|
# @param schema_member [GraphQL::Schema::Field, GraphQL::Schema::Argument, Class<GraphQL::Schema::InputObject>]
|
59
44
|
# @param validates_hash [Hash{Symbol => Hash}, Hash{Class => Hash} nil] A configuration passed as `validates:`
|
60
45
|
# @return [Array<Validator>]
|
@@ -62,6 +47,24 @@ module GraphQL
|
|
62
47
|
if validates_hash.nil? || validates_hash.empty?
|
63
48
|
EMPTY_ARRAY
|
64
49
|
else
|
50
|
+
validates_hash = validates_hash.dup
|
51
|
+
allow_null = validates_hash.delete(:allow_null)
|
52
|
+
allow_blank = validates_hash.delete(:allow_blank)
|
53
|
+
|
54
|
+
# This could be {...}.compact on Ruby 2.4+
|
55
|
+
default_options = {}
|
56
|
+
if !allow_null.nil?
|
57
|
+
default_options[:allow_null] = allow_null
|
58
|
+
end
|
59
|
+
if !allow_blank.nil?
|
60
|
+
default_options[:allow_blank] = allow_blank
|
61
|
+
end
|
62
|
+
|
63
|
+
# allow_nil or allow_blank are the _only_ validations:
|
64
|
+
if validates_hash.empty?
|
65
|
+
validates_hash = default_options
|
66
|
+
end
|
67
|
+
|
65
68
|
validates_hash.map do |validator_name, options|
|
66
69
|
validator_class = case validator_name
|
67
70
|
when Class
|
@@ -69,7 +72,11 @@ module GraphQL
|
|
69
72
|
else
|
70
73
|
all_validators[validator_name] || raise(ArgumentError, "unknown validation: #{validator_name.inspect}")
|
71
74
|
end
|
72
|
-
|
75
|
+
if options.is_a?(Hash)
|
76
|
+
validator_class.new(validated: schema_member, **(default_options.merge(options)))
|
77
|
+
else
|
78
|
+
validator_class.new(options, validated: schema_member, **default_options)
|
79
|
+
end
|
73
80
|
end
|
74
81
|
end
|
75
82
|
end
|
@@ -122,10 +129,10 @@ module GraphQL
|
|
122
129
|
|
123
130
|
validators.each do |validator|
|
124
131
|
validated = as || validator.validated
|
125
|
-
errors = validator.
|
132
|
+
errors = validator.validate(object, context, value)
|
126
133
|
if errors &&
|
127
|
-
|
128
|
-
|
134
|
+
(errors.is_a?(Array) && errors != EMPTY_ARRAY) ||
|
135
|
+
(errors.is_a?(String))
|
129
136
|
if all_errors.frozen? # It's empty
|
130
137
|
all_errors = []
|
131
138
|
end
|
@@ -161,3 +168,7 @@ require "graphql/schema/validator/exclusion_validator"
|
|
161
168
|
GraphQL::Schema::Validator.install(:exclusion, GraphQL::Schema::Validator::ExclusionValidator)
|
162
169
|
require "graphql/schema/validator/required_validator"
|
163
170
|
GraphQL::Schema::Validator.install(:required, GraphQL::Schema::Validator::RequiredValidator)
|
171
|
+
require "graphql/schema/validator/allow_null_validator"
|
172
|
+
GraphQL::Schema::Validator.install(:allow_null, GraphQL::Schema::Validator::AllowNullValidator)
|
173
|
+
require "graphql/schema/validator/allow_blank_validator"
|
174
|
+
GraphQL::Schema::Validator.install(:allow_blank, GraphQL::Schema::Validator::AllowBlankValidator)
|
@@ -10,6 +10,7 @@ module GraphQL
|
|
10
10
|
#
|
11
11
|
# Original Algorithm: https://github.com/graphql/graphql-js/blob/master/src/validation/rules/OverlappingFieldsCanBeMerged.js
|
12
12
|
NO_ARGS = {}.freeze
|
13
|
+
|
13
14
|
Field = Struct.new(:node, :definition, :owner_type, :parents)
|
14
15
|
FragmentSpread = Struct.new(:name, :parents)
|
15
16
|
|
@@ -17,24 +18,47 @@ module GraphQL
|
|
17
18
|
super
|
18
19
|
@visited_fragments = {}
|
19
20
|
@compared_fragments = {}
|
21
|
+
@conflict_count = 0
|
20
22
|
end
|
21
23
|
|
22
24
|
def on_operation_definition(node, _parent)
|
23
|
-
conflicts_within_selection_set(node, type_definition)
|
25
|
+
setting_errors { conflicts_within_selection_set(node, type_definition) }
|
24
26
|
super
|
25
27
|
end
|
26
28
|
|
27
29
|
def on_field(node, _parent)
|
28
|
-
conflicts_within_selection_set(node, type_definition)
|
30
|
+
setting_errors { conflicts_within_selection_set(node, type_definition) }
|
29
31
|
super
|
30
32
|
end
|
31
33
|
|
32
34
|
private
|
33
35
|
|
36
|
+
def field_conflicts
|
37
|
+
@field_conflicts ||= Hash.new do |errors, field|
|
38
|
+
errors[field] = GraphQL::StaticValidation::FieldsWillMergeError.new(kind: :field, field_name: field)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def arg_conflicts
|
43
|
+
@arg_conflicts ||= Hash.new do |errors, field|
|
44
|
+
errors[field] = GraphQL::StaticValidation::FieldsWillMergeError.new(kind: :argument, field_name: field)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def setting_errors
|
49
|
+
@field_conflicts = nil
|
50
|
+
@arg_conflicts = nil
|
51
|
+
|
52
|
+
yield
|
53
|
+
# don't initialize these if they weren't initialized in the block:
|
54
|
+
@field_conflicts && @field_conflicts.each_value { |error| add_error(error) }
|
55
|
+
@arg_conflicts && @arg_conflicts.each_value { |error| add_error(error) }
|
56
|
+
end
|
57
|
+
|
34
58
|
def conflicts_within_selection_set(node, parent_type)
|
35
59
|
return if parent_type.nil?
|
36
60
|
|
37
|
-
fields, fragment_spreads = fields_and_fragments_from_selection(node, owner_type: parent_type, parents:
|
61
|
+
fields, fragment_spreads = fields_and_fragments_from_selection(node, owner_type: parent_type, parents: nil)
|
38
62
|
|
39
63
|
# (A) Find find all conflicts "within" the fields of this selection set.
|
40
64
|
find_conflicts_within(fields)
|
@@ -174,15 +198,21 @@ module GraphQL
|
|
174
198
|
response_keys.each do |key, fields|
|
175
199
|
next if fields.size < 2
|
176
200
|
# find conflicts within nodes
|
177
|
-
|
178
|
-
|
201
|
+
i = 0
|
202
|
+
while i < fields.size
|
203
|
+
j = i + 1
|
204
|
+
while j < fields.size
|
179
205
|
find_conflict(key, fields[i], fields[j])
|
206
|
+
j += 1
|
180
207
|
end
|
208
|
+
i += 1
|
181
209
|
end
|
182
210
|
end
|
183
211
|
end
|
184
212
|
|
185
213
|
def find_conflict(response_key, field1, field2, mutually_exclusive: false)
|
214
|
+
return if @conflict_count >= context.max_errors
|
215
|
+
|
186
216
|
node1 = field1.node
|
187
217
|
node2 = field2.node
|
188
218
|
|
@@ -191,28 +221,21 @@ module GraphQL
|
|
191
221
|
|
192
222
|
if !are_mutually_exclusive
|
193
223
|
if node1.name != node2.name
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
field_name: response_key,
|
201
|
-
conflicts: errored_nodes
|
202
|
-
))
|
224
|
+
conflict = field_conflicts[response_key]
|
225
|
+
|
226
|
+
conflict.add_conflict(node1, node1.name)
|
227
|
+
conflict.add_conflict(node2, node2.name)
|
228
|
+
|
229
|
+
@conflict_count += 1
|
203
230
|
end
|
204
231
|
|
205
232
|
if !same_arguments?(node1, node2)
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
path: [],
|
213
|
-
field_name: response_key,
|
214
|
-
conflicts: conflicts
|
215
|
-
))
|
233
|
+
conflict = arg_conflicts[response_key]
|
234
|
+
|
235
|
+
conflict.add_conflict(node1, GraphQL::Language.serialize(serialize_field_args(node1)))
|
236
|
+
conflict.add_conflict(node2, GraphQL::Language.serialize(serialize_field_args(node2)))
|
237
|
+
|
238
|
+
@conflict_count += 1
|
216
239
|
end
|
217
240
|
end
|
218
241
|
|
@@ -224,7 +247,9 @@ module GraphQL
|
|
224
247
|
end
|
225
248
|
|
226
249
|
def find_conflicts_between_sub_selection_sets(field1, field2, mutually_exclusive:)
|
227
|
-
return if field1.definition.nil? ||
|
250
|
+
return if field1.definition.nil? ||
|
251
|
+
field2.definition.nil? ||
|
252
|
+
(field1.node.selections.empty? && field2.node.selections.empty?)
|
228
253
|
|
229
254
|
return_type1 = field1.definition.type.unwrap
|
230
255
|
return_type2 = field2.definition.type.unwrap
|
@@ -304,6 +329,7 @@ module GraphQL
|
|
304
329
|
if node.selections.empty?
|
305
330
|
NO_SELECTIONS
|
306
331
|
else
|
332
|
+
parents ||= []
|
307
333
|
fields, fragment_spreads = find_fields_and_fragments(node.selections, owner_type: owner_type, parents: parents, fields: [], fragment_spreads: [])
|
308
334
|
response_keys = fields.group_by { |f| f.node.alias || f.node.name }
|
309
335
|
[response_keys, fragment_spreads]
|
@@ -3,12 +3,33 @@ module GraphQL
|
|
3
3
|
module StaticValidation
|
4
4
|
class FieldsWillMergeError < StaticValidation::Error
|
5
5
|
attr_reader :field_name
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :kind
|
7
|
+
|
8
|
+
def initialize(kind:, field_name:)
|
9
|
+
super(nil)
|
7
10
|
|
8
|
-
def initialize(message, path: nil, nodes: [], field_name:, conflicts:)
|
9
|
-
super(message, path: path, nodes: nodes)
|
10
11
|
@field_name = field_name
|
11
|
-
@
|
12
|
+
@kind = kind
|
13
|
+
@conflicts = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def message
|
17
|
+
"Field '#{field_name}' has #{kind == :argument ? 'an' : 'a'} #{kind} conflict: #{conflicts}?"
|
18
|
+
end
|
19
|
+
|
20
|
+
def path
|
21
|
+
[]
|
22
|
+
end
|
23
|
+
|
24
|
+
def conflicts
|
25
|
+
@conflicts.join(' or ')
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_conflict(node, conflict_str)
|
29
|
+
return if nodes.include?(node)
|
30
|
+
|
31
|
+
@nodes << node
|
32
|
+
@conflicts << conflict_str
|
12
33
|
end
|
13
34
|
|
14
35
|
# A hash representation of this Message
|
@@ -16,6 +16,8 @@ module GraphQL
|
|
16
16
|
private
|
17
17
|
|
18
18
|
def assert_required_args(ast_node, defn)
|
19
|
+
args = defn.arguments
|
20
|
+
return if args.empty?
|
19
21
|
present_argument_names = ast_node.arguments.map(&:name)
|
20
22
|
required_argument_names = defn.arguments.each_value
|
21
23
|
.select { |a| a.type.kind.non_null? && !a.default_value? && context.warden.get_argument(defn, a.name) }
|
@@ -1,10 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module GraphQL
|
3
3
|
class StringEncodingError < GraphQL::RuntimeTypeError
|
4
|
-
attr_reader :string
|
5
|
-
def initialize(str)
|
4
|
+
attr_reader :string, :field, :path
|
5
|
+
def initialize(str, context:)
|
6
6
|
@string = str
|
7
|
-
|
7
|
+
@field = context[:current_field]
|
8
|
+
@path = context[:current_path]
|
9
|
+
message = "String #{str.inspect} was encoded as #{str.encoding}".dup
|
10
|
+
if @path
|
11
|
+
message << " @ #{@path.join(".")}"
|
12
|
+
end
|
13
|
+
if @field
|
14
|
+
message << " (#{@field.path})"
|
15
|
+
end
|
16
|
+
message << ". GraphQL requires an encoding compatible with UTF-8."
|
17
|
+
super(message)
|
8
18
|
end
|
9
19
|
end
|
10
20
|
end
|
@@ -53,6 +53,36 @@ module GraphQL
|
|
53
53
|
|
54
54
|
class << self
|
55
55
|
private
|
56
|
+
|
57
|
+
# This method does not support cyclic references in the Hash,
|
58
|
+
# nor does it support Hashes whose keys are not sortable
|
59
|
+
# with respect to their peers ( cases where a <=> b might throw an error )
|
60
|
+
def deep_sort_hash_keys(hash_to_sort)
|
61
|
+
raise ArgumentError.new("Argument must be a Hash") unless hash_to_sort.is_a?(Hash)
|
62
|
+
hash_to_sort.keys.sort.map do |k|
|
63
|
+
if hash_to_sort[k].is_a?(Hash)
|
64
|
+
[k, deep_sort_hash_keys(hash_to_sort[k])]
|
65
|
+
elsif hash_to_sort[k].is_a?(Array)
|
66
|
+
[k, deep_sort_array_hashes(hash_to_sort[k])]
|
67
|
+
else
|
68
|
+
[k, hash_to_sort[k]]
|
69
|
+
end
|
70
|
+
end.to_h
|
71
|
+
end
|
72
|
+
|
73
|
+
def deep_sort_array_hashes(array_to_inspect)
|
74
|
+
raise ArgumentError.new("Argument must be an Array") unless array_to_inspect.is_a?(Array)
|
75
|
+
array_to_inspect.map do |v|
|
76
|
+
if v.is_a?(Hash)
|
77
|
+
deep_sort_hash_keys(v)
|
78
|
+
elsif v.is_a?(Array)
|
79
|
+
deep_sort_array_hashes(v)
|
80
|
+
else
|
81
|
+
v
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
56
86
|
def stringify_args(arg_owner, args)
|
57
87
|
arg_owner = arg_owner.respond_to?(:unwrap) ? arg_owner.unwrap : arg_owner # remove list and non-null wrappers
|
58
88
|
case args
|
@@ -65,12 +95,26 @@ module GraphQL
|
|
65
95
|
|
66
96
|
if arg_defn
|
67
97
|
normalized_arg_name = camelized_arg_name
|
68
|
-
next_args[normalized_arg_name] = stringify_args(arg_defn.type, v)
|
69
98
|
else
|
70
99
|
normalized_arg_name = arg_name
|
71
100
|
arg_defn = get_arg_definition(arg_owner, normalized_arg_name)
|
72
101
|
end
|
73
|
-
|
102
|
+
arg_base_type = arg_defn.type.unwrap
|
103
|
+
# In the case where the value being emitted is seen as a "JSON"
|
104
|
+
# type, treat the value as one atomic unit of serialization
|
105
|
+
is_json_definition = arg_base_type && arg_base_type <= GraphQL::Types::JSON
|
106
|
+
if is_json_definition
|
107
|
+
sorted_value = if v.is_a?(Hash)
|
108
|
+
deep_sort_hash_keys(v)
|
109
|
+
elsif v.is_a?(Array)
|
110
|
+
deep_sort_array_hashes(v)
|
111
|
+
else
|
112
|
+
v
|
113
|
+
end
|
114
|
+
next_args[normalized_arg_name] = sorted_value.respond_to?(:to_json) ? sorted_value.to_json : sorted_value
|
115
|
+
else
|
116
|
+
next_args[normalized_arg_name] = stringify_args(arg_base_type, v)
|
117
|
+
end
|
74
118
|
end
|
75
119
|
# Make sure they're deeply sorted
|
76
120
|
next_args.sort.to_h
|
data/lib/graphql/types/int.rb
CHANGED
data/lib/graphql/types/string.rb
CHANGED
@@ -12,7 +12,7 @@ module GraphQL
|
|
12
12
|
attr_reader :type
|
13
13
|
|
14
14
|
# @return [GraphQL::Query::Context] the context for the current query
|
15
|
-
|
15
|
+
attr_accessor :context
|
16
16
|
|
17
17
|
def initialize(message = nil, object: nil, type: nil, context: nil)
|
18
18
|
if message.nil? && object.nil? && type.nil?
|
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: 1.12.
|
4
|
+
version: 1.12.24
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: benchmark-ips
|
@@ -544,6 +544,8 @@ files:
|
|
544
544
|
- lib/graphql/schema/unique_within_type.rb
|
545
545
|
- lib/graphql/schema/validation.rb
|
546
546
|
- lib/graphql/schema/validator.rb
|
547
|
+
- lib/graphql/schema/validator/allow_blank_validator.rb
|
548
|
+
- lib/graphql/schema/validator/allow_null_validator.rb
|
547
549
|
- lib/graphql/schema/validator/exclusion_validator.rb
|
548
550
|
- lib/graphql/schema/validator/format_validator.rb
|
549
551
|
- lib/graphql/schema/validator/inclusion_validator.rb
|
@@ -685,7 +687,7 @@ metadata:
|
|
685
687
|
source_code_uri: https://github.com/rmosolgo/graphql-ruby
|
686
688
|
bug_tracker_uri: https://github.com/rmosolgo/graphql-ruby/issues
|
687
689
|
mailing_list_uri: https://tinyletter.com/graphql-ruby
|
688
|
-
post_install_message:
|
690
|
+
post_install_message:
|
689
691
|
rdoc_options: []
|
690
692
|
require_paths:
|
691
693
|
- lib
|
@@ -700,8 +702,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
700
702
|
- !ruby/object:Gem::Version
|
701
703
|
version: '0'
|
702
704
|
requirements: []
|
703
|
-
rubygems_version: 3.
|
704
|
-
signing_key:
|
705
|
+
rubygems_version: 3.1.6
|
706
|
+
signing_key:
|
705
707
|
specification_version: 4
|
706
708
|
summary: A GraphQL language and runtime for Ruby
|
707
709
|
test_files: []
|