graphql 1.12.17 → 1.12.25
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.
- 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 +7 -3
- data/lib/graphql/dataloader/source.rb +32 -2
- data/lib/graphql/dataloader.rb +13 -0
- data/lib/graphql/deprecated_dsl.rb +11 -3
- data/lib/graphql/deprecation.rb +1 -5
- data/lib/graphql/integer_encoding_error.rb +18 -2
- data/lib/graphql/language/nodes.rb +13 -1
- data/lib/graphql/pagination/connections.rb +35 -16
- data/lib/graphql/query/validation_pipeline.rb +1 -1
- data/lib/graphql/schema/argument.rb +58 -31
- data/lib/graphql/schema/build_from_definition.rb +8 -7
- data/lib/graphql/schema/directive.rb +4 -0
- data/lib/graphql/schema/enum_value.rb +1 -1
- data/lib/graphql/schema/field.rb +15 -4
- data/lib/graphql/schema/input_object.rb +10 -11
- data/lib/graphql/schema/member/has_arguments.rb +90 -44
- data/lib/graphql/schema/resolver.rb +20 -57
- data/lib/graphql/schema/subscription.rb +4 -4
- 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/schema.rb +18 -5
- data/lib/graphql/static_validation/base_visitor.rb +3 -0
- 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/fragments_are_finite.rb +2 -2
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +2 -0
- data/lib/graphql/static_validation/validation_context.rb +8 -2
- data/lib/graphql/static_validation/validator.rb +15 -12
- data/lib/graphql/string_encoding_error.rb +13 -3
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +7 -1
- data/lib/graphql/subscriptions/event.rb +47 -2
- data/lib/graphql/subscriptions/serialize.rb +1 -1
- data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
- 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 +5 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 582e38c72afb32d38c7a256d1eb811a7ef0e13b8984c10370e278f038c639343
|
|
4
|
+
data.tar.gz: 2fc62afb65909e4e08d5ad42c2c5c40a48a7acedca8afa5ac512a4aa60284a8e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 922e24767106339ff4e441cc9597a6a974814371180aa5bdaab2db7608ac590610199af451f0dfc85019dd4a531bb7934e57a60f7cf976be1c095f3722866474
|
|
7
|
+
data.tar.gz: dd2b9fb0f9f26dd8ad3b3841516eadd66b05765de3f0490eafce49fc29599cc6894f1a0d376b3f3bef47d850fd3948ff7b03256721575a2ae6b06f6fd6741736
|
|
@@ -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
|
|
@@ -37,7 +41,7 @@ module GraphQL
|
|
|
37
41
|
|
|
38
42
|
if argument.definition.type.kind.input_object?
|
|
39
43
|
extract_deprecated_arguments(argument.value.arguments.argument_values)
|
|
40
|
-
elsif argument.definition.type.list?
|
|
44
|
+
elsif argument.definition.type.list? && !argument.value.nil?
|
|
41
45
|
argument
|
|
42
46
|
.value
|
|
43
47
|
.select { |value| value.respond_to?(:arguments) }
|
|
@@ -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
|
|
@@ -107,6 +128,8 @@ module GraphQL
|
|
|
107
128
|
[*batch_args, **batch_kwargs]
|
|
108
129
|
end
|
|
109
130
|
|
|
131
|
+
attr_reader :pending_keys
|
|
132
|
+
|
|
110
133
|
private
|
|
111
134
|
|
|
112
135
|
# Reads and returns the result for the key from the internal cache, or raises an error if the result was an error
|
|
@@ -114,6 +137,13 @@ module GraphQL
|
|
|
114
137
|
# @return [Object] The result from {#fetch} for `key`.
|
|
115
138
|
# @api private
|
|
116
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
|
|
117
147
|
result = @results[key]
|
|
118
148
|
|
|
119
149
|
raise result if result.class <= StandardError
|
data/lib/graphql/dataloader.rb
CHANGED
|
@@ -90,6 +90,16 @@ module GraphQL
|
|
|
90
90
|
# Use a self-contained queue for the work in the block.
|
|
91
91
|
def run_isolated
|
|
92
92
|
prev_queue = @pending_jobs
|
|
93
|
+
prev_pending_keys = {}
|
|
94
|
+
@source_cache.each do |source_class, batched_sources|
|
|
95
|
+
batched_sources.each do |batch_args, batched_source_instance|
|
|
96
|
+
if batched_source_instance.pending?
|
|
97
|
+
prev_pending_keys[batched_source_instance] = batched_source_instance.pending_keys.dup
|
|
98
|
+
batched_source_instance.pending_keys.clear
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
93
103
|
@pending_jobs = []
|
|
94
104
|
res = nil
|
|
95
105
|
# Make sure the block is inside a Fiber, so it can `Fiber.yield`
|
|
@@ -100,6 +110,9 @@ module GraphQL
|
|
|
100
110
|
res
|
|
101
111
|
ensure
|
|
102
112
|
@pending_jobs = prev_queue
|
|
113
|
+
prev_pending_keys.each do |source_instance, pending_keys|
|
|
114
|
+
source_instance.pending_keys.concat(pending_keys)
|
|
115
|
+
end
|
|
103
116
|
end
|
|
104
117
|
|
|
105
118
|
# @api private Move along, move along
|
|
@@ -38,9 +38,17 @@ module GraphQL
|
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
if defined?(::Refinement) && Refinement.private_method_defined?(:import_methods)
|
|
42
|
+
TYPE_CLASSES.each do |type_class|
|
|
43
|
+
refine type_class.singleton_class do
|
|
44
|
+
import_methods Methods
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
else
|
|
48
|
+
TYPE_CLASSES.each do |type_class|
|
|
49
|
+
refine type_class.singleton_class do
|
|
50
|
+
include Methods
|
|
51
|
+
end
|
|
44
52
|
end
|
|
45
53
|
end
|
|
46
54
|
end
|
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
|
|
@@ -133,6 +133,8 @@ module GraphQL
|
|
|
133
133
|
end
|
|
134
134
|
|
|
135
135
|
class << self
|
|
136
|
+
# rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
|
|
137
|
+
|
|
136
138
|
# Add a default `#visit_method` and `#children_method_name` using the class name
|
|
137
139
|
def inherited(child_class)
|
|
138
140
|
super
|
|
@@ -197,7 +199,16 @@ module GraphQL
|
|
|
197
199
|
else
|
|
198
200
|
module_eval <<-RUBY, __FILE__, __LINE__
|
|
199
201
|
def children
|
|
200
|
-
@children ||=
|
|
202
|
+
@children ||= begin
|
|
203
|
+
if #{children_of_type.keys.map { |k| "@#{k}.any?" }.join(" || ")}
|
|
204
|
+
new_children = []
|
|
205
|
+
#{children_of_type.keys.map { |k| "new_children.concat(@#{k})" }.join("; ")}
|
|
206
|
+
new_children.freeze
|
|
207
|
+
new_children
|
|
208
|
+
else
|
|
209
|
+
NO_CHILDREN
|
|
210
|
+
end
|
|
211
|
+
end
|
|
201
212
|
end
|
|
202
213
|
RUBY
|
|
203
214
|
end
|
|
@@ -267,6 +278,7 @@ module GraphQL
|
|
|
267
278
|
RUBY
|
|
268
279
|
end
|
|
269
280
|
end
|
|
281
|
+
# rubocop:enable Development/NoEvalCop
|
|
270
282
|
end
|
|
271
283
|
end
|
|
272
284
|
|
|
@@ -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
|
|
|
@@ -55,6 +55,7 @@ module GraphQL
|
|
|
55
55
|
def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NO_DEFAULT, as: nil, from_resolver: false, camelize: true, prepare: nil, method_access: true, owner:, validates: nil, directives: nil, deprecation_reason: nil, &definition_block)
|
|
56
56
|
arg_name ||= name
|
|
57
57
|
@name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
|
|
58
|
+
NameValidator.validate!(@name)
|
|
58
59
|
@type_expr = type_expr || type
|
|
59
60
|
@description = desc || description
|
|
60
61
|
@null = !required
|
|
@@ -78,11 +79,8 @@ module GraphQL
|
|
|
78
79
|
self.validates(validates)
|
|
79
80
|
|
|
80
81
|
if definition_block
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
else
|
|
84
|
-
instance_eval(&definition_block)
|
|
85
|
-
end
|
|
82
|
+
# `self` will still be self, it will also be the first argument to the block:
|
|
83
|
+
instance_exec(self, &definition_block)
|
|
86
84
|
end
|
|
87
85
|
end
|
|
88
86
|
|
|
@@ -260,38 +258,67 @@ module GraphQL
|
|
|
260
258
|
type.coerce_input(value, context)
|
|
261
259
|
end
|
|
262
260
|
|
|
263
|
-
#
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
context.
|
|
268
|
-
|
|
269
|
-
context.query.with_error_handling do
|
|
270
|
-
owner.load_application_object(self, loads, coerced_value, context)
|
|
261
|
+
# If this isn't lazy, then the block returns eagerly and assigns the result here
|
|
262
|
+
# If it _is_ lazy, then we write the lazy to the hash, then update it later
|
|
263
|
+
argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |resolved_coerced_value|
|
|
264
|
+
if loads && !from_resolver?
|
|
265
|
+
loaded_value = context.query.with_error_handling do
|
|
266
|
+
load_and_authorize_value(owner, coerced_value, context)
|
|
271
267
|
end
|
|
272
268
|
end
|
|
273
|
-
end
|
|
274
269
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
270
|
+
maybe_loaded_value = loaded_value || resolved_coerced_value
|
|
271
|
+
context.schema.after_lazy(maybe_loaded_value) do |resolved_loaded_value|
|
|
272
|
+
owner.validate_directive_argument(self, resolved_loaded_value)
|
|
273
|
+
prepared_value = context.schema.error_handler.with_error_handling(context) do
|
|
274
|
+
prepare_value(parent_object, resolved_loaded_value, context: context)
|
|
275
|
+
end
|
|
280
276
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
277
|
+
# TODO code smell to access such a deeply-nested constant in a distant module
|
|
278
|
+
argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
|
|
279
|
+
value: prepared_value,
|
|
280
|
+
definition: self,
|
|
281
|
+
default_used: default_used,
|
|
282
|
+
)
|
|
287
283
|
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
288
286
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
287
|
+
def load_and_authorize_value(load_method_owner, coerced_value, context)
|
|
288
|
+
if coerced_value.nil?
|
|
289
|
+
return nil
|
|
290
|
+
end
|
|
291
|
+
arg_load_method = "load_#{keyword}"
|
|
292
|
+
if load_method_owner.respond_to?(arg_load_method)
|
|
293
|
+
custom_loaded_value = if load_method_owner.is_a?(Class)
|
|
294
|
+
load_method_owner.public_send(arg_load_method, coerced_value, context)
|
|
295
|
+
else
|
|
296
|
+
load_method_owner.public_send(arg_load_method, coerced_value)
|
|
297
|
+
end
|
|
298
|
+
context.schema.after_lazy(custom_loaded_value) do |custom_value|
|
|
299
|
+
if loads
|
|
300
|
+
if type.list?
|
|
301
|
+
loaded_values = custom_value.each_with_index.map { |custom_val, idx|
|
|
302
|
+
id = coerced_value[idx]
|
|
303
|
+
load_method_owner.authorize_application_object(self, id, context, custom_val)
|
|
304
|
+
}
|
|
305
|
+
context.schema.after_any_lazies(loaded_values, &:itself)
|
|
306
|
+
else
|
|
307
|
+
load_method_owner.authorize_application_object(self, coerced_value, context, custom_loaded_value)
|
|
308
|
+
end
|
|
309
|
+
else
|
|
310
|
+
custom_value
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
elsif loads
|
|
314
|
+
if type.list?
|
|
315
|
+
loaded_values = coerced_value.map { |val| load_method_owner.load_and_authorize_application_object(self, val, context) }
|
|
316
|
+
context.schema.after_any_lazies(loaded_values, &:itself)
|
|
317
|
+
else
|
|
318
|
+
load_method_owner.load_and_authorize_application_object(self, coerced_value, context)
|
|
319
|
+
end
|
|
320
|
+
else
|
|
321
|
+
coerced_value
|
|
295
322
|
end
|
|
296
323
|
end
|
|
297
324
|
|
|
@@ -426,17 +426,18 @@ module GraphQL
|
|
|
426
426
|
|
|
427
427
|
# Don't do this for interfaces
|
|
428
428
|
if default_resolve
|
|
429
|
-
owner
|
|
430
|
-
# frozen_string_literal: true
|
|
431
|
-
def #{resolve_method_name}(**args)
|
|
432
|
-
field_instance = self.class.get_field("#{field_definition.name}")
|
|
433
|
-
context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
|
|
434
|
-
end
|
|
435
|
-
RUBY
|
|
429
|
+
define_field_resolve_method(owner, resolve_method_name, field_definition.name)
|
|
436
430
|
end
|
|
437
431
|
end
|
|
438
432
|
end
|
|
439
433
|
|
|
434
|
+
def define_field_resolve_method(owner, method_name, field_name)
|
|
435
|
+
owner.define_method(method_name) { |**args|
|
|
436
|
+
field_instance = self.class.get_field(field_name)
|
|
437
|
+
context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
|
|
438
|
+
}
|
|
439
|
+
end
|
|
440
|
+
|
|
440
441
|
def build_resolve_type(lookup_hash, directives, missing_type_handler)
|
|
441
442
|
resolve_type_proc = nil
|
|
442
443
|
resolve_type_proc = ->(ast_node) {
|
data/lib/graphql/schema/field.rb
CHANGED
|
@@ -122,6 +122,9 @@ module GraphQL
|
|
|
122
122
|
else
|
|
123
123
|
kwargs[:type] = type
|
|
124
124
|
end
|
|
125
|
+
if type.is_a?(Class) && type < GraphQL::Schema::Mutation
|
|
126
|
+
raise ArgumentError, "Use `field #{name.inspect}, mutation: Mutation, ...` to provide a mutation to this field instead"
|
|
127
|
+
end
|
|
125
128
|
end
|
|
126
129
|
new(**kwargs, &block)
|
|
127
130
|
end
|
|
@@ -228,6 +231,7 @@ module GraphQL
|
|
|
228
231
|
name_s = -name.to_s
|
|
229
232
|
@underscored_name = -Member::BuildType.underscore(name_s)
|
|
230
233
|
@name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
|
|
234
|
+
NameValidator.validate!(@name)
|
|
231
235
|
@description = description
|
|
232
236
|
if field.is_a?(GraphQL::Schema::Field)
|
|
233
237
|
raise ArgumentError, "Instead of passing a field as `field:`, use `add_field(field)` to add an already-defined field."
|
|
@@ -510,6 +514,7 @@ module GraphQL
|
|
|
510
514
|
field_defn
|
|
511
515
|
end
|
|
512
516
|
|
|
517
|
+
class MissingReturnTypeError < GraphQL::Error; end
|
|
513
518
|
attr_writer :type
|
|
514
519
|
|
|
515
520
|
def type
|
|
@@ -517,14 +522,21 @@ module GraphQL
|
|
|
517
522
|
Member::BuildType.parse_type(@function.type, null: false)
|
|
518
523
|
elsif @field
|
|
519
524
|
Member::BuildType.parse_type(@field.type, null: false)
|
|
525
|
+
elsif @return_type_expr.nil?
|
|
526
|
+
# Not enough info to determine type
|
|
527
|
+
message = "Can't determine the return type for #{self.path}"
|
|
528
|
+
if @resolver_class
|
|
529
|
+
message += " (it has `resolver: #{@resolver_class}`, consider configuration a `type ...` for that class)"
|
|
530
|
+
end
|
|
531
|
+
raise MissingReturnTypeError, message
|
|
520
532
|
else
|
|
521
533
|
Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
|
|
522
534
|
end
|
|
523
|
-
rescue GraphQL::Schema::InvalidDocumentError => err
|
|
535
|
+
rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
|
|
524
536
|
# Let this propagate up
|
|
525
537
|
raise err
|
|
526
538
|
rescue StandardError => err
|
|
527
|
-
raise
|
|
539
|
+
raise MissingReturnTypeError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: (#{err.class}) #{err.message}", err.backtrace
|
|
528
540
|
end
|
|
529
541
|
|
|
530
542
|
def visible?(context)
|
|
@@ -608,8 +620,7 @@ module GraphQL
|
|
|
608
620
|
if is_authorized
|
|
609
621
|
public_send_field(object, args, ctx)
|
|
610
622
|
else
|
|
611
|
-
|
|
612
|
-
ctx.schema.unauthorized_field(err)
|
|
623
|
+
raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
|
|
613
624
|
end
|
|
614
625
|
end
|
|
615
626
|
rescue GraphQL::UnauthorizedFieldError => err
|
|
@@ -40,11 +40,7 @@ module GraphQL
|
|
|
40
40
|
# With the interpreter, it's done during `coerce_arguments`
|
|
41
41
|
if loads && !arg_defn.from_resolver? && !context.interpreter?
|
|
42
42
|
value = @ruby_style_hash[ruby_kwargs_key]
|
|
43
|
-
loaded_value =
|
|
44
|
-
value.map { |val| load_application_object(arg_defn, loads, val, context) }
|
|
45
|
-
else
|
|
46
|
-
load_application_object(arg_defn, loads, value, context)
|
|
47
|
-
end
|
|
43
|
+
loaded_value = arg_defn.load_and_authorize_value(self, value, context)
|
|
48
44
|
maybe_lazies << context.schema.after_lazy(loaded_value) do |loaded_value|
|
|
49
45
|
overwrite_argument(ruby_kwargs_key, loaded_value)
|
|
50
46
|
end
|
|
@@ -127,12 +123,8 @@ module GraphQL
|
|
|
127
123
|
def argument(*args, **kwargs, &block)
|
|
128
124
|
argument_defn = super(*args, **kwargs, &block)
|
|
129
125
|
# Add a method access
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def #{method_name}
|
|
133
|
-
self[#{method_name.inspect}]
|
|
134
|
-
end
|
|
135
|
-
RUBY
|
|
126
|
+
define_accessor_method(argument_defn.keyword)
|
|
127
|
+
argument_defn
|
|
136
128
|
end
|
|
137
129
|
|
|
138
130
|
def to_graphql
|
|
@@ -240,6 +232,13 @@ module GraphQL
|
|
|
240
232
|
|
|
241
233
|
result
|
|
242
234
|
end
|
|
235
|
+
|
|
236
|
+
private
|
|
237
|
+
|
|
238
|
+
def define_accessor_method(method_name)
|
|
239
|
+
define_method(method_name) { self[method_name] }
|
|
240
|
+
alias_method(method_name, method_name)
|
|
241
|
+
end
|
|
243
242
|
end
|
|
244
243
|
|
|
245
244
|
private
|