graphql 1.9.3 → 1.9.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|