graphql 1.12.23 → 1.13.3

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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (134) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +3 -1
  3. data/lib/generators/graphql/install_generator.rb +9 -2
  4. data/lib/generators/graphql/mutation_generator.rb +1 -1
  5. data/lib/generators/graphql/type_generator.rb +0 -1
  6. data/lib/graphql/analysis/ast/field_usage.rb +2 -2
  7. data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
  8. data/lib/graphql/analysis/ast/visitor.rb +4 -4
  9. data/lib/graphql/backtrace/table.rb +1 -1
  10. data/lib/graphql/base_type.rb +4 -2
  11. data/lib/graphql/boolean_type.rb +1 -1
  12. data/lib/graphql/dataloader.rb +55 -22
  13. data/lib/graphql/directive/deprecated_directive.rb +1 -1
  14. data/lib/graphql/directive/include_directive.rb +1 -1
  15. data/lib/graphql/directive/skip_directive.rb +1 -1
  16. data/lib/graphql/directive.rb +0 -4
  17. data/lib/graphql/enum_type.rb +5 -1
  18. data/lib/graphql/execution/errors.rb +1 -0
  19. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  20. data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -2
  21. data/lib/graphql/execution/interpreter/runtime.rb +31 -19
  22. data/lib/graphql/execution/lookahead.rb +2 -2
  23. data/lib/graphql/execution/multiplex.rb +4 -1
  24. data/lib/graphql/float_type.rb +1 -1
  25. data/lib/graphql/id_type.rb +1 -1
  26. data/lib/graphql/int_type.rb +1 -1
  27. data/lib/graphql/introspection/directive_type.rb +1 -1
  28. data/lib/graphql/introspection/entry_points.rb +2 -2
  29. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  30. data/lib/graphql/introspection/field_type.rb +2 -2
  31. data/lib/graphql/introspection/input_value_type.rb +4 -4
  32. data/lib/graphql/introspection/schema_type.rb +2 -2
  33. data/lib/graphql/introspection/type_type.rb +10 -10
  34. data/lib/graphql/language/block_string.rb +2 -6
  35. data/lib/graphql/language/document_from_schema_definition.rb +10 -4
  36. data/lib/graphql/language/lexer.rb +0 -3
  37. data/lib/graphql/language/lexer.rl +0 -4
  38. data/lib/graphql/language/nodes.rb +3 -2
  39. data/lib/graphql/language/parser.rb +442 -434
  40. data/lib/graphql/language/parser.y +5 -4
  41. data/lib/graphql/language/printer.rb +6 -1
  42. data/lib/graphql/language/sanitized_printer.rb +5 -5
  43. data/lib/graphql/language/token.rb +0 -4
  44. data/lib/graphql/name_validator.rb +0 -4
  45. data/lib/graphql/pagination/active_record_relation_connection.rb +43 -6
  46. data/lib/graphql/pagination/relation_connection.rb +55 -28
  47. data/lib/graphql/query/arguments.rb +1 -1
  48. data/lib/graphql/query/arguments_cache.rb +1 -1
  49. data/lib/graphql/query/context.rb +15 -2
  50. data/lib/graphql/query/literal_input.rb +1 -1
  51. data/lib/graphql/query/null_context.rb +12 -7
  52. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  53. data/lib/graphql/query/variables.rb +5 -1
  54. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  55. data/lib/graphql/relay/global_id_resolve.rb +1 -1
  56. data/lib/graphql/relay/page_info.rb +1 -1
  57. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  58. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  59. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  60. data/lib/graphql/rubocop.rb +4 -0
  61. data/lib/graphql/schema/addition.rb +37 -28
  62. data/lib/graphql/schema/argument.rb +13 -15
  63. data/lib/graphql/schema/build_from_definition.rb +5 -5
  64. data/lib/graphql/schema/directive/feature.rb +1 -1
  65. data/lib/graphql/schema/directive/flagged.rb +2 -2
  66. data/lib/graphql/schema/directive/include.rb +1 -1
  67. data/lib/graphql/schema/directive/skip.rb +1 -1
  68. data/lib/graphql/schema/directive/transform.rb +1 -1
  69. data/lib/graphql/schema/directive.rb +7 -3
  70. data/lib/graphql/schema/enum.rb +60 -10
  71. data/lib/graphql/schema/enum_value.rb +6 -0
  72. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  73. data/lib/graphql/schema/field.rb +229 -77
  74. data/lib/graphql/schema/field_extension.rb +89 -2
  75. data/lib/graphql/schema/find_inherited_value.rb +1 -0
  76. data/lib/graphql/schema/finder.rb +5 -5
  77. data/lib/graphql/schema/input_object.rb +23 -5
  78. data/lib/graphql/schema/interface.rb +11 -20
  79. data/lib/graphql/schema/introspection_system.rb +1 -1
  80. data/lib/graphql/schema/list.rb +3 -1
  81. data/lib/graphql/schema/member/accepts_definition.rb +15 -3
  82. data/lib/graphql/schema/member/build_type.rb +0 -4
  83. data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
  84. data/lib/graphql/schema/member/has_arguments.rb +55 -13
  85. data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
  86. data/lib/graphql/schema/member/has_fields.rb +76 -18
  87. data/lib/graphql/schema/member/has_interfaces.rb +90 -0
  88. data/lib/graphql/schema/member.rb +1 -0
  89. data/lib/graphql/schema/non_null.rb +7 -1
  90. data/lib/graphql/schema/object.rb +10 -75
  91. data/lib/graphql/schema/printer.rb +1 -1
  92. data/lib/graphql/schema/relay_classic_mutation.rb +37 -3
  93. data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
  94. data/lib/graphql/schema/resolver.rb +37 -17
  95. data/lib/graphql/schema/scalar.rb +2 -0
  96. data/lib/graphql/schema/subscription.rb +11 -1
  97. data/lib/graphql/schema/traversal.rb +1 -1
  98. data/lib/graphql/schema/type_expression.rb +1 -1
  99. data/lib/graphql/schema/type_membership.rb +18 -4
  100. data/lib/graphql/schema/union.rb +8 -1
  101. data/lib/graphql/schema/validator/format_validator.rb +0 -4
  102. data/lib/graphql/schema/validator/numericality_validator.rb +1 -0
  103. data/lib/graphql/schema/validator/required_validator.rb +29 -15
  104. data/lib/graphql/schema/validator.rb +4 -7
  105. data/lib/graphql/schema/warden.rb +116 -52
  106. data/lib/graphql/schema.rb +111 -23
  107. data/lib/graphql/static_validation/all_rules.rb +1 -0
  108. data/lib/graphql/static_validation/base_visitor.rb +5 -5
  109. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  110. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  111. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  112. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  113. data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
  114. data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
  115. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +2 -2
  116. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
  117. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +13 -7
  118. data/lib/graphql/string_type.rb +1 -1
  119. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -4
  120. data/lib/graphql/subscriptions/event.rb +20 -12
  121. data/lib/graphql/subscriptions/serialize.rb +22 -2
  122. data/lib/graphql/subscriptions.rb +17 -19
  123. data/lib/graphql/tracing/active_support_notifications_tracing.rb +6 -20
  124. data/lib/graphql/tracing/notifications_tracing.rb +59 -0
  125. data/lib/graphql/types/relay/connection_behaviors.rb +26 -9
  126. data/lib/graphql/types/relay/default_relay.rb +5 -1
  127. data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
  128. data/lib/graphql/types/relay/has_node_field.rb +1 -1
  129. data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
  130. data/lib/graphql/types/relay/node_field.rb +14 -3
  131. data/lib/graphql/types/relay/nodes_field.rb +13 -3
  132. data/lib/graphql/version.rb +1 -1
  133. data/lib/graphql.rb +10 -32
  134. metadata +13 -5
@@ -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)
@@ -189,6 +122,8 @@ module GraphQL
189
122
  all_fields
190
123
  end
191
124
 
125
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
126
+
192
127
  # @return [GraphQL::ObjectType]
193
128
  def to_graphql
194
129
  obj_type = GraphQL::ObjectType.new
@@ -198,9 +133,9 @@ module GraphQL
198
133
  obj_type.introspection = introspection
199
134
  obj_type.mutation = mutation
200
135
  obj_type.ast_node = ast_node
201
- fields.each do |field_name, field_inst|
202
- field_defn = field_inst.to_graphql
203
- obj_type.fields[field_defn.name] = field_defn
136
+ fields.each do |field_name, field_inst| # rubocop:disable Development/ContextIsPassedCop -- legacy-related
137
+ field_defn = field_inst.to_graphql(silence_deprecation_warning: true)
138
+ obj_type.fields[field_defn.name] = field_defn # rubocop:disable Development/ContextIsPassedCop -- legacy-related
204
139
  end
205
140
 
206
141
  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,20 +140,29 @@ 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
129
155
  end
130
156
  end
131
157
  end
158
+
159
+ private
160
+
161
+ def authorize_arguments(args, values)
162
+ # remove the `input` wrapper to match values
163
+ input_args = args["input"].type.unwrap.arguments(context)
164
+ super(input_args, values)
165
+ end
132
166
  end
133
167
  end
134
168
  end
@@ -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,19 +145,9 @@ 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|
149
- arg_keyword = argument.keyword
150
- if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
151
- arg_auth, err = argument.authorized?(self, arg_value, context)
152
- if !arg_auth
153
- return arg_auth, err
154
- else
155
- true
156
- end
157
- else
158
- true
159
- end
160
- end
148
+ arg_owner = @field # || self.class
149
+ args = arg_owner.arguments(context)
150
+ authorize_arguments(args, inputs)
161
151
  end
162
152
 
163
153
  # Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type.
@@ -172,6 +162,22 @@ module GraphQL
172
162
 
173
163
  private
174
164
 
165
+ def authorize_arguments(args, inputs)
166
+ args.each_value do |argument|
167
+ arg_keyword = argument.keyword
168
+ if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
169
+ arg_auth, err = argument.authorized?(self, arg_value, context)
170
+ if !arg_auth
171
+ return arg_auth, err
172
+ else
173
+ true
174
+ end
175
+ else
176
+ true
177
+ end
178
+ end
179
+ end
180
+
175
181
  def load_arguments(args)
176
182
  prepared_args = {}
177
183
  prepare_lazies = []
@@ -199,8 +205,8 @@ module GraphQL
199
205
  end
200
206
  end
201
207
 
202
- def get_argument(name)
203
- self.class.get_argument(name)
208
+ def get_argument(name, context = GraphQL::Query::NullContext)
209
+ self.class.get_argument(name, context)
204
210
  end
205
211
 
206
212
  class << self
@@ -305,13 +311,27 @@ module GraphQL
305
311
  end
306
312
 
307
313
  def field_options
314
+
315
+ all_args = {}
316
+ all_argument_definitions.each do |arg|
317
+ if (prev_entry = all_args[arg.graphql_name])
318
+ if prev_entry.is_a?(Array)
319
+ prev_entry << arg
320
+ else
321
+ all_args[arg.graphql_name] = [prev_entry, arg]
322
+ end
323
+ else
324
+ all_args[arg.graphql_name] = arg
325
+ end
326
+ end
327
+
308
328
  field_opts = {
309
329
  type: type_expr,
310
330
  description: description,
311
331
  extras: extras,
312
332
  resolver_method: :resolve_with_support,
313
333
  resolver_class: self,
314
- arguments: arguments,
334
+ arguments: all_args,
315
335
  null: null,
316
336
  complexity: complexity,
317
337
  broadcastable: broadcastable?,
@@ -14,6 +14,8 @@ module GraphQL
14
14
  val
15
15
  end
16
16
 
17
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
18
+
17
19
  def to_graphql
18
20
  type_defn = GraphQL::ScalarType.new
19
21
  type_defn.name = graphql_name
@@ -103,10 +103,12 @@ module GraphQL
103
103
  # Call this method to provide a new subscription_scope; OR
104
104
  # call it without an argument to get the subscription_scope
105
105
  # @param new_scope [Symbol]
106
+ # @param optional [Boolean] If true, then don't require `scope:` to be provided to updates to this subscription.
106
107
  # @return [Symbol]
107
- def self.subscription_scope(new_scope = READING_SCOPE)
108
+ def self.subscription_scope(new_scope = READING_SCOPE, optional: false)
108
109
  if new_scope != READING_SCOPE
109
110
  @subscription_scope = new_scope
111
+ @subscription_scope_optional = optional
110
112
  elsif defined?(@subscription_scope)
111
113
  @subscription_scope
112
114
  else
@@ -114,6 +116,14 @@ module GraphQL
114
116
  end
115
117
  end
116
118
 
119
+ def self.subscription_scope_optional?
120
+ if defined?(@subscription_scope_optional)
121
+ @subscription_scope_optional
122
+ else
123
+ find_inherited_value(:subscription_scope_optional, false)
124
+ end
125
+ end
126
+
117
127
  # This is called during initial subscription to get a "name" for this subscription.
118
128
  # Later, when `.trigger` is called, this will be called again to build another "name".
119
129
  # Any subscribers with matching topic will begin the update flow.
@@ -173,7 +173,7 @@ Some late-bound types couldn't be resolved:
173
173
  end
174
174
  when Class
175
175
  if member.respond_to?(:graphql_definition)
176
- graphql_member = member.graphql_definition
176
+ graphql_member = member.graphql_definition(silence_deprecation_warning: true)
177
177
  visit(schema, graphql_member, context_description)
178
178
  else
179
179
  raise GraphQL::Schema::InvalidTypeError.new("Unexpected traversal member: #{member} (#{member.class.name})")
@@ -11,7 +11,7 @@ module GraphQL
11
11
  def self.build_type(type_owner, ast_node)
12
12
  case ast_node
13
13
  when GraphQL::Language::Nodes::TypeName
14
- type_owner.get_type(ast_node.name)
14
+ type_owner.get_type(ast_node.name) # rubocop:disable Development/ContextIsPassedCop -- this is a `context` or `warden`, it's already query-aware
15
15
  when GraphQL::Language::Nodes::NonNullType
16
16
  ast_inner_type = ast_node.of_type
17
17
  inner_type = build_type(type_owner, ast_inner_type)
@@ -4,8 +4,6 @@ module GraphQL
4
4
  class Schema
5
5
  # This class joins an object type to an abstract type (interface or union) of which
6
6
  # it is a member.
7
- #
8
- # TODO: Not yet implemented for interfaces.
9
7
  class TypeMembership
10
8
  # @return [Class<GraphQL::Schema::Object>]
11
9
  attr_accessor :object_type
@@ -26,9 +24,25 @@ module GraphQL
26
24
  end
27
25
 
28
26
  # @return [Boolean] if false, {#object_type} will be treated as _not_ a member of {#abstract_type}
29
- def visible?(_ctx)
30
- true
27
+ def visible?(ctx)
28
+ warden = Warden.from_context(ctx)
29
+ (@object_type.respond_to?(:visible?) ? warden.visible_type?(@object_type, ctx) : true) &&
30
+ (@abstract_type.respond_to?(:visible?) ? warden.visible_type?(@abstract_type, ctx) : true)
31
31
  end
32
+
33
+ def graphql_name
34
+ "#{@object_type.graphql_name}.#{@abstract_type.kind.interface? ? "implements" : "belongsTo" }.#{@abstract_type.graphql_name}"
35
+ end
36
+
37
+ def path
38
+ graphql_name
39
+ end
40
+
41
+ def inspect
42
+ "#<#{self.class} #{@object_type.inspect} => #{@abstract_type.inspect}>"
43
+ end
44
+
45
+ alias :type_class :itself
32
46
  end
33
47
  end
34
48
  end
@@ -19,8 +19,9 @@ module GraphQL
19
19
  end
20
20
  else
21
21
  visible_types = []
22
+ warden = Warden.from_context(context)
22
23
  type_memberships.each do |type_membership|
23
- if type_membership.visible?(context)
24
+ if warden.visible_type_membership?(type_membership, context)
24
25
  visible_types << type_membership.object_type
25
26
  end
26
27
  end
@@ -28,6 +29,12 @@ module GraphQL
28
29
  end
29
30
  end
30
31
 
32
+ def all_possible_types
33
+ type_memberships.map(&:object_type)
34
+ end
35
+
36
+ prepend GraphQL::Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
37
+
31
38
  def to_graphql
32
39
  type_defn = GraphQL::UnionType.new
33
40
  type_defn.name = graphql_name
@@ -18,10 +18,6 @@ module GraphQL
18
18
  # # It's pretty hard to come up with a legitimate use case for `without:`
19
19
  #
20
20
  class FormatValidator < Validator
21
- if !String.method_defined?(:match?)
22
- using GraphQL::StringMatchBackport
23
- end
24
-
25
21
  # @param with [RegExp, nil]
26
22
  # @param without [Regexp, nil]
27
23
  # @param message [String]
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module GraphQL
2
3
  class Schema
3
4
  class Validator
@@ -14,7 +14,7 @@ module GraphQL
14
14
  # argument :ingredient_id, ID, required: true
15
15
  # argument :cups, Integer, required: false
16
16
  # argument :tablespoons, Integer, required: false
17
- # argument :teaspoons, Integer, required: true
17
+ # argument :teaspoons, Integer, required: false
18
18
  # validates required: { one_of: [:cups, :tablespoons, :teaspoons] }
19
19
  # end
20
20
  #
@@ -28,11 +28,23 @@ module GraphQL
28
28
  # validates required: { one_of: [:node_id, [:object_type, :object_id]] }
29
29
  # end
30
30
  #
31
+ # @example require _some_ value for an argument, even if it's null
32
+ # field :update_settings, AccountSettings do
33
+ # # `required: :nullable` means this argument must be given, but may be `null`
34
+ # argument :age, Integer, required: :nullable
35
+ # end
36
+ #
31
37
  class RequiredValidator < Validator
32
38
  # @param one_of [Symbol, Array<Symbol>] An argument, or a list of arguments, that represents a valid set of inputs for this field
33
39
  # @param message [String]
34
- def initialize(one_of:, message: "%{validated} has the wrong arguments", **default_options)
35
- @one_of = one_of
40
+ def initialize(one_of: nil, argument: nil, message: "%{validated} has the wrong arguments", **default_options)
41
+ @one_of = if one_of
42
+ one_of
43
+ elsif argument
44
+ [argument]
45
+ else
46
+ raise ArgumentError, "`one_of:` or `argument:` must be given in `validates required: {...}`"
47
+ end
36
48
  @message = message
37
49
  super(**default_options)
38
50
  end
@@ -40,19 +52,21 @@ module GraphQL
40
52
  def validate(_object, _context, value)
41
53
  matched_conditions = 0
42
54
 
43
- @one_of.each do |one_of_condition|
44
- case one_of_condition
45
- when Symbol
46
- if value.key?(one_of_condition)
47
- matched_conditions += 1
48
- end
49
- when Array
50
- if one_of_condition.all? { |k| value.key?(k) }
51
- matched_conditions += 1
52
- break
55
+ if !value.nil?
56
+ @one_of.each do |one_of_condition|
57
+ case one_of_condition
58
+ when Symbol
59
+ if value.key?(one_of_condition)
60
+ matched_conditions += 1
61
+ end
62
+ when Array
63
+ if one_of_condition.all? { |k| value.key?(k) }
64
+ matched_conditions += 1
65
+ break
66
+ end
67
+ else
68
+ raise ArgumentError, "Unknown one_of condition: #{one_of_condition.inspect}"
53
69
  end
54
- else
55
- raise ArgumentError, "Unknown one_of condition: #{one_of_condition.inspect}"
56
70
  end
57
71
  end
58
72
 
@@ -48,16 +48,13 @@ module GraphQL
48
48
  EMPTY_ARRAY
49
49
  else
50
50
  validates_hash = validates_hash.dup
51
- allow_null = validates_hash.delete(:allow_null)
52
- allow_blank = validates_hash.delete(:allow_blank)
53
51
 
54
- # This could be {...}.compact on Ruby 2.4+
55
52
  default_options = {}
56
- if !allow_null.nil?
57
- default_options[:allow_null] = allow_null
53
+ if validates_hash[:allow_null]
54
+ default_options[:allow_null] = validates_hash.delete(:allow_null)
58
55
  end
59
- if !allow_blank.nil?
60
- default_options[:allow_blank] = allow_blank
56
+ if validates_hash[:allow_blank]
57
+ default_options[:allow_blank] = validates_hash.delete(:allow_blank)
61
58
  end
62
59
 
63
60
  # allow_nil or allow_blank are the _only_ validations: