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