graphql 1.12.18 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/mutation_generator.rb +1 -1
  3. data/lib/generators/graphql/object_generator.rb +2 -1
  4. data/lib/generators/graphql/relay.rb +19 -11
  5. data/lib/generators/graphql/templates/schema.erb +14 -2
  6. data/lib/generators/graphql/type_generator.rb +0 -1
  7. data/lib/graphql/analysis/ast/field_usage.rb +2 -2
  8. data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
  9. data/lib/graphql/analysis/ast/visitor.rb +4 -4
  10. data/lib/graphql/backtrace/table.rb +1 -1
  11. data/lib/graphql/dataloader/source.rb +30 -2
  12. data/lib/graphql/dataloader.rb +55 -22
  13. data/lib/graphql/deprecation.rb +1 -5
  14. data/lib/graphql/directive.rb +0 -4
  15. data/lib/graphql/enum_type.rb +5 -1
  16. data/lib/graphql/execution/errors.rb +1 -0
  17. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  18. data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -2
  19. data/lib/graphql/execution/interpreter/runtime.rb +20 -12
  20. data/lib/graphql/execution/lookahead.rb +2 -2
  21. data/lib/graphql/execution/multiplex.rb +1 -1
  22. data/lib/graphql/integer_encoding_error.rb +18 -2
  23. data/lib/graphql/introspection/directive_type.rb +1 -1
  24. data/lib/graphql/introspection/entry_points.rb +2 -2
  25. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  26. data/lib/graphql/introspection/field_type.rb +2 -2
  27. data/lib/graphql/introspection/input_value_type.rb +4 -4
  28. data/lib/graphql/introspection/schema_type.rb +2 -2
  29. data/lib/graphql/introspection/type_type.rb +10 -10
  30. data/lib/graphql/language/block_string.rb +0 -4
  31. data/lib/graphql/language/document_from_schema_definition.rb +4 -2
  32. data/lib/graphql/language/lexer.rb +0 -3
  33. data/lib/graphql/language/lexer.rl +0 -4
  34. data/lib/graphql/language/nodes.rb +2 -1
  35. data/lib/graphql/language/parser.rb +442 -434
  36. data/lib/graphql/language/parser.y +5 -4
  37. data/lib/graphql/language/printer.rb +6 -1
  38. data/lib/graphql/language/sanitized_printer.rb +5 -5
  39. data/lib/graphql/language/token.rb +0 -4
  40. data/lib/graphql/name_validator.rb +0 -4
  41. data/lib/graphql/pagination/connections.rb +35 -16
  42. data/lib/graphql/query/arguments.rb +1 -1
  43. data/lib/graphql/query/arguments_cache.rb +1 -1
  44. data/lib/graphql/query/context.rb +5 -2
  45. data/lib/graphql/query/literal_input.rb +1 -1
  46. data/lib/graphql/query/null_context.rb +12 -7
  47. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  48. data/lib/graphql/query/validation_pipeline.rb +1 -1
  49. data/lib/graphql/query/variables.rb +5 -1
  50. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  51. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  52. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  53. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  54. data/lib/graphql/rubocop.rb +4 -0
  55. data/lib/graphql/schema/addition.rb +37 -28
  56. data/lib/graphql/schema/argument.rb +6 -6
  57. data/lib/graphql/schema/build_from_definition.rb +5 -5
  58. data/lib/graphql/schema/directive/feature.rb +1 -1
  59. data/lib/graphql/schema/directive/flagged.rb +2 -2
  60. data/lib/graphql/schema/directive/include.rb +1 -1
  61. data/lib/graphql/schema/directive/skip.rb +1 -1
  62. data/lib/graphql/schema/directive/transform.rb +1 -1
  63. data/lib/graphql/schema/directive.rb +2 -2
  64. data/lib/graphql/schema/enum.rb +57 -9
  65. data/lib/graphql/schema/enum_value.rb +4 -0
  66. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  67. data/lib/graphql/schema/field.rb +92 -17
  68. data/lib/graphql/schema/find_inherited_value.rb +1 -0
  69. data/lib/graphql/schema/finder.rb +5 -5
  70. data/lib/graphql/schema/input_object.rb +6 -5
  71. data/lib/graphql/schema/interface.rb +8 -19
  72. data/lib/graphql/schema/member/accepts_definition.rb +8 -1
  73. data/lib/graphql/schema/member/build_type.rb +0 -4
  74. data/lib/graphql/schema/member/has_arguments.rb +62 -14
  75. data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
  76. data/lib/graphql/schema/member/has_fields.rb +76 -18
  77. data/lib/graphql/schema/member/has_interfaces.rb +90 -0
  78. data/lib/graphql/schema/member.rb +1 -0
  79. data/lib/graphql/schema/object.rb +7 -74
  80. data/lib/graphql/schema/printer.rb +1 -1
  81. data/lib/graphql/schema/relay_classic_mutation.rb +29 -3
  82. data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
  83. data/lib/graphql/schema/resolver.rb +29 -5
  84. data/lib/graphql/schema/subscription.rb +11 -1
  85. data/lib/graphql/schema/type_expression.rb +1 -1
  86. data/lib/graphql/schema/type_membership.rb +18 -4
  87. data/lib/graphql/schema/union.rb +6 -1
  88. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  89. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  90. data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
  91. data/lib/graphql/schema/validator/format_validator.rb +4 -5
  92. data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
  93. data/lib/graphql/schema/validator/length_validator.rb +5 -3
  94. data/lib/graphql/schema/validator/numericality_validator.rb +8 -1
  95. data/lib/graphql/schema/validator.rb +36 -25
  96. data/lib/graphql/schema/warden.rb +116 -52
  97. data/lib/graphql/schema.rb +87 -15
  98. data/lib/graphql/static_validation/base_visitor.rb +5 -5
  99. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  100. data/lib/graphql/static_validation/error.rb +3 -1
  101. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  102. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  103. data/lib/graphql/static_validation/rules/fields_will_merge.rb +41 -22
  104. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
  105. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  106. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
  107. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +7 -7
  108. data/lib/graphql/static_validation/validation_context.rb +2 -1
  109. data/lib/graphql/string_encoding_error.rb +13 -3
  110. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +6 -4
  111. data/lib/graphql/subscriptions/event.rb +65 -13
  112. data/lib/graphql/subscriptions.rb +17 -19
  113. data/lib/graphql/types/int.rb +1 -1
  114. data/lib/graphql/types/relay/has_node_field.rb +1 -1
  115. data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
  116. data/lib/graphql/types/string.rb +1 -1
  117. data/lib/graphql/unauthorized_error.rb +1 -1
  118. data/lib/graphql/version.rb +1 -1
  119. data/lib/graphql.rb +9 -31
  120. metadata +12 -5
@@ -78,30 +78,72 @@ module GraphQL
78
78
  # @return [GraphQL::Schema::Argument]
79
79
  def add_argument(arg_defn)
80
80
  @own_arguments ||= {}
81
- own_arguments[arg_defn.name] = arg_defn
81
+ prev_defn = own_arguments[arg_defn.name]
82
+ case prev_defn
83
+ when nil
84
+ own_arguments[arg_defn.name] = arg_defn
85
+ when Array
86
+ prev_defn << arg_defn
87
+ when GraphQL::Schema::Argument
88
+ own_arguments[arg_defn.name] = [prev_defn, arg_defn]
89
+ else
90
+ raise "Invariant: unexpected `@own_arguments[#{arg_defn.name.inspect}]`: #{prev_defn.inspect}"
91
+ end
82
92
  arg_defn
83
93
  end
84
94
 
85
95
  # @return [Hash<String => GraphQL::Schema::Argument] Arguments defined on this thing, keyed by name. Includes inherited definitions
86
- def arguments
87
- inherited_arguments = ((self.is_a?(Class) && superclass.respond_to?(:arguments)) ? superclass.arguments : nil)
96
+ def arguments(context = GraphQL::Query::NullContext)
97
+ inherited_arguments = ((self.is_a?(Class) && superclass.respond_to?(:arguments)) ? superclass.arguments(context) : nil)
88
98
  # Local definitions override inherited ones
99
+ if own_arguments.any?
100
+ own_arguments_that_apply = {}
101
+ own_arguments.each do |name, args_entry|
102
+ if (visible_defn = Warden.visible_entry?(:visible_argument?, args_entry, context))
103
+ own_arguments_that_apply[visible_defn.graphql_name] = visible_defn
104
+ end
105
+ end
106
+ end
107
+
89
108
  if inherited_arguments
90
- inherited_arguments.merge(own_arguments)
109
+ if own_arguments_that_apply
110
+ inherited_arguments.merge(own_arguments_that_apply)
111
+ else
112
+ inherited_arguments
113
+ end
91
114
  else
92
- own_arguments
115
+ # might be nil if there are actually no arguments
116
+ own_arguments_that_apply || own_arguments
93
117
  end
94
118
  end
95
119
 
96
- # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
97
- def get_argument(argument_name)
98
- a = own_arguments[argument_name]
120
+ def all_argument_definitions
121
+ if self.is_a?(Class)
122
+ all_defns = {}
123
+ ancestors.reverse_each do |ancestor|
124
+ if ancestor.respond_to?(:own_arguments)
125
+ all_defns.merge!(ancestor.own_arguments)
126
+ end
127
+ end
128
+ else
129
+ all_defns = own_arguments
130
+ end
131
+ all_defns = all_defns.values
132
+ all_defns.flatten!
133
+ all_defns
134
+ end
99
135
 
100
- if a || !self.is_a?(Class)
101
- a
136
+ # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
137
+ def get_argument(argument_name, context = GraphQL::Query::NullContext)
138
+ warden = Warden.from_context(context)
139
+ if !self.is_a?(Class)
140
+ a = own_arguments[argument_name]
141
+ a && Warden.visible_entry?(:visible_argument?, a, context, warden)
102
142
  else
103
143
  for ancestor in ancestors
104
- if ancestor.respond_to?(:own_arguments) && a = ancestor.own_arguments[argument_name]
144
+ if ancestor.respond_to?(:own_arguments) &&
145
+ (a = ancestor.own_arguments[argument_name]) &&
146
+ (a = Warden.visible_entry?(:visible_argument?, a, context, warden))
105
147
  return a
106
148
  end
107
149
  end
@@ -125,7 +167,7 @@ module GraphQL
125
167
  # @return [Interpreter::Arguments, Execution::Lazy<Interpeter::Arguments>]
126
168
  def coerce_arguments(parent_object, values, context, &block)
127
169
  # Cache this hash to avoid re-merging it
128
- arg_defns = self.arguments
170
+ arg_defns = self.arguments(context)
129
171
  total_args_count = arg_defns.size
130
172
 
131
173
  finished_args = nil
@@ -191,7 +233,7 @@ module GraphQL
191
233
  def arguments_statically_coercible?
192
234
  return @arguments_statically_coercible if defined?(@arguments_statically_coercible)
193
235
 
194
- @arguments_statically_coercible = arguments.each_value.all?(&:statically_coercible?)
236
+ @arguments_statically_coercible = all_argument_definitions.all?(&:statically_coercible?)
195
237
  end
196
238
 
197
239
  module ArgumentClassAccessor
@@ -254,11 +296,17 @@ module GraphQL
254
296
  if authed
255
297
  application_object
256
298
  else
257
- raise GraphQL::UnauthorizedError.new(
299
+ err = GraphQL::UnauthorizedError.new(
258
300
  object: application_object,
259
301
  type: class_based_type,
260
302
  context: context,
261
303
  )
304
+ if self.respond_to?(:unauthorized_object)
305
+ err.set_backtrace(caller)
306
+ unauthorized_object(err)
307
+ else
308
+ raise err
309
+ end
262
310
  end
263
311
  end
264
312
  else
@@ -7,7 +7,7 @@ module GraphQL
7
7
  # @return [String, nil] Explains why this member was deprecated (if present, this will be marked deprecated in introspection)
8
8
  def deprecation_reason
9
9
  dir = self.directives.find { |d| d.is_a?(GraphQL::Schema::Directive::Deprecated) }
10
- dir && dir.arguments[:reason]
10
+ dir && dir.arguments[:reason] # rubocop:disable Development/ContextIsPassedCop -- definition-related
11
11
  end
12
12
 
13
13
  # Set the deprecation reason for this member, or remove it by assigning `nil`
@@ -3,7 +3,7 @@
3
3
  module GraphQL
4
4
  class Schema
5
5
  class Member
6
- # Shared code for Object and Interface
6
+ # Shared code for Objects, Interfaces, Mutations, Subscriptions
7
7
  module HasFields
8
8
  # Add a field to this object or interface with the given definition
9
9
  # @see {GraphQL::Schema::Field#initialize} for method signature
@@ -15,28 +15,39 @@ module GraphQL
15
15
  end
16
16
 
17
17
  # @return [Hash<String => GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields
18
- def fields
18
+ def fields(context = GraphQL::Query::NullContext)
19
+ warden = Warden.from_context(context)
20
+ is_object = self.respond_to?(:kind) && self.kind.object?
19
21
  # Local overrides take precedence over inherited fields
20
- all_fields = {}
21
- ancestors.reverse_each do |ancestor|
22
- if ancestor.respond_to?(:own_fields)
23
- all_fields.merge!(ancestor.own_fields)
22
+ visible_fields = {}
23
+ for ancestor in ancestors
24
+ if ancestor.respond_to?(:own_fields) &&
25
+ (is_object ? visible_interface_implementation?(ancestor, context, warden) : true)
26
+
27
+ ancestor.own_fields.each do |field_name, fields_entry|
28
+ # Choose the most local definition that passes `.visible?` --
29
+ # stop checking for fields by name once one has been found.
30
+ if !visible_fields.key?(field_name) && (f = Warden.visible_entry?(:visible_field?, fields_entry, context, warden))
31
+ visible_fields[field_name] = f
32
+ end
33
+ end
24
34
  end
25
35
  end
26
- all_fields
36
+ visible_fields
27
37
  end
28
38
 
29
- def get_field(field_name)
30
- if (f = own_fields[field_name])
31
- f
32
- else
33
- for ancestor in ancestors
34
- if ancestor.respond_to?(:own_fields) && f = ancestor.own_fields[field_name]
35
- return f
36
- end
39
+ def get_field(field_name, context = GraphQL::Query::NullContext)
40
+ warden = Warden.from_context(context)
41
+ is_object = self.respond_to?(:kind) && self.kind.object?
42
+ for ancestor in ancestors
43
+ if ancestor.respond_to?(:own_fields) &&
44
+ (is_object ? visible_interface_implementation?(ancestor, context, warden) : true) &&
45
+ (f_entry = ancestor.own_fields[field_name]) &&
46
+ (f = Warden.visible_entry?(:visible_field?, f_entry, context, warden))
47
+ return f
37
48
  end
38
- nil
39
49
  end
50
+ nil
40
51
  end
41
52
 
42
53
  # A list of Ruby keywords.
@@ -64,7 +75,19 @@ module GraphQL
64
75
  if method_conflict_warning && CONFLICT_FIELD_NAMES.include?(field_defn.resolver_method) && field_defn.original_name == field_defn.resolver_method && field_defn.original_name == field_defn.method_sym
65
76
  warn(conflict_field_name_warning(field_defn))
66
77
  end
67
- own_fields[field_defn.name] = field_defn
78
+ prev_defn = own_fields[field_defn.name]
79
+
80
+ case prev_defn
81
+ when nil
82
+ own_fields[field_defn.name] = field_defn
83
+ when Array
84
+ prev_defn << field_defn
85
+ when GraphQL::Schema::Field
86
+ own_fields[field_defn.name] = [prev_defn, field_defn]
87
+ else
88
+ raise "Invariant: unexpected previous field definition for #{field_defn.name.inspect}: #{prev_defn.inspect}"
89
+ end
90
+
68
91
  nil
69
92
  end
70
93
 
@@ -87,13 +110,48 @@ module GraphQL
87
110
  end
88
111
  end
89
112
 
90
- # @return [Array<GraphQL::Schema::Field>] Fields defined on this class _specifically_, not parent classes
113
+ # @return [Hash<String => GraphQL::Schema::Field, Array<GraphQL::Schema::Field>>] Fields defined on this class _specifically_, not parent classes
91
114
  def own_fields
92
115
  @own_fields ||= {}
93
116
  end
94
117
 
118
+ def all_field_definitions
119
+ all_fields = {}
120
+ ancestors.reverse_each do |ancestor|
121
+ if ancestor.respond_to?(:own_fields)
122
+ all_fields.merge!(ancestor.own_fields)
123
+ end
124
+ end
125
+ all_fields = all_fields.values
126
+ all_fields.flatten!
127
+ all_fields
128
+ end
129
+
95
130
  private
96
131
 
132
+ # If `type` is an interface, and `self` has a type membership for `type`, then make sure it's visible.
133
+ def visible_interface_implementation?(type, context, warden)
134
+ if type.respond_to?(:kind) && type.kind.interface?
135
+ implements_this_interface = false
136
+ implementation_is_visible = false
137
+ interface_type_memberships.each do |tm|
138
+ if tm.abstract_type == type
139
+ implements_this_interface ||= true
140
+ if warden.visible_type_membership?(tm, context)
141
+ implementation_is_visible = true
142
+ break
143
+ end
144
+ end
145
+ end
146
+ # It's possible this interface came by way of `include` in another interface which this
147
+ # object type _does_ implement, and that's ok
148
+ implements_this_interface ? implementation_is_visible : true
149
+ else
150
+ # If there's no implementation, then we're looking at Ruby-style inheritance instead
151
+ true
152
+ end
153
+ end
154
+
97
155
  # @param [GraphQL::Schema::Field]
98
156
  # @return [String] A warning to give when this field definition might conflict with a built-in method
99
157
  def conflict_field_name_warning(field_defn)
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Member
6
+ module HasInterfaces
7
+ def implements(*new_interfaces, **options)
8
+ new_memberships = []
9
+ new_interfaces.each do |int|
10
+ if int.is_a?(Module)
11
+ unless int.include?(GraphQL::Schema::Interface)
12
+ raise "#{int} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules."
13
+ end
14
+
15
+ new_memberships << int.type_membership_class.new(int, self, **options)
16
+
17
+ # Include the methods here,
18
+ # `.fields` will use the inheritance chain
19
+ # to find inherited fields
20
+ include(int)
21
+
22
+ # If this interface has interfaces of its own, add those, too
23
+ int.interfaces.each do |next_interface|
24
+ implements(next_interface)
25
+ end
26
+ elsif int.is_a?(GraphQL::InterfaceType)
27
+ new_memberships << int.type_membership_class.new(int, self, **options)
28
+ elsif int.is_a?(String) || int.is_a?(GraphQL::Schema::LateBoundType)
29
+ if options.any?
30
+ raise ArgumentError, "`implements(...)` doesn't support options with late-loaded types yet. Remove #{options} and open an issue to request this feature."
31
+ end
32
+ new_memberships << int
33
+ else
34
+ raise ArgumentError, "Unexpected interface definition (expected module): #{int} (#{int.class})"
35
+ end
36
+ end
37
+
38
+ # Remove any String or late-bound interfaces which are being replaced
39
+ own_interface_type_memberships.reject! { |old_i_m|
40
+ if !(old_i_m.respond_to?(:abstract_type) && old_i_m.abstract_type.is_a?(Module))
41
+ old_int_type = old_i_m.respond_to?(:abstract_type) ? old_i_m.abstract_type : old_i_m
42
+ old_name = Schema::Member::BuildType.to_type_name(old_int_type)
43
+
44
+ new_memberships.any? { |new_i_m|
45
+ new_int_type = new_i_m.respond_to?(:abstract_type) ? new_i_m.abstract_type : new_i_m
46
+ new_name = Schema::Member::BuildType.to_type_name(new_int_type)
47
+
48
+ new_name == old_name
49
+ }
50
+ end
51
+ }
52
+ own_interface_type_memberships.concat(new_memberships)
53
+ end
54
+
55
+ def own_interface_type_memberships
56
+ @own_interface_type_memberships ||= []
57
+ end
58
+
59
+ def interface_type_memberships
60
+ own_interface_type_memberships + ((self.is_a?(Class) && superclass.respond_to?(:interface_type_memberships)) ? superclass.interface_type_memberships : [])
61
+ end
62
+
63
+ # param context [Query::Context] If omitted, skip filtering.
64
+ def interfaces(context = GraphQL::Query::NullContext)
65
+ warden = Warden.from_context(context)
66
+ visible_interfaces = []
67
+ own_interface_type_memberships.each do |type_membership|
68
+ # During initialization, `type_memberships` can hold late-bound types
69
+ case type_membership
70
+ when String, Schema::LateBoundType
71
+ visible_interfaces << type_membership
72
+ when Schema::TypeMembership
73
+ if warden.visible_type_membership?(type_membership, context)
74
+ visible_interfaces << type_membership.abstract_type
75
+ end
76
+ else
77
+ raise "Invariant: Unexpected type_membership #{type_membership.class}: #{type_membership.inspect}"
78
+ end
79
+ end
80
+
81
+ if self.is_a?(Class) && superclass <= GraphQL::Schema::Object
82
+ visible_interfaces.concat(superclass.interfaces(context))
83
+ end
84
+
85
+ visible_interfaces
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -6,6 +6,7 @@ require 'graphql/schema/member/graphql_type_names'
6
6
  require 'graphql/schema/member/has_ast_node'
7
7
  require 'graphql/schema/member/has_directives'
8
8
  require 'graphql/schema/member/has_deprecation_reason'
9
+ require 'graphql/schema/member/has_interfaces'
9
10
  require 'graphql/schema/member/has_path'
10
11
  require 'graphql/schema/member/has_unresolved_type_error'
11
12
  require 'graphql/schema/member/has_validators'
@@ -7,6 +7,7 @@ module GraphQL
7
7
  class Object < GraphQL::Schema::Member
8
8
  extend GraphQL::Schema::Member::AcceptsDefinition
9
9
  extend GraphQL::Schema::Member::HasFields
10
+ extend GraphQL::Schema::Member::HasInterfaces
10
11
 
11
12
  # @return [Object] the application object this type is wrapping
12
13
  attr_reader :object
@@ -103,84 +104,16 @@ module GraphQL
103
104
  super
104
105
  end
105
106
 
106
- def implements(*new_interfaces, **options)
107
- new_memberships = []
108
- new_interfaces.each do |int|
109
- if int.is_a?(Module)
110
- unless int.include?(GraphQL::Schema::Interface)
111
- raise "#{int} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules."
112
- end
113
-
114
- new_memberships << int.type_membership_class.new(int, self, **options)
115
-
116
- # Include the methods here,
117
- # `.fields` will use the inheritance chain
118
- # to find inherited fields
119
- include(int)
120
- elsif int.is_a?(GraphQL::InterfaceType)
121
- new_memberships << int.type_membership_class.new(int, self, **options)
122
- elsif int.is_a?(String) || int.is_a?(GraphQL::Schema::LateBoundType)
123
- if options.any?
124
- raise ArgumentError, "`implements(...)` doesn't support options with late-loaded types yet. Remove #{options} and open an issue to request this feature."
125
- end
126
- new_memberships << int
127
- else
128
- raise ArgumentError, "Unexpected interface definition (expected module): #{int} (#{int.class})"
129
- end
130
- end
131
-
132
- # Remove any interfaces which are being replaced (late-bound types are updated in place this way)
133
- own_interface_type_memberships.reject! { |old_i_m|
134
- old_int_type = old_i_m.respond_to?(:abstract_type) ? old_i_m.abstract_type : old_i_m
135
- old_name = Schema::Member::BuildType.to_type_name(old_int_type)
136
-
137
- new_memberships.any? { |new_i_m|
138
- new_int_type = new_i_m.respond_to?(:abstract_type) ? new_i_m.abstract_type : new_i_m
139
- new_name = Schema::Member::BuildType.to_type_name(new_int_type)
140
-
141
- new_name == old_name
142
- }
143
- }
144
- own_interface_type_memberships.concat(new_memberships)
145
- end
146
-
147
- def own_interface_type_memberships
148
- @own_interface_type_memberships ||= []
149
- end
150
-
151
- def interface_type_memberships
152
- own_interface_type_memberships + (superclass.respond_to?(:interface_type_memberships) ? superclass.interface_type_memberships : [])
153
- end
154
-
155
- # param context [Query::Context] If omitted, skip filtering.
156
- def interfaces(context = GraphQL::Query::NullContext)
157
- visible_interfaces = []
158
- unfiltered = context == GraphQL::Query::NullContext
159
- own_interface_type_memberships.each do |type_membership|
160
- # During initialization, `type_memberships` can hold late-bound types
161
- case type_membership
162
- when String, Schema::LateBoundType
163
- visible_interfaces << type_membership
164
- when Schema::TypeMembership
165
- if unfiltered || type_membership.visible?(context)
166
- visible_interfaces << type_membership.abstract_type
167
- end
168
- else
169
- raise "Invariant: Unexpected type_membership #{type_membership.class}: #{type_membership.inspect}"
170
- end
171
- end
172
- visible_interfaces + (superclass <= GraphQL::Schema::Object ? superclass.interfaces(context) : [])
173
- end
174
-
175
107
  # @return [Hash<String => GraphQL::Schema::Field>] All of this object's fields, indexed by name
176
108
  # @see get_field A faster way to find one field by name ({#fields} merges hashes of inherited fields; {#get_field} just looks up one field.)
177
- def fields
109
+ def fields(context = GraphQL::Query::NullContext)
178
110
  all_fields = super
111
+ # This adds fields from legacy-style interfaces only.
112
+ # Multi-fields are not supported here.
179
113
  interfaces.each do |int|
180
- # Include legacy-style interfaces, too
181
114
  if int.is_a?(GraphQL::InterfaceType)
182
115
  int_f = {}
183
- int.fields.each do |name, legacy_field|
116
+ int.fields.each do |name, legacy_field| # rubocop:disable Development/ContextIsPassedCop -- legacy-related
184
117
  int_f[name] = field_class.from_options(name, field: legacy_field)
185
118
  end
186
119
  all_fields = int_f.merge(all_fields)
@@ -198,9 +131,9 @@ module GraphQL
198
131
  obj_type.introspection = introspection
199
132
  obj_type.mutation = mutation
200
133
  obj_type.ast_node = ast_node
201
- fields.each do |field_name, field_inst|
134
+ fields.each do |field_name, field_inst| # rubocop:disable Development/ContextIsPassedCop -- legacy-related
202
135
  field_defn = field_inst.to_graphql
203
- obj_type.fields[field_defn.name] = field_defn
136
+ obj_type.fields[field_defn.name] = field_defn # rubocop:disable Development/ContextIsPassedCop -- legacy-related
204
137
  end
205
138
 
206
139
  obj_type.metadata[:type_class] = self
@@ -56,7 +56,7 @@ module GraphQL
56
56
  def self.print_introspection_schema
57
57
  query_root = Class.new(GraphQL::Schema::Object) do
58
58
  graphql_name "Root"
59
- field :throwaway_field, String, null: true
59
+ field :throwaway_field, String
60
60
  end
61
61
  schema = Class.new(GraphQL::Schema) { query(query_root) }
62
62
 
@@ -22,7 +22,7 @@ module GraphQL
22
22
  #
23
23
  class RelayClassicMutation < GraphQL::Schema::Mutation
24
24
  # The payload should always include this field
25
- field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.", null: true)
25
+ field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.")
26
26
  # Relay classic default:
27
27
  null(true)
28
28
 
@@ -81,6 +81,31 @@ module GraphQL
81
81
  end
82
82
 
83
83
  class << self
84
+
85
+ # Also apply this argument to the input type:
86
+ def argument(*args, **kwargs, &block)
87
+ it = input_type # make sure any inherited arguments are already added to it
88
+ arg = super
89
+
90
+ # This definition might be overriding something inherited;
91
+ # if it is, remove the inherited definition so it's not confused at runtime as having multiple definitions
92
+ prev_args = it.own_arguments[arg.graphql_name]
93
+ case prev_args
94
+ when GraphQL::Schema::Argument
95
+ if prev_args.owner != self
96
+ it.own_arguments.delete(arg.graphql_name)
97
+ end
98
+ when Array
99
+ prev_args.reject! { |a| a.owner != self }
100
+ if prev_args.empty?
101
+ it.own_arguments.delete(arg.graphql_name)
102
+ end
103
+ end
104
+
105
+ it.add_argument(arg)
106
+ arg
107
+ end
108
+
84
109
  # The base class for generated input object types
85
110
  # @param new_class [Class] The base class to use for generating input object definitions
86
111
  # @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject})
@@ -115,14 +140,15 @@ module GraphQL
115
140
  # To customize how input objects are generated, override this method
116
141
  # @return [Class] a subclass of {.input_object_class}
117
142
  def generate_input_type
118
- mutation_args = arguments
143
+ mutation_args = all_argument_definitions
119
144
  mutation_name = graphql_name
120
145
  mutation_class = self
121
146
  Class.new(input_object_class) do
122
147
  graphql_name("#{mutation_name}Input")
123
148
  description("Autogenerated input type of #{mutation_name}")
124
149
  mutation(mutation_class)
125
- mutation_args.each do |_name, arg|
150
+ # these might be inherited:
151
+ mutation_args.each do |arg|
126
152
  add_argument(arg)
127
153
  end
128
154
  argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
@@ -38,6 +38,9 @@ module GraphQL
38
38
  # @return [Class]
39
39
  def object_class(new_class = nil)
40
40
  if new_class
41
+ if defined?(@payload_type)
42
+ raise "Can't configure `object_class(...)` after the payload type has already been initialized. Move this configuration higher up the class definition."
43
+ end
41
44
  @object_class = new_class
42
45
  else
43
46
  @object_class || find_inherited_value(:object_class, GraphQL::Schema::Object)
@@ -46,6 +49,28 @@ module GraphQL
46
49
 
47
50
  NO_INTERFACES = [].freeze
48
51
 
52
+ def field(*args, **kwargs, &block)
53
+ pt = payload_type # make sure it's initialized with any inherited fields
54
+ field_defn = super
55
+
56
+ # Remove any inherited fields to avoid false conflicts at runtime
57
+ prev_fields = pt.own_fields[field_defn.graphql_name]
58
+ case prev_fields
59
+ when GraphQL::Schema::Field
60
+ if prev_fields.owner != self
61
+ pt.own_fields.delete(field_defn.graphql_name)
62
+ end
63
+ when Array
64
+ prev_fields.reject! { |f| f.owner != self }
65
+ if prev_fields.empty?
66
+ pt.own_fields.delete(field_defn.graphql_name)
67
+ end
68
+ end
69
+
70
+ pt.add_field(field_defn, method_conflict_warning: false)
71
+ field_defn
72
+ end
73
+
49
74
  private
50
75
 
51
76
  # Build a subclass of {.object_class} based on `self`.
@@ -53,11 +78,11 @@ module GraphQL
53
78
  # Override this hook to customize return type generation.
54
79
  def generate_payload_type
55
80
  resolver_name = graphql_name
56
- resolver_fields = fields
81
+ resolver_fields = all_field_definitions
57
82
  Class.new(object_class) do
58
83
  graphql_name("#{resolver_name}Payload")
59
84
  description("Autogenerated return type of #{resolver_name}")
60
- resolver_fields.each do |name, f|
85
+ resolver_fields.each do |f|
61
86
  # Reattach the already-defined field here
62
87
  # (The field's `.owner` will still point to the mutation, not the object type, I think)
63
88
  # Don't re-warn about a method conflict. Since this type is generated, it should be fixed in the resolver instead.
@@ -37,7 +37,7 @@ module GraphQL
37
37
  @field = field
38
38
  # Since this hash is constantly rebuilt, cache it for this call
39
39
  @arguments_by_keyword = {}
40
- self.class.arguments.each do |name, arg|
40
+ self.class.arguments(context).each do |name, arg|
41
41
  @arguments_by_keyword[arg.keyword] = arg
42
42
  end
43
43
  @prepared_arguments = nil
@@ -145,7 +145,7 @@ module GraphQL
145
145
  # @raise [GraphQL::UnauthorizedError] To signal an authorization failure
146
146
  # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.)
147
147
  def authorized?(**inputs)
148
- self.class.arguments.each_value do |argument|
148
+ self.class.arguments(context).each_value do |argument|
149
149
  arg_keyword = argument.keyword
150
150
  if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
151
151
  arg_auth, err = argument.authorized?(self, arg_value, context)
@@ -160,6 +160,16 @@ module GraphQL
160
160
  end
161
161
  end
162
162
 
163
+ # Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type.
164
+ #
165
+ # By default, the error is re-raised and passed along to {{Schema.unauthorized_object}}.
166
+ #
167
+ # Any value returned here will be used _instead of_ of the loaded object.
168
+ # @param err [GraphQL::UnauthorizedError]
169
+ def unauthorized_object(err)
170
+ raise err
171
+ end
172
+
163
173
  private
164
174
 
165
175
  def load_arguments(args)
@@ -189,8 +199,8 @@ module GraphQL
189
199
  end
190
200
  end
191
201
 
192
- def get_argument(name)
193
- self.class.get_argument(name)
202
+ def get_argument(name, context = GraphQL::Query::NullContext)
203
+ self.class.get_argument(name, context)
194
204
  end
195
205
 
196
206
  class << self
@@ -295,13 +305,27 @@ module GraphQL
295
305
  end
296
306
 
297
307
  def field_options
308
+
309
+ all_args = {}
310
+ all_argument_definitions.each do |arg|
311
+ if (prev_entry = all_args[arg.graphql_name])
312
+ if prev_entry.is_a?(Array)
313
+ prev_entry << arg
314
+ else
315
+ all_args[arg.graphql_name] = [prev_entry, arg]
316
+ end
317
+ else
318
+ all_args[arg.graphql_name] = arg
319
+ end
320
+ end
321
+
298
322
  field_opts = {
299
323
  type: type_expr,
300
324
  description: description,
301
325
  extras: extras,
302
326
  resolver_method: :resolve_with_support,
303
327
  resolver_class: self,
304
- arguments: arguments,
328
+ arguments: all_args,
305
329
  null: null,
306
330
  complexity: complexity,
307
331
  broadcastable: broadcastable?,