graphql 2.3.14 → 2.4.0

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  3. data/lib/generators/graphql/templates/base_resolver.erb +2 -0
  4. data/lib/generators/graphql/type_generator.rb +1 -1
  5. data/lib/graphql/analysis.rb +1 -1
  6. data/lib/graphql/dataloader/async_dataloader.rb +3 -2
  7. data/lib/graphql/dataloader/source.rb +1 -1
  8. data/lib/graphql/dataloader.rb +31 -10
  9. data/lib/graphql/execution/interpreter/resolve.rb +10 -6
  10. data/lib/graphql/invalid_null_error.rb +1 -1
  11. data/lib/graphql/language/comment.rb +18 -0
  12. data/lib/graphql/language/document_from_schema_definition.rb +38 -4
  13. data/lib/graphql/language/lexer.rb +15 -12
  14. data/lib/graphql/language/nodes.rb +22 -14
  15. data/lib/graphql/language/parser.rb +5 -0
  16. data/lib/graphql/language/printer.rb +23 -7
  17. data/lib/graphql/language.rb +6 -5
  18. data/lib/graphql/query/null_context.rb +1 -1
  19. data/lib/graphql/query.rb +49 -16
  20. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +23 -8
  21. data/lib/graphql/schema/always_visible.rb +6 -3
  22. data/lib/graphql/schema/argument.rb +14 -1
  23. data/lib/graphql/schema/build_from_definition.rb +1 -0
  24. data/lib/graphql/schema/enum.rb +3 -0
  25. data/lib/graphql/schema/enum_value.rb +9 -1
  26. data/lib/graphql/schema/field.rb +35 -14
  27. data/lib/graphql/schema/input_object.rb +20 -7
  28. data/lib/graphql/schema/interface.rb +1 -0
  29. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  30. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  31. data/lib/graphql/schema/member/has_fields.rb +2 -2
  32. data/lib/graphql/schema/printer.rb +1 -0
  33. data/lib/graphql/schema/resolver.rb +3 -4
  34. data/lib/graphql/schema/validator/required_validator.rb +28 -4
  35. data/lib/graphql/schema/visibility/migration.rb +186 -0
  36. data/lib/graphql/schema/visibility/profile.rb +523 -0
  37. data/lib/graphql/schema/visibility.rb +75 -0
  38. data/lib/graphql/schema/warden.rb +77 -15
  39. data/lib/graphql/schema.rb +203 -61
  40. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
  41. data/lib/graphql/static_validation/rules/directives_are_defined.rb +2 -1
  42. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  43. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +2 -1
  44. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -0
  45. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +11 -1
  46. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +10 -1
  47. data/lib/graphql/static_validation/validation_context.rb +15 -0
  48. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
  49. data/lib/graphql/subscriptions.rb +3 -1
  50. data/lib/graphql/testing/helpers.rb +2 -1
  51. data/lib/graphql/tracing/notifications_trace.rb +2 -2
  52. data/lib/graphql/version.rb +1 -1
  53. metadata +11 -9
  54. data/lib/graphql/schema/subset.rb +0 -509
  55. data/lib/graphql/schema/types_migration.rb +0 -187
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/language/block_string"
3
+ require "graphql/language/comment"
3
4
  require "graphql/language/printer"
4
5
  require "graphql/language/sanitized_printer"
5
6
  require "graphql/language/document_from_schema_definition"
@@ -48,19 +49,19 @@ module GraphQL
48
49
  inside_single_quoted_string = false
49
50
  new_query_str = nil
50
51
  while !scanner.eos?
51
- if (match = scanner.scan(/(?:\\"|[^"\n\r]|""")+/m)) && new_query_str
52
- new_query_str << match
53
- elsif scanner.scan('"')
52
+ if scanner.skip(/(?:\\"|[^"\n\r]|""")+/m)
53
+ new_query_str && (new_query_str << scanner.matched)
54
+ elsif scanner.skip('"')
54
55
  new_query_str && (new_query_str << '"')
55
56
  inside_single_quoted_string = !inside_single_quoted_string
56
- elsif scanner.scan("\n")
57
+ elsif scanner.skip("\n")
57
58
  if inside_single_quoted_string
58
59
  new_query_str ||= query_str[0, scanner.pos - 1]
59
60
  new_query_str << '\\n'
60
61
  else
61
62
  new_query_str && (new_query_str << "\n")
62
63
  end
63
- elsif scanner.scan("\r")
64
+ elsif scanner.skip("\r")
64
65
  if inside_single_quoted_string
65
66
  new_query_str ||= query_str[0, scanner.pos - 1]
66
67
  new_query_str << '\\r'
@@ -28,7 +28,7 @@ module GraphQL
28
28
  end
29
29
 
30
30
  def types
31
- @types ||= GraphQL::Schema::Warden::SchemaSubset.new(@warden)
31
+ @types ||= Schema::Warden::VisibilityProfile.new(@warden)
32
32
  end
33
33
  end
34
34
  end
data/lib/graphql/query.rb CHANGED
@@ -95,21 +95,24 @@ module GraphQL
95
95
  # @param root_value [Object] the object used to resolve fields on the root type
96
96
  # @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value)
97
97
  # @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
98
- def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil, use_schema_subset: nil)
98
+ # @param visibility_profile [Symbol]
99
+ def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, visibility_profile: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil, use_visibility_profile: nil)
99
100
  # Even if `variables: nil` is passed, use an empty hash for simpler logic
100
101
  variables ||= {}
101
102
  @schema = schema
102
103
  @context = schema.context_class.new(query: self, values: context)
103
104
 
104
- if use_schema_subset.nil?
105
- use_schema_subset = warden ? false : schema.use_schema_subset?
105
+ if use_visibility_profile.nil?
106
+ use_visibility_profile = warden ? false : schema.use_visibility_profile?
106
107
  end
107
108
 
108
- if use_schema_subset
109
- @schema_subset = @schema.subset_class.new(context: @context, schema: @schema)
109
+ @visibility_profile = visibility_profile
110
+
111
+ if use_visibility_profile
112
+ @visibility_profile = @schema.visibility.profile_for(@context, visibility_profile)
110
113
  @warden = Schema::Warden::NullWarden.new(context: @context, schema: @schema)
111
114
  else
112
- @schema_subset = nil
115
+ @visibility_profile = nil
113
116
  @warden = warden
114
117
  end
115
118
 
@@ -187,6 +190,9 @@ module GraphQL
187
190
  @query_string ||= (document ? document.to_query_string : nil)
188
191
  end
189
192
 
193
+ # @return [Symbol, nil]
194
+ attr_reader :visibility_profile
195
+
190
196
  attr_accessor :multiplex
191
197
 
192
198
  # @return [GraphQL::Tracing::Trace]
@@ -203,15 +209,19 @@ module GraphQL
203
209
  def lookahead
204
210
  @lookahead ||= begin
205
211
  ast_node = selected_operation
206
- root_type = case ast_node.operation_type
207
- when nil, "query"
208
- types.query_root # rubocop:disable Development/ContextIsPassedCop
209
- when "mutation"
210
- types.mutation_root # rubocop:disable Development/ContextIsPassedCop
211
- when "subscription"
212
- types.subscription_root # rubocop:disable Development/ContextIsPassedCop
212
+ if ast_node.nil?
213
+ GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
214
+ else
215
+ root_type = case ast_node.operation_type
216
+ when nil, "query"
217
+ types.query_root # rubocop:disable Development/ContextIsPassedCop
218
+ when "mutation"
219
+ types.mutation_root # rubocop:disable Development/ContextIsPassedCop
220
+ when "subscription"
221
+ types.subscription_root # rubocop:disable Development/ContextIsPassedCop
222
+ end
223
+ GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [ast_node])
213
224
  end
214
- GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [ast_node])
215
225
  end
216
226
  end
217
227
 
@@ -343,10 +353,33 @@ module GraphQL
343
353
  with_prepared_ast { @warden }
344
354
  end
345
355
 
346
- def_delegators :warden, :get_type, :get_field, :possible_types, :root_type_for_operation
356
+ def get_type(type_name)
357
+ types.type(type_name) # rubocop:disable Development/ContextIsPassedCop
358
+ end
359
+
360
+ def get_field(owner, field_name)
361
+ types.field(owner, field_name) # rubocop:disable Development/ContextIsPassedCop
362
+ end
363
+
364
+ def possible_types(type)
365
+ types.possible_types(type) # rubocop:disable Development/ContextIsPassedCop
366
+ end
367
+
368
+ def root_type_for_operation(op_type)
369
+ case op_type
370
+ when "query"
371
+ types.query_root # rubocop:disable Development/ContextIsPassedCop
372
+ when "mutation"
373
+ types.mutation_root # rubocop:disable Development/ContextIsPassedCop
374
+ when "subscription"
375
+ types.subscription_root # rubocop:disable Development/ContextIsPassedCop
376
+ else
377
+ raise ArgumentError, "unexpected root type name: #{op_type.inspect}; expected 'query', 'mutation', or 'subscription'"
378
+ end
379
+ end
347
380
 
348
381
  def types
349
- @schema_subset || warden.schema_subset
382
+ @visibility_profile || warden.visibility_profile
350
383
  end
351
384
 
352
385
  # @param abstract_type [GraphQL::UnionType, GraphQL::InterfaceType]
@@ -29,7 +29,7 @@ module GraphQL
29
29
  def_node_matcher :field_config_with_inline_type_and_block, <<-Pattern
30
30
  (
31
31
  block
32
- (send {nil? _} :field sym ${const array}) ...
32
+ (send {nil? _} :field sym ${const array} ...) ...
33
33
  (args)
34
34
  _
35
35
 
@@ -37,8 +37,8 @@ module GraphQL
37
37
  Pattern
38
38
 
39
39
  def on_block(node)
40
+ ignore_node(node)
40
41
  field_config_with_inline_type_and_block(node) do |type_const|
41
- ignore_node(type_const)
42
42
  type_const_str = get_type_argument_str(node, type_const)
43
43
  if ignore_inline_type_str?(type_const_str)
44
44
  # Do nothing ...
@@ -54,8 +54,8 @@ module GraphQL
54
54
  end
55
55
 
56
56
  def on_send(node)
57
+ return if part_of_ignored_node?(node)
57
58
  field_config_with_inline_type(node) do |type_const|
58
- return if ignored_node?(type_const)
59
59
  type_const_str = get_type_argument_str(node, type_const)
60
60
  if ignore_inline_type_str?(type_const_str)
61
61
  # Do nothing -- not loading from another file
@@ -74,7 +74,13 @@ module GraphQL
74
74
  private
75
75
 
76
76
  def ignore_inline_type_str?(type_str)
77
- BUILT_IN_SCALAR_NAMES.include?(type_str)
77
+ if BUILT_IN_SCALAR_NAMES.include?(type_str)
78
+ true
79
+ elsif (inner_type_str = type_str.sub(/\[([A-Za-z]+)(, null: (true|false))?\]/, '\1')) && BUILT_IN_SCALAR_NAMES.include?(inner_type_str)
80
+ true
81
+ else
82
+ false
83
+ end
78
84
  end
79
85
 
80
86
  def get_type_argument_str(send_node, type_const)
@@ -110,11 +116,20 @@ module GraphQL
110
116
  end
111
117
 
112
118
  def determine_field_indent(send_node)
113
- surrounding_node = send_node.parent.parent
114
- surrounding_source = surrounding_node.source
115
- indent_test_idx = send_node.location.expression.begin_pos - surrounding_node.source_range.begin_pos - 1
119
+ type_defn_node = send_node
120
+
121
+ while (type_defn_node && !(type_defn_node.class_definition? || type_defn_node.module_definition?))
122
+ type_defn_node = type_defn_node.parent
123
+ end
124
+
125
+ if type_defn_node.nil?
126
+ raise "Invariant: Something went wrong in GraphQL-Ruby, couldn't find surrounding class definition for field (#{send_node}).\n\nPlease report this error on GitHub."
127
+ end
128
+
129
+ type_defn_source = type_defn_node.source
130
+ indent_test_idx = send_node.location.expression.begin_pos - type_defn_node.source_range.begin_pos - 1
116
131
  field_indent = "".dup
117
- while surrounding_source[indent_test_idx] == " "
132
+ while type_defn_source[indent_test_idx] == " "
118
133
  field_indent << " "
119
134
  indent_test_idx -= 1
120
135
  if indent_test_idx == 0
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  class Schema
4
- class AlwaysVisible
4
+ module AlwaysVisible
5
5
  def self.use(schema, **opts)
6
- schema.warden_class = GraphQL::Schema::Warden::NullWarden
7
- schema.subset_class = GraphQL::Schema::Warden::NullWarden::NullSubset
6
+ schema.extend(self)
7
+ end
8
+
9
+ def visible?(_member, _context)
10
+ true
8
11
  end
9
12
  end
10
13
  end
@@ -50,11 +50,12 @@ module GraphQL
50
50
  # @param deprecation_reason [String]
51
51
  # @param validates [Hash, nil] Options for building validators, if any should be applied
52
52
  # @param replace_null_with_default [Boolean] if `true`, incoming values of `null` will be replaced with the configured `default_value`
53
- def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NOT_CONFIGURED, as: nil, from_resolver: false, camelize: true, prepare: nil, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block)
53
+ def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, comment: nil, ast_node: nil, default_value: NOT_CONFIGURED, as: nil, from_resolver: false, camelize: true, prepare: nil, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block)
54
54
  arg_name ||= name
55
55
  @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
56
56
  @type_expr = type_expr || type
57
57
  @description = desc || description
58
+ @comment = comment
58
59
  @null = required != true
59
60
  @default_value = default_value
60
61
  if replace_null_with_default
@@ -129,6 +130,17 @@ module GraphQL
129
130
  end
130
131
  end
131
132
 
133
+ attr_writer :comment
134
+
135
+ # @return [String] Comment for this argument
136
+ def comment(text = nil)
137
+ if text
138
+ @comment = text
139
+ else
140
+ @comment
141
+ end
142
+ end
143
+
132
144
  # @return [String] Deprecation reason for this argument
133
145
  def deprecation_reason(text = nil)
134
146
  if text
@@ -352,6 +364,7 @@ module GraphQL
352
364
 
353
365
  # @api private
354
366
  def validate_default_value
367
+ return unless default_value?
355
368
  coerced_default_value = begin
356
369
  # This is weird, but we should accept single-item default values for list-type arguments.
357
370
  # If we used `coerce_isolated_input` below, it would do this for us, but it's not really
@@ -188,6 +188,7 @@ module GraphQL
188
188
 
189
189
  def self.inherited(child_class)
190
190
  child_class.definition_default_resolve = self.definition_default_resolve
191
+ super
191
192
  end
192
193
  end
193
194
 
@@ -59,6 +59,7 @@ module GraphQL
59
59
  # Define a value for this enum
60
60
  # @option kwargs [String, Symbol] :graphql_name the GraphQL value for this, usually `SCREAMING_CASE`
61
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
62
63
  # @option kwargs [::Object] :value the translated Ruby value for this object (defaults to `graphql_name`)
63
64
  # @option kwargs [String] :deprecation_reason if this object is deprecated, include a message here
64
65
  # @return [void]
@@ -152,6 +153,8 @@ module GraphQL
152
153
  else
153
154
  nil
154
155
  end
156
+ # rescue MissingValuesError
157
+ # nil
155
158
  end
156
159
 
157
160
  # Called by the runtime when a field returns a value to give back to the client.
@@ -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
@@ -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]
@@ -212,6 +216,7 @@ module GraphQL
212
216
  # @param owner [Class] The type that this field belongs to
213
217
  # @param null [Boolean] (defaults to `true`) `true` if this field may return `null`, `false` if it is never `null`
214
218
  # @param description [String] Field description
219
+ # @param comment [String] Field comment
215
220
  # @param deprecation_reason [String] If present, the field is marked "deprecated" with this message
216
221
  # @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`)
217
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`)
@@ -236,7 +241,7 @@ module GraphQL
236
241
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
237
242
  # @param validates [Array<Hash>] Configurations for validating this field
238
243
  # @param fallback_value [Object] A fallback value if the method is not defined
239
- 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)
240
245
  if name.nil?
241
246
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
242
247
  end
@@ -252,6 +257,7 @@ module GraphQL
252
257
  @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
253
258
 
254
259
  @description = description
260
+ @comment = comment
255
261
  @type = @owner_type = @own_validators = @own_directives = @own_arguments = @arguments_statically_coercible = nil # these will be prepared later if necessary
256
262
 
257
263
  self.deprecation_reason = deprecation_reason
@@ -307,7 +313,7 @@ module GraphQL
307
313
  @ast_node = ast_node
308
314
  @method_conflict_warning = method_conflict_warning
309
315
  @fallback_value = fallback_value
310
- @definition_block = nil
316
+ @definition_block = definition_block
311
317
 
312
318
  arguments.each do |name, arg|
313
319
  case arg
@@ -326,14 +332,15 @@ module GraphQL
326
332
  @subscription_scope = subscription_scope
327
333
 
328
334
  @extensions = EMPTY_ARRAY
335
+ @call_after_define = false
329
336
  set_pagination_extensions(connection_extension: connection_extension)
330
337
  # Do this last so we have as much context as possible when initializing them:
331
338
  if extensions.any?
332
- self.extensions(extensions, call_after_define: false)
339
+ self.extensions(extensions)
333
340
  end
334
341
 
335
342
  if resolver_class && resolver_class.extensions.any?
336
- self.extensions(resolver_class.extensions, call_after_define: false)
343
+ self.extensions(resolver_class.extensions)
337
344
  end
338
345
 
339
346
  if directives.any?
@@ -346,10 +353,9 @@ module GraphQL
346
353
  self.validates(validates)
347
354
  end
348
355
 
349
- if block_given?
350
- @definition_block = definition_block
351
- else
356
+ if @definition_block.nil?
352
357
  self.extensions.each(&:after_define_apply)
358
+ @call_after_define = true
353
359
  end
354
360
  end
355
361
 
@@ -366,6 +372,7 @@ module GraphQL
366
372
  instance_eval(&@definition_block)
367
373
  end
368
374
  self.extensions.each(&:after_define_apply)
375
+ @call_after_define = true
369
376
  @definition_block = nil
370
377
  end
371
378
  self
@@ -400,6 +407,20 @@ module GraphQL
400
407
  end
401
408
  end
402
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
+
403
424
  # Read extension instances from this field,
404
425
  # or add new classes/options to be initialized on this field.
405
426
  # Extensions are executed in the order they are added.
@@ -415,14 +436,14 @@ module GraphQL
415
436
  #
416
437
  # @param extensions [Array<Class, Hash<Class => Hash>>] Add extensions to this field. For hash elements, only the first key/value is used.
417
438
  # @return [Array<GraphQL::Schema::FieldExtension>] extensions to apply to this field
418
- def extensions(new_extensions = nil, call_after_define: !@definition_block)
439
+ def extensions(new_extensions = nil)
419
440
  if new_extensions
420
441
  new_extensions.each do |extension_config|
421
442
  if extension_config.is_a?(Hash)
422
443
  extension_class, options = *extension_config.to_a[0]
423
- self.extension(extension_class, call_after_define: call_after_define, **options)
444
+ self.extension(extension_class, **options)
424
445
  else
425
- self.extension(extension_config, call_after_define: call_after_define)
446
+ self.extension(extension_config)
426
447
  end
427
448
  end
428
449
  end
@@ -440,12 +461,12 @@ module GraphQL
440
461
  # @param extension_class [Class] subclass of {Schema::FieldExtension}
441
462
  # @param options [Hash] if provided, given as `options:` when initializing `extension`.
442
463
  # @return [void]
443
- def extension(extension_class, call_after_define: !@definition_block, **options)
464
+ def extension(extension_class, **options)
444
465
  extension_inst = extension_class.new(field: self, options: options)
445
466
  if @extensions.frozen?
446
467
  @extensions = @extensions.dup
447
468
  end
448
- if call_after_define
469
+ if @call_after_define
449
470
  extension_inst.after_define_apply
450
471
  end
451
472
  @extensions << extension_inst
@@ -490,7 +511,7 @@ module GraphQL
490
511
  if arguments[:last] && (max_possible_page_size.nil? || arguments[:last] > max_possible_page_size)
491
512
  max_possible_page_size = arguments[:last]
492
513
  end
493
- elsif arguments.is_a?(GraphQL::UnauthorizedError)
514
+ elsif arguments.is_a?(GraphQL::ExecutionError) || arguments.is_a?(GraphQL::UnauthorizedError)
494
515
  raise arguments
495
516
  end
496
517
 
@@ -133,12 +133,14 @@ module GraphQL
133
133
  end
134
134
  # Add a method access
135
135
  method_name = argument_defn.keyword
136
- class_eval <<-RUBY, __FILE__, __LINE__
137
- def #{method_name}
138
- self[#{method_name.inspect}]
139
- end
140
- alias_method :#{method_name}, :#{method_name}
141
- RUBY
136
+ suppress_redefinition_warning do
137
+ class_eval <<-RUBY, __FILE__, __LINE__
138
+ def #{method_name}
139
+ self[#{method_name.inspect}]
140
+ end
141
+ alias_method :#{method_name}, :#{method_name}
142
+ RUBY
143
+ end
142
144
  argument_defn
143
145
  end
144
146
 
@@ -163,7 +165,7 @@ module GraphQL
163
165
 
164
166
  # Inject missing required arguments
165
167
  missing_required_inputs = ctx.types.arguments(self).reduce({}) do |m, (argument)|
166
- if !input.key?(argument.graphql_name) && argument.type.non_null? && types.argument(self, argument.graphql_name)
168
+ if !input.key?(argument.graphql_name) && argument.type.non_null? && !argument.default_value? && types.argument(self, argument.graphql_name)
167
169
  m[argument.graphql_name] = nil
168
170
  end
169
171
 
@@ -243,6 +245,17 @@ module GraphQL
243
245
 
244
246
  result
245
247
  end
248
+
249
+ private
250
+
251
+ # Suppress redefinition warning for objectId arguments
252
+ def suppress_redefinition_warning
253
+ verbose = $VERBOSE
254
+ $VERBOSE = nil
255
+ yield
256
+ ensure
257
+ $VERBOSE = verbose
258
+ end
246
259
  end
247
260
 
248
261
  private
@@ -63,6 +63,7 @@ module GraphQL
63
63
 
64
64
  child_class.introspection(introspection)
65
65
  child_class.description(description)
66
+ child_class.comment(nil)
66
67
  # If interfaces are mixed into each other, only define this class once
67
68
  if !child_class.const_defined?(:UnresolvedTypeError, false)
68
69
  add_unresolved_type_error(child_class)
@@ -50,12 +50,27 @@ module GraphQL
50
50
  end
51
51
  end
52
52
 
53
+ # Call this method to provide a new comment; OR
54
+ # call it without an argument to get the comment
55
+ # @param new_comment [String]
56
+ # @return [String, nil]
57
+ def comment(new_comment = NOT_CONFIGURED)
58
+ if !NOT_CONFIGURED.equal?(new_comment)
59
+ @comment = new_comment
60
+ elsif defined?(@comment)
61
+ @comment
62
+ else
63
+ nil
64
+ end
65
+ end
66
+
53
67
  # This pushes some configurations _down_ the inheritance tree,
54
68
  # in order to prevent repetitive lookups at runtime.
55
69
  module ConfigurationExtension
56
70
  def inherited(child_class)
57
71
  child_class.introspection(introspection)
58
72
  child_class.description(description)
73
+ child_class.comment(nil)
59
74
  child_class.default_graphql_name = nil
60
75
 
61
76
  if defined?(@graphql_name) && @graphql_name && (self.name.nil? || graphql_name != default_graphql_name)
@@ -135,7 +135,7 @@ module GraphQL
135
135
 
136
136
  def get_argument(argument_name, context = GraphQL::Query::NullContext.instance)
137
137
  warden = Warden.from_context(context)
138
- skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)
138
+ skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)
139
139
  for ancestor in ancestors
140
140
  if ancestor.respond_to?(:own_arguments) &&
141
141
  (a = ancestor.own_arguments[argument_name]) &&
@@ -210,7 +210,7 @@ module GraphQL
210
210
  # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
211
211
  def get_argument(argument_name, context = GraphQL::Query::NullContext.instance)
212
212
  warden = Warden.from_context(context)
213
- if (arg_config = own_arguments[argument_name]) && ((context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)) || (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden)))
213
+ if (arg_config = own_arguments[argument_name]) && ((context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)) || (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden)))
214
214
  visible_arg || arg_config
215
215
  elsif defined?(@resolver_class) && @resolver_class
216
216
  @resolver_class.get_field_argument(argument_name, context)
@@ -99,7 +99,7 @@ module GraphQL
99
99
  module InterfaceMethods
100
100
  def get_field(field_name, context = GraphQL::Query::NullContext.instance)
101
101
  warden = Warden.from_context(context)
102
- skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)
102
+ skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)
103
103
  for ancestor in ancestors
104
104
  if ancestor.respond_to?(:own_fields) &&
105
105
  (f_entry = ancestor.own_fields[field_name]) &&
@@ -135,7 +135,7 @@ module GraphQL
135
135
  # Objects need to check that the interface implementation is visible, too
136
136
  warden = Warden.from_context(context)
137
137
  ancs = ancestors
138
- skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)
138
+ skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)
139
139
  i = 0
140
140
  while (ancestor = ancs[i])
141
141
  if ancestor.respond_to?(:own_fields) &&
@@ -58,6 +58,7 @@ module GraphQL
58
58
  end
59
59
  end
60
60
  schema = Class.new(GraphQL::Schema) {
61
+ use GraphQL::Schema::Visibility
61
62
  query(query_root)
62
63
  def self.visible?(member, _ctx)
63
64
  member.graphql_name != "Root"
@@ -8,6 +8,7 @@ module GraphQL
8
8
  # - Arguments, via `.argument(...)` helper, which will be applied to the field.
9
9
  # - Return type, via `.type(..., null: ...)`, which will be applied to the field.
10
10
  # - Description, via `.description(...)`, which will be applied to the field
11
+ # - Comment, via `.comment(...)`, which will be applied to the field
11
12
  # - Resolution, via `#resolve(**args)` method, which will be called to resolve the field.
12
13
  # - `#object` and `#context` accessors for use during `#resolve`.
13
14
  #
@@ -19,7 +20,7 @@ module GraphQL
19
20
  # @see {GraphQL::Function} `Resolver` is a replacement for `GraphQL::Function`
20
21
  class Resolver
21
22
  include Schema::Member::GraphQLTypeNames
22
- # Really we only need description from here, but:
23
+ # Really we only need description & comment from here, but:
23
24
  extend Schema::Member::BaseDSLMethods
24
25
  extend GraphQL::Schema::Member::HasArguments
25
26
  extend GraphQL::Schema::Member::HasValidators
@@ -408,9 +409,7 @@ module GraphQL
408
409
 
409
410
  private
410
411
 
411
- def own_extensions
412
- @own_extensions
413
- end
412
+ attr_reader :own_extensions
414
413
  end
415
414
  end
416
415
  end