graphql 1.8.6 → 1.8.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/generators/graphql/mutation_generator.rb +1 -1
- data/lib/generators/graphql/templates/base_enum.erb +3 -1
- data/lib/generators/graphql/templates/base_input_object.erb +3 -1
- data/lib/generators/graphql/templates/base_interface.erb +4 -2
- data/lib/generators/graphql/templates/base_object.erb +3 -1
- data/lib/generators/graphql/templates/base_union.erb +3 -1
- data/lib/generators/graphql/templates/enum.erb +5 -3
- data/lib/generators/graphql/templates/interface.erb +6 -4
- data/lib/generators/graphql/templates/loader.erb +14 -12
- data/lib/generators/graphql/templates/mutation.erb +9 -6
- data/lib/generators/graphql/templates/mutation_type.erb +8 -6
- data/lib/generators/graphql/templates/object.erb +6 -4
- data/lib/generators/graphql/templates/query_type.erb +13 -11
- data/lib/generators/graphql/templates/union.erb +5 -3
- data/lib/graphql/argument.rb +19 -2
- data/lib/graphql/base_type.rb +1 -1
- data/lib/graphql/define/instance_definable.rb +2 -0
- data/lib/graphql/enum_type.rb +1 -0
- data/lib/graphql/execution/instrumentation.rb +28 -20
- data/lib/graphql/field.rb +3 -3
- data/lib/graphql/input_object_type.rb +2 -1
- data/lib/graphql/query.rb +1 -9
- data/lib/graphql/query/variables.rb +1 -18
- data/lib/graphql/relay.rb +0 -1
- data/lib/graphql/relay/mongo_relation_connection.rb +10 -0
- data/lib/graphql/relay/mutation.rb +2 -1
- data/lib/graphql/schema.rb +5 -4
- data/lib/graphql/schema/base_64_bp.rb +25 -0
- data/lib/graphql/schema/base_64_encoder.rb +5 -1
- data/lib/graphql/schema/field.rb +52 -22
- data/lib/graphql/schema/interface.rb +2 -0
- data/lib/graphql/schema/list.rb +3 -15
- data/lib/graphql/schema/member.rb +8 -4
- data/lib/graphql/schema/member/base_dsl_methods.rb +12 -6
- data/lib/graphql/schema/member/has_arguments.rb +7 -0
- data/lib/graphql/schema/member/relay_shortcuts.rb +47 -0
- data/lib/graphql/schema/member/scoped.rb +21 -0
- data/lib/graphql/schema/non_null.rb +8 -17
- data/lib/graphql/schema/resolver.rb +99 -76
- data/lib/graphql/schema/unique_within_type.rb +4 -1
- data/lib/graphql/schema/wrapper.rb +29 -0
- data/lib/graphql/static_validation/validation_context.rb +1 -3
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
- data/lib/graphql/type_kinds.rb +32 -8
- data/lib/graphql/types/relay/base_connection.rb +17 -3
- data/lib/graphql/types/relay/base_edge.rb +12 -0
- data/lib/graphql/version.rb +1 -1
- data/spec/generators/graphql/enum_generator_spec.rb +8 -6
- data/spec/generators/graphql/install_generator_spec.rb +24 -20
- data/spec/generators/graphql/interface_generator_spec.rb +6 -4
- data/spec/generators/graphql/loader_generator_spec.rb +30 -26
- data/spec/generators/graphql/mutation_generator_spec.rb +18 -13
- data/spec/generators/graphql/object_generator_spec.rb +12 -6
- data/spec/generators/graphql/union_generator_spec.rb +10 -4
- data/spec/graphql/authorization_spec.rb +55 -14
- data/spec/graphql/input_object_type_spec.rb +11 -0
- data/spec/graphql/language/generation_spec.rb +8 -6
- data/spec/graphql/language/nodes_spec.rb +8 -6
- data/spec/graphql/relay/connection_instrumentation_spec.rb +1 -1
- data/spec/graphql/relay/mongo_relation_connection_spec.rb +54 -0
- data/spec/graphql/schema/field_spec.rb +9 -0
- data/spec/graphql/schema/list_spec.rb +46 -0
- data/spec/graphql/schema/member/scoped_spec.rb +161 -0
- data/spec/graphql/schema/non_null_spec.rb +46 -0
- data/spec/graphql/schema/object_spec.rb +15 -0
- data/spec/graphql/schema/resolver_spec.rb +165 -25
- data/spec/graphql/subscriptions/serialize_spec.rb +0 -22
- data/spec/graphql/subscriptions_spec.rb +19 -31
- data/spec/support/global_id.rb +23 -0
- data/spec/support/lazy_helpers.rb +1 -1
- data/spec/support/star_trek/data.rb +19 -2
- data/spec/support/star_trek/schema.rb +37 -22
- data/spec/support/star_wars/schema.rb +24 -17
- metadata +220 -208
@@ -2,20 +2,19 @@
|
|
2
2
|
|
3
3
|
module GraphQL
|
4
4
|
class Schema
|
5
|
+
# Represents a non null type in the schema.
|
5
6
|
# Wraps a {Schema::Member} when it is required.
|
6
7
|
# @see {Schema::Member::TypeSystemHelpers#to_non_null_type}
|
7
|
-
class NonNull
|
8
|
-
include GraphQL::Schema::Member::CachedGraphQLDefinition
|
9
|
-
include GraphQL::Schema::Member::TypeSystemHelpers
|
10
|
-
attr_reader :of_type
|
11
|
-
def initialize(of_type)
|
12
|
-
@of_type = of_type
|
13
|
-
end
|
14
|
-
|
8
|
+
class NonNull < GraphQL::Schema::Wrapper
|
15
9
|
def to_graphql
|
16
10
|
@of_type.graphql_definition.to_non_null_type
|
17
11
|
end
|
18
12
|
|
13
|
+
# @return [GraphQL::TypeKinds::NON_NULL]
|
14
|
+
def kind
|
15
|
+
GraphQL::TypeKinds::NON_NULL
|
16
|
+
end
|
17
|
+
|
19
18
|
# @return [true]
|
20
19
|
def non_null?
|
21
20
|
true
|
@@ -25,15 +24,7 @@ module GraphQL
|
|
25
24
|
def list?
|
26
25
|
@of_type.list?
|
27
26
|
end
|
28
|
-
|
29
|
-
def kind
|
30
|
-
GraphQL::TypeKinds::NON_NULL
|
31
|
-
end
|
32
|
-
|
33
|
-
def unwrap
|
34
|
-
@of_type.unwrap
|
35
|
-
end
|
36
|
-
|
27
|
+
|
37
28
|
def to_type_signature
|
38
29
|
"#{@of_type.to_type_signature}!"
|
39
30
|
end
|
@@ -48,25 +48,50 @@ module GraphQL
|
|
48
48
|
# the user-defined `#resolve` method.
|
49
49
|
# @api private
|
50
50
|
def resolve_with_support(**args)
|
51
|
-
# First call the
|
52
|
-
|
53
|
-
|
51
|
+
# First call the ready? hook which may raise
|
52
|
+
ready_val = if args.any?
|
53
|
+
ready?(**args)
|
54
54
|
else
|
55
|
-
|
55
|
+
ready?
|
56
56
|
end
|
57
|
-
context.schema.after_lazy(
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
57
|
+
context.schema.after_lazy(ready_val) do |is_ready, ready_early_return|
|
58
|
+
if ready_early_return
|
59
|
+
if is_ready != false
|
60
|
+
raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{authorized_result.inspect}, #{ready_early_return.inspect}]"
|
61
|
+
else
|
62
|
+
ready_early_return
|
63
|
+
end
|
64
|
+
elsif is_ready
|
65
|
+
# Then call each prepare hook, which may return a different value
|
66
|
+
# for that argument, or may return a lazy object
|
67
|
+
load_arguments_val = load_arguments(args)
|
68
|
+
context.schema.after_lazy(load_arguments_val) do |loaded_args|
|
69
|
+
# Then call `authorized?`, which may raise or may return a lazy object
|
70
|
+
authorized_val = if loaded_args.any?
|
71
|
+
authorized?(loaded_args)
|
68
72
|
else
|
69
|
-
|
73
|
+
authorized?
|
74
|
+
end
|
75
|
+
authorized?(loaded_args)
|
76
|
+
context.schema.after_lazy(authorized_val) do |(authorized_result, early_return)|
|
77
|
+
# If the `authorized?` returned two values, `false, early_return`,
|
78
|
+
# then use the early return value instead of continuing
|
79
|
+
if early_return
|
80
|
+
if authorized_result == false
|
81
|
+
early_return
|
82
|
+
else
|
83
|
+
raise "Unexpected result from #authorized? (expected `true`, `false` or `[false, {...}]`): [#{authorized_result.inspect}, #{early_return.inspect}]"
|
84
|
+
end
|
85
|
+
elsif authorized_result
|
86
|
+
# Finally, all the hooks have passed, so resolve it
|
87
|
+
if loaded_args.any?
|
88
|
+
public_send(self.class.resolve_method, **loaded_args)
|
89
|
+
else
|
90
|
+
public_send(self.class.resolve_method)
|
91
|
+
end
|
92
|
+
else
|
93
|
+
nil
|
94
|
+
end
|
70
95
|
end
|
71
96
|
end
|
72
97
|
end
|
@@ -88,7 +113,20 @@ module GraphQL
|
|
88
113
|
# @param args [Hash] The input arguments, if there are any
|
89
114
|
# @raise [GraphQL::ExecutionError] To add an error to the response
|
90
115
|
# @raise [GraphQL::UnauthorizedError] To signal an authorization failure
|
91
|
-
|
116
|
+
# @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.)
|
117
|
+
def ready?(**args)
|
118
|
+
true
|
119
|
+
end
|
120
|
+
|
121
|
+
# Called after arguments are loaded, but before resolving.
|
122
|
+
#
|
123
|
+
# Override it to check everything before calling the mutation.
|
124
|
+
# @param args [Hash] The input arguments
|
125
|
+
# @raise [GraphQL::ExecutionError] To add an error to the response
|
126
|
+
# @raise [GraphQL::UnauthorizedError] To signal an authorization failure
|
127
|
+
# @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.)
|
128
|
+
def authorized?(**args)
|
129
|
+
true
|
92
130
|
end
|
93
131
|
|
94
132
|
private
|
@@ -124,31 +162,6 @@ module GraphQL
|
|
124
162
|
public_send("load_#{name}", value)
|
125
163
|
end
|
126
164
|
|
127
|
-
# TODO dedup with load_arguments
|
128
|
-
def validate_arguments(args)
|
129
|
-
validate_lazies = []
|
130
|
-
args.each do |key, value|
|
131
|
-
arg_defn = @arguments_by_keyword[key]
|
132
|
-
if arg_defn
|
133
|
-
validate_return = validate_argument(key, value)
|
134
|
-
if context.schema.lazy?(validate_return)
|
135
|
-
validate_lazies << context.schema.after_lazy(validate_return).then { "no-op" }
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
# Avoid returning a lazy if none are needed
|
141
|
-
if validate_lazies.any?
|
142
|
-
GraphQL::Execution::Lazy.all(validate_lazies).then { args }
|
143
|
-
else
|
144
|
-
args
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def validate_argument(name, value)
|
149
|
-
public_send("validate_#{name}", value)
|
150
|
-
end
|
151
|
-
|
152
165
|
class LoadApplicationObjectFailedError < GraphQL::ExecutionError
|
153
166
|
# @return [GraphQL::Schema::Argument] the argument definition for the argument that was looked up
|
154
167
|
attr_reader :argument
|
@@ -164,43 +177,58 @@ module GraphQL
|
|
164
177
|
end
|
165
178
|
end
|
166
179
|
|
180
|
+
# Look up the corresponding object for a provided ID.
|
181
|
+
# By default, it uses Relay-style {Schema.object_from_id},
|
182
|
+
# override this to find objects another way.
|
183
|
+
#
|
184
|
+
# @param type [Class, Module] A GraphQL type definition
|
185
|
+
# @param id [String] A client-provided to look up
|
186
|
+
# @param context [GraphQL::Query::Context] the current context
|
187
|
+
def object_from_id(type, id, context)
|
188
|
+
context.schema.object_from_id(id, context)
|
189
|
+
end
|
190
|
+
|
167
191
|
def load_application_object(arg_kwarg, id)
|
168
192
|
argument = @arguments_by_keyword[arg_kwarg]
|
169
|
-
# See if any object can be found for this ID
|
170
|
-
application_object = context.schema.object_from_id(id, context)
|
171
|
-
if application_object.nil?
|
172
|
-
raise LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
|
173
|
-
end
|
174
|
-
# Double-check that the located object is actually of this type
|
175
|
-
# (Don't want to allow arbitrary access to objects this way)
|
176
193
|
lookup_as_type = @arguments_loads_as_type[arg_kwarg]
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
context.schema.
|
187
|
-
|
188
|
-
|
194
|
+
# See if any object can be found for this ID
|
195
|
+
loaded_application_object = object_from_id(lookup_as_type, id, context)
|
196
|
+
context.schema.after_lazy(loaded_application_object) do |application_object|
|
197
|
+
begin
|
198
|
+
if application_object.nil?
|
199
|
+
raise LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
|
200
|
+
end
|
201
|
+
# Double-check that the located object is actually of this type
|
202
|
+
# (Don't want to allow arbitrary access to objects this way)
|
203
|
+
application_object_type = context.schema.resolve_type(lookup_as_type, application_object, context)
|
204
|
+
possible_object_types = context.schema.possible_types(lookup_as_type)
|
205
|
+
if !possible_object_types.include?(application_object_type)
|
206
|
+
raise LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
|
207
|
+
else
|
208
|
+
# This object was loaded successfully
|
209
|
+
# and resolved to the right type,
|
210
|
+
# now apply the `.authorized?` class method if there is one
|
211
|
+
if (class_based_type = application_object_type.metadata[:type_class])
|
212
|
+
context.schema.after_lazy(class_based_type.authorized?(application_object, context)) do |authed|
|
213
|
+
if authed
|
214
|
+
application_object
|
215
|
+
else
|
216
|
+
raise GraphQL::UnauthorizedError.new(
|
217
|
+
object: application_object,
|
218
|
+
type: class_based_type,
|
219
|
+
context: context,
|
220
|
+
)
|
221
|
+
end
|
222
|
+
end
|
189
223
|
else
|
190
|
-
|
191
|
-
object: application_object,
|
192
|
-
type: class_based_type,
|
193
|
-
context: context,
|
194
|
-
)
|
224
|
+
application_object
|
195
225
|
end
|
196
226
|
end
|
197
|
-
|
198
|
-
|
227
|
+
rescue LoadApplicationObjectFailedError => err
|
228
|
+
# pass it to a handler
|
229
|
+
load_application_object_failed(err)
|
199
230
|
end
|
200
231
|
end
|
201
|
-
rescue LoadApplicationObjectFailedError => err
|
202
|
-
# pass it to a handler
|
203
|
-
load_application_object_failed(err)
|
204
232
|
end
|
205
233
|
|
206
234
|
def load_application_object_failed(err)
|
@@ -315,11 +343,6 @@ module GraphQL
|
|
315
343
|
RUBY
|
316
344
|
end
|
317
345
|
|
318
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
319
|
-
def validate_#{arg_defn.keyword}(value)
|
320
|
-
# No-op
|
321
|
-
end
|
322
|
-
RUBY
|
323
346
|
arg_defn
|
324
347
|
end
|
325
348
|
|
@@ -1,4 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require 'graphql/schema/base_64_bp'
|
3
|
+
|
2
4
|
module GraphQL
|
3
5
|
class Schema
|
4
6
|
module UniqueWithinType
|
@@ -25,7 +27,8 @@ module GraphQL
|
|
25
27
|
# @param node_id [String] A unique ID generated by {.encode}
|
26
28
|
# @return [Array<(String, String)>] The type name & value passed to {.encode}
|
27
29
|
def decode(node_id, separator: self.default_id_separator)
|
28
|
-
|
30
|
+
# urlsafe_decode64 is for forward compatibility
|
31
|
+
Base64Bp.urlsafe_decode64(node_id).split(separator, 2)
|
29
32
|
end
|
30
33
|
end
|
31
34
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Wrapper
|
6
|
+
include GraphQL::Schema::Member::CachedGraphQLDefinition
|
7
|
+
include GraphQL::Schema::Member::TypeSystemHelpers
|
8
|
+
|
9
|
+
# @return [Class, Module] The inner type of this wrapping type, the type of which one or more objects may be present.
|
10
|
+
attr_reader :of_type
|
11
|
+
|
12
|
+
def initialize(of_type)
|
13
|
+
@of_type = of_type
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_graphql
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
def unwrap
|
21
|
+
@of_type.unwrap
|
22
|
+
end
|
23
|
+
|
24
|
+
def ==(other)
|
25
|
+
self.class == other.class && of_type == other.of_type
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -14,9 +14,7 @@ module GraphQL
|
|
14
14
|
class ValidationContext
|
15
15
|
extend Forwardable
|
16
16
|
|
17
|
-
attr_reader :query, :
|
18
|
-
:document, :errors, :visitor,
|
19
|
-
:warden, :dependencies, :each_irep_node_handlers
|
17
|
+
attr_reader :query, :errors, :visitor, :dependencies, :each_irep_node_handlers
|
20
18
|
|
21
19
|
def_delegators :@query, :schema, :document, :fragments, :operations, :warden
|
22
20
|
|
data/lib/graphql/type_kinds.rb
CHANGED
@@ -29,6 +29,38 @@ module GraphQL
|
|
29
29
|
def to_s; @name; end
|
30
30
|
# Is this TypeKind composed of many values?
|
31
31
|
def composite?; @composite; end
|
32
|
+
|
33
|
+
def scalar?
|
34
|
+
self == TypeKinds::SCALAR
|
35
|
+
end
|
36
|
+
|
37
|
+
def object?
|
38
|
+
self == TypeKinds::OBJECT
|
39
|
+
end
|
40
|
+
|
41
|
+
def interface?
|
42
|
+
self == TypeKinds::INTERFACE
|
43
|
+
end
|
44
|
+
|
45
|
+
def union?
|
46
|
+
self == TypeKinds::UNION
|
47
|
+
end
|
48
|
+
|
49
|
+
def enum?
|
50
|
+
self == TypeKinds::ENUM
|
51
|
+
end
|
52
|
+
|
53
|
+
def input_object?
|
54
|
+
self == TypeKinds::INPUT_OBJECT
|
55
|
+
end
|
56
|
+
|
57
|
+
def list?
|
58
|
+
self == TypeKinds::LIST
|
59
|
+
end
|
60
|
+
|
61
|
+
def non_null?
|
62
|
+
self == TypeKinds::NON_NULL
|
63
|
+
end
|
32
64
|
end
|
33
65
|
|
34
66
|
TYPE_KINDS = [
|
@@ -41,13 +73,5 @@ module GraphQL
|
|
41
73
|
LIST = TypeKind.new("LIST", wraps: true, description: 'Indicates this type is a list. `ofType` is a valid field.'),
|
42
74
|
NON_NULL = TypeKind.new("NON_NULL", wraps: true, description: 'Indicates this type is a non-null. `ofType` is a valid field.'),
|
43
75
|
]
|
44
|
-
|
45
|
-
class TypeKind
|
46
|
-
TYPE_KINDS.map(&:name).each do |kind_name|
|
47
|
-
define_method("#{kind_name.downcase}?") do
|
48
|
-
self.name == kind_name
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
76
|
end
|
53
77
|
end
|
@@ -35,9 +35,6 @@ module GraphQL
|
|
35
35
|
# @return [Class]
|
36
36
|
attr_reader :node_type
|
37
37
|
|
38
|
-
# @return [Class]
|
39
|
-
attr_reader :edge_type
|
40
|
-
|
41
38
|
# Configure this connection to return `edges` and `nodes` based on `edge_type_class`.
|
42
39
|
#
|
43
40
|
# This method will use the inputs to create:
|
@@ -66,11 +63,28 @@ module GraphQL
|
|
66
63
|
description("The connection type for #{node_type_name}.")
|
67
64
|
end
|
68
65
|
|
66
|
+
# Filter this list according to the way its node type would scope them
|
67
|
+
def scope_items(items, context)
|
68
|
+
node_type.scope_items(items, context)
|
69
|
+
end
|
70
|
+
|
69
71
|
# Add the shortcut `nodes` field to this connection and its subclasses
|
70
72
|
def nodes_field
|
71
73
|
define_nodes_field
|
72
74
|
end
|
73
75
|
|
76
|
+
def authorized?(obj, ctx)
|
77
|
+
true # Let nodes be filtered out
|
78
|
+
end
|
79
|
+
|
80
|
+
def accessible?(ctx)
|
81
|
+
node_type.accessible?(ctx)
|
82
|
+
end
|
83
|
+
|
84
|
+
def visible?(ctx)
|
85
|
+
node_type.visible?(ctx)
|
86
|
+
end
|
87
|
+
|
74
88
|
private
|
75
89
|
|
76
90
|
def define_nodes_field
|
data/lib/graphql/version.rb
CHANGED