graphql 1.12.21 → 1.13.2

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 (123) 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 +4 -2
  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 +12 -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/query/arguments.rb +1 -1
  46. data/lib/graphql/query/arguments_cache.rb +1 -1
  47. data/lib/graphql/query/context.rb +15 -2
  48. data/lib/graphql/query/literal_input.rb +1 -1
  49. data/lib/graphql/query/null_context.rb +12 -7
  50. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  51. data/lib/graphql/query/variables.rb +5 -1
  52. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  53. data/lib/graphql/relay/global_id_resolve.rb +1 -1
  54. data/lib/graphql/relay/page_info.rb +1 -1
  55. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  56. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  57. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  58. data/lib/graphql/rubocop.rb +4 -0
  59. data/lib/graphql/schema/addition.rb +37 -28
  60. data/lib/graphql/schema/argument.rb +8 -6
  61. data/lib/graphql/schema/build_from_definition.rb +5 -5
  62. data/lib/graphql/schema/directive/feature.rb +1 -1
  63. data/lib/graphql/schema/directive/flagged.rb +2 -2
  64. data/lib/graphql/schema/directive/include.rb +1 -1
  65. data/lib/graphql/schema/directive/skip.rb +1 -1
  66. data/lib/graphql/schema/directive/transform.rb +1 -1
  67. data/lib/graphql/schema/directive.rb +7 -3
  68. data/lib/graphql/schema/enum.rb +60 -10
  69. data/lib/graphql/schema/enum_value.rb +6 -0
  70. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  71. data/lib/graphql/schema/field.rb +126 -38
  72. data/lib/graphql/schema/field_extension.rb +52 -2
  73. data/lib/graphql/schema/find_inherited_value.rb +1 -0
  74. data/lib/graphql/schema/finder.rb +5 -5
  75. data/lib/graphql/schema/input_object.rb +8 -5
  76. data/lib/graphql/schema/interface.rb +11 -20
  77. data/lib/graphql/schema/introspection_system.rb +1 -1
  78. data/lib/graphql/schema/list.rb +3 -1
  79. data/lib/graphql/schema/member/accepts_definition.rb +15 -3
  80. data/lib/graphql/schema/member/build_type.rb +0 -4
  81. data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
  82. data/lib/graphql/schema/member/has_arguments.rb +55 -13
  83. data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
  84. data/lib/graphql/schema/member/has_fields.rb +76 -18
  85. data/lib/graphql/schema/member/has_interfaces.rb +90 -0
  86. data/lib/graphql/schema/member.rb +1 -0
  87. data/lib/graphql/schema/non_null.rb +3 -1
  88. data/lib/graphql/schema/object.rb +10 -75
  89. data/lib/graphql/schema/printer.rb +1 -1
  90. data/lib/graphql/schema/relay_classic_mutation.rb +37 -3
  91. data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
  92. data/lib/graphql/schema/resolver.rb +37 -17
  93. data/lib/graphql/schema/scalar.rb +2 -0
  94. data/lib/graphql/schema/subscription.rb +11 -1
  95. data/lib/graphql/schema/traversal.rb +1 -1
  96. data/lib/graphql/schema/type_expression.rb +1 -1
  97. data/lib/graphql/schema/type_membership.rb +18 -4
  98. data/lib/graphql/schema/union.rb +8 -1
  99. data/lib/graphql/schema/validator/format_validator.rb +0 -4
  100. data/lib/graphql/schema/validator/numericality_validator.rb +1 -0
  101. data/lib/graphql/schema/validator.rb +4 -7
  102. data/lib/graphql/schema/warden.rb +116 -52
  103. data/lib/graphql/schema.rb +106 -22
  104. data/lib/graphql/static_validation/base_visitor.rb +5 -5
  105. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  106. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  107. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  108. data/lib/graphql/static_validation/rules/fields_will_merge.rb +15 -8
  109. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -1
  110. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
  111. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +7 -7
  112. data/lib/graphql/string_type.rb +1 -1
  113. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -4
  114. data/lib/graphql/subscriptions/event.rb +20 -12
  115. data/lib/graphql/subscriptions.rb +17 -19
  116. data/lib/graphql/types/relay/connection_behaviors.rb +26 -9
  117. data/lib/graphql/types/relay/default_relay.rb +5 -1
  118. data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
  119. data/lib/graphql/types/relay/has_node_field.rb +1 -1
  120. data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
  121. data/lib/graphql/version.rb +1 -1
  122. data/lib/graphql.rb +10 -32
  123. metadata +10 -5
@@ -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
@@ -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: