graphql 1.9.17 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/templates/schema.erb +7 -0
  3. data/lib/graphql/analysis/ast/field_usage.rb +1 -1
  4. data/lib/graphql/analysis/ast/visitor.rb +3 -3
  5. data/lib/graphql/analysis/ast.rb +12 -11
  6. data/lib/graphql/argument.rb +7 -35
  7. data/lib/graphql/backtrace/table.rb +10 -2
  8. data/lib/graphql/base_type.rb +4 -0
  9. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +5 -9
  10. data/lib/graphql/define/assign_enum_value.rb +1 -1
  11. data/lib/graphql/define/assign_object_field.rb +3 -3
  12. data/lib/graphql/define/defined_object_proxy.rb +8 -2
  13. data/lib/graphql/define/instance_definable.rb +10 -106
  14. data/lib/graphql/directive/deprecated_directive.rb +1 -12
  15. data/lib/graphql/directive.rb +4 -1
  16. data/lib/graphql/enum_type.rb +5 -71
  17. data/lib/graphql/execution/directive_checks.rb +2 -2
  18. data/lib/graphql/execution/errors.rb +2 -3
  19. data/lib/graphql/execution/execute.rb +1 -1
  20. data/lib/graphql/execution/interpreter/runtime.rb +106 -55
  21. data/lib/graphql/execution/interpreter.rb +5 -11
  22. data/lib/graphql/execution/lazy/lazy_method_map.rb +4 -0
  23. data/lib/graphql/execution/lookahead.rb +5 -5
  24. data/lib/graphql/execution/multiplex.rb +13 -3
  25. data/lib/graphql/field.rb +9 -117
  26. data/lib/graphql/filter.rb +1 -1
  27. data/lib/graphql/function.rb +1 -30
  28. data/lib/graphql/input_object_type.rb +2 -24
  29. data/lib/graphql/interface_type.rb +2 -23
  30. data/lib/graphql/introspection/base_object.rb +2 -5
  31. data/lib/graphql/introspection/directive_type.rb +1 -1
  32. data/lib/graphql/introspection/entry_points.rb +7 -7
  33. data/lib/graphql/introspection/input_value_type.rb +27 -9
  34. data/lib/graphql/introspection/schema_type.rb +1 -6
  35. data/lib/graphql/introspection/type_type.rb +5 -5
  36. data/lib/graphql/language/definition_slice.rb +21 -10
  37. data/lib/graphql/language/document_from_schema_definition.rb +50 -44
  38. data/lib/graphql/language/nodes.rb +3 -3
  39. data/lib/graphql/language/parser.rb +644 -646
  40. data/lib/graphql/language/parser.y +6 -4
  41. data/lib/graphql/language.rb +1 -1
  42. data/lib/graphql/non_null_type.rb +0 -10
  43. data/lib/graphql/object_type.rb +1 -21
  44. data/lib/graphql/pagination/active_record_relation_connection.rb +35 -0
  45. data/lib/graphql/pagination/array_connection.rb +77 -0
  46. data/lib/graphql/pagination/connection.rb +171 -0
  47. data/lib/graphql/pagination/connections.rb +108 -0
  48. data/lib/graphql/pagination/mongoid_relation_connection.rb +25 -0
  49. data/lib/graphql/pagination/relation_connection.rb +151 -0
  50. data/lib/graphql/pagination/sequel_dataset_connection.rb +28 -0
  51. data/lib/graphql/pagination.rb +6 -0
  52. data/lib/graphql/query/arguments.rb +2 -1
  53. data/lib/graphql/query/context.rb +2 -5
  54. data/lib/graphql/query/literal_input.rb +30 -10
  55. data/lib/graphql/query/variable_validation_error.rb +1 -1
  56. data/lib/graphql/query/variables.rb +7 -3
  57. data/lib/graphql/query.rb +9 -5
  58. data/lib/graphql/relay/base_connection.rb +4 -0
  59. data/lib/graphql/relay/connection_type.rb +2 -1
  60. data/lib/graphql/relay/edge_type.rb +1 -0
  61. data/lib/graphql/relay/edges_instrumentation.rb +1 -1
  62. data/lib/graphql/relay/mutation.rb +1 -86
  63. data/lib/graphql/relay/node.rb +2 -2
  64. data/lib/graphql/scalar_type.rb +1 -58
  65. data/lib/graphql/schema/argument.rb +51 -6
  66. data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +1 -1
  67. data/lib/graphql/schema/build_from_definition/resolve_map.rb +10 -4
  68. data/lib/graphql/schema/build_from_definition.rb +167 -178
  69. data/lib/graphql/schema/built_in_types.rb +5 -5
  70. data/lib/graphql/schema/directive/deprecated.rb +18 -0
  71. data/lib/graphql/schema/directive.rb +28 -2
  72. data/lib/graphql/schema/enum.rb +40 -3
  73. data/lib/graphql/schema/enum_value.rb +5 -1
  74. data/lib/graphql/schema/field/connection_extension.rb +11 -1
  75. data/lib/graphql/schema/field.rb +59 -31
  76. data/lib/graphql/schema/find_inherited_value.rb +13 -0
  77. data/lib/graphql/schema/finder.rb +13 -11
  78. data/lib/graphql/schema/input_object.rb +107 -2
  79. data/lib/graphql/schema/interface.rb +10 -7
  80. data/lib/graphql/schema/introspection_system.rb +108 -37
  81. data/lib/graphql/schema/late_bound_type.rb +1 -0
  82. data/lib/graphql/schema/list.rb +41 -0
  83. data/lib/graphql/schema/loader.rb +16 -4
  84. data/lib/graphql/schema/member/base_dsl_methods.rb +21 -11
  85. data/lib/graphql/schema/member/build_type.rb +5 -1
  86. data/lib/graphql/schema/member/cached_graphql_definition.rb +5 -0
  87. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  88. data/lib/graphql/schema/member/has_ast_node.rb +17 -0
  89. data/lib/graphql/schema/member/has_fields.rb +4 -4
  90. data/lib/graphql/schema/member/validates_input.rb +33 -0
  91. data/lib/graphql/schema/member.rb +5 -0
  92. data/lib/graphql/schema/mutation.rb +1 -1
  93. data/lib/graphql/schema/non_null.rb +25 -0
  94. data/lib/graphql/schema/object.rb +15 -5
  95. data/lib/graphql/schema/printer.rb +1 -2
  96. data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
  97. data/lib/graphql/schema/resolver.rb +3 -15
  98. data/lib/graphql/schema/scalar.rb +19 -3
  99. data/lib/graphql/schema/subscription.rb +5 -5
  100. data/lib/graphql/schema/traversal.rb +1 -1
  101. data/lib/graphql/schema/type_expression.rb +21 -13
  102. data/lib/graphql/schema/type_membership.rb +2 -2
  103. data/lib/graphql/schema/union.rb +2 -3
  104. data/lib/graphql/schema/validation.rb +2 -2
  105. data/lib/graphql/schema/warden.rb +45 -20
  106. data/lib/graphql/schema.rb +764 -151
  107. data/lib/graphql/static_validation/base_visitor.rb +10 -6
  108. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +9 -4
  109. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +10 -7
  110. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +1 -1
  111. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
  112. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -5
  113. data/lib/graphql/static_validation/rules/fields_will_merge.rb +4 -4
  114. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  115. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
  116. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +3 -3
  117. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +5 -6
  118. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  119. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +1 -1
  120. data/lib/graphql/static_validation/type_stack.rb +2 -2
  121. data/lib/graphql/static_validation/validator.rb +1 -1
  122. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +3 -3
  123. data/lib/graphql/subscriptions/event.rb +7 -4
  124. data/lib/graphql/subscriptions/instrumentation.rb +10 -5
  125. data/lib/graphql/subscriptions/subscription_root.rb +0 -1
  126. data/lib/graphql/subscriptions.rb +34 -9
  127. data/lib/graphql/tracing/active_support_notifications_tracing.rb +14 -10
  128. data/lib/graphql/tracing/appsignal_tracing.rb +8 -0
  129. data/lib/graphql/tracing/data_dog_tracing.rb +8 -0
  130. data/lib/graphql/tracing/new_relic_tracing.rb +8 -0
  131. data/lib/graphql/tracing/platform_tracing.rb +26 -6
  132. data/lib/graphql/tracing/prometheus_tracing.rb +8 -0
  133. data/lib/graphql/tracing/scout_tracing.rb +8 -0
  134. data/lib/graphql/tracing/skylight_tracing.rb +8 -0
  135. data/lib/graphql/tracing.rb +7 -3
  136. data/lib/graphql/types/int.rb +1 -1
  137. data/lib/graphql/types/relay/base_connection.rb +3 -1
  138. data/lib/graphql/union_type.rb +13 -28
  139. data/lib/graphql/unresolved_type_error.rb +2 -2
  140. data/lib/graphql/version.rb +1 -1
  141. data/lib/graphql.rb +2 -1
  142. metadata +15 -4
@@ -28,6 +28,17 @@ module GraphQL
28
28
  nil
29
29
  elsif value.nil?
30
30
  nil
31
+ elsif value.is_a?(GraphQL::Pagination::Connection)
32
+ # update the connection with some things that may not have been provided
33
+ value.context ||= context
34
+ value.first_value ||= arguments[:first]
35
+ value.after_value ||= arguments[:after]
36
+ value.last_value ||= arguments[:last]
37
+ value.before_value ||= arguments[:before]
38
+ value.max_page_size ||= field.max_page_size
39
+ value
40
+ elsif context.schema.new_connections?
41
+ context.schema.connections.wrap(field, value, arguments, context)
31
42
  else
32
43
  if object.is_a?(GraphQL::Schema::Object)
33
44
  object = object.object
@@ -43,7 +54,6 @@ module GraphQL
43
54
  )
44
55
  end
45
56
  end
46
-
47
57
  end
48
58
  end
49
59
  end
@@ -13,8 +13,10 @@ module GraphQL
13
13
  include GraphQL::Schema::Member::CachedGraphQLDefinition
14
14
  include GraphQL::Schema::Member::AcceptsDefinition
15
15
  include GraphQL::Schema::Member::HasArguments
16
+ include GraphQL::Schema::Member::HasAstNode
16
17
  include GraphQL::Schema::Member::HasPath
17
18
  extend GraphQL::Schema::FindInheritedValue
19
+ include GraphQL::Schema::FindInheritedValue::EmptyObjects
18
20
 
19
21
  # @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided
20
22
  attr_reader :name
@@ -35,9 +37,9 @@ module GraphQL
35
37
  attr_reader :resolver_method
36
38
 
37
39
  # @return [Class] The type that this field belongs to
38
- attr_reader :owner
40
+ attr_accessor :owner
39
41
 
40
- # @return [Symobol] the original name of the field, passed in by the user
42
+ # @return [Symbol] the original name of the field, passed in by the user
41
43
  attr_reader :original_name
42
44
 
43
45
  # @return [Class, nil] The {Schema::Resolver} this field was derived from, if there is one
@@ -51,7 +53,7 @@ module GraphQL
51
53
  attr_reader :trace
52
54
 
53
55
  # @return [String, nil]
54
- attr_reader :subscription_scope
56
+ attr_accessor :subscription_scope
55
57
 
56
58
  # Create a field instance from a list of arguments, keyword arguments, and a block.
57
59
  #
@@ -152,6 +154,14 @@ module GraphQL
152
154
  end
153
155
  end
154
156
 
157
+ # @return Boolean
158
+ attr_reader :relay_node_field
159
+
160
+ # @return [Boolean] Should we warn if this field's name conflicts with a built-in method?
161
+ def method_conflict_warning?
162
+ @method_conflict_warning
163
+ end
164
+
155
165
  # @param name [Symbol] The underscore-cased version of this field name (will be camelized for the GraphQL API)
156
166
  # @param type [Class, GraphQL::BaseType, Array] The return type of this field
157
167
  # @param owner [Class] The type that this field belongs to
@@ -175,7 +185,9 @@ module GraphQL
175
185
  # @param subscription_scope [Symbol, String] A key in `context` which will be used to scope subscription payloads
176
186
  # @param extensions [Array<Class, Hash<Class => Object>>] Named extensions to apply to this field (see also {#extension})
177
187
  # @param trace [Boolean] If true, a {GraphQL::Tracing} tracer will measure this scalar field
178
- def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: nil, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, extras: [], extensions: [], resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, arguments: {}, &definition_block)
188
+ # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
189
+ # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
190
+ def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: nil, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: [], extensions: EMPTY_ARRAY, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, arguments: EMPTY_HASH, &definition_block)
179
191
  if name.nil?
180
192
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
181
193
  end
@@ -191,8 +203,9 @@ module GraphQL
191
203
  raise ArgumentError, "keyword `extras:` may only be used with method-based resolve and class-based field such as mutation class, please remove `field:`, `function:` or `resolve:`"
192
204
  end
193
205
  @original_name = name
194
- @underscored_name = -Member::BuildType.underscore(name.to_s)
195
- @name = -(camelize ? Member::BuildType.camelize(name.to_s) : name.to_s)
206
+ name_s = -name.to_s
207
+ @underscored_name = -Member::BuildType.underscore(name_s)
208
+ @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
196
209
  @description = description
197
210
  if field.is_a?(GraphQL::Schema::Field)
198
211
  raise ArgumentError, "Instead of passing a field as `field:`, use `add_field(field)` to add an already-defined field."
@@ -236,14 +249,14 @@ module GraphQL
236
249
  @trace = trace
237
250
  @relay_node_field = relay_node_field
238
251
  @relay_nodes_field = relay_nodes_field
252
+ @ast_node = ast_node
253
+ @method_conflict_warning = method_conflict_warning
239
254
 
240
- # Override the default from HasArguments
241
- @own_arguments = {}
242
255
  arguments.each do |name, arg|
243
256
  if arg.is_a?(Hash)
244
257
  argument(name: name, **arg)
245
258
  else
246
- @own_arguments[name] = arg
259
+ own_arguments[name] = arg
247
260
  end
248
261
  end
249
262
 
@@ -347,7 +360,7 @@ module GraphQL
347
360
  end
348
361
  end
349
362
 
350
- def complexity(new_complexity)
363
+ def complexity(new_complexity = nil)
351
364
  case new_complexity
352
365
  when Proc
353
366
  if new_complexity.parameters.size != 3
@@ -360,6 +373,8 @@ module GraphQL
360
373
  end
361
374
  when Numeric
362
375
  @complexity = new_complexity
376
+ when nil
377
+ @complexity
363
378
  else
364
379
  raise("Invalid complexity: #{new_complexity.inspect} on #{@name}")
365
380
  end
@@ -416,6 +431,7 @@ module GraphQL
416
431
  field_defn.introspection = @introspection
417
432
  field_defn.complexity = @complexity
418
433
  field_defn.subscription_scope = @subscription_scope
434
+ field_defn.ast_node = ast_node
419
435
 
420
436
  arguments.each do |name, defn|
421
437
  arg_graphql = defn.to_graphql
@@ -433,14 +449,25 @@ module GraphQL
433
449
 
434
450
  # Ok, `self` isn't a class, but this is for consistency with the classes
435
451
  field_defn.metadata[:type_class] = self
436
-
452
+ field_defn.arguments_class = GraphQL::Query::Arguments.construct_arguments_class(field_defn)
437
453
  field_defn
438
454
  end
439
455
 
456
+ attr_writer :type
457
+
440
458
  def type
441
- @type ||= Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
442
- rescue
443
- raise ArgumentError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: #{$!.message}", $!.backtrace
459
+ @type ||= if @function
460
+ Member::BuildType.parse_type(@function.type, null: false)
461
+ elsif @field
462
+ Member::BuildType.parse_type(@field.type, null: false)
463
+ else
464
+ Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
465
+ end
466
+ rescue GraphQL::Schema::InvalidDocumentError => err
467
+ # Let this propagate up
468
+ raise err
469
+ rescue StandardError => err
470
+ raise ArgumentError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: (#{err.class}) #{err.message}", err.backtrace
444
471
  end
445
472
 
446
473
  def visible?(context)
@@ -459,14 +486,14 @@ module GraphQL
459
486
  end
460
487
  end
461
488
 
462
- def authorized?(object, context)
489
+ def authorized?(object, args, context)
463
490
  if @resolver_class
464
491
  # The resolver will check itself during `resolve()`
465
492
  @resolver_class.authorized?(object, context)
466
493
  else
467
494
  # Faster than `.any?`
468
495
  arguments.each_value do |arg|
469
- if !arg.authorized?(object, context)
496
+ if args.key?(arg.keyword) && !arg.authorized?(object, args[arg.keyword], context)
470
497
  return false
471
498
  end
472
499
  end
@@ -485,21 +512,22 @@ module GraphQL
485
512
  # Some legacy fields can have `nil` here, not exactly sure why.
486
513
  # @see https://github.com/rmosolgo/graphql-ruby/issues/1990 before removing
487
514
  inner_obj = after_obj && after_obj.object
488
- if authorized?(inner_obj, query_ctx)
489
- ruby_args = to_ruby_args(after_obj, args, ctx)
490
- # Then if it passed, resolve the field
491
- if @resolve_proc
492
- # Might be nil, still want to call the func in that case
493
- with_extensions(inner_obj, ruby_args, query_ctx) do |extended_obj, extended_args|
494
- # Pass the GraphQL args here for compatibility:
495
- @resolve_proc.call(extended_obj, args, ctx)
515
+ ctx.schema.after_lazy(to_ruby_args(after_obj, args, ctx)) do |ruby_args|
516
+ if authorized?(inner_obj, ruby_args, query_ctx)
517
+ # Then if it passed, resolve the field
518
+ if @resolve_proc
519
+ # Might be nil, still want to call the func in that case
520
+ with_extensions(inner_obj, ruby_args, query_ctx) do |extended_obj, extended_args|
521
+ # Pass the GraphQL args here for compatibility:
522
+ @resolve_proc.call(extended_obj, args, ctx)
523
+ end
524
+ else
525
+ public_send_field(after_obj, ruby_args, ctx)
496
526
  end
497
527
  else
498
- public_send_field(after_obj, ruby_args, ctx)
528
+ err = GraphQL::UnauthorizedFieldError.new(object: inner_obj, type: obj.class, context: ctx, field: self)
529
+ query_ctx.schema.unauthorized_field(err)
499
530
  end
500
- else
501
- err = GraphQL::UnauthorizedFieldError.new(object: inner_obj, type: obj.class, context: ctx, field: self)
502
- query_ctx.schema.unauthorized_field(err)
503
531
  end
504
532
  end
505
533
  end
@@ -516,7 +544,7 @@ module GraphQL
516
544
  begin
517
545
  # Unwrap the GraphQL object to get the application object.
518
546
  application_object = object.object
519
- if self.authorized?(application_object, ctx)
547
+ if self.authorized?(application_object, args, ctx)
520
548
  # Apply field extensions
521
549
  with_extensions(object, args, ctx) do |extended_obj, extended_args|
522
550
  field_receiver = if @resolver_class
@@ -594,7 +622,7 @@ module GraphQL
594
622
 
595
623
  # @param ctx [GraphQL::Query::Context::FieldResolutionContext]
596
624
  def fetch_extra(extra_name, ctx)
597
- if extra_name != :path && respond_to?(extra_name)
625
+ if extra_name != :path && extra_name != :ast_node && respond_to?(extra_name)
598
626
  self.public_send(extra_name)
599
627
  elsif ctx.respond_to?(extra_name)
600
628
  ctx.public_send(extra_name)
@@ -661,7 +689,7 @@ module GraphQL
661
689
  # Written iteratively to avoid big stack traces.
662
690
  # @return [Object] Whatever the
663
691
  def with_extensions(obj, args, ctx)
664
- if @extensions.empty?
692
+ if @extensions.nil?
665
693
  yield(obj, args)
666
694
  else
667
695
  # Save these so that the originals can be re-given to `after_resolve` handlers.
@@ -1,6 +1,19 @@
1
1
  module GraphQL
2
2
  class Schema
3
3
  module FindInheritedValue
4
+ module EmptyObjects
5
+ EMPTY_HASH = {}.freeze
6
+ EMPTY_ARRAY = [].freeze
7
+ end
8
+
9
+ def self.extended(child_cls)
10
+ child_cls.singleton_class.include(EmptyObjects)
11
+ end
12
+
13
+ def self.included(child_cls)
14
+ child_cls.include(EmptyObjects)
15
+ end
16
+
4
17
  private
5
18
 
6
19
  def find_inherited_value(method_name, default_value = nil)
@@ -38,7 +38,7 @@ module GraphQL
38
38
 
39
39
  find_in_directive(directive, path: path)
40
40
  else
41
- type = schema.types[type_or_directive]
41
+ type = schema.get_type(type_or_directive)
42
42
 
43
43
  if type.nil?
44
44
  raise MemberNotFoundError, "Could not find type `#{type_or_directive}` in schema."
@@ -66,22 +66,24 @@ module GraphQL
66
66
  end
67
67
 
68
68
  def find_in_type(type, path:)
69
- case type
70
- when GraphQL::ObjectType
69
+ case type.kind.name
70
+ when "OBJECT"
71
71
  find_in_fields_type(type, kind: "object", path: path)
72
- when GraphQL::InterfaceType
72
+ when "INTERFACE"
73
73
  find_in_fields_type(type, kind: "interface", path: path)
74
- when GraphQL::InputObjectType
74
+ when "INPUT_OBJECT"
75
75
  find_in_input_object(type, path: path)
76
- when GraphQL::UnionType
76
+ when "UNION"
77
77
  # Error out if path that was provided is too long
78
78
  # i.e UnionType.PossibleType.aField
79
79
  # Use PossibleType.aField instead.
80
80
  if invalid = path.first
81
81
  raise MemberNotFoundError, "Cannot select union possible type `#{invalid}`. Select the type directly instead."
82
82
  end
83
- when GraphQL::EnumType
83
+ when "ENUM"
84
84
  find_in_enum_type(type, path: path)
85
+ else
86
+ raise "Unexpected find_in_type: #{type.inspect} (#{path})"
85
87
  end
86
88
  end
87
89
 
@@ -90,7 +92,7 @@ module GraphQL
90
92
  field = schema.get_field(type, field_name)
91
93
 
92
94
  if field.nil?
93
- raise MemberNotFoundError, "Could not find field `#{field_name}` on #{kind} type `#{type}`."
95
+ raise MemberNotFoundError, "Could not find field `#{field_name}` on #{kind} type `#{type.graphql_name}`."
94
96
  end
95
97
 
96
98
  return field if path.empty?
@@ -117,10 +119,10 @@ module GraphQL
117
119
 
118
120
  def find_in_input_object(input_object, path:)
119
121
  field_name = path.shift
120
- input_field = input_object.input_fields[field_name]
122
+ input_field = input_object.arguments[field_name]
121
123
 
122
124
  if input_field.nil?
123
- raise MemberNotFoundError, "Could not find input field `#{field_name}` on input object type `#{input_object}`."
125
+ raise MemberNotFoundError, "Could not find input field `#{field_name}` on input object type `#{input_object.graphql_name}`."
124
126
  end
125
127
 
126
128
  # Error out if path that was provided is too long
@@ -137,7 +139,7 @@ module GraphQL
137
139
  enum_value = enum_type.values[value_name]
138
140
 
139
141
  if enum_value.nil?
140
- raise MemberNotFoundError, "Could not find enum value `#{value_name}` on enum type `#{enum_type}`."
142
+ raise MemberNotFoundError, "Could not find enum value `#{value_name}` on enum type `#{enum_type.graphql_name}`."
141
143
  end
142
144
 
143
145
  # Error out if path that was provided is too long
@@ -5,6 +5,8 @@ module GraphQL
5
5
  extend GraphQL::Schema::Member::AcceptsDefinition
6
6
  extend Forwardable
7
7
  extend GraphQL::Schema::Member::HasArguments
8
+ extend GraphQL::Schema::Member::ValidatesInput
9
+
8
10
  include GraphQL::Dig
9
11
 
10
12
  def initialize(values = nil, ruby_kwargs: nil, context:, defaults_used:)
@@ -26,7 +28,7 @@ module GraphQL
26
28
  if @ruby_style_hash.key?(ruby_kwargs_key) && loads && !arg_defn.from_resolver?
27
29
  value = @ruby_style_hash[ruby_kwargs_key]
28
30
  @ruby_style_hash[ruby_kwargs_key] = if arg_defn.type.list?
29
- GraphQL::Execution::Lazy.all(value.map { |val| load_application_object(arg_defn, loads, val) })
31
+ value.map { |val| load_application_object(arg_defn, loads, val) }
30
32
  else
31
33
  load_application_object(arg_defn, loads, value)
32
34
  end
@@ -90,7 +92,7 @@ module GraphQL
90
92
  end
91
93
 
92
94
  def key?(key)
93
- @ruby_style_hash.key?(key) || (@arguments && @arguments.key?(key))
95
+ @ruby_style_hash.key?(key) || (@arguments && @arguments.key?(key)) || false
94
96
  end
95
97
 
96
98
  # A copy of the Ruby-style hash
@@ -117,6 +119,7 @@ module GraphQL
117
119
  type_defn.description = description
118
120
  type_defn.metadata[:type_class] = self
119
121
  type_defn.mutation = mutation
122
+ type_defn.ast_node = ast_node
120
123
  arguments.each do |name, arg|
121
124
  type_defn.arguments[arg.graphql_definition.name] = arg.graphql_definition
122
125
  end
@@ -130,6 +133,108 @@ module GraphQL
130
133
  def kind
131
134
  GraphQL::TypeKinds::INPUT_OBJECT
132
135
  end
136
+
137
+ # @api private
138
+ INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object responding to `to_h` or `to_unsafe_h`."
139
+
140
+
141
+ def validate_non_null_input(input, ctx)
142
+ result = GraphQL::Query::InputValidationResult.new
143
+
144
+ warden = ctx.warden
145
+
146
+ if input.is_a?(Array)
147
+ result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
148
+ return result
149
+ end
150
+
151
+ # We're not actually _using_ the coerced result, we're just
152
+ # using these methods to make sure that the object will
153
+ # behave like a hash below, when we call `each` on it.
154
+ begin
155
+ input.to_h
156
+ rescue
157
+ begin
158
+ # Handle ActionController::Parameters:
159
+ input.to_unsafe_h
160
+ rescue
161
+ # We're not sure it'll act like a hash, so reject it:
162
+ result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
163
+ return result
164
+ end
165
+ end
166
+
167
+ visible_arguments_map = warden.arguments(self).reduce({}) { |m, f| m[f.name] = f; m}
168
+
169
+ # Items in the input that are unexpected
170
+ input.each do |name, value|
171
+ if visible_arguments_map[name].nil?
172
+ result.add_problem("Argument is not defined on #{self.graphql_name}", [name])
173
+ end
174
+ end
175
+
176
+ # Items in the input that are expected, but have invalid values
177
+ visible_arguments_map.map do |name, argument|
178
+ argument_result = argument.type.validate_input(input[name], ctx)
179
+ if !argument_result.valid?
180
+ result.merge_result!(name, argument_result)
181
+ end
182
+ end
183
+
184
+ result
185
+ end
186
+
187
+ def coerce_input(value, ctx)
188
+ input_values = {}
189
+
190
+ arguments.each do |name, argument_defn|
191
+ arg_key = argument_defn.keyword
192
+ has_value = false
193
+ # Accept either string or symbol
194
+ field_value = if value.key?(name)
195
+ has_value = true
196
+ value[name]
197
+ elsif value.key?(arg_key)
198
+ has_value = true
199
+ value[arg_key]
200
+ elsif argument_defn.default_value?
201
+ has_value = true
202
+ argument_defn.default_value
203
+ else
204
+ nil
205
+ end
206
+ # Only continue if some value was found for this argument
207
+ if has_value
208
+ coerced_value = argument_defn.type.coerce_input(field_value, ctx)
209
+ prepared_value = argument_defn.prepare_value(nil, coerced_value, context: ctx)
210
+ input_values[arg_key] = prepared_value
211
+ end
212
+ end
213
+
214
+ input_values
215
+ end
216
+
217
+ # It's funny to think of a _result_ of an input object.
218
+ # This is used for rendering the default value in introspection responses.
219
+ def coerce_result(value, ctx)
220
+ # Allow the application to provide values as :symbols, and convert them to the strings
221
+ value = value.reduce({}) { |memo, (k, v)| memo[k.to_s] = v; memo }
222
+
223
+ result = {}
224
+
225
+ arguments.each do |input_key, input_field_defn|
226
+ input_value = value[input_key]
227
+ if value.key?(input_key)
228
+ result[input_key] = if input_value.nil?
229
+ nil
230
+ else
231
+ input_field_defn.type.coerce_result(input_value, ctx)
232
+ end
233
+ end
234
+ end
235
+
236
+ result
237
+ end
133
238
  end
134
239
  end
135
240
  end
@@ -7,11 +7,13 @@ module GraphQL
7
7
  include GraphQL::Schema::Member::CachedGraphQLDefinition
8
8
  include GraphQL::Relay::TypeExtensions
9
9
  include GraphQL::Schema::Member::BaseDSLMethods
10
+ # ConfigurationExtension's responsibilities are in `def included` below
10
11
  include GraphQL::Schema::Member::TypeSystemHelpers
11
12
  include GraphQL::Schema::Member::HasFields
12
13
  include GraphQL::Schema::Member::HasPath
13
14
  include GraphQL::Schema::Member::RelayShortcuts
14
15
  include GraphQL::Schema::Member::Scoped
16
+ include GraphQL::Schema::Member::HasAstNode
15
17
 
16
18
  # Methods defined in this block will be:
17
19
  # - Added as class methods to this interface
@@ -20,14 +22,9 @@ module GraphQL
20
22
  self::DefinitionMethods.module_eval(&block)
21
23
  end
22
24
 
23
- # The interface is visible if any of its possible types are visible
25
+ # @see {Schema::Warden} hides interfaces without visible implementations
24
26
  def visible?(context)
25
- context.schema.possible_types(self).each do |type|
26
- if context.schema.visible?(type, context)
27
- return true
28
- end
29
- end
30
- false
27
+ true
31
28
  end
32
29
 
33
30
  # The interface is accessible if any of its possible types are accessible
@@ -63,6 +60,11 @@ module GraphQL
63
60
  child_class.const_set(:DefinitionMethods, defn_methods_module)
64
61
  child_class.extend(child_class::DefinitionMethods)
65
62
  end
63
+ child_class.introspection(introspection)
64
+ child_class.description(description)
65
+ if overridden_graphql_name
66
+ child_class.graphql_name(overridden_graphql_name)
67
+ end
66
68
  elsif child_class < GraphQL::Schema::Object
67
69
  # This is being included into an object type, make sure it's using `implements(...)`
68
70
  backtrace_line = caller(0, 10).find { |line| line.include?("schema/object.rb") && line.include?("in `implements'")}
@@ -89,6 +91,7 @@ module GraphQL
89
91
  type_defn.name = graphql_name
90
92
  type_defn.description = description
91
93
  type_defn.orphan_types = orphan_types
94
+ type_defn.ast_node = ast_node
92
95
  fields.each do |field_name, field_inst|
93
96
  field_defn = field_inst.graphql_definition
94
97
  type_defn.fields[field_defn.name] = field_defn