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
@@ -101,7 +101,8 @@ module GraphQL
101
101
  return_field: GraphQL::Define::AssignObjectField,
102
102
  function: GraphQL::Define::AssignMutationFunction,
103
103
  )
104
- attr_accessor :name, :description, :fields, :arguments, :return_type, :return_interfaces
104
+ attr_accessor :name, :description, :fields, :arguments
105
+ attr_writer :return_type, :return_interfaces
105
106
 
106
107
  ensure_defined(
107
108
  :input_fields, :return_fields, :name, :description,
@@ -21,6 +21,7 @@ require "graphql/schema/build_from_definition"
21
21
 
22
22
 
23
23
  require "graphql/schema/member"
24
+ require "graphql/schema/wrapper"
24
25
  require "graphql/schema/list"
25
26
  require "graphql/schema/non_null"
26
27
  require "graphql/schema/argument"
@@ -122,7 +123,7 @@ module GraphQL
122
123
  attr_accessor :context_class
123
124
 
124
125
  class << self
125
- attr_accessor :default_execution_strategy
126
+ attr_writer :default_execution_strategy
126
127
  end
127
128
 
128
129
  def default_filter
@@ -654,7 +655,7 @@ module GraphQL
654
655
  # Execution
655
656
  :execute, :multiplex,
656
657
  :static_validator, :introspection_system,
657
- :query_analyzers, :middleware, :tracers, :instrumenters,
658
+ :query_analyzers, :tracers, :instrumenters,
658
659
  :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
659
660
  :validate, :multiplex_analyzers, :lazy?, :lazy_method_name, :after_lazy,
660
661
  # Configuration
@@ -663,7 +664,7 @@ module GraphQL
663
664
  :default_mask,
664
665
  :default_filter, :redefine,
665
666
  :id_from_object_proc, :object_from_id_proc,
666
- :id_from_object=, :object_from_id=, :type_error,
667
+ :id_from_object=, :object_from_id=,
667
668
  :remove_handler,
668
669
  # Members
669
670
  :types, :get_fields, :find,
@@ -671,7 +672,7 @@ module GraphQL
671
672
  :subscriptions,
672
673
  :union_memberships,
673
674
  :get_field, :root_types, :references_to, :type_from_ast,
674
- :possible_types, :get_field
675
+ :possible_types
675
676
 
676
677
  def graphql_definition
677
678
  @graphql_definition ||= to_graphql
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ # backport from ruby v2.5 to v2.2 that has no `padding` things
6
+ # @api private
7
+ module Base64Bp
8
+ extend Base64
9
+
10
+ module_function
11
+
12
+ def urlsafe_encode64(bin, padding:)
13
+ str = strict_encode64(bin).tr("+/", "-_")
14
+ str = str.delete("=") unless padding
15
+ str
16
+ end
17
+
18
+ def urlsafe_decode64(str)
19
+ str = str.tr("-_", "+/")
20
+ if !str.end_with?("=") && str.length % 4 != 0
21
+ str = str.ljust((str.length + 3) & ~3, "=")
22
+ end
23
+ strict_decode64(str)
24
+ end
25
+ end
@@ -1,4 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require 'graphql/schema/base_64_bp'
4
+
2
5
  module GraphQL
3
6
  class Schema
4
7
  # @api private
@@ -8,7 +11,8 @@ module GraphQL
8
11
  end
9
12
 
10
13
  def self.decode(ciphertext, nonce: false)
11
- Base64.decode64(ciphertext)
14
+ # urlsafe_decode64 is for forward compatibility
15
+ Base64Bp.urlsafe_decode64(ciphertext)
12
16
  end
13
17
  end
14
18
  end
@@ -11,8 +11,7 @@ module GraphQL
11
11
  attr_reader :name
12
12
  alias :graphql_name :name
13
13
 
14
- # @return [String]
15
- attr_accessor :description
14
+ attr_writer :description
16
15
 
17
16
  # @return [String, nil] If present, the field is marked as deprecated with this documentation
18
17
  attr_accessor :deprecation_reason
@@ -77,6 +76,34 @@ module GraphQL
77
76
  new(**kwargs, &block)
78
77
  end
79
78
 
79
+ # Can be set with `connection: true|false` or inferred from a type name ending in `*Connection`
80
+ # @return [Boolean] if true, this field will be wrapped with Relay connection behavior
81
+ def connection?
82
+ if @connection.nil?
83
+ # Provide default based on type name
84
+ return_type_name = if (contains_type = @field || @function)
85
+ Member::BuildType.to_type_name(contains_type.type)
86
+ elsif @return_type_expr
87
+ Member::BuildType.to_type_name(@return_type_expr)
88
+ else
89
+ raise "No connection info possible"
90
+ end
91
+ @connection = return_type_name.end_with?("Connection")
92
+ else
93
+ @connection
94
+ end
95
+ end
96
+
97
+ # @return [Boolean] if true, the return type's `.scope_items` method will be applied to this field's return value
98
+ def scoped?
99
+ if !@scope.nil?
100
+ # The default was overridden
101
+ @scope
102
+ else
103
+ @return_type_expr && type.unwrap.respond_to?(:scope_items) && (connection? || type.list?)
104
+ end
105
+ end
106
+
80
107
  # @param name [Symbol] The underscore-cased version of this field name (will be camelized for the GraphQL API)
81
108
  # @param return_type_expr [Class, GraphQL::BaseType, Array] The return type of this field
82
109
  # @param desc [String] Field description
@@ -96,13 +123,14 @@ module GraphQL
96
123
  # @param arguments [{String=>GraphQL::Schema::Argument, Hash}] Arguments for this field (may be added in the block, also)
97
124
  # @param camelize [Boolean] If true, the field name will be camelized when building the schema
98
125
  # @param complexity [Numeric] When provided, set the complexity for this field
126
+ # @param scope [Boolean] If true, the return type's `.scope_items` method will be called on the return value
99
127
  # @param subscription_scope [Symbol, String] A key in `context` which will be used to scope subscription payloads
100
- def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, connection: nil, max_page_size: nil, resolve: nil, introspection: false, hash_key: nil, camelize: true, complexity: 1, extras: [], resolver_class: nil, subscription_scope: nil, arguments: {}, &definition_block)
128
+ def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, connection: nil, max_page_size: nil, scope: nil, resolve: nil, introspection: false, hash_key: nil, camelize: true, complexity: 1, extras: [], resolver_class: nil, subscription_scope: nil, arguments: {}, &definition_block)
101
129
 
102
130
  if name.nil?
103
131
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
104
132
  end
105
- if !(field || function || mutation || resolver)
133
+ if !(field || function || resolver_class)
106
134
  if type.nil?
107
135
  raise ArgumentError, "missing second `type` argument or keyword `type:`"
108
136
  end
@@ -110,8 +138,8 @@ module GraphQL
110
138
  raise ArgumentError, "missing keyword argument null:"
111
139
  end
112
140
  end
113
- if (field || function || resolve || mutation) && extras.any?
114
- raise ArgumentError, "keyword `extras:` may only be used with method-based resolve, please remove `field:`, `function:`, `resolve:`, or `mutation:`"
141
+ if (field || function || resolve) && extras.any?
142
+ raise ArgumentError, "keyword `extras:` may only be used with method-based resolve and class-based field such as mutation class, please remove `field:`, `function:` or `resolve:`"
115
143
  end
116
144
  @name = camelize ? Member::BuildType.camelize(name.to_s) : name.to_s
117
145
  @description = description
@@ -140,6 +168,7 @@ module GraphQL
140
168
  @introspection = introspection
141
169
  @extras = extras
142
170
  @resolver_class = resolver_class
171
+ @scope = scope
143
172
 
144
173
  # Override the default from HasArguments
145
174
  @own_arguments = {}
@@ -163,6 +192,8 @@ module GraphQL
163
192
  end
164
193
  end
165
194
 
195
+ # @param text [String]
196
+ # @return [String]
166
197
  def description(text = nil)
167
198
  if text
168
199
  @description = text
@@ -211,18 +242,6 @@ module GraphQL
211
242
  field_defn.type = -> { type }
212
243
  end
213
244
 
214
- if @connection.nil?
215
- # Provide default based on type name
216
- return_type_name = if @field || @function
217
- Member::BuildType.to_type_name(field_defn.type)
218
- elsif @return_type_expr
219
- Member::BuildType.to_type_name(@return_type_expr)
220
- else
221
- raise "No connection info possible"
222
- end
223
- @connection = return_type_name.end_with?("Connection")
224
- end
225
-
226
245
  if @description
227
246
  field_defn.description = @description
228
247
  end
@@ -239,14 +258,14 @@ module GraphQL
239
258
  end
240
259
 
241
260
  field_defn.resolve = self.method(:resolve_field)
242
- field_defn.connection = @connection
261
+ field_defn.connection = connection?
243
262
  field_defn.connection_max_page_size = @max_page_size
244
263
  field_defn.introspection = @introspection
245
264
  field_defn.complexity = @complexity
246
265
  field_defn.subscription_scope = @subscription_scope
247
266
 
248
267
  # apply this first, so it can be overriden below
249
- if @connection
268
+ if connection?
250
269
  # TODO: this could be a bit weird, because these fields won't be present
251
270
  # after initialization, only in the `to_graphql` response.
252
271
  # This calculation _could_ be moved up if need be.
@@ -316,7 +335,7 @@ module GraphQL
316
335
  inner_obj = after_obj && after_obj.object
317
336
  if authorized?(inner_obj, query_ctx) && arguments.each_value.all? { |a| a.authorized?(inner_obj, query_ctx) }
318
337
  # Then if it passed, resolve the field
319
- if @resolve_proc
338
+ v = if @resolve_proc
320
339
  # Might be nil, still want to call the func in that case
321
340
  @resolve_proc.call(inner_obj, args, ctx)
322
341
  elsif @resolver_class
@@ -325,6 +344,7 @@ module GraphQL
325
344
  else
326
345
  public_send_field(after_obj, args, ctx)
327
346
  end
347
+ apply_scope(v, ctx)
328
348
  else
329
349
  nil
330
350
  end
@@ -370,6 +390,16 @@ module GraphQL
370
390
 
371
391
  private
372
392
 
393
+ def apply_scope(value, ctx)
394
+ if scoped?
395
+ ctx.schema.after_lazy(value) do |inner_value|
396
+ @type.unwrap.scope_items(inner_value, ctx)
397
+ end
398
+ else
399
+ value
400
+ end
401
+ end
402
+
373
403
  NO_ARGS = {}.freeze
374
404
 
375
405
  def public_send_field(obj, graphql_args, field_ctx)
@@ -384,7 +414,7 @@ module GraphQL
384
414
  end
385
415
  end
386
416
 
387
- if @connection
417
+ if connection?
388
418
  # Remove pagination args before passing it to a user method
389
419
  ruby_kwargs.delete(:first)
390
420
  ruby_kwargs.delete(:last)
@@ -9,6 +9,8 @@ module GraphQL
9
9
  include GraphQL::Schema::Member::BaseDSLMethods
10
10
  include GraphQL::Schema::Member::TypeSystemHelpers
11
11
  include GraphQL::Schema::Member::HasFields
12
+ include GraphQL::Schema::Member::RelayShortcuts
13
+ include GraphQL::Schema::Member::Scoped
12
14
 
13
15
  # Methods defined in this block will be:
14
16
  # - Added as class methods to this interface
@@ -5,29 +5,17 @@ module GraphQL
5
5
  # Represents a list type in the schema.
6
6
  # Wraps a {Schema::Member} as a list type.
7
7
  # @see {Schema::Member::TypeSystemHelpers#to_list_type}
8
- class List
9
- include GraphQL::Schema::Member::CachedGraphQLDefinition
10
- include GraphQL::Schema::Member::TypeSystemHelpers
11
-
12
- # @return [Class, Module] The inner type of this list, the type of which one or more objects may be present.
13
- attr_reader :of_type
14
-
15
- def initialize(of_type)
16
- @of_type = of_type
17
- end
18
-
8
+ class List < GraphQL::Schema::Wrapper
19
9
  def to_graphql
20
10
  @of_type.graphql_definition.to_list_type
21
11
  end
22
12
 
13
+ # @return [GraphQL::TypeKinds::LIST]
23
14
  def kind
24
15
  GraphQL::TypeKinds::LIST
25
16
  end
26
17
 
27
- def unwrap
28
- @of_type.unwrap
29
- end
30
-
18
+ # @return [true]
31
19
  def list?
32
20
  true
33
21
  end
@@ -3,21 +3,25 @@ require 'graphql/schema/member/accepts_definition'
3
3
  require 'graphql/schema/member/base_dsl_methods'
4
4
  require 'graphql/schema/member/cached_graphql_definition'
5
5
  require 'graphql/schema/member/graphql_type_names'
6
+ require 'graphql/schema/member/relay_shortcuts'
7
+ require 'graphql/schema/member/scoped'
6
8
  require 'graphql/schema/member/type_system_helpers'
7
9
  require "graphql/relay/type_extensions"
8
10
 
9
11
  module GraphQL
10
- # The base class for things that make up the schema,
11
- # eg objects, enums, scalars.
12
- #
13
- # @api private
14
12
  class Schema
13
+ # The base class for things that make up the schema,
14
+ # eg objects, enums, scalars.
15
+ #
16
+ # @api private
15
17
  class Member
16
18
  include GraphQLTypeNames
17
19
  extend CachedGraphQLDefinition
18
20
  extend GraphQL::Relay::TypeExtensions
19
21
  extend BaseDSLMethods
20
22
  extend TypeSystemHelpers
23
+ extend Scoped
24
+ extend RelayShortcuts
21
25
  end
22
26
  end
23
27
  end
@@ -10,8 +10,7 @@ module GraphQL
10
10
  # Call this with a new name to override the default name for this schema member; OR
11
11
  # call it without an argument to get the name of this schema member
12
12
  #
13
- # The default name is the Ruby constant name,
14
- # without any namespaces and with any `-Type` suffix removed
13
+ # The default name is implemented in default_graphql_name
15
14
  # @param new_name [String]
16
15
  # @return [String]
17
16
  def graphql_name(new_name = nil)
@@ -20,10 +19,8 @@ module GraphQL
20
19
  @graphql_name = new_name
21
20
  when overridden = overridden_graphql_name
22
21
  overridden
23
- else # Fallback to Ruby constant name
24
- raise NotImplementedError, 'Anonymous class should declare an `graphql_name`' if name.nil?
25
-
26
- name.split("::").last.sub(/Type\Z/, "")
22
+ else
23
+ default_graphql_name
27
24
  end
28
25
  end
29
26
 
@@ -78,6 +75,15 @@ module GraphQL
78
75
  @graphql_name || find_inherited_method(:overridden_graphql_name, nil)
79
76
  end
80
77
 
78
+ # Creates the default name for a schema member.
79
+ # The default name is the Ruby constant name,
80
+ # without any namespaces and with any `-Type` suffix removed
81
+ def default_graphql_name
82
+ raise NotImplementedError, 'Anonymous class should declare a `graphql_name`' if name.nil?
83
+
84
+ name.split("::").last.sub(/Type\Z/, "")
85
+ end
86
+
81
87
  def visible?(context)
82
88
  if @mutation
83
89
  @mutation.visible?(context)
@@ -16,6 +16,13 @@ module GraphQL
16
16
  def argument(*args, **kwargs, &block)
17
17
  kwargs[:owner] = self
18
18
  arg_defn = self.argument_class.new(*args, **kwargs, &block)
19
+ add_argument(arg_defn)
20
+ end
21
+
22
+ # Register this argument with the class.
23
+ # @param arg_defn [GraphQL::Schema::Argument]
24
+ # @return [GraphQL::Schema::Argument]
25
+ def add_argument(arg_defn)
19
26
  own_arguments[arg_defn.name] = arg_defn
20
27
  arg_defn
21
28
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Member
6
+ module RelayShortcuts
7
+ def edge_type_class(new_edge_type_class = nil)
8
+ if new_edge_type_class
9
+ @edge_type_class = new_edge_type_class
10
+ else
11
+ @edge_type_class || find_inherited_method(:edge_type_class, Types::Relay::BaseEdge)
12
+ end
13
+ end
14
+
15
+ def connection_type_class(new_connection_type_class = nil)
16
+ if new_connection_type_class
17
+ @connection_type_class = new_connection_type_class
18
+ else
19
+ @connection_type_class || find_inherited_method(:connection_type_class, Types::Relay::BaseConnection)
20
+ end
21
+ end
22
+
23
+ def edge_type
24
+ @edge_type ||= begin
25
+ edge_name = self.graphql_name + "Edge"
26
+ node_type_class = self
27
+ Class.new(edge_type_class) do
28
+ graphql_name(edge_name)
29
+ node_type(node_type_class)
30
+ end
31
+ end
32
+ end
33
+
34
+ def connection_type
35
+ @connection_type ||= begin
36
+ conn_name = self.graphql_name + "Connection"
37
+ edge_type_class = self.edge_type
38
+ Class.new(connection_type_class) do
39
+ graphql_name(conn_name)
40
+ edge_type(edge_type_class)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Member
6
+ module Scoped
7
+ # This is called when a field has `scope: true`.
8
+ # The field's return type class receives this call.
9
+ #
10
+ # By default, it's a no-op. Override it to scope your objects.
11
+ #
12
+ # @param items [Object] Some list-like object (eg, Array, ActiveRecord::Relation)
13
+ # @param context [GraphQL::Query::Context]
14
+ # @return [Object] Another list-like object, scoped to the current context
15
+ def scope_items(items, context)
16
+ items
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end