graphql 1.8.6 → 1.8.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +5 -5
  2. data/lib/generators/graphql/mutation_generator.rb +1 -1
  3. data/lib/generators/graphql/templates/base_enum.erb +3 -1
  4. data/lib/generators/graphql/templates/base_input_object.erb +3 -1
  5. data/lib/generators/graphql/templates/base_interface.erb +4 -2
  6. data/lib/generators/graphql/templates/base_object.erb +3 -1
  7. data/lib/generators/graphql/templates/base_union.erb +3 -1
  8. data/lib/generators/graphql/templates/enum.erb +5 -3
  9. data/lib/generators/graphql/templates/interface.erb +6 -4
  10. data/lib/generators/graphql/templates/loader.erb +14 -12
  11. data/lib/generators/graphql/templates/mutation.erb +9 -6
  12. data/lib/generators/graphql/templates/mutation_type.erb +8 -6
  13. data/lib/generators/graphql/templates/object.erb +6 -4
  14. data/lib/generators/graphql/templates/query_type.erb +13 -11
  15. data/lib/generators/graphql/templates/union.erb +5 -3
  16. data/lib/graphql/argument.rb +19 -2
  17. data/lib/graphql/base_type.rb +1 -1
  18. data/lib/graphql/define/instance_definable.rb +2 -0
  19. data/lib/graphql/enum_type.rb +1 -0
  20. data/lib/graphql/execution/instrumentation.rb +28 -20
  21. data/lib/graphql/field.rb +3 -3
  22. data/lib/graphql/input_object_type.rb +2 -1
  23. data/lib/graphql/query.rb +1 -9
  24. data/lib/graphql/query/variables.rb +1 -18
  25. data/lib/graphql/relay.rb +0 -1
  26. data/lib/graphql/relay/mongo_relation_connection.rb +10 -0
  27. data/lib/graphql/relay/mutation.rb +2 -1
  28. data/lib/graphql/schema.rb +5 -4
  29. data/lib/graphql/schema/base_64_bp.rb +25 -0
  30. data/lib/graphql/schema/base_64_encoder.rb +5 -1
  31. data/lib/graphql/schema/field.rb +52 -22
  32. data/lib/graphql/schema/interface.rb +2 -0
  33. data/lib/graphql/schema/list.rb +3 -15
  34. data/lib/graphql/schema/member.rb +8 -4
  35. data/lib/graphql/schema/member/base_dsl_methods.rb +12 -6
  36. data/lib/graphql/schema/member/has_arguments.rb +7 -0
  37. data/lib/graphql/schema/member/relay_shortcuts.rb +47 -0
  38. data/lib/graphql/schema/member/scoped.rb +21 -0
  39. data/lib/graphql/schema/non_null.rb +8 -17
  40. data/lib/graphql/schema/resolver.rb +99 -76
  41. data/lib/graphql/schema/unique_within_type.rb +4 -1
  42. data/lib/graphql/schema/wrapper.rb +29 -0
  43. data/lib/graphql/static_validation/validation_context.rb +1 -3
  44. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  45. data/lib/graphql/type_kinds.rb +32 -8
  46. data/lib/graphql/types/relay/base_connection.rb +17 -3
  47. data/lib/graphql/types/relay/base_edge.rb +12 -0
  48. data/lib/graphql/version.rb +1 -1
  49. data/spec/generators/graphql/enum_generator_spec.rb +8 -6
  50. data/spec/generators/graphql/install_generator_spec.rb +24 -20
  51. data/spec/generators/graphql/interface_generator_spec.rb +6 -4
  52. data/spec/generators/graphql/loader_generator_spec.rb +30 -26
  53. data/spec/generators/graphql/mutation_generator_spec.rb +18 -13
  54. data/spec/generators/graphql/object_generator_spec.rb +12 -6
  55. data/spec/generators/graphql/union_generator_spec.rb +10 -4
  56. data/spec/graphql/authorization_spec.rb +55 -14
  57. data/spec/graphql/input_object_type_spec.rb +11 -0
  58. data/spec/graphql/language/generation_spec.rb +8 -6
  59. data/spec/graphql/language/nodes_spec.rb +8 -6
  60. data/spec/graphql/relay/connection_instrumentation_spec.rb +1 -1
  61. data/spec/graphql/relay/mongo_relation_connection_spec.rb +54 -0
  62. data/spec/graphql/schema/field_spec.rb +9 -0
  63. data/spec/graphql/schema/list_spec.rb +46 -0
  64. data/spec/graphql/schema/member/scoped_spec.rb +161 -0
  65. data/spec/graphql/schema/non_null_spec.rb +46 -0
  66. data/spec/graphql/schema/object_spec.rb +15 -0
  67. data/spec/graphql/schema/resolver_spec.rb +165 -25
  68. data/spec/graphql/subscriptions/serialize_spec.rb +0 -22
  69. data/spec/graphql/subscriptions_spec.rb +19 -31
  70. data/spec/support/global_id.rb +23 -0
  71. data/spec/support/lazy_helpers.rb +1 -1
  72. data/spec/support/star_trek/data.rb +19 -2
  73. data/spec/support/star_trek/schema.rb +37 -22
  74. data/spec/support/star_wars/schema.rb +24 -17
  75. 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 before_prepare hook which may raise
52
- before_prepare_val = if args.any?
53
- before_prepare(**args)
51
+ # First call the ready? hook which may raise
52
+ ready_val = if args.any?
53
+ ready?(**args)
54
54
  else
55
- before_prepare
55
+ ready?
56
56
  end
57
- context.schema.after_lazy(before_prepare_val) do
58
- # Then call each prepare hook, which may return a different value
59
- # for that argument, or may return a lazy object
60
- load_arguments_val = load_arguments(args)
61
- context.schema.after_lazy(load_arguments_val) do |loaded_args|
62
- # Then validate each argument, which may raise or may return a lazy object
63
- validate_arguments_val = validate_arguments(loaded_args)
64
- context.schema.after_lazy(validate_arguments_val) do |validated_args|
65
- # Finally, all the hooks have passed, so resolve it
66
- if validated_args.any?
67
- public_send(self.class.resolve_method, **validated_args)
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
- public_send(self.class.resolve_method)
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
- def before_prepare(**args)
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
- application_object_type = context.schema.resolve_type(lookup_as_type, application_object, context)
178
- possible_object_types = context.schema.possible_types(lookup_as_type)
179
- if !possible_object_types.include?(application_object_type)
180
- raise LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
181
- else
182
- # This object was loaded successfully
183
- # and resolved to the right type,
184
- # now apply the `.authorized?` class method if there is one
185
- if (class_based_type = application_object_type.metadata[:type_class])
186
- context.schema.after_lazy(class_based_type.authorized?(application_object, context)) do |authed|
187
- if authed
188
- application_object
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
- raise GraphQL::UnauthorizedError.new(
191
- object: application_object,
192
- type: class_based_type,
193
- context: context,
194
- )
224
+ application_object
195
225
  end
196
226
  end
197
- else
198
- application_object
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
- Base64.decode64(node_id).split(separator, 2)
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, :schema,
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
 
@@ -39,7 +39,7 @@ module GraphQL
39
39
  # })
40
40
  #
41
41
  # payload = {
42
- # result: result.subscription? ? {data: nil} : result.to_h,
42
+ # result: result.subscription? ? nil : result.to_h,
43
43
  # more: result.subscription?,
44
44
  # }
45
45
  #
@@ -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
@@ -39,6 +39,18 @@ module GraphQL
39
39
  end
40
40
  @node_type
41
41
  end
42
+
43
+ def authorized?(obj, ctx)
44
+ true
45
+ end
46
+
47
+ def accessible?(ctx)
48
+ node_type.accessible?(ctx)
49
+ end
50
+
51
+ def visible?(ctx)
52
+ node_type.visible?(ctx)
53
+ end
42
54
  end
43
55
 
44
56
 
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.8.6"
3
+ VERSION = "1.8.7"
4
4
  end