graphql 1.12.24 → 1.13.19

Sign up to get free protection for your applications and to get access to all the features.
Files changed (189) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +3 -8
  3. data/lib/generators/graphql/enum_generator.rb +4 -10
  4. data/lib/generators/graphql/field_extractor.rb +31 -0
  5. data/lib/generators/graphql/input_generator.rb +50 -0
  6. data/lib/generators/graphql/install/mutation_root_generator.rb +34 -0
  7. data/lib/generators/graphql/install_generator.rb +10 -3
  8. data/lib/generators/graphql/interface_generator.rb +7 -7
  9. data/lib/generators/graphql/mutation_create_generator.rb +22 -0
  10. data/lib/generators/graphql/mutation_delete_generator.rb +22 -0
  11. data/lib/generators/graphql/mutation_generator.rb +5 -30
  12. data/lib/generators/graphql/mutation_update_generator.rb +22 -0
  13. data/lib/generators/graphql/object_generator.rb +8 -37
  14. data/lib/generators/graphql/orm_mutations_base.rb +40 -0
  15. data/lib/generators/graphql/scalar_generator.rb +4 -2
  16. data/lib/generators/graphql/templates/enum.erb +5 -1
  17. data/lib/generators/graphql/templates/input.erb +9 -0
  18. data/lib/generators/graphql/templates/interface.erb +4 -2
  19. data/lib/generators/graphql/templates/mutation.erb +1 -1
  20. data/lib/generators/graphql/templates/mutation_create.erb +20 -0
  21. data/lib/generators/graphql/templates/mutation_delete.erb +20 -0
  22. data/lib/generators/graphql/templates/mutation_update.erb +21 -0
  23. data/lib/generators/graphql/templates/object.erb +4 -2
  24. data/lib/generators/graphql/templates/scalar.erb +3 -1
  25. data/lib/generators/graphql/templates/union.erb +4 -2
  26. data/lib/generators/graphql/type_generator.rb +46 -10
  27. data/lib/generators/graphql/union_generator.rb +5 -5
  28. data/lib/graphql/analysis/ast/field_usage.rb +2 -2
  29. data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
  30. data/lib/graphql/analysis/ast/visitor.rb +5 -4
  31. data/lib/graphql/argument.rb +1 -1
  32. data/lib/graphql/backtrace/table.rb +1 -1
  33. data/lib/graphql/base_type.rb +5 -3
  34. data/lib/graphql/boolean_type.rb +1 -1
  35. data/lib/graphql/dataloader/source.rb +2 -2
  36. data/lib/graphql/dataloader.rb +55 -22
  37. data/lib/graphql/date_encoding_error.rb +16 -0
  38. data/lib/graphql/define/instance_definable.rb +15 -0
  39. data/lib/graphql/directive/deprecated_directive.rb +1 -1
  40. data/lib/graphql/directive/include_directive.rb +1 -1
  41. data/lib/graphql/directive/skip_directive.rb +1 -1
  42. data/lib/graphql/directive.rb +1 -5
  43. data/lib/graphql/enum_type.rb +7 -3
  44. data/lib/graphql/execution/errors.rb +1 -0
  45. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  46. data/lib/graphql/execution/interpreter/arguments_cache.rb +6 -4
  47. data/lib/graphql/execution/interpreter/runtime.rb +66 -38
  48. data/lib/graphql/execution/lookahead.rb +2 -2
  49. data/lib/graphql/execution/multiplex.rb +4 -1
  50. data/lib/graphql/field.rb +1 -1
  51. data/lib/graphql/float_type.rb +1 -1
  52. data/lib/graphql/id_type.rb +1 -1
  53. data/lib/graphql/input_object_type.rb +1 -1
  54. data/lib/graphql/int_type.rb +1 -1
  55. data/lib/graphql/interface_type.rb +1 -1
  56. data/lib/graphql/introspection/directive_location_enum.rb +2 -2
  57. data/lib/graphql/introspection/directive_type.rb +5 -3
  58. data/lib/graphql/introspection/entry_points.rb +2 -2
  59. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  60. data/lib/graphql/introspection/field_type.rb +3 -3
  61. data/lib/graphql/introspection/input_value_type.rb +4 -4
  62. data/lib/graphql/introspection/schema_type.rb +9 -4
  63. data/lib/graphql/introspection/type_type.rb +18 -12
  64. data/lib/graphql/introspection.rb +4 -1
  65. data/lib/graphql/language/block_string.rb +2 -6
  66. data/lib/graphql/language/document_from_schema_definition.rb +11 -4
  67. data/lib/graphql/language/lexer.rb +50 -28
  68. data/lib/graphql/language/lexer.rl +2 -4
  69. data/lib/graphql/language/nodes.rb +4 -3
  70. data/lib/graphql/language/parser.rb +841 -820
  71. data/lib/graphql/language/parser.y +13 -6
  72. data/lib/graphql/language/printer.rb +10 -1
  73. data/lib/graphql/language/sanitized_printer.rb +5 -5
  74. data/lib/graphql/language/token.rb +0 -4
  75. data/lib/graphql/name_validator.rb +0 -4
  76. data/lib/graphql/object_type.rb +2 -2
  77. data/lib/graphql/pagination/active_record_relation_connection.rb +43 -6
  78. data/lib/graphql/pagination/relation_connection.rb +59 -29
  79. data/lib/graphql/query/arguments.rb +1 -1
  80. data/lib/graphql/query/arguments_cache.rb +1 -1
  81. data/lib/graphql/query/context.rb +15 -2
  82. data/lib/graphql/query/input_validation_result.rb +9 -0
  83. data/lib/graphql/query/literal_input.rb +1 -1
  84. data/lib/graphql/query/null_context.rb +12 -7
  85. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  86. data/lib/graphql/query/validation_pipeline.rb +2 -3
  87. data/lib/graphql/query/variable_validation_error.rb +2 -2
  88. data/lib/graphql/query/variables.rb +35 -4
  89. data/lib/graphql/query.rb +0 -1
  90. data/lib/graphql/relay/connection_type.rb +15 -2
  91. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  92. data/lib/graphql/relay/global_id_resolve.rb +1 -2
  93. data/lib/graphql/relay/mutation.rb +1 -1
  94. data/lib/graphql/relay/page_info.rb +1 -1
  95. data/lib/graphql/relay/range_add.rb +4 -0
  96. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  97. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  98. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  99. data/lib/graphql/rubocop.rb +4 -0
  100. data/lib/graphql/scalar_type.rb +1 -1
  101. data/lib/graphql/schema/addition.rb +37 -28
  102. data/lib/graphql/schema/argument.rb +30 -15
  103. data/lib/graphql/schema/build_from_definition.rb +6 -5
  104. data/lib/graphql/schema/directive/feature.rb +1 -1
  105. data/lib/graphql/schema/directive/flagged.rb +2 -2
  106. data/lib/graphql/schema/directive/include.rb +1 -1
  107. data/lib/graphql/schema/directive/skip.rb +1 -1
  108. data/lib/graphql/schema/directive/transform.rb +1 -1
  109. data/lib/graphql/schema/directive.rb +23 -4
  110. data/lib/graphql/schema/enum.rb +61 -12
  111. data/lib/graphql/schema/enum_value.rb +6 -0
  112. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  113. data/lib/graphql/schema/field.rb +261 -83
  114. data/lib/graphql/schema/field_extension.rb +89 -2
  115. data/lib/graphql/schema/find_inherited_value.rb +1 -0
  116. data/lib/graphql/schema/finder.rb +5 -5
  117. data/lib/graphql/schema/input_object.rb +24 -7
  118. data/lib/graphql/schema/interface.rb +11 -20
  119. data/lib/graphql/schema/introspection_system.rb +1 -1
  120. data/lib/graphql/schema/list.rb +21 -4
  121. data/lib/graphql/schema/loader.rb +3 -0
  122. data/lib/graphql/schema/member/accepts_definition.rb +15 -3
  123. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
  124. data/lib/graphql/schema/member/build_type.rb +0 -4
  125. data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
  126. data/lib/graphql/schema/member/has_arguments.rb +56 -14
  127. data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
  128. data/lib/graphql/schema/member/has_fields.rb +76 -18
  129. data/lib/graphql/schema/member/has_interfaces.rb +100 -0
  130. data/lib/graphql/schema/member/validates_input.rb +2 -2
  131. data/lib/graphql/schema/member.rb +1 -0
  132. data/lib/graphql/schema/non_null.rb +9 -3
  133. data/lib/graphql/schema/object.rb +10 -75
  134. data/lib/graphql/schema/printer.rb +1 -1
  135. data/lib/graphql/schema/relay_classic_mutation.rb +37 -3
  136. data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
  137. data/lib/graphql/schema/resolver.rb +37 -17
  138. data/lib/graphql/schema/scalar.rb +15 -1
  139. data/lib/graphql/schema/subscription.rb +11 -1
  140. data/lib/graphql/schema/traversal.rb +1 -1
  141. data/lib/graphql/schema/type_expression.rb +1 -1
  142. data/lib/graphql/schema/type_membership.rb +18 -4
  143. data/lib/graphql/schema/union.rb +8 -1
  144. data/lib/graphql/schema/validator/format_validator.rb +0 -4
  145. data/lib/graphql/schema/validator/numericality_validator.rb +1 -0
  146. data/lib/graphql/schema/validator/required_validator.rb +29 -15
  147. data/lib/graphql/schema/validator.rb +4 -7
  148. data/lib/graphql/schema/warden.rb +126 -53
  149. data/lib/graphql/schema.rb +120 -24
  150. data/lib/graphql/static_validation/all_rules.rb +1 -0
  151. data/lib/graphql/static_validation/base_visitor.rb +6 -6
  152. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  153. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  154. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  155. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  156. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +1 -1
  157. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  158. data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
  159. data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
  160. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -3
  161. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
  162. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  163. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +13 -7
  164. data/lib/graphql/static_validation/validation_context.rb +4 -0
  165. data/lib/graphql/string_type.rb +1 -1
  166. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -4
  167. data/lib/graphql/subscriptions/event.rb +20 -12
  168. data/lib/graphql/subscriptions/serialize.rb +22 -2
  169. data/lib/graphql/subscriptions.rb +17 -19
  170. data/lib/graphql/tracing/active_support_notifications_tracing.rb +6 -20
  171. data/lib/graphql/tracing/data_dog_tracing.rb +24 -2
  172. data/lib/graphql/tracing/notifications_tracing.rb +59 -0
  173. data/lib/graphql/tracing/platform_tracing.rb +20 -10
  174. data/lib/graphql/types/iso_8601_date.rb +13 -5
  175. data/lib/graphql/types/iso_8601_date_time.rb +8 -1
  176. data/lib/graphql/types/relay/connection_behaviors.rb +28 -10
  177. data/lib/graphql/types/relay/default_relay.rb +5 -1
  178. data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
  179. data/lib/graphql/types/relay/has_node_field.rb +1 -1
  180. data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
  181. data/lib/graphql/types/relay/node_field.rb +2 -3
  182. data/lib/graphql/types/relay/nodes_field.rb +19 -3
  183. data/lib/graphql/types/string.rb +1 -1
  184. data/lib/graphql/union_type.rb +1 -1
  185. data/lib/graphql/version.rb +1 -1
  186. data/lib/graphql.rb +22 -32
  187. metadata +31 -11
  188. /data/lib/generators/graphql/{templates → install/templates}/base_mutation.erb +0 -0
  189. /data/lib/generators/graphql/{templates → install/templates}/mutation_type.erb +0 -0
@@ -3,7 +3,7 @@
3
3
  module GraphQL
4
4
  class Schema
5
5
  class Member
6
- # Shared code for Object and Interface
6
+ # Shared code for Objects, Interfaces, Mutations, Subscriptions
7
7
  module HasFields
8
8
  # Add a field to this object or interface with the given definition
9
9
  # @see {GraphQL::Schema::Field#initialize} for method signature
@@ -15,28 +15,39 @@ module GraphQL
15
15
  end
16
16
 
17
17
  # @return [Hash<String => GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields
18
- def fields
18
+ def fields(context = GraphQL::Query::NullContext)
19
+ warden = Warden.from_context(context)
20
+ is_object = self.respond_to?(:kind) && self.kind.object?
19
21
  # Local overrides take precedence over inherited fields
20
- all_fields = {}
21
- ancestors.reverse_each do |ancestor|
22
- if ancestor.respond_to?(:own_fields)
23
- all_fields.merge!(ancestor.own_fields)
22
+ visible_fields = {}
23
+ for ancestor in ancestors
24
+ if ancestor.respond_to?(:own_fields) &&
25
+ (is_object ? visible_interface_implementation?(ancestor, context, warden) : true)
26
+
27
+ ancestor.own_fields.each do |field_name, fields_entry|
28
+ # Choose the most local definition that passes `.visible?` --
29
+ # stop checking for fields by name once one has been found.
30
+ if !visible_fields.key?(field_name) && (f = Warden.visible_entry?(:visible_field?, fields_entry, context, warden))
31
+ visible_fields[field_name] = f
32
+ end
33
+ end
24
34
  end
25
35
  end
26
- all_fields
36
+ visible_fields
27
37
  end
28
38
 
29
- def get_field(field_name)
30
- if (f = own_fields[field_name])
31
- f
32
- else
33
- for ancestor in ancestors
34
- if ancestor.respond_to?(:own_fields) && f = ancestor.own_fields[field_name]
35
- return f
36
- end
39
+ def get_field(field_name, context = GraphQL::Query::NullContext)
40
+ warden = Warden.from_context(context)
41
+ is_object = self.respond_to?(:kind) && self.kind.object?
42
+ for ancestor in ancestors
43
+ if ancestor.respond_to?(:own_fields) &&
44
+ (is_object ? visible_interface_implementation?(ancestor, context, warden) : true) &&
45
+ (f_entry = ancestor.own_fields[field_name]) &&
46
+ (f = Warden.visible_entry?(:visible_field?, f_entry, context, warden))
47
+ return f
37
48
  end
38
- nil
39
49
  end
50
+ nil
40
51
  end
41
52
 
42
53
  # A list of Ruby keywords.
@@ -64,7 +75,19 @@ module GraphQL
64
75
  if method_conflict_warning && CONFLICT_FIELD_NAMES.include?(field_defn.resolver_method) && field_defn.original_name == field_defn.resolver_method && field_defn.original_name == field_defn.method_sym
65
76
  warn(conflict_field_name_warning(field_defn))
66
77
  end
67
- own_fields[field_defn.name] = field_defn
78
+ prev_defn = own_fields[field_defn.name]
79
+
80
+ case prev_defn
81
+ when nil
82
+ own_fields[field_defn.name] = field_defn
83
+ when Array
84
+ prev_defn << field_defn
85
+ when GraphQL::Schema::Field
86
+ own_fields[field_defn.name] = [prev_defn, field_defn]
87
+ else
88
+ raise "Invariant: unexpected previous field definition for #{field_defn.name.inspect}: #{prev_defn.inspect}"
89
+ end
90
+
68
91
  nil
69
92
  end
70
93
 
@@ -87,13 +110,48 @@ module GraphQL
87
110
  end
88
111
  end
89
112
 
90
- # @return [Array<GraphQL::Schema::Field>] Fields defined on this class _specifically_, not parent classes
113
+ # @return [Hash<String => GraphQL::Schema::Field, Array<GraphQL::Schema::Field>>] Fields defined on this class _specifically_, not parent classes
91
114
  def own_fields
92
115
  @own_fields ||= {}
93
116
  end
94
117
 
118
+ def all_field_definitions
119
+ all_fields = {}
120
+ ancestors.reverse_each do |ancestor|
121
+ if ancestor.respond_to?(:own_fields)
122
+ all_fields.merge!(ancestor.own_fields)
123
+ end
124
+ end
125
+ all_fields = all_fields.values
126
+ all_fields.flatten!
127
+ all_fields
128
+ end
129
+
95
130
  private
96
131
 
132
+ # If `type` is an interface, and `self` has a type membership for `type`, then make sure it's visible.
133
+ def visible_interface_implementation?(type, context, warden)
134
+ if type.respond_to?(:kind) && type.kind.interface?
135
+ implements_this_interface = false
136
+ implementation_is_visible = false
137
+ warden.interface_type_memberships(self, context).each do |tm|
138
+ if tm.abstract_type == type
139
+ implements_this_interface ||= true
140
+ if warden.visible_type_membership?(tm, context)
141
+ implementation_is_visible = true
142
+ break
143
+ end
144
+ end
145
+ end
146
+ # It's possible this interface came by way of `include` in another interface which this
147
+ # object type _does_ implement, and that's ok
148
+ implements_this_interface ? implementation_is_visible : true
149
+ else
150
+ # If there's no implementation, then we're looking at Ruby-style inheritance instead
151
+ true
152
+ end
153
+ end
154
+
97
155
  # @param [GraphQL::Schema::Field]
98
156
  # @return [String] A warning to give when this field definition might conflict with a built-in method
99
157
  def conflict_field_name_warning(field_defn)
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Member
6
+ module HasInterfaces
7
+ def implements(*new_interfaces, **options)
8
+ new_memberships = []
9
+ new_interfaces.each do |int|
10
+ if int.is_a?(Module)
11
+ unless int.include?(GraphQL::Schema::Interface)
12
+ raise "#{int} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules."
13
+ end
14
+
15
+ new_memberships << int.type_membership_class.new(int, self, **options)
16
+
17
+ # Include the methods here,
18
+ # `.fields` will use the inheritance chain
19
+ # to find inherited fields
20
+ include(int)
21
+
22
+ # If this interface has interfaces of its own, add those, too
23
+ int.interfaces.each do |next_interface|
24
+ implements(next_interface)
25
+ end
26
+ elsif int.is_a?(GraphQL::InterfaceType)
27
+ new_memberships << int.type_membership_class.new(int, self, **options)
28
+ elsif int.is_a?(String) || int.is_a?(GraphQL::Schema::LateBoundType)
29
+ if options.any?
30
+ raise ArgumentError, "`implements(...)` doesn't support options with late-loaded types yet. Remove #{options} and open an issue to request this feature."
31
+ end
32
+ new_memberships << int
33
+ else
34
+ raise ArgumentError, "Unexpected interface definition (expected module): #{int} (#{int.class})"
35
+ end
36
+ end
37
+
38
+ # Remove any String or late-bound interfaces which are being replaced
39
+ own_interface_type_memberships.reject! { |old_i_m|
40
+ if !(old_i_m.respond_to?(:abstract_type) && old_i_m.abstract_type.is_a?(Module))
41
+ old_int_type = old_i_m.respond_to?(:abstract_type) ? old_i_m.abstract_type : old_i_m
42
+ old_name = Schema::Member::BuildType.to_type_name(old_int_type)
43
+
44
+ new_memberships.any? { |new_i_m|
45
+ new_int_type = new_i_m.respond_to?(:abstract_type) ? new_i_m.abstract_type : new_i_m
46
+ new_name = Schema::Member::BuildType.to_type_name(new_int_type)
47
+
48
+ new_name == old_name
49
+ }
50
+ end
51
+ }
52
+ own_interface_type_memberships.concat(new_memberships)
53
+ end
54
+
55
+ def own_interface_type_memberships
56
+ @own_interface_type_memberships ||= []
57
+ end
58
+
59
+ def interface_type_memberships
60
+ own_tms = own_interface_type_memberships
61
+ if (self.is_a?(Class) && superclass.respond_to?(:interface_type_memberships))
62
+ inherited_tms = superclass.interface_type_memberships
63
+ if inherited_tms.size > 0
64
+ own_tms + inherited_tms
65
+ else
66
+ own_tms
67
+ end
68
+ else
69
+ own_tms
70
+ end
71
+ end
72
+
73
+ # param context [Query::Context] If omitted, skip filtering.
74
+ def interfaces(context = GraphQL::Query::NullContext)
75
+ warden = Warden.from_context(context)
76
+ visible_interfaces = []
77
+ own_interface_type_memberships.each do |type_membership|
78
+ # During initialization, `type_memberships` can hold late-bound types
79
+ case type_membership
80
+ when String, Schema::LateBoundType
81
+ visible_interfaces << type_membership
82
+ when Schema::TypeMembership
83
+ if warden.visible_type_membership?(type_membership, context)
84
+ visible_interfaces << type_membership.abstract_type
85
+ end
86
+ else
87
+ raise "Invariant: Unexpected type_membership #{type_membership.class}: #{type_membership.inspect}"
88
+ end
89
+ end
90
+
91
+ if self.is_a?(Class) && superclass <= GraphQL::Schema::Object
92
+ visible_interfaces.concat(superclass.interfaces(context))
93
+ end
94
+
95
+ visible_interfaces
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -8,11 +8,11 @@ module GraphQL
8
8
  validate_input(val, ctx).valid?
9
9
  end
10
10
 
11
- def validate_input(val, ctx)
11
+ def validate_input(val, ctx, max_errors: nil)
12
12
  if val.nil?
13
13
  GraphQL::Query::InputValidationResult.new
14
14
  else
15
- validate_non_null_input(val, ctx)
15
+ validate_non_null_input(val, ctx, max_errors: max_errors) || Query::InputValidationResult::VALID
16
16
  end
17
17
  end
18
18
 
@@ -6,6 +6,7 @@ require 'graphql/schema/member/graphql_type_names'
6
6
  require 'graphql/schema/member/has_ast_node'
7
7
  require 'graphql/schema/member/has_directives'
8
8
  require 'graphql/schema/member/has_deprecation_reason'
9
+ require 'graphql/schema/member/has_interfaces'
9
10
  require 'graphql/schema/member/has_path'
10
11
  require 'graphql/schema/member/has_unresolved_type_error'
11
12
  require 'graphql/schema/member/has_validators'
@@ -8,8 +8,10 @@ module GraphQL
8
8
  class NonNull < GraphQL::Schema::Wrapper
9
9
  include Schema::Member::ValidatesInput
10
10
 
11
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
12
+
11
13
  def to_graphql
12
- @of_type.graphql_definition.to_non_null_type
14
+ @of_type.graphql_definition(silence_deprecation_warning: true).to_non_null_type
13
15
  end
14
16
 
15
17
  # @return [GraphQL::TypeKinds::NON_NULL]
@@ -35,13 +37,13 @@ module GraphQL
35
37
  "#<#{self.class.name} @of_type=#{@of_type.inspect}>"
36
38
  end
37
39
 
38
- def validate_input(value, ctx)
40
+ def validate_input(value, ctx, max_errors: nil)
39
41
  if value.nil?
40
42
  result = GraphQL::Query::InputValidationResult.new
41
43
  result.add_problem("Expected value to not be null")
42
44
  result
43
45
  else
44
- of_type.validate_input(value, ctx)
46
+ of_type.validate_input(value, ctx, max_errors: max_errors)
45
47
  end
46
48
  end
47
49
 
@@ -51,6 +53,10 @@ module GraphQL
51
53
  end
52
54
 
53
55
  def coerce_input(value, ctx)
56
+ # `.validate_input` above is used for variables, but this method is used for arguments
57
+ if value.nil?
58
+ raise GraphQL::ExecutionError, "`null` is not a valid input for `#{to_type_signature}`, please provide a value for this argument."
59
+ end
54
60
  of_type.coerce_input(value, ctx)
55
61
  end
56
62
 
@@ -7,6 +7,7 @@ module GraphQL
7
7
  class Object < GraphQL::Schema::Member
8
8
  extend GraphQL::Schema::Member::AcceptsDefinition
9
9
  extend GraphQL::Schema::Member::HasFields
10
+ extend GraphQL::Schema::Member::HasInterfaces
10
11
 
11
12
  # @return [Object] the application object this type is wrapping
12
13
  attr_reader :object
@@ -103,84 +104,16 @@ module GraphQL
103
104
  super
104
105
  end
105
106
 
106
- def implements(*new_interfaces, **options)
107
- new_memberships = []
108
- new_interfaces.each do |int|
109
- if int.is_a?(Module)
110
- unless int.include?(GraphQL::Schema::Interface)
111
- raise "#{int} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules."
112
- end
113
-
114
- new_memberships << int.type_membership_class.new(int, self, **options)
115
-
116
- # Include the methods here,
117
- # `.fields` will use the inheritance chain
118
- # to find inherited fields
119
- include(int)
120
- elsif int.is_a?(GraphQL::InterfaceType)
121
- new_memberships << int.type_membership_class.new(int, self, **options)
122
- elsif int.is_a?(String) || int.is_a?(GraphQL::Schema::LateBoundType)
123
- if options.any?
124
- raise ArgumentError, "`implements(...)` doesn't support options with late-loaded types yet. Remove #{options} and open an issue to request this feature."
125
- end
126
- new_memberships << int
127
- else
128
- raise ArgumentError, "Unexpected interface definition (expected module): #{int} (#{int.class})"
129
- end
130
- end
131
-
132
- # Remove any interfaces which are being replaced (late-bound types are updated in place this way)
133
- own_interface_type_memberships.reject! { |old_i_m|
134
- old_int_type = old_i_m.respond_to?(:abstract_type) ? old_i_m.abstract_type : old_i_m
135
- old_name = Schema::Member::BuildType.to_type_name(old_int_type)
136
-
137
- new_memberships.any? { |new_i_m|
138
- new_int_type = new_i_m.respond_to?(:abstract_type) ? new_i_m.abstract_type : new_i_m
139
- new_name = Schema::Member::BuildType.to_type_name(new_int_type)
140
-
141
- new_name == old_name
142
- }
143
- }
144
- own_interface_type_memberships.concat(new_memberships)
145
- end
146
-
147
- def own_interface_type_memberships
148
- @own_interface_type_memberships ||= []
149
- end
150
-
151
- def interface_type_memberships
152
- own_interface_type_memberships + (superclass.respond_to?(:interface_type_memberships) ? superclass.interface_type_memberships : [])
153
- end
154
-
155
- # param context [Query::Context] If omitted, skip filtering.
156
- def interfaces(context = GraphQL::Query::NullContext)
157
- visible_interfaces = []
158
- unfiltered = context == GraphQL::Query::NullContext
159
- own_interface_type_memberships.each do |type_membership|
160
- # During initialization, `type_memberships` can hold late-bound types
161
- case type_membership
162
- when String, Schema::LateBoundType
163
- visible_interfaces << type_membership
164
- when Schema::TypeMembership
165
- if unfiltered || type_membership.visible?(context)
166
- visible_interfaces << type_membership.abstract_type
167
- end
168
- else
169
- raise "Invariant: Unexpected type_membership #{type_membership.class}: #{type_membership.inspect}"
170
- end
171
- end
172
- visible_interfaces + (superclass <= GraphQL::Schema::Object ? superclass.interfaces(context) : [])
173
- end
174
-
175
107
  # @return [Hash<String => GraphQL::Schema::Field>] All of this object's fields, indexed by name
176
108
  # @see get_field A faster way to find one field by name ({#fields} merges hashes of inherited fields; {#get_field} just looks up one field.)
177
- def fields
109
+ def fields(context = GraphQL::Query::NullContext)
178
110
  all_fields = super
111
+ # This adds fields from legacy-style interfaces only.
112
+ # Multi-fields are not supported here.
179
113
  interfaces.each do |int|
180
- # Include legacy-style interfaces, too
181
114
  if int.is_a?(GraphQL::InterfaceType)
182
115
  int_f = {}
183
- int.fields.each do |name, legacy_field|
116
+ int.fields.each do |name, legacy_field| # rubocop:disable Development/ContextIsPassedCop -- legacy-related
184
117
  int_f[name] = field_class.from_options(name, field: legacy_field)
185
118
  end
186
119
  all_fields = int_f.merge(all_fields)
@@ -189,6 +122,8 @@ module GraphQL
189
122
  all_fields
190
123
  end
191
124
 
125
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
126
+
192
127
  # @return [GraphQL::ObjectType]
193
128
  def to_graphql
194
129
  obj_type = GraphQL::ObjectType.new
@@ -198,9 +133,9 @@ module GraphQL
198
133
  obj_type.introspection = introspection
199
134
  obj_type.mutation = mutation
200
135
  obj_type.ast_node = ast_node
201
- fields.each do |field_name, field_inst|
202
- field_defn = field_inst.to_graphql
203
- obj_type.fields[field_defn.name] = field_defn
136
+ fields.each do |field_name, field_inst| # rubocop:disable Development/ContextIsPassedCop -- legacy-related
137
+ field_defn = field_inst.to_graphql(silence_deprecation_warning: true)
138
+ obj_type.fields[field_defn.name] = field_defn # rubocop:disable Development/ContextIsPassedCop -- legacy-related
204
139
  end
205
140
 
206
141
  obj_type.metadata[:type_class] = self
@@ -56,7 +56,7 @@ module GraphQL
56
56
  def self.print_introspection_schema
57
57
  query_root = Class.new(GraphQL::Schema::Object) do
58
58
  graphql_name "Root"
59
- field :throwaway_field, String, null: true
59
+ field :throwaway_field, String
60
60
  end
61
61
  schema = Class.new(GraphQL::Schema) { query(query_root) }
62
62
 
@@ -22,7 +22,7 @@ module GraphQL
22
22
  #
23
23
  class RelayClassicMutation < GraphQL::Schema::Mutation
24
24
  # The payload should always include this field
25
- field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.", null: true)
25
+ field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.")
26
26
  # Relay classic default:
27
27
  null(true)
28
28
 
@@ -81,6 +81,31 @@ module GraphQL
81
81
  end
82
82
 
83
83
  class << self
84
+
85
+ # Also apply this argument to the input type:
86
+ def argument(*args, **kwargs, &block)
87
+ it = input_type # make sure any inherited arguments are already added to it
88
+ arg = super
89
+
90
+ # This definition might be overriding something inherited;
91
+ # if it is, remove the inherited definition so it's not confused at runtime as having multiple definitions
92
+ prev_args = it.own_arguments[arg.graphql_name]
93
+ case prev_args
94
+ when GraphQL::Schema::Argument
95
+ if prev_args.owner != self
96
+ it.own_arguments.delete(arg.graphql_name)
97
+ end
98
+ when Array
99
+ prev_args.reject! { |a| a.owner != self }
100
+ if prev_args.empty?
101
+ it.own_arguments.delete(arg.graphql_name)
102
+ end
103
+ end
104
+
105
+ it.add_argument(arg)
106
+ arg
107
+ end
108
+
84
109
  # The base class for generated input object types
85
110
  # @param new_class [Class] The base class to use for generating input object definitions
86
111
  # @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject})
@@ -115,20 +140,29 @@ module GraphQL
115
140
  # To customize how input objects are generated, override this method
116
141
  # @return [Class] a subclass of {.input_object_class}
117
142
  def generate_input_type
118
- mutation_args = arguments
143
+ mutation_args = all_argument_definitions
119
144
  mutation_name = graphql_name
120
145
  mutation_class = self
121
146
  Class.new(input_object_class) do
122
147
  graphql_name("#{mutation_name}Input")
123
148
  description("Autogenerated input type of #{mutation_name}")
124
149
  mutation(mutation_class)
125
- mutation_args.each do |_name, arg|
150
+ # these might be inherited:
151
+ mutation_args.each do |arg|
126
152
  add_argument(arg)
127
153
  end
128
154
  argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
129
155
  end
130
156
  end
131
157
  end
158
+
159
+ private
160
+
161
+ def authorize_arguments(args, values)
162
+ # remove the `input` wrapper to match values
163
+ input_args = args["input"].type.unwrap.arguments(context)
164
+ super(input_args, values)
165
+ end
132
166
  end
133
167
  end
134
168
  end
@@ -38,6 +38,9 @@ module GraphQL
38
38
  # @return [Class]
39
39
  def object_class(new_class = nil)
40
40
  if new_class
41
+ if defined?(@payload_type)
42
+ raise "Can't configure `object_class(...)` after the payload type has already been initialized. Move this configuration higher up the class definition."
43
+ end
41
44
  @object_class = new_class
42
45
  else
43
46
  @object_class || find_inherited_value(:object_class, GraphQL::Schema::Object)
@@ -46,6 +49,28 @@ module GraphQL
46
49
 
47
50
  NO_INTERFACES = [].freeze
48
51
 
52
+ def field(*args, **kwargs, &block)
53
+ pt = payload_type # make sure it's initialized with any inherited fields
54
+ field_defn = super
55
+
56
+ # Remove any inherited fields to avoid false conflicts at runtime
57
+ prev_fields = pt.own_fields[field_defn.graphql_name]
58
+ case prev_fields
59
+ when GraphQL::Schema::Field
60
+ if prev_fields.owner != self
61
+ pt.own_fields.delete(field_defn.graphql_name)
62
+ end
63
+ when Array
64
+ prev_fields.reject! { |f| f.owner != self }
65
+ if prev_fields.empty?
66
+ pt.own_fields.delete(field_defn.graphql_name)
67
+ end
68
+ end
69
+
70
+ pt.add_field(field_defn, method_conflict_warning: false)
71
+ field_defn
72
+ end
73
+
49
74
  private
50
75
 
51
76
  # Build a subclass of {.object_class} based on `self`.
@@ -53,11 +78,11 @@ module GraphQL
53
78
  # Override this hook to customize return type generation.
54
79
  def generate_payload_type
55
80
  resolver_name = graphql_name
56
- resolver_fields = fields
81
+ resolver_fields = all_field_definitions
57
82
  Class.new(object_class) do
58
83
  graphql_name("#{resolver_name}Payload")
59
84
  description("Autogenerated return type of #{resolver_name}")
60
- resolver_fields.each do |name, f|
85
+ resolver_fields.each do |f|
61
86
  # Reattach the already-defined field here
62
87
  # (The field's `.owner` will still point to the mutation, not the object type, I think)
63
88
  # Don't re-warn about a method conflict. Since this type is generated, it should be fixed in the resolver instead.
@@ -37,7 +37,7 @@ module GraphQL
37
37
  @field = field
38
38
  # Since this hash is constantly rebuilt, cache it for this call
39
39
  @arguments_by_keyword = {}
40
- self.class.arguments.each do |name, arg|
40
+ self.class.arguments(context).each do |name, arg|
41
41
  @arguments_by_keyword[arg.keyword] = arg
42
42
  end
43
43
  @prepared_arguments = nil
@@ -145,19 +145,9 @@ module GraphQL
145
145
  # @raise [GraphQL::UnauthorizedError] To signal an authorization failure
146
146
  # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.)
147
147
  def authorized?(**inputs)
148
- self.class.arguments.each_value do |argument|
149
- arg_keyword = argument.keyword
150
- if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
151
- arg_auth, err = argument.authorized?(self, arg_value, context)
152
- if !arg_auth
153
- return arg_auth, err
154
- else
155
- true
156
- end
157
- else
158
- true
159
- end
160
- end
148
+ arg_owner = @field # || self.class
149
+ args = arg_owner.arguments(context)
150
+ authorize_arguments(args, inputs)
161
151
  end
162
152
 
163
153
  # Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type.
@@ -172,6 +162,22 @@ module GraphQL
172
162
 
173
163
  private
174
164
 
165
+ def authorize_arguments(args, inputs)
166
+ args.each_value do |argument|
167
+ arg_keyword = argument.keyword
168
+ if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
169
+ arg_auth, err = argument.authorized?(self, arg_value, context)
170
+ if !arg_auth
171
+ return arg_auth, err
172
+ else
173
+ true
174
+ end
175
+ else
176
+ true
177
+ end
178
+ end
179
+ end
180
+
175
181
  def load_arguments(args)
176
182
  prepared_args = {}
177
183
  prepare_lazies = []
@@ -199,8 +205,8 @@ module GraphQL
199
205
  end
200
206
  end
201
207
 
202
- def get_argument(name)
203
- self.class.get_argument(name)
208
+ def get_argument(name, context = GraphQL::Query::NullContext)
209
+ self.class.get_argument(name, context)
204
210
  end
205
211
 
206
212
  class << self
@@ -305,13 +311,27 @@ module GraphQL
305
311
  end
306
312
 
307
313
  def field_options
314
+
315
+ all_args = {}
316
+ all_argument_definitions.each do |arg|
317
+ if (prev_entry = all_args[arg.graphql_name])
318
+ if prev_entry.is_a?(Array)
319
+ prev_entry << arg
320
+ else
321
+ all_args[arg.graphql_name] = [prev_entry, arg]
322
+ end
323
+ else
324
+ all_args[arg.graphql_name] = arg
325
+ end
326
+ end
327
+
308
328
  field_opts = {
309
329
  type: type_expr,
310
330
  description: description,
311
331
  extras: extras,
312
332
  resolver_method: :resolve_with_support,
313
333
  resolver_class: self,
314
- arguments: arguments,
334
+ arguments: all_args,
315
335
  null: null,
316
336
  complexity: complexity,
317
337
  broadcastable: broadcastable?,
@@ -14,6 +14,8 @@ module GraphQL
14
14
  val
15
15
  end
16
16
 
17
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
18
+
17
19
  def to_graphql
18
20
  type_defn = GraphQL::ScalarType.new
19
21
  type_defn.name = graphql_name
@@ -30,6 +32,18 @@ module GraphQL
30
32
  GraphQL::TypeKinds::SCALAR
31
33
  end
32
34
 
35
+ def specified_by_url(new_url = nil)
36
+ if new_url
37
+ @specified_by_url = new_url
38
+ elsif defined?(@specified_by_url)
39
+ @specified_by_url
40
+ elsif superclass.respond_to?(:specified_by_url)
41
+ superclass.specified_by_url
42
+ else
43
+ nil
44
+ end
45
+ end
46
+
33
47
  def default_scalar(is_default = nil)
34
48
  if !is_default.nil?
35
49
  @default_scalar = is_default
@@ -41,7 +55,7 @@ module GraphQL
41
55
  @default_scalar ||= false
42
56
  end
43
57
 
44
- def validate_non_null_input(value, ctx)
58
+ def validate_non_null_input(value, ctx, max_errors: nil)
45
59
  result = Query::InputValidationResult.new
46
60
  coerced_result = begin
47
61
  ctx.query.with_error_handling do