graphql 2.3.7 → 2.4.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) 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 +9 -5
  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 +52 -6
  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/default_subscription_resolve_extension.rb +12 -10
  113. data/lib/graphql/subscriptions/event.rb +13 -2
  114. data/lib/graphql/subscriptions/serialize.rb +2 -0
  115. data/lib/graphql/subscriptions.rb +6 -4
  116. data/lib/graphql/testing/helpers.rb +10 -6
  117. data/lib/graphql/tracing/active_support_notifications_trace.rb +1 -1
  118. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  119. data/lib/graphql/tracing/appoptics_trace.rb +2 -0
  120. data/lib/graphql/tracing/appoptics_tracing.rb +2 -0
  121. data/lib/graphql/tracing/appsignal_trace.rb +2 -0
  122. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  123. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  124. data/lib/graphql/tracing/data_dog_trace.rb +2 -0
  125. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  126. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  127. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  128. data/lib/graphql/tracing/new_relic_trace.rb +2 -0
  129. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  130. data/lib/graphql/tracing/notifications_trace.rb +2 -2
  131. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  132. data/lib/graphql/tracing/null_trace.rb +9 -0
  133. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  134. data/lib/graphql/tracing/prometheus_trace.rb +5 -0
  135. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  136. data/lib/graphql/tracing/scout_trace.rb +2 -0
  137. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  138. data/lib/graphql/tracing/sentry_trace.rb +2 -0
  139. data/lib/graphql/tracing/statsd_trace.rb +2 -0
  140. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  141. data/lib/graphql/tracing/trace.rb +3 -0
  142. data/lib/graphql/tracing.rb +28 -30
  143. data/lib/graphql/types/relay/connection_behaviors.rb +12 -2
  144. data/lib/graphql/types/relay/edge_behaviors.rb +11 -1
  145. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  146. data/lib/graphql/types.rb +18 -11
  147. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  148. data/lib/graphql/version.rb +1 -1
  149. data/lib/graphql.rb +53 -45
  150. metadata +33 -8
  151. data/lib/graphql/language/token.rb +0 -34
  152. 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