graphql 1.8.6 → 1.8.7

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.
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