graphql 2.3.7 → 2.4.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.

Potentially problematic release.


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

Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +46 -0
  3. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  4. data/lib/generators/graphql/templates/base_resolver.erb +2 -0
  5. data/lib/generators/graphql/type_generator.rb +1 -1
  6. data/lib/graphql/analysis/field_usage.rb +1 -1
  7. data/lib/graphql/analysis/query_complexity.rb +3 -3
  8. data/lib/graphql/analysis/visitor.rb +8 -7
  9. data/lib/graphql/analysis.rb +4 -4
  10. data/lib/graphql/autoload.rb +38 -0
  11. data/lib/graphql/current.rb +52 -0
  12. data/lib/graphql/dataloader/async_dataloader.rb +7 -6
  13. data/lib/graphql/dataloader/source.rb +7 -4
  14. data/lib/graphql/dataloader.rb +40 -19
  15. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  16. data/lib/graphql/execution/interpreter/resolve.rb +13 -9
  17. data/lib/graphql/execution/interpreter/runtime.rb +35 -31
  18. data/lib/graphql/execution/interpreter.rb +6 -4
  19. data/lib/graphql/execution/lookahead.rb +18 -11
  20. data/lib/graphql/introspection/directive_type.rb +1 -1
  21. data/lib/graphql/introspection/entry_points.rb +2 -2
  22. data/lib/graphql/introspection/field_type.rb +1 -1
  23. data/lib/graphql/introspection/schema_type.rb +6 -11
  24. data/lib/graphql/introspection/type_type.rb +5 -5
  25. data/lib/graphql/invalid_null_error.rb +1 -1
  26. data/lib/graphql/language/cache.rb +13 -0
  27. data/lib/graphql/language/comment.rb +18 -0
  28. data/lib/graphql/language/document_from_schema_definition.rb +62 -34
  29. data/lib/graphql/language/lexer.rb +18 -15
  30. data/lib/graphql/language/nodes.rb +24 -16
  31. data/lib/graphql/language/parser.rb +14 -1
  32. data/lib/graphql/language/printer.rb +31 -15
  33. data/lib/graphql/language/sanitized_printer.rb +1 -1
  34. data/lib/graphql/language.rb +6 -6
  35. data/lib/graphql/pagination/connection.rb +1 -1
  36. data/lib/graphql/query/context/scoped_context.rb +1 -1
  37. data/lib/graphql/query/context.rb +13 -6
  38. data/lib/graphql/query/null_context.rb +3 -5
  39. data/lib/graphql/query/variable_validation_error.rb +1 -1
  40. data/lib/graphql/query.rb +72 -18
  41. data/lib/graphql/railtie.rb +7 -0
  42. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  43. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  44. data/lib/graphql/rubocop.rb +2 -0
  45. data/lib/graphql/schema/addition.rb +2 -1
  46. data/lib/graphql/schema/always_visible.rb +6 -2
  47. data/lib/graphql/schema/argument.rb +14 -1
  48. data/lib/graphql/schema/build_from_definition.rb +9 -1
  49. data/lib/graphql/schema/directive/flagged.rb +2 -2
  50. data/lib/graphql/schema/directive.rb +1 -1
  51. data/lib/graphql/schema/enum.rb +71 -23
  52. data/lib/graphql/schema/enum_value.rb +10 -2
  53. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  54. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  55. data/lib/graphql/schema/field.rb +102 -47
  56. data/lib/graphql/schema/field_extension.rb +1 -1
  57. data/lib/graphql/schema/has_single_input_argument.rb +5 -2
  58. data/lib/graphql/schema/input_object.rb +90 -39
  59. data/lib/graphql/schema/interface.rb +22 -5
  60. data/lib/graphql/schema/introspection_system.rb +5 -16
  61. data/lib/graphql/schema/loader.rb +1 -1
  62. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  63. data/lib/graphql/schema/member/has_arguments.rb +36 -23
  64. data/lib/graphql/schema/member/has_directives.rb +3 -3
  65. data/lib/graphql/schema/member/has_fields.rb +26 -6
  66. data/lib/graphql/schema/member/has_interfaces.rb +4 -4
  67. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  68. data/lib/graphql/schema/member/has_validators.rb +1 -1
  69. data/lib/graphql/schema/object.rb +8 -0
  70. data/lib/graphql/schema/printer.rb +1 -0
  71. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  72. data/lib/graphql/schema/resolver.rb +12 -14
  73. data/lib/graphql/schema/subscription.rb +2 -2
  74. data/lib/graphql/schema/type_expression.rb +2 -2
  75. data/lib/graphql/schema/union.rb +1 -1
  76. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  77. data/lib/graphql/schema/validator/required_validator.rb +28 -4
  78. data/lib/graphql/schema/validator.rb +3 -1
  79. data/lib/graphql/schema/visibility/migration.rb +188 -0
  80. data/lib/graphql/schema/visibility/profile.rb +359 -0
  81. data/lib/graphql/schema/visibility/visit.rb +190 -0
  82. data/lib/graphql/schema/visibility.rb +294 -0
  83. data/lib/graphql/schema/warden.rb +179 -16
  84. data/lib/graphql/schema.rb +348 -94
  85. data/lib/graphql/static_validation/base_visitor.rb +6 -5
  86. data/lib/graphql/static_validation/literal_validator.rb +4 -4
  87. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  88. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  89. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  90. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  91. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  92. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  93. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +2 -2
  94. data/lib/graphql/static_validation/rules/fields_will_merge.rb +8 -7
  95. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  96. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  97. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  98. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  99. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  100. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  101. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
  102. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
  103. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
  104. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  105. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  106. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  107. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  108. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  109. data/lib/graphql/static_validation/validation_context.rb +18 -2
  110. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +3 -2
  111. data/lib/graphql/subscriptions/broadcast_analyzer.rb +10 -4
  112. data/lib/graphql/subscriptions/event.rb +1 -1
  113. data/lib/graphql/subscriptions/serialize.rb +2 -0
  114. data/lib/graphql/subscriptions.rb +6 -4
  115. data/lib/graphql/testing/helpers.rb +10 -6
  116. data/lib/graphql/tracing/notifications_trace.rb +2 -2
  117. data/lib/graphql/types/relay/connection_behaviors.rb +12 -2
  118. data/lib/graphql/types/relay/edge_behaviors.rb +11 -1
  119. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  120. data/lib/graphql/types.rb +18 -11
  121. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  122. data/lib/graphql/version.rb +1 -1
  123. data/lib/graphql.rb +53 -45
  124. metadata +31 -8
  125. data/lib/graphql/language/token.rb +0 -34
  126. data/lib/graphql/schema/invalid_type_error.rb +0 -7
@@ -22,9 +22,21 @@ module GraphQL
22
22
  class Enum < GraphQL::Schema::Member
23
23
  extend GraphQL::Schema::Member::ValidatesInput
24
24
 
25
+ # This is raised when either:
26
+ #
27
+ # - A resolver returns a value which doesn't match any of the enum's configured values;
28
+ # - Or, the resolver returns a value which matches a value, but that value's `authorized?` check returns false.
29
+ #
30
+ # In either case, the field should be modified so that the invalid value isn't returned.
31
+ #
32
+ # {GraphQL::Schema::Enum} subclasses get their own subclass of this error, so that bug trackers can better show where they came from.
25
33
  class UnresolvedValueError < GraphQL::Error
26
- def initialize(value:, enum:, context:)
27
- fix_message = ", but this isn't a valid value for `#{enum.graphql_name}`. Update the field or resolver to return one of `#{enum.graphql_name}`'s values instead."
34
+ def initialize(value:, enum:, context:, authorized:)
35
+ fix_message = if authorized == false
36
+ ", but this value was unauthorized. Update the field or resolver to return a different value in this case (or return `nil`)."
37
+ else
38
+ ", but this isn't a valid value for `#{enum.graphql_name}`. Update the field or resolver to return one of `#{enum.graphql_name}`'s values instead."
39
+ end
28
40
  message = if (cp = context[:current_path]) && (cf = context[:current_field])
29
41
  "`#{cf.path}` returned `#{value.inspect}` at `#{cp.join(".")}`#{fix_message}"
30
42
  else
@@ -34,6 +46,8 @@ module GraphQL
34
46
  end
35
47
  end
36
48
 
49
+ # Raised when a {GraphQL::Schema::Enum} is defined to have no values.
50
+ # This can also happen when all values return false for `.visible?`.
37
51
  class MissingValuesError < GraphQL::Error
38
52
  def initialize(enum_type)
39
53
  @enum_type = enum_type
@@ -43,10 +57,11 @@ module GraphQL
43
57
 
44
58
  class << self
45
59
  # Define a value for this enum
46
- # @param graphql_name [String, Symbol] the GraphQL value for this, usually `SCREAMING_CASE`
47
- # @param description [String], the GraphQL description for this value, present in documentation
48
- # @param value [Object], the translated Ruby value for this object (defaults to `graphql_name`)
49
- # @param deprecation_reason [String] if this object is deprecated, include a message here
60
+ # @option kwargs [String, Symbol] :graphql_name the GraphQL value for this, usually `SCREAMING_CASE`
61
+ # @option kwargs [String] :description, the GraphQL description for this value, present in documentation
62
+ # @option kwargs [String] :comment, the GraphQL comment for this value, present in documentation
63
+ # @option kwargs [::Object] :value the translated Ruby value for this object (defaults to `graphql_name`)
64
+ # @option kwargs [String] :deprecation_reason if this object is deprecated, include a message here
50
65
  # @return [void]
51
66
  # @see {Schema::EnumValue} which handles these inputs by default
52
67
  def value(*args, **kwargs, &block)
@@ -71,10 +86,24 @@ module GraphQL
71
86
  def enum_values(context = GraphQL::Query::NullContext.instance)
72
87
  inherited_values = superclass.respond_to?(:enum_values) ? superclass.enum_values(context) : nil
73
88
  visible_values = []
74
- warden = Warden.from_context(context)
89
+ types = Warden.types_from_context(context)
75
90
  own_values.each do |key, values_entry|
76
- if (v = Warden.visible_entry?(:visible_enum_value?, values_entry, context, warden))
77
- visible_values << v
91
+ visible_value = nil
92
+ if values_entry.is_a?(Array)
93
+ values_entry.each do |v|
94
+ if types.visible_enum_value?(v, context)
95
+ if visible_value.nil?
96
+ visible_value = v
97
+ visible_values << v
98
+ else
99
+ raise DuplicateNamesError.new(
100
+ duplicated_name: v.path, duplicated_definition_1: visible_value.inspect, duplicated_definition_2: v.inspect
101
+ )
102
+ end
103
+ end
104
+ end
105
+ elsif types.visible_enum_value?(values_entry, context)
106
+ visible_values << values_entry
78
107
  end
79
108
  end
80
109
 
@@ -130,7 +159,7 @@ module GraphQL
130
159
  end
131
160
 
132
161
  def validate_non_null_input(value_name, ctx, max_errors: nil)
133
- allowed_values = ctx.warden.enum_values(self)
162
+ allowed_values = ctx.types.enum_values(self)
134
163
  matching_value = allowed_values.find { |v| v.graphql_name == value_name }
135
164
 
136
165
  if matching_value.nil?
@@ -138,35 +167,54 @@ module GraphQL
138
167
  else
139
168
  nil
140
169
  end
170
+ # rescue MissingValuesError
171
+ # nil
141
172
  end
142
173
 
174
+ # Called by the runtime when a field returns a value to give back to the client.
175
+ # This method checks that the incoming {value} matches one of the enum's defined values.
176
+ # @param value [Object] Any value matching the values for this enum.
177
+ # @param ctx [GraphQL::Query::Context]
178
+ # @raise [GraphQL::Schema::Enum::UnresolvedValueError] if {value} doesn't match a configured value or if the matching value isn't authorized.
179
+ # @return [String] The GraphQL-ready string for {value}
143
180
  def coerce_result(value, ctx)
144
- warden = ctx.warden
145
- all_values = warden ? warden.enum_values(self) : values.each_value
181
+ types = ctx.types
182
+ all_values = types ? types.enum_values(self) : values.each_value
146
183
  enum_value = all_values.find { |val| val.value == value }
147
- if enum_value
184
+ if enum_value && (was_authed = enum_value.authorized?(ctx))
148
185
  enum_value.graphql_name
149
186
  else
150
- raise self::UnresolvedValueError.new(enum: self, value: value, context: ctx)
187
+ raise self::UnresolvedValueError.new(enum: self, value: value, context: ctx, authorized: was_authed)
151
188
  end
152
189
  end
153
190
 
191
+ # Called by the runtime with incoming string representations from a query.
192
+ # It will match the string to a configured by name or by Ruby value.
193
+ # @param value_name [String, Object] A string from a GraphQL query, or a Ruby value matching a `value(..., value: ...)` configuration
194
+ # @param ctx [GraphQL::Query::Context]
195
+ # @raise [GraphQL::UnauthorizedEnumValueError] if an {EnumValue} matches but returns false for `.authorized?`. Goes to {Schema.unauthorized_object}.
196
+ # @return [Object] The Ruby value for the matched {GraphQL::Schema::EnumValue}
154
197
  def coerce_input(value_name, ctx)
155
- all_values = ctx.warden ? ctx.warden.enum_values(self) : values.each_value
156
-
157
- if v = all_values.find { |val| val.graphql_name == value_name }
158
- v.value
159
- elsif v = all_values.find { |val| val.value == value_name }
160
- # this is for matching default values, which are "inputs", but they're
161
- # the Ruby value, not the GraphQL string.
162
- v.value
198
+ all_values = ctx.types ? ctx.types.enum_values(self) : values.each_value
199
+
200
+ # This tries matching by incoming GraphQL string, then checks Ruby-defined values
201
+ if v = (all_values.find { |val| val.graphql_name == value_name } || all_values.find { |val| val.value == value_name })
202
+ if v.authorized?(ctx)
203
+ v.value
204
+ else
205
+ raise GraphQL::UnauthorizedEnumValueError.new(type: self, enum_value: v, context: ctx)
206
+ end
163
207
  else
164
208
  nil
165
209
  end
166
210
  end
167
211
 
168
212
  def inherited(child_class)
169
- child_class.const_set(:UnresolvedValueError, Class.new(Schema::Enum::UnresolvedValueError))
213
+ if child_class.name
214
+ # Don't assign a custom error class to anonymous classes
215
+ # because they would end up with names like `#<Class0x1234>::UnresolvedValueError` which messes up bug trackers
216
+ child_class.const_set(:UnresolvedValueError, Class.new(Schema::Enum::UnresolvedValueError))
217
+ end
170
218
  super
171
219
  end
172
220
 
@@ -30,10 +30,11 @@ module GraphQL
30
30
  # @return [Class] The enum type that owns this value
31
31
  attr_reader :owner
32
32
 
33
- def initialize(graphql_name, desc = nil, owner:, ast_node: nil, directives: nil, description: nil, value: NOT_CONFIGURED, deprecation_reason: nil, &block)
33
+ def initialize(graphql_name, desc = nil, owner:, ast_node: nil, directives: nil, description: nil, comment: nil, value: NOT_CONFIGURED, deprecation_reason: nil, &block)
34
34
  @graphql_name = graphql_name.to_s
35
35
  GraphQL::NameValidator.validate!(@graphql_name)
36
36
  @description = desc || description
37
+ @comment = comment
37
38
  @value = value == NOT_CONFIGURED ? @graphql_name : value
38
39
  if deprecation_reason
39
40
  self.deprecation_reason = deprecation_reason
@@ -58,6 +59,13 @@ module GraphQL
58
59
  @description
59
60
  end
60
61
 
62
+ def comment(new_comment = nil)
63
+ if new_comment
64
+ @comment = new_comment
65
+ end
66
+ @comment
67
+ end
68
+
61
69
  def value(new_val = nil)
62
70
  unless new_val.nil?
63
71
  @value = new_val
@@ -66,7 +74,7 @@ module GraphQL
66
74
  end
67
75
 
68
76
  def inspect
69
- "#<#{self.class} #{path} @value=#{@value.inspect}#{description ? " @description=#{description.inspect}" : ""}>"
77
+ "#<#{self.class} #{path} @value=#{@value.inspect}#{description ? " @description=#{description.inspect}" : ""}#{deprecation_reason ? " @deprecation_reason=#{deprecation_reason.inspect}" : ""}>"
70
78
  end
71
79
 
72
80
  def visible?(_ctx); true; end
@@ -50,7 +50,7 @@ module GraphQL
50
50
  if field.has_default_page_size? && !value.has_default_page_size_override?
51
51
  value.default_page_size = field.default_page_size
52
52
  end
53
- if context.schema.new_connections? && (custom_t = context.schema.connections.edge_class_for_field(@field))
53
+ if (custom_t = context.schema.connections.edge_class_for_field(@field))
54
54
  value.edge_class = custom_t
55
55
  end
56
56
  value
@@ -12,7 +12,7 @@ module GraphQL
12
12
  if ret_type.respond_to?(:scope_items)
13
13
  scoped_items = ret_type.scope_items(value, context)
14
14
  if !scoped_items.equal?(value) && !ret_type.reauthorize_scoped_objects
15
- if (current_runtime_state = Thread.current[:__graphql_runtime_info]) &&
15
+ if (current_runtime_state = Fiber[:__graphql_runtime_info]) &&
16
16
  (query_runtime_state = current_runtime_state[context.query])
17
17
  query_runtime_state.was_authorized_by_scope_items = true
18
18
  end
@@ -42,8 +42,8 @@ module GraphQL
42
42
  end
43
43
 
44
44
  def directives
45
- if @resolver_class && (r_dirs = @resolver_class.directives).any?
46
- if (own_dirs = super).any?
45
+ if @resolver_class && !(r_dirs = @resolver_class.directives).empty?
46
+ if !(own_dirs = super).empty?
47
47
  own_dirs + r_dirs
48
48
  else
49
49
  r_dirs
@@ -81,7 +81,7 @@ module GraphQL
81
81
  end
82
82
 
83
83
  def inspect
84
- "#<#{self.class} #{path}#{all_argument_definitions.any? ? "(...)" : ""}: #{type.to_type_signature}>"
84
+ "#<#{self.class} #{path}#{!all_argument_definitions.empty? ? "(...)" : ""}: #{type.to_type_signature}>"
85
85
  end
86
86
 
87
87
  alias :mutation :resolver
@@ -106,7 +106,7 @@ module GraphQL
106
106
  # @param subscription [Class] A {GraphQL::Schema::Subscription} class to use for field configuration
107
107
  # @return [GraphQL::Schema:Field] an instance of `self`
108
108
  # @see {.initialize} for other options
109
- def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
109
+ def self.from_options(name = nil, type = nil, desc = nil, comment: nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
110
110
  if (resolver_class = resolver || mutation || subscription)
111
111
  # Add a reference to that parent class
112
112
  kwargs[:resolver_class] = resolver_class
@@ -116,6 +116,10 @@ module GraphQL
116
116
  kwargs[:name] = name
117
117
  end
118
118
 
119
+ if comment
120
+ kwargs[:comment] = comment
121
+ end
122
+
119
123
  if !type.nil?
120
124
  if desc
121
125
  if kwargs[:description]
@@ -146,11 +150,16 @@ module GraphQL
146
150
  Member::BuildType.to_type_name(@return_type_expr)
147
151
  elsif @resolver_class && @resolver_class.type
148
152
  Member::BuildType.to_type_name(@resolver_class.type)
149
- else
153
+ elsif type
150
154
  # As a last ditch, try to force loading the return type:
151
155
  type.unwrap.name
152
156
  end
153
- @connection = return_type_name.end_with?("Connection") && return_type_name != "Connection"
157
+ if return_type_name
158
+ @connection = return_type_name.end_with?("Connection") && return_type_name != "Connection"
159
+ else
160
+ # TODO set this when type is set by method
161
+ false # not loaded yet?
162
+ end
154
163
  else
155
164
  @connection
156
165
  end
@@ -207,6 +216,7 @@ module GraphQL
207
216
  # @param owner [Class] The type that this field belongs to
208
217
  # @param null [Boolean] (defaults to `true`) `true` if this field may return `null`, `false` if it is never `null`
209
218
  # @param description [String] Field description
219
+ # @param comment [String] Field comment
210
220
  # @param deprecation_reason [String] If present, the field is marked "deprecated" with this message
211
221
  # @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`)
212
222
  # @param hash_key [String, Symbol] The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`)
@@ -231,13 +241,13 @@ module GraphQL
231
241
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
232
242
  # @param validates [Array<Hash>] Configurations for validating this field
233
243
  # @param fallback_value [Object] A fallback value if the method is not defined
234
- def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
244
+ def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
235
245
  if name.nil?
236
246
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
237
247
  end
238
248
  if !(resolver_class)
239
- if type.nil?
240
- raise ArgumentError, "missing second `type` argument or keyword `type:`"
249
+ if type.nil? && !block_given?
250
+ raise ArgumentError, "missing second `type` argument, keyword `type:`, or a block containing `type(...)`"
241
251
  end
242
252
  end
243
253
  @original_name = name
@@ -247,6 +257,7 @@ module GraphQL
247
257
  @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
248
258
 
249
259
  @description = description
260
+ @comment = comment
250
261
  @type = @owner_type = @own_validators = @own_directives = @own_arguments = @arguments_statically_coercible = nil # these will be prepared later if necessary
251
262
 
252
263
  self.deprecation_reason = deprecation_reason
@@ -302,6 +313,7 @@ module GraphQL
302
313
  @ast_node = ast_node
303
314
  @method_conflict_warning = method_conflict_warning
304
315
  @fallback_value = fallback_value
316
+ @definition_block = definition_block
305
317
 
306
318
  arguments.each do |name, arg|
307
319
  case arg
@@ -321,28 +333,17 @@ module GraphQL
321
333
 
322
334
  @extensions = EMPTY_ARRAY
323
335
  @call_after_define = false
324
- # This should run before connection extension,
325
- # but should it run after the definition block?
326
- if scoped?
327
- self.extension(ScopeExtension)
328
- end
329
-
330
- # The problem with putting this after the definition_block
331
- # is that it would override arguments
332
- if connection? && connection_extension
333
- self.extension(connection_extension)
334
- end
335
-
336
+ set_pagination_extensions(connection_extension: connection_extension)
336
337
  # Do this last so we have as much context as possible when initializing them:
337
- if extensions.any?
338
+ if !extensions.empty?
338
339
  self.extensions(extensions)
339
340
  end
340
341
 
341
- if resolver_class && resolver_class.extensions.any?
342
+ if resolver_class && !resolver_class.extensions.empty?
342
343
  self.extensions(resolver_class.extensions)
343
344
  end
344
345
 
345
- if directives.any?
346
+ if !directives.empty?
346
347
  directives.each do |(dir_class, options)|
347
348
  self.directive(dir_class, **options)
348
349
  end
@@ -352,16 +353,29 @@ module GraphQL
352
353
  self.validates(validates)
353
354
  end
354
355
 
355
- if block_given?
356
- if definition_block.arity == 1
357
- yield self
356
+ if @definition_block.nil?
357
+ self.extensions.each(&:after_define_apply)
358
+ @call_after_define = true
359
+ end
360
+ end
361
+
362
+ # Calls the definition block, if one was given.
363
+ # This is deferred so that references to the return type
364
+ # can be lazily evaluated, reducing Rails boot time.
365
+ # @return [self]
366
+ # @api private
367
+ def ensure_loaded
368
+ if @definition_block
369
+ if @definition_block.arity == 1
370
+ @definition_block.call(self)
358
371
  else
359
- instance_eval(&definition_block)
372
+ instance_eval(&@definition_block)
360
373
  end
374
+ self.extensions.each(&:after_define_apply)
375
+ @call_after_define = true
376
+ @definition_block = nil
361
377
  end
362
-
363
- self.extensions.each(&:after_define_apply)
364
- @call_after_define = true
378
+ self
365
379
  end
366
380
 
367
381
  attr_accessor :dynamic_introspection
@@ -393,6 +407,20 @@ module GraphQL
393
407
  end
394
408
  end
395
409
 
410
+ # @param text [String]
411
+ # @return [String, nil]
412
+ def comment(text = nil)
413
+ if text
414
+ @comment = text
415
+ elsif !NOT_CONFIGURED.equal?(@comment)
416
+ @comment
417
+ elsif @resolver_class
418
+ @resolver_class.comment
419
+ else
420
+ nil
421
+ end
422
+ end
423
+
396
424
  # Read extension instances from this field,
397
425
  # or add new classes/options to be initialized on this field.
398
426
  # Extensions are executed in the order they are added.
@@ -413,7 +441,7 @@ module GraphQL
413
441
  new_extensions.each do |extension_config|
414
442
  if extension_config.is_a?(Hash)
415
443
  extension_class, options = *extension_config.to_a[0]
416
- self.extension(extension_class, options)
444
+ self.extension(extension_class, **options)
417
445
  else
418
446
  self.extension(extension_config)
419
447
  end
@@ -433,7 +461,7 @@ module GraphQL
433
461
  # @param extension_class [Class] subclass of {Schema::FieldExtension}
434
462
  # @param options [Hash] if provided, given as `options:` when initializing `extension`.
435
463
  # @return [void]
436
- def extension(extension_class, options = nil)
464
+ def extension(extension_class, **options)
437
465
  extension_inst = extension_class.new(field: self, options: options)
438
466
  if @extensions.frozen?
439
467
  @extensions = @extensions.dup
@@ -454,7 +482,7 @@ module GraphQL
454
482
  if new_extras.nil?
455
483
  # Read the value
456
484
  field_extras = @extras
457
- if @resolver_class && @resolver_class.extras.any?
485
+ if @resolver_class && !@resolver_class.extras.empty?
458
486
  field_extras + @resolver_class.extras
459
487
  else
460
488
  field_extras
@@ -483,7 +511,7 @@ module GraphQL
483
511
  if arguments[:last] && (max_possible_page_size.nil? || arguments[:last] > max_possible_page_size)
484
512
  max_possible_page_size = arguments[:last]
485
513
  end
486
- elsif arguments.is_a?(GraphQL::UnauthorizedError)
514
+ elsif arguments.is_a?(GraphQL::ExecutionError) || arguments.is_a?(GraphQL::UnauthorizedError)
487
515
  raise arguments
488
516
  end
489
517
 
@@ -577,16 +605,29 @@ module GraphQL
577
605
  class MissingReturnTypeError < GraphQL::Error; end
578
606
  attr_writer :type
579
607
 
580
- def type
581
- if @resolver_class
582
- return_type = @return_type_expr || @resolver_class.type_expr
583
- if return_type.nil?
584
- raise MissingReturnTypeError, "Can't determine the return type for #{self.path} (it has `resolver: #{@resolver_class}`, perhaps that class is missing a `type ...` declaration, or perhaps its type causes a cyclical loading issue)"
608
+ # Get or set the return type of this field.
609
+ #
610
+ # It may return nil if no type was configured or if the given definition block wasn't called yet.
611
+ # @param new_type [Module, GraphQL::Schema::NonNull, GraphQL::Schema::List] A GraphQL return type
612
+ # @return [Module, GraphQL::Schema::NonNull, GraphQL::Schema::List, nil] the configured type for this field
613
+ def type(new_type = NOT_CONFIGURED)
614
+ if NOT_CONFIGURED.equal?(new_type)
615
+ if @resolver_class
616
+ return_type = @return_type_expr || @resolver_class.type_expr
617
+ if return_type.nil?
618
+ raise MissingReturnTypeError, "Can't determine the return type for #{self.path} (it has `resolver: #{@resolver_class}`, perhaps that class is missing a `type ...` declaration, or perhaps its type causes a cyclical loading issue)"
619
+ end
620
+ nullable = @return_type_null.nil? ? @resolver_class.null : @return_type_null
621
+ Member::BuildType.parse_type(return_type, null: nullable)
622
+ elsif !@return_type_expr.nil?
623
+ @type ||= Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
585
624
  end
586
- nullable = @return_type_null.nil? ? @resolver_class.null : @return_type_null
587
- Member::BuildType.parse_type(return_type, null: nullable)
588
625
  else
589
- @type ||= Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
626
+ @return_type_expr = new_type
627
+ # If `type` is set in the definition block, then the `connection_extension: ...` given as a keyword won't be used, hmm...
628
+ # Also, arguments added by `connection_extension` will clobber anything previously defined,
629
+ # so `type(...)` should go first.
630
+ set_pagination_extensions(connection_extension: self.class.connection_extension)
590
631
  end
591
632
  rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
592
633
  # Let this propagate up
@@ -618,7 +659,7 @@ module GraphQL
618
659
  using_arg_values = false
619
660
  end
620
661
 
621
- args = context.warden.arguments(self)
662
+ args = context.types.arguments(self)
622
663
  args.each do |arg|
623
664
  arg_key = arg.keyword
624
665
  if arg_values.key?(arg_key)
@@ -691,7 +732,7 @@ module GraphQL
691
732
  method_to_call = resolver_method
692
733
  method_receiver = obj
693
734
  # Call the method with kwargs, if there are any
694
- if ruby_kwargs.any?
735
+ if !ruby_kwargs.empty?
695
736
  obj.public_send(resolver_method, **ruby_kwargs)
696
737
  else
697
738
  obj.public_send(resolver_method)
@@ -711,7 +752,7 @@ module GraphQL
711
752
  elsif inner_object.respond_to?(@method_sym)
712
753
  method_to_call = @method_sym
713
754
  method_receiver = obj.object
714
- if ruby_kwargs.any?
755
+ if !ruby_kwargs.empty?
715
756
  inner_object.public_send(@method_sym, **ruby_kwargs)
716
757
  else
717
758
  inner_object.public_send(@method_sym)
@@ -798,7 +839,7 @@ module GraphQL
798
839
  unsatisfied_ruby_kwargs.clear
799
840
  end
800
841
 
801
- if unsatisfied_ruby_kwargs.any? || unsatisfied_method_params.any?
842
+ if !unsatisfied_ruby_kwargs.empty? || !unsatisfied_method_params.empty?
802
843
  raise FieldImplementationFailed.new, <<-ERR
803
844
  Failed to call `#{method_name.inspect}` on #{receiver.inspect} because the Ruby method params were incompatible with the GraphQL arguments:
804
845
 
@@ -897,6 +938,20 @@ ERR
897
938
  raise ArgumentError, "Invalid complexity for #{self.path}: #{own_complexity.inspect}"
898
939
  end
899
940
  end
941
+
942
+ def set_pagination_extensions(connection_extension:)
943
+ # This should run before connection extension,
944
+ # but should it run after the definition block?
945
+ if scoped?
946
+ self.extension(ScopeExtension, call_after_define: false)
947
+ end
948
+
949
+ # The problem with putting this after the definition_block
950
+ # is that it would override arguments
951
+ if connection? && connection_extension
952
+ self.extension(connection_extension, call_after_define: false)
953
+ end
954
+ end
900
955
  end
901
956
  end
902
957
  end
@@ -104,7 +104,7 @@ module GraphQL
104
104
  end
105
105
  end
106
106
  end
107
- if (extras = self.class.extras).any?
107
+ if !(extras = self.class.extras).empty?
108
108
  @added_extras = extras - field.extras
109
109
  field.extras(@added_extras)
110
110
  else
@@ -32,7 +32,7 @@ module GraphQL
32
32
  input_kwargs = {}
33
33
  end
34
34
 
35
- if input_kwargs.any?
35
+ if !input_kwargs.empty?
36
36
  super(**input_kwargs)
37
37
  else
38
38
  super()
@@ -136,6 +136,8 @@ module GraphQL
136
136
  super || "Autogenerated input type of #{self.mutation.graphql_name}"
137
137
  end
138
138
  end
139
+ # For compatibility, in case no arguments are defined:
140
+ has_no_arguments(true)
139
141
  mutation(mutation_class)
140
142
  # these might be inherited:
141
143
  mutation_args.each do |arg|
@@ -149,7 +151,8 @@ module GraphQL
149
151
 
150
152
  def authorize_arguments(args, values)
151
153
  # remove the `input` wrapper to match values
152
- input_args = args["input"].type.unwrap.arguments(context)
154
+ input_type = args.find { |a| a.graphql_name == "input" }.type.unwrap
155
+ input_args = context.types.arguments(input_type)
153
156
  super(input_args, values)
154
157
  end
155
158
  end