graphql 1.9.3 → 1.9.4
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 +5 -5
- data/lib/graphql.rb +1 -0
- data/lib/graphql/analysis/ast/field_usage.rb +1 -1
- data/lib/graphql/analysis/ast/max_query_complexity.rb +1 -1
- data/lib/graphql/analysis/ast/query_complexity.rb +3 -2
- data/lib/graphql/execution/execute.rb +10 -6
- data/lib/graphql/execution/interpreter/runtime.rb +23 -12
- data/lib/graphql/execution/lookahead.rb +0 -4
- data/lib/graphql/integer_encoding_error.rb +9 -1
- data/lib/graphql/language/visitor.rb +6 -0
- data/lib/graphql/load_application_object_failed_error.rb +22 -0
- data/lib/graphql/query.rb +6 -1
- data/lib/graphql/query/arguments.rb +1 -1
- data/lib/graphql/query/variables.rb +14 -8
- data/lib/graphql/rake_task/validate.rb +1 -1
- data/lib/graphql/schema/field_extension.rb +2 -2
- data/lib/graphql/schema/input_object.rb +15 -6
- data/lib/graphql/schema/member/has_arguments.rb +80 -0
- data/lib/graphql/schema/member/instrumentation.rb +6 -0
- data/lib/graphql/schema/resolver.rb +8 -92
- data/lib/graphql/static_validation/base_visitor.rb +8 -0
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
- data/lib/graphql/tracing.rb +2 -2
- data/lib/graphql/tracing/data_dog_tracing.rb +19 -0
- data/lib/graphql/tracing/platform_tracing.rb +2 -1
- data/lib/graphql/types.rb +1 -0
- data/lib/graphql/types/big_int.rb +19 -0
- data/lib/graphql/types/int.rb +1 -0
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/authorization_spec.rb +26 -0
- data/spec/graphql/language/visitor_spec.rb +19 -0
- data/spec/graphql/schema/argument_spec.rb +26 -1
- data/spec/graphql/schema/field_extension_spec.rb +24 -2
- data/spec/graphql/schema/input_object_spec.rb +119 -10
- data/spec/graphql/schema/introspection_system_spec.rb +9 -0
- data/spec/graphql/schema/resolver_spec.rb +2 -2
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +2 -2
- data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/required_input_object_attributes_are_present_spec.rb +16 -2
- data/spec/graphql/subscriptions_spec.rb +38 -0
- data/spec/graphql/tracing/new_relic_tracing_spec.rb +21 -0
- data/spec/graphql/types/big_int_spec.rb +24 -0
- data/spec/support/jazz.rb +11 -0
- metadata +13 -51
- data/spec/dummy/Gemfile.lock +0 -157
- data/spec/dummy/log/test.log +0 -199
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/4w/4wzXRZrAkwKdgYaSE0pid5eB-fer8vSfSku_NPg4rMA.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/7I/7IHVBiJT06QSpgLpLoJIxboQ0B-D_tMTxsvoezBTV3Q.cache +0 -1
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/8w/8wY_SKagj8wHuwGNAAf6JnQ8joMbC6cEYpHrTAI8Urc.cache +0 -1
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/AK/AKzz1u6bGb4auXcrObA_g5LL-oV0ejNGa448AgAi_WQ.cache +0 -1
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/ET/ETW4uxvaYpruL8y6_ZptUH82ZowMaHIqvg5WexBFdEM.cache +0 -3
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/F1/F1TWpjjyA56k9Z90n5B3xRn7DUdGjX73QCkYC6k07JQ.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/F8/F8MUNRzORGFgr329fNM0xLaoWCXdv3BIalT7dsvLfjs.cache +0 -2
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/KB/KB07ZaKNC5uXJ7TjLi-WqnY6g7dq8wWp_8N3HNjBNxg.cache +0 -2
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Ms/MsKSimH_UCB-H1tLvDABDHuvGciuoW6kVqQWDrXU5FQ.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Mt/Mtci-Kim50aPOmeClD4AIicKn1d1WJ0n454IjSd94sk.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/QH/QHt3Tc1Y6M66Oo_pDuMyWrQNs4Pp3SMeZR5K1wJj2Ts.cache +0 -1
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/XU/XU4k1OXnfMils5SrirorPvDSyDSqiOWLZNtmAH1HH8k.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/ZI/ZIof7mZxWWCnraIFOCuV6a8QRWzKJXJnx2Xd7C0ZyX0.cache +0 -1
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/cG/cGc_puuPS5pZKgUcy1Y_i1L6jl5UtsiIrMH59rTzR6c.cache +0 -3
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/df/dfro_B6bx3KP1Go-7jEOqqZ2j4hVRseXIc3es9PKQno.cache +0 -1
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/jO/jO1DfbqnG0mTULsjJJANc3fefrG2zt7DIMmcptMT628.cache +0 -1
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/pE/pE7gO6pQ-z187Swb4hT554wmqsq-cNzgPWLrCz-LQQQ.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/r9/r9iU1l58a6rxkZSW5RSC52_tD-_UQuHxoMVnkfJ7Mhs.cache +0 -1
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/xi/xitPPFfPIyDMpaznV0sBBcw8eSCV8PJcLLWin78sCgE.cache +0 -0
- data/spec/dummy/tmp/screenshots/failures_test_it_handles_subscriptions.png +0 -0
- data/spec/integration/tmp/app/graphql/types/family_type.rb +0 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c4a10bf3dce4841a53a78308eab58db972aeeb6daade67cf019a45ad48d2c1f6
|
|
4
|
+
data.tar.gz: ca20d0a586c0076ee47b65c09e12a39d8c52b2db4a4c232cf9d1fee95202280c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c3ebc480c7e4c209b319f382aae6eed1f0d1c61c6886ad620950b51dc457863f9da1af8d451f382a22a467230daa95c5d8a28fd86d1bde22f499aa9a797d6f75
|
|
7
|
+
data.tar.gz: 86655364098750c3430395b637def8952a1bdb5082643ebe74fb0110d04114411f9077539621cd50c38ff151c64eb54c275ae0b23f248ac27a99f75e356f918d
|
data/lib/graphql.rb
CHANGED
|
@@ -9,7 +9,7 @@ module GraphQL
|
|
|
9
9
|
@used_deprecated_fields = Set.new
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def on_leave_field(
|
|
12
|
+
def on_leave_field(node, parent, visitor)
|
|
13
13
|
field_defn = visitor.field_definition
|
|
14
14
|
field = "#{visitor.parent_type_definition.name}.#{field_defn.name}"
|
|
15
15
|
@used_fields << field
|
|
@@ -9,7 +9,7 @@ module GraphQL
|
|
|
9
9
|
def result
|
|
10
10
|
return if query.max_complexity.nil?
|
|
11
11
|
|
|
12
|
-
total_complexity =
|
|
12
|
+
total_complexity = max_possible_complexity
|
|
13
13
|
|
|
14
14
|
if total_complexity > query.max_complexity
|
|
15
15
|
GraphQL::AnalysisError.new("Query has complexity of #{total_complexity}, which exceeds max complexity of #{query.max_complexity}")
|
|
@@ -13,7 +13,7 @@ module GraphQL
|
|
|
13
13
|
|
|
14
14
|
# Overide this method to use the complexity result
|
|
15
15
|
def result
|
|
16
|
-
|
|
16
|
+
max_possible_complexity
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def on_enter_field(node, parent, visitor)
|
|
@@ -57,7 +57,8 @@ module GraphQL
|
|
|
57
57
|
visitor.leave_fragment_spread_inline(node)
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
# @return [Integer]
|
|
61
|
+
def max_possible_complexity
|
|
61
62
|
@complexities_on_type.last.max_possible_complexity
|
|
62
63
|
end
|
|
63
64
|
|
|
@@ -50,12 +50,16 @@ module GraphQL
|
|
|
50
50
|
operation = query.selected_operation
|
|
51
51
|
op_type = operation.operation_type
|
|
52
52
|
root_type = query.root_type_for_operation(op_type)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
if query.context[:__root_unauthorized]
|
|
54
|
+
# This was set by member/instrumentation.rb so that we wouldn't continue.
|
|
55
|
+
else
|
|
56
|
+
resolve_selection(
|
|
57
|
+
query.root_value,
|
|
58
|
+
root_type,
|
|
59
|
+
query.context,
|
|
60
|
+
mutation: query.mutation?
|
|
61
|
+
)
|
|
62
|
+
end
|
|
59
63
|
end
|
|
60
64
|
end
|
|
61
65
|
|
|
@@ -49,9 +49,15 @@ module GraphQL
|
|
|
49
49
|
root_type = legacy_root_type.metadata[:type_class] || raise("Invariant: type must be class-based: #{legacy_root_type}")
|
|
50
50
|
object_proxy = root_type.authorized_new(query.root_value, context)
|
|
51
51
|
object_proxy = schema.sync_lazy(object_proxy)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
if object_proxy.nil?
|
|
53
|
+
# Root .authorized? returned false.
|
|
54
|
+
write_in_response([], nil)
|
|
55
|
+
nil
|
|
56
|
+
else
|
|
57
|
+
path = []
|
|
58
|
+
evaluate_selections(path, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
|
|
59
|
+
nil
|
|
60
|
+
end
|
|
55
61
|
end
|
|
56
62
|
|
|
57
63
|
def gather_selections(owner_object, owner_type, selections, selections_by_name)
|
|
@@ -158,8 +164,13 @@ module GraphQL
|
|
|
158
164
|
object = field_defn.owner.authorized_new(object, context)
|
|
159
165
|
end
|
|
160
166
|
|
|
167
|
+
begin
|
|
168
|
+
kwarg_arguments = arguments(object, field_defn, ast_node)
|
|
169
|
+
rescue GraphQL::ExecutionError => e
|
|
170
|
+
continue_value(next_path, e, field_defn, return_type.non_null?, ast_node)
|
|
171
|
+
next
|
|
172
|
+
end
|
|
161
173
|
|
|
162
|
-
kwarg_arguments = arguments(object, field_defn, ast_node)
|
|
163
174
|
# It might turn out that making arguments for every field is slow.
|
|
164
175
|
# If we have to cache them, we'll need a more subtle approach here.
|
|
165
176
|
field_defn.extras.each do |extra|
|
|
@@ -194,10 +205,10 @@ module GraphQL
|
|
|
194
205
|
|
|
195
206
|
field_result = resolve_with_directives(object, ast_node) do
|
|
196
207
|
# Actually call the field resolver and capture the result
|
|
197
|
-
app_result = query.trace("execute_field", {field: field_defn, path: next_path}) do
|
|
208
|
+
app_result = query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path}) do
|
|
198
209
|
field_defn.resolve(object, kwarg_arguments, context)
|
|
199
210
|
end
|
|
200
|
-
after_lazy(app_result, field: field_defn, path: next_path) do |inner_result|
|
|
211
|
+
after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path) do |inner_result|
|
|
201
212
|
continue_value = continue_value(next_path, inner_result, field_defn, return_type.non_null?, ast_node)
|
|
202
213
|
if HALT != continue_value
|
|
203
214
|
continue_field(next_path, continue_value, field_defn, return_type, ast_node, next_selections, false)
|
|
@@ -271,7 +282,7 @@ module GraphQL
|
|
|
271
282
|
r
|
|
272
283
|
when "UNION", "INTERFACE"
|
|
273
284
|
resolved_type_or_lazy = query.resolve_type(type, value)
|
|
274
|
-
after_lazy(resolved_type_or_lazy, path: path, field: field) do |resolved_type|
|
|
285
|
+
after_lazy(resolved_type_or_lazy, owner: type, path: path, field: field) do |resolved_type|
|
|
275
286
|
possible_types = query.possible_types(type)
|
|
276
287
|
|
|
277
288
|
if !possible_types.include?(resolved_type)
|
|
@@ -291,7 +302,7 @@ module GraphQL
|
|
|
291
302
|
rescue GraphQL::ExecutionError => err
|
|
292
303
|
err
|
|
293
304
|
end
|
|
294
|
-
after_lazy(object_proxy, path: path, field: field) do |inner_object|
|
|
305
|
+
after_lazy(object_proxy, owner: type, path: path, field: field) do |inner_object|
|
|
295
306
|
continue_value = continue_value(path, inner_object, field, is_non_null, ast_node)
|
|
296
307
|
if HALT != continue_value
|
|
297
308
|
response_hash = {}
|
|
@@ -312,7 +323,7 @@ module GraphQL
|
|
|
312
323
|
idx += 1
|
|
313
324
|
set_type_at_path(next_path, inner_type)
|
|
314
325
|
# This will update `response_list` with the lazy
|
|
315
|
-
after_lazy(inner_value, path: next_path, field: field) do |inner_inner_value|
|
|
326
|
+
after_lazy(inner_value, owner: inner_type, path: next_path, field: field) do |inner_inner_value|
|
|
316
327
|
# reset `is_non_null` here and below, because the inner type will have its own nullability constraint
|
|
317
328
|
continue_value = continue_value(next_path, inner_inner_value, field, false, ast_node)
|
|
318
329
|
if HALT != continue_value
|
|
@@ -378,7 +389,7 @@ module GraphQL
|
|
|
378
389
|
# @param field [GraphQL::Schema::Field]
|
|
379
390
|
# @param eager [Boolean] Set to `true` for mutation root fields only
|
|
380
391
|
# @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
|
|
381
|
-
def after_lazy(obj, field:, path:, eager: false)
|
|
392
|
+
def after_lazy(obj, owner:, field:, path:, eager: false)
|
|
382
393
|
@interpreter_context[:current_path] = path
|
|
383
394
|
@interpreter_context[:current_field] = field
|
|
384
395
|
if schema.lazy?(obj)
|
|
@@ -387,14 +398,14 @@ module GraphQL
|
|
|
387
398
|
@interpreter_context[:current_field] = field
|
|
388
399
|
# Wrap the execution of _this_ method with tracing,
|
|
389
400
|
# but don't wrap the continuation below
|
|
390
|
-
inner_obj = query.trace("execute_field_lazy", {field: field, path: path}) do
|
|
401
|
+
inner_obj = query.trace("execute_field_lazy", {owner: owner, field: field, path: path}) do
|
|
391
402
|
begin
|
|
392
403
|
schema.sync_lazy(obj)
|
|
393
404
|
rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
|
|
394
405
|
yield(err)
|
|
395
406
|
end
|
|
396
407
|
end
|
|
397
|
-
after_lazy(inner_obj, field: field, path: path, eager: eager) do |really_inner_obj|
|
|
408
|
+
after_lazy(inner_obj, owner: owner, field: field, path: path, eager: eager) do |really_inner_obj|
|
|
398
409
|
yield(really_inner_obj)
|
|
399
410
|
end
|
|
400
411
|
end
|
|
@@ -9,10 +9,6 @@ module GraphQL
|
|
|
9
9
|
# A field may get access to its lookahead by adding `extras: [:lookahead]`
|
|
10
10
|
# to its configuration.
|
|
11
11
|
#
|
|
12
|
-
# __NOTE__: Lookahead for typed fragments (eg `node { ... on Thing { ... } }`)
|
|
13
|
-
# hasn't been implemented yet. It's possible, I just didn't need it yet.
|
|
14
|
-
# Feel free to open a PR or an issue if you want to add it.
|
|
15
|
-
#
|
|
16
12
|
# @example looking ahead in a field
|
|
17
13
|
# field :articles, [Types::Article], null: false,
|
|
18
14
|
# extras: [:lookahead]
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
module GraphQL
|
|
3
|
+
# This error is raised when `Types::Int` is asked to return a value outside of 32-bit integer range.
|
|
4
|
+
#
|
|
5
|
+
# For values outside that range, consider:
|
|
6
|
+
#
|
|
7
|
+
# - `ID` for database primary keys or other identifiers
|
|
8
|
+
# - `GraphQL::Types::BigInt` for really big integer values
|
|
9
|
+
#
|
|
10
|
+
# @see GraphQL::Types::Int which raises this error
|
|
3
11
|
class IntegerEncodingError < GraphQL::RuntimeTypeError
|
|
4
12
|
# The value which couldn't be encoded
|
|
5
13
|
attr_reader :integer_value
|
|
6
14
|
|
|
7
15
|
def initialize(value)
|
|
8
16
|
@integer_value = value
|
|
9
|
-
super(
|
|
17
|
+
super("Integer out of bounds: #{value}. \nConsider using ID or GraphQL::Types::BigInt instead.")
|
|
10
18
|
end
|
|
11
19
|
end
|
|
12
20
|
end
|
|
@@ -168,6 +168,8 @@ module GraphQL
|
|
|
168
168
|
# Run the hooks for `node`, and if the hooks return a copy of `node`,
|
|
169
169
|
# copy `parent` so that it contains the copy of that node as a child,
|
|
170
170
|
# then return the copies
|
|
171
|
+
# If a non-array value is returned, consuming functions should ignore
|
|
172
|
+
# said value
|
|
171
173
|
def on_node_with_modifications(node, parent)
|
|
172
174
|
new_node_and_new_parent = visit_node(node, parent)
|
|
173
175
|
if new_node_and_new_parent.is_a?(Array)
|
|
@@ -181,6 +183,10 @@ module GraphQL
|
|
|
181
183
|
# The user-provided hook requested to remove this node
|
|
182
184
|
new_parent = new_parent && new_parent.delete_child(node)
|
|
183
185
|
return nil, new_parent
|
|
186
|
+
elsif new_node_and_new_parent.none? { |n| n == nil || n.class < Nodes::AbstractNode }
|
|
187
|
+
# The user-provided hook returned an array of who-knows-what
|
|
188
|
+
# return nil here to signify that no changes should be made
|
|
189
|
+
nil
|
|
184
190
|
else
|
|
185
191
|
new_node_and_new_parent
|
|
186
192
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphQL
|
|
4
|
+
# Raised when a argument is configured with `loads:` and the client provides an `ID`,
|
|
5
|
+
# but no object is loaded for that ID.
|
|
6
|
+
#
|
|
7
|
+
# @see GraphQL::Schema::Member::HasArguments::ArgumentObjectLoader#load_application_object_failed, A hook which you can override in resolvers, mutations and input objects.
|
|
8
|
+
class LoadApplicationObjectFailedError < GraphQL::ExecutionError
|
|
9
|
+
# @return [GraphQL::Schema::Argument] the argument definition for the argument that was looked up
|
|
10
|
+
attr_reader :argument
|
|
11
|
+
# @return [String] The ID provided by the client
|
|
12
|
+
attr_reader :id
|
|
13
|
+
# @return [Object] The value found with this ID
|
|
14
|
+
attr_reader :object
|
|
15
|
+
def initialize(argument:, id:, object:)
|
|
16
|
+
@id = id
|
|
17
|
+
@argument = argument
|
|
18
|
+
@object = object
|
|
19
|
+
super("No object found for `#{argument.graphql_name}: #{id.inspect}`")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/graphql/query.rb
CHANGED
|
@@ -40,7 +40,7 @@ module GraphQL
|
|
|
40
40
|
# @return [Boolean] if false, static validation is skipped (execution behavior for invalid queries is undefined)
|
|
41
41
|
attr_accessor :validate
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
attr_writer :query_string
|
|
44
44
|
|
|
45
45
|
# @return [GraphQL::Language::Nodes::Document]
|
|
46
46
|
def document
|
|
@@ -135,6 +135,11 @@ module GraphQL
|
|
|
135
135
|
end
|
|
136
136
|
end
|
|
137
137
|
|
|
138
|
+
# If a document was provided to `GraphQL::Schema#execute` instead of the raw query string, we will need to get it from the document
|
|
139
|
+
def query_string
|
|
140
|
+
@query_string ||= (document ? document.to_query_string : nil)
|
|
141
|
+
end
|
|
142
|
+
|
|
138
143
|
def_delegators :@schema, :interpreter?
|
|
139
144
|
|
|
140
145
|
def subscription_update?
|
|
@@ -19,7 +19,7 @@ module GraphQL
|
|
|
19
19
|
method_names = [expose_as, expose_as_underscored].uniq
|
|
20
20
|
method_names.each do |method_name|
|
|
21
21
|
# Don't define a helper method if it would override something.
|
|
22
|
-
if instance_methods.include?(method_name)
|
|
22
|
+
if instance_methods.include?(method_name.to_sym)
|
|
23
23
|
warn(
|
|
24
24
|
"Unable to define a helper for argument with name '#{method_name}' "\
|
|
25
25
|
"as this is a reserved name. If you're using an argument such as "\
|
|
@@ -32,20 +32,26 @@ module GraphQL
|
|
|
32
32
|
|
|
33
33
|
begin
|
|
34
34
|
validation_result = variable_type.validate_input(provided_value, ctx)
|
|
35
|
-
|
|
35
|
+
if validation_result.valid?
|
|
36
|
+
if value_was_provided
|
|
37
|
+
# Add the variable if a value was provided
|
|
38
|
+
memo[variable_name] = variable_type.coerce_input(provided_value, ctx)
|
|
39
|
+
elsif default_value != nil
|
|
40
|
+
# Add the variable if it wasn't provided but it has a default value (including `null`)
|
|
41
|
+
memo[variable_name] = GraphQL::Query::LiteralInput.coerce(variable_type, default_value, self)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
rescue GraphQL::CoercionError, GraphQL::ExecutionError => ex
|
|
45
|
+
# TODO: This should really include the path to the problematic node in the variable value
|
|
46
|
+
# like InputValidationResults generated by validate_non_null_input but unfortunately we don't
|
|
47
|
+
# have this information available in the coerce_input call chain. Note this path is the path
|
|
48
|
+
# that appears under errors.extensions.problems.path and NOT the result path under errors.path.
|
|
36
49
|
validation_result = GraphQL::Query::InputValidationResult.new
|
|
37
50
|
validation_result.add_problem(ex.message)
|
|
38
51
|
end
|
|
39
52
|
|
|
40
53
|
if !validation_result.valid?
|
|
41
|
-
# This finds variables that were required but not provided
|
|
42
54
|
@errors << GraphQL::Query::VariableValidationError.new(ast_variable, variable_type, provided_value, validation_result)
|
|
43
|
-
elsif value_was_provided
|
|
44
|
-
# Add the variable if a value was provided
|
|
45
|
-
memo[variable_name] = variable_type.coerce_input(provided_value, ctx)
|
|
46
|
-
elsif default_value != nil
|
|
47
|
-
# Add the variable if it wasn't provided but it has a default value (including `null`)
|
|
48
|
-
memo[variable_name] = GraphQL::Query::LiteralInput.coerce(variable_type, default_value, self)
|
|
49
55
|
end
|
|
50
56
|
end
|
|
51
57
|
end
|
|
@@ -37,7 +37,7 @@ module GraphQL
|
|
|
37
37
|
# Remove final newline from .txt file
|
|
38
38
|
github_digest = Net::HTTP.get(github_uri).chomp
|
|
39
39
|
|
|
40
|
-
docs_uri = URI("https://
|
|
40
|
+
docs_uri = URI("https://graphql-ruby.org/pro/checksums/graphql-pro-#{version}.txt")
|
|
41
41
|
docs_digest = Net::HTTP.get(docs_uri).chomp
|
|
42
42
|
|
|
43
43
|
if docs_digest == gem_digest && github_digest == gem_digest
|
|
@@ -18,10 +18,10 @@ module GraphQL
|
|
|
18
18
|
# Called when the extension is mounted with `extension(name, options)`.
|
|
19
19
|
# The instance is frozen to avoid improper use of state during execution.
|
|
20
20
|
# @param field [GraphQL::Schema::Field] The field where this extension was mounted
|
|
21
|
-
# @param options [Object] The second argument to `extension`, or `
|
|
21
|
+
# @param options [Object] The second argument to `extension`, or `{}` if nothing was passed.
|
|
22
22
|
def initialize(field:, options:)
|
|
23
23
|
@field = field
|
|
24
|
-
@options = options
|
|
24
|
+
@options = options || {}
|
|
25
25
|
apply
|
|
26
26
|
freeze
|
|
27
27
|
end
|
|
@@ -17,7 +17,9 @@ module GraphQL
|
|
|
17
17
|
@ruby_style_hash = @arguments.to_kwargs
|
|
18
18
|
end
|
|
19
19
|
# Apply prepares, not great to have it duplicated here.
|
|
20
|
+
@arguments_by_keyword = {}
|
|
20
21
|
self.class.arguments.each do |name, arg_defn|
|
|
22
|
+
@arguments_by_keyword[arg_defn.keyword] = arg_defn
|
|
21
23
|
ruby_kwargs_key = arg_defn.keyword
|
|
22
24
|
if @ruby_style_hash.key?(ruby_kwargs_key) && arg_defn.prepare
|
|
23
25
|
@ruby_style_hash[ruby_kwargs_key] = arg_defn.prepare_value(self, @ruby_style_hash[ruby_kwargs_key])
|
|
@@ -81,13 +83,20 @@ module GraphQL
|
|
|
81
83
|
# @return [Class<GraphQL::Arguments>]
|
|
82
84
|
attr_accessor :arguments_class
|
|
83
85
|
|
|
84
|
-
def argument(*
|
|
85
|
-
argument_defn = super
|
|
86
|
+
def argument(name, type, *rest, loads: nil, **kwargs, &block)
|
|
87
|
+
argument_defn = super(*argument_with_loads(name, type, *rest, loads: loads, **kwargs, &block))
|
|
86
88
|
# Add a method access
|
|
87
|
-
|
|
88
|
-
method_name = Member::BuildType.underscore(arg_name).to_sym
|
|
89
|
+
method_name = argument_defn.keyword
|
|
89
90
|
define_method(method_name) do
|
|
90
|
-
@ruby_style_hash[method_name]
|
|
91
|
+
value = @ruby_style_hash[method_name]
|
|
92
|
+
argument = @arguments_by_keyword[method_name]
|
|
93
|
+
if loads && argument_defn.type.list?
|
|
94
|
+
GraphQL::Execution::Lazy.all(value.map { |val| load_application_object(argument, loads, val) })
|
|
95
|
+
elsif loads
|
|
96
|
+
load_application_object(argument, loads, value)
|
|
97
|
+
else
|
|
98
|
+
value
|
|
99
|
+
end
|
|
91
100
|
end
|
|
92
101
|
end
|
|
93
102
|
|
|
@@ -113,4 +122,4 @@ module GraphQL
|
|
|
113
122
|
end
|
|
114
123
|
end
|
|
115
124
|
end
|
|
116
|
-
end
|
|
125
|
+
end
|
|
@@ -5,10 +5,33 @@ module GraphQL
|
|
|
5
5
|
module HasArguments
|
|
6
6
|
def self.included(cls)
|
|
7
7
|
cls.extend(ArgumentClassAccessor)
|
|
8
|
+
cls.include(ArgumentObjectLoader)
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
def self.extended(cls)
|
|
11
12
|
cls.extend(ArgumentClassAccessor)
|
|
13
|
+
cls.include(ArgumentObjectLoader)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def argument_with_loads(name, type, *rest, loads: nil, **kwargs)
|
|
17
|
+
if loads
|
|
18
|
+
name_as_string = name.to_s
|
|
19
|
+
|
|
20
|
+
inferred_arg_name = case name_as_string
|
|
21
|
+
when /_id$/
|
|
22
|
+
name_as_string.sub(/_id$/, "").to_sym
|
|
23
|
+
when /_ids$/
|
|
24
|
+
name_as_string.sub(/_ids$/, "")
|
|
25
|
+
.sub(/([^s])$/, "\\1s")
|
|
26
|
+
.to_sym
|
|
27
|
+
else
|
|
28
|
+
name
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
kwargs[:as] ||= inferred_arg_name
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
return [name, type, *rest, **kwargs]
|
|
12
35
|
end
|
|
13
36
|
|
|
14
37
|
# @see {GraphQL::Schema::Argument#initialize} for parameters
|
|
@@ -53,6 +76,63 @@ module GraphQL
|
|
|
53
76
|
end
|
|
54
77
|
end
|
|
55
78
|
|
|
79
|
+
module ArgumentObjectLoader
|
|
80
|
+
# Look up the corresponding object for a provided ID.
|
|
81
|
+
# By default, it uses Relay-style {Schema.object_from_id},
|
|
82
|
+
# override this to find objects another way.
|
|
83
|
+
#
|
|
84
|
+
# @param type [Class, Module] A GraphQL type definition
|
|
85
|
+
# @param id [String] A client-provided to look up
|
|
86
|
+
# @param context [GraphQL::Query::Context] the current context
|
|
87
|
+
def object_from_id(type, id, context)
|
|
88
|
+
context.schema.object_from_id(id, context)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def load_application_object(argument, lookup_as_type, id)
|
|
92
|
+
# See if any object can be found for this ID
|
|
93
|
+
loaded_application_object = object_from_id(lookup_as_type, id, context)
|
|
94
|
+
context.schema.after_lazy(loaded_application_object) do |application_object|
|
|
95
|
+
if application_object.nil?
|
|
96
|
+
err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
|
|
97
|
+
load_application_object_failed(err)
|
|
98
|
+
end
|
|
99
|
+
# Double-check that the located object is actually of this type
|
|
100
|
+
# (Don't want to allow arbitrary access to objects this way)
|
|
101
|
+
resolved_application_object_type = context.schema.resolve_type(lookup_as_type, application_object, context)
|
|
102
|
+
context.schema.after_lazy(resolved_application_object_type) do |application_object_type|
|
|
103
|
+
possible_object_types = context.schema.possible_types(lookup_as_type)
|
|
104
|
+
if !possible_object_types.include?(application_object_type)
|
|
105
|
+
err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
|
|
106
|
+
load_application_object_failed(err)
|
|
107
|
+
else
|
|
108
|
+
# This object was loaded successfully
|
|
109
|
+
# and resolved to the right type,
|
|
110
|
+
# now apply the `.authorized?` class method if there is one
|
|
111
|
+
if (class_based_type = application_object_type.metadata[:type_class])
|
|
112
|
+
context.schema.after_lazy(class_based_type.authorized?(application_object, context)) do |authed|
|
|
113
|
+
if authed
|
|
114
|
+
application_object
|
|
115
|
+
else
|
|
116
|
+
raise GraphQL::UnauthorizedError.new(
|
|
117
|
+
object: application_object,
|
|
118
|
+
type: class_based_type,
|
|
119
|
+
context: context,
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
else
|
|
124
|
+
application_object
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def load_application_object_failed(err)
|
|
132
|
+
raise err
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
56
136
|
def own_arguments
|
|
57
137
|
@own_arguments ||= {}
|
|
58
138
|
end
|