graphql 1.12.10 → 1.13.4

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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +3 -1
  3. data/lib/generators/graphql/install_generator.rb +9 -2
  4. data/lib/generators/graphql/mutation_generator.rb +1 -1
  5. data/lib/generators/graphql/object_generator.rb +2 -1
  6. data/lib/generators/graphql/relay.rb +19 -11
  7. data/lib/generators/graphql/templates/schema.erb +14 -2
  8. data/lib/generators/graphql/type_generator.rb +0 -1
  9. data/lib/graphql/analysis/ast/field_usage.rb +28 -1
  10. data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
  11. data/lib/graphql/analysis/ast/visitor.rb +4 -4
  12. data/lib/graphql/backtrace/table.rb +15 -3
  13. data/lib/graphql/backtrace/tracer.rb +7 -4
  14. data/lib/graphql/base_type.rb +4 -2
  15. data/lib/graphql/boolean_type.rb +1 -1
  16. data/lib/graphql/dataloader/null_dataloader.rb +1 -0
  17. data/lib/graphql/dataloader/source.rb +50 -2
  18. data/lib/graphql/dataloader.rb +110 -41
  19. data/lib/graphql/define/instance_definable.rb +1 -1
  20. data/lib/graphql/deprecated_dsl.rb +11 -3
  21. data/lib/graphql/deprecation.rb +1 -5
  22. data/lib/graphql/directive/deprecated_directive.rb +1 -1
  23. data/lib/graphql/directive/include_directive.rb +1 -1
  24. data/lib/graphql/directive/skip_directive.rb +1 -1
  25. data/lib/graphql/directive.rb +0 -4
  26. data/lib/graphql/enum_type.rb +5 -1
  27. data/lib/graphql/execution/errors.rb +1 -0
  28. data/lib/graphql/execution/execute.rb +1 -1
  29. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  30. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -4
  31. data/lib/graphql/execution/interpreter/resolve.rb +6 -2
  32. data/lib/graphql/execution/interpreter/runtime.rb +513 -213
  33. data/lib/graphql/execution/interpreter.rb +4 -8
  34. data/lib/graphql/execution/lazy.rb +5 -1
  35. data/lib/graphql/execution/lookahead.rb +2 -2
  36. data/lib/graphql/execution/multiplex.rb +4 -1
  37. data/lib/graphql/float_type.rb +1 -1
  38. data/lib/graphql/id_type.rb +1 -1
  39. data/lib/graphql/int_type.rb +1 -1
  40. data/lib/graphql/integer_encoding_error.rb +18 -2
  41. data/lib/graphql/introspection/directive_type.rb +1 -1
  42. data/lib/graphql/introspection/entry_points.rb +2 -2
  43. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  44. data/lib/graphql/introspection/field_type.rb +2 -2
  45. data/lib/graphql/introspection/input_value_type.rb +10 -4
  46. data/lib/graphql/introspection/schema_type.rb +3 -3
  47. data/lib/graphql/introspection/type_type.rb +10 -10
  48. data/lib/graphql/language/block_string.rb +2 -6
  49. data/lib/graphql/language/document_from_schema_definition.rb +10 -4
  50. data/lib/graphql/language/lexer.rb +0 -3
  51. data/lib/graphql/language/lexer.rl +0 -4
  52. data/lib/graphql/language/nodes.rb +13 -3
  53. data/lib/graphql/language/parser.rb +442 -434
  54. data/lib/graphql/language/parser.y +5 -4
  55. data/lib/graphql/language/printer.rb +6 -1
  56. data/lib/graphql/language/sanitized_printer.rb +5 -5
  57. data/lib/graphql/language/token.rb +0 -4
  58. data/lib/graphql/name_validator.rb +0 -4
  59. data/lib/graphql/pagination/active_record_relation_connection.rb +43 -6
  60. data/lib/graphql/pagination/connections.rb +40 -16
  61. data/lib/graphql/pagination/relation_connection.rb +57 -27
  62. data/lib/graphql/query/arguments.rb +1 -1
  63. data/lib/graphql/query/arguments_cache.rb +1 -1
  64. data/lib/graphql/query/context.rb +15 -2
  65. data/lib/graphql/query/literal_input.rb +1 -1
  66. data/lib/graphql/query/null_context.rb +12 -7
  67. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  68. data/lib/graphql/query/validation_pipeline.rb +1 -1
  69. data/lib/graphql/query/variables.rb +5 -1
  70. data/lib/graphql/query.rb +5 -1
  71. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  72. data/lib/graphql/relay/global_id_resolve.rb +1 -1
  73. data/lib/graphql/relay/page_info.rb +1 -1
  74. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  75. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  76. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  77. data/lib/graphql/rubocop.rb +4 -0
  78. data/lib/graphql/schema/addition.rb +247 -0
  79. data/lib/graphql/schema/argument.rb +103 -45
  80. data/lib/graphql/schema/build_from_definition.rb +13 -7
  81. data/lib/graphql/schema/directive/feature.rb +1 -1
  82. data/lib/graphql/schema/directive/flagged.rb +2 -2
  83. data/lib/graphql/schema/directive/include.rb +1 -1
  84. data/lib/graphql/schema/directive/skip.rb +1 -1
  85. data/lib/graphql/schema/directive/transform.rb +14 -2
  86. data/lib/graphql/schema/directive.rb +7 -3
  87. data/lib/graphql/schema/enum.rb +70 -11
  88. data/lib/graphql/schema/enum_value.rb +6 -0
  89. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  90. data/lib/graphql/schema/field.rb +243 -81
  91. data/lib/graphql/schema/field_extension.rb +89 -2
  92. data/lib/graphql/schema/find_inherited_value.rb +1 -0
  93. data/lib/graphql/schema/finder.rb +5 -5
  94. data/lib/graphql/schema/input_object.rb +39 -29
  95. data/lib/graphql/schema/interface.rb +11 -20
  96. data/lib/graphql/schema/introspection_system.rb +1 -1
  97. data/lib/graphql/schema/list.rb +3 -1
  98. data/lib/graphql/schema/member/accepts_definition.rb +15 -3
  99. data/lib/graphql/schema/member/build_type.rb +1 -4
  100. data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
  101. data/lib/graphql/schema/member/has_arguments.rb +145 -57
  102. data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
  103. data/lib/graphql/schema/member/has_fields.rb +76 -18
  104. data/lib/graphql/schema/member/has_interfaces.rb +90 -0
  105. data/lib/graphql/schema/member.rb +1 -0
  106. data/lib/graphql/schema/non_null.rb +7 -1
  107. data/lib/graphql/schema/object.rb +10 -75
  108. data/lib/graphql/schema/printer.rb +12 -17
  109. data/lib/graphql/schema/relay_classic_mutation.rb +37 -3
  110. data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
  111. data/lib/graphql/schema/resolver.rb +75 -65
  112. data/lib/graphql/schema/scalar.rb +2 -0
  113. data/lib/graphql/schema/subscription.rb +36 -8
  114. data/lib/graphql/schema/traversal.rb +1 -1
  115. data/lib/graphql/schema/type_expression.rb +1 -1
  116. data/lib/graphql/schema/type_membership.rb +18 -4
  117. data/lib/graphql/schema/union.rb +8 -1
  118. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  119. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  120. data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
  121. data/lib/graphql/schema/validator/format_validator.rb +4 -5
  122. data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
  123. data/lib/graphql/schema/validator/length_validator.rb +5 -3
  124. data/lib/graphql/schema/validator/numericality_validator.rb +13 -2
  125. data/lib/graphql/schema/validator/required_validator.rb +29 -15
  126. data/lib/graphql/schema/validator.rb +33 -25
  127. data/lib/graphql/schema/warden.rb +116 -52
  128. data/lib/graphql/schema.rb +162 -227
  129. data/lib/graphql/static_validation/all_rules.rb +1 -0
  130. data/lib/graphql/static_validation/base_visitor.rb +8 -5
  131. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  132. data/lib/graphql/static_validation/error.rb +3 -1
  133. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  134. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  135. data/lib/graphql/static_validation/rules/fields_will_merge.rb +52 -26
  136. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
  137. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  138. data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
  139. data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
  140. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -1
  141. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
  142. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +13 -7
  143. data/lib/graphql/static_validation/validation_context.rb +8 -2
  144. data/lib/graphql/static_validation/validator.rb +15 -12
  145. data/lib/graphql/string_encoding_error.rb +13 -3
  146. data/lib/graphql/string_type.rb +1 -1
  147. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +36 -6
  148. data/lib/graphql/subscriptions/event.rb +68 -31
  149. data/lib/graphql/subscriptions/serialize.rb +23 -3
  150. data/lib/graphql/subscriptions.rb +17 -19
  151. data/lib/graphql/tracing/active_support_notifications_tracing.rb +6 -20
  152. data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
  153. data/lib/graphql/tracing/notifications_tracing.rb +59 -0
  154. data/lib/graphql/types/big_int.rb +5 -1
  155. data/lib/graphql/types/int.rb +1 -1
  156. data/lib/graphql/types/relay/connection_behaviors.rb +26 -9
  157. data/lib/graphql/types/relay/default_relay.rb +5 -1
  158. data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
  159. data/lib/graphql/types/relay/has_node_field.rb +2 -2
  160. data/lib/graphql/types/relay/has_nodes_field.rb +2 -2
  161. data/lib/graphql/types/relay/node_field.rb +15 -4
  162. data/lib/graphql/types/relay/nodes_field.rb +14 -4
  163. data/lib/graphql/types/string.rb +1 -1
  164. data/lib/graphql/unauthorized_error.rb +1 -1
  165. data/lib/graphql/version.rb +1 -1
  166. data/lib/graphql.rb +10 -28
  167. data/readme.md +1 -4
  168. metadata +17 -21
  169. data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ require_relative "base_cop"
3
+
4
+ module GraphQL
5
+ module Rubocop
6
+ module GraphQL
7
+ # Identify (and auto-correct) any field configuration which duplicates
8
+ # the default `null: true` property.
9
+ #
10
+ # `null: true` is default because nullable fields can always be converted
11
+ # to non-null fields (`null: false`) without a breaking change. (The opposite change, from `null: false`
12
+ # to `null: true`, change.)
13
+ #
14
+ # @example
15
+ # # Both of these define `name: String` in GraphQL:
16
+ #
17
+ # # bad
18
+ # field :name, String, null: true
19
+ #
20
+ # # good
21
+ # field :name, String
22
+ #
23
+ class DefaultNullTrue < BaseCop
24
+ MSG = "`null: true` is the default and can be removed."
25
+
26
+ def_node_matcher :field_config_with_null_true?, <<-Pattern
27
+ (
28
+ send nil? :field ... (hash $(pair (sym :null) (true)) ...)
29
+ )
30
+ Pattern
31
+
32
+ def on_send(node)
33
+ field_config_with_null_true?(node) do |null_config|
34
+ add_offense(null_config) do |corrector|
35
+ cleaned_node_source = source_without_keyword_argument(node, null_config)
36
+ corrector.replace(node.source_range, cleaned_node_source)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ require_relative "./base_cop"
3
+
4
+ module GraphQL
5
+ module Rubocop
6
+ module GraphQL
7
+ # Identify (and auto-correct) any argument configuration which duplicates
8
+ # the default `required: true` property.
9
+ #
10
+ # `required: true` is default because required arguments can always be converted
11
+ # to optional arguments (`required: false`) without a breaking change. (The opposite change, from `required: false`
12
+ # to `required: true`, change.)
13
+ #
14
+ # @example
15
+ # # Both of these define `id: ID!` in GraphQL:
16
+ #
17
+ # # bad
18
+ # argument :id, ID, required: true
19
+ #
20
+ # # good
21
+ # argument :id, ID
22
+ #
23
+ class DefaultRequiredTrue < BaseCop
24
+ MSG = "`required: true` is the default and can be removed."
25
+
26
+ def_node_matcher :argument_config_with_required_true?, <<-Pattern
27
+ (
28
+ send nil? :argument ... (hash <$(pair (sym :required) (true)) ...>)
29
+ )
30
+ Pattern
31
+
32
+ def on_send(node)
33
+ argument_config_with_required_true?(node) do |required_config|
34
+ add_offense(required_config) do |corrector|
35
+ cleaned_node_source = source_without_keyword_argument(node, required_config)
36
+ corrector.replace(node, cleaned_node_source)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "graphql/rubocop/graphql/default_null_true"
4
+ require "graphql/rubocop/graphql/default_required_true"
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Addition
6
+ attr_reader :directives, :possible_types, :types, :union_memberships, :references, :arguments_with_default_values
7
+
8
+ def initialize(schema:, own_types:, new_types:)
9
+ @schema = schema
10
+ @own_types = own_types
11
+ @directives = Set.new
12
+ @possible_types = {}
13
+ @types = {}
14
+ @union_memberships = {}
15
+ @references = Hash.new { |h, k| h[k] = [] }
16
+ @arguments_with_default_values = []
17
+ add_type_and_traverse(new_types)
18
+ end
19
+
20
+ private
21
+
22
+ def references_to(thing, from:)
23
+ @references[thing] << from
24
+ end
25
+
26
+ def get_type(name)
27
+ local_type = @types[name]
28
+ # This isn't really sophisticated, but
29
+ # I think it's good enough to support the current usage of LateBoundTypes
30
+ if local_type.is_a?(Array)
31
+ local_type = local_type.first
32
+ end
33
+ local_type || @schema.get_type(name)
34
+ end
35
+
36
+ # Lookup using `own_types` here because it's ok to override
37
+ # inherited types by name
38
+ def get_local_type(name)
39
+ @types[name] || @own_types[name]
40
+ end
41
+
42
+ def add_directives_from(owner)
43
+ dirs = owner.directives.map(&:class)
44
+ @directives.merge(dirs)
45
+ add_type_and_traverse(dirs)
46
+ end
47
+
48
+ def add_type_and_traverse(new_types)
49
+ late_types = []
50
+ new_types.each { |t| add_type(t, owner: nil, late_types: late_types, path: [t.graphql_name]) }
51
+ missed_late_types = 0
52
+ while (late_type_vals = late_types.shift)
53
+ type_owner, lt = late_type_vals
54
+ if lt.is_a?(String)
55
+ type = Member::BuildType.constantize(lt)
56
+ # Reset the counter, since we might succeed next go-round
57
+ missed_late_types = 0
58
+ update_type_owner(type_owner, type)
59
+ add_type(type, owner: type_owner, late_types: late_types, path: [type.graphql_name])
60
+ elsif lt.is_a?(LateBoundType)
61
+ if (type = get_type(lt.name))
62
+ # Reset the counter, since we might succeed next go-round
63
+ missed_late_types = 0
64
+ update_type_owner(type_owner, type)
65
+ add_type(type, owner: type_owner, late_types: late_types, path: [type.graphql_name])
66
+ else
67
+ missed_late_types += 1
68
+ # Add it back to the list, maybe we'll be able to resolve it later.
69
+ late_types << [type_owner, lt]
70
+ if missed_late_types == late_types.size
71
+ # We've looked at all of them and haven't resolved one.
72
+ raise UnresolvedLateBoundTypeError.new(type: lt)
73
+ else
74
+ # Try the next one
75
+ end
76
+ end
77
+ else
78
+ raise ArgumentError, "Unexpected late type: #{lt.inspect}"
79
+ end
80
+ end
81
+ nil
82
+ end
83
+
84
+ def update_type_owner(owner, type)
85
+ case owner
86
+ when Module
87
+ if owner.kind.union?
88
+ # It's a union with possible_types
89
+ # Replace the item by class name
90
+ owner.assign_type_membership_object_type(type)
91
+ @possible_types[owner.graphql_name] = owner.possible_types
92
+ elsif type.kind.interface? && (owner.kind.object? || owner.kind.interface?)
93
+ new_interfaces = []
94
+ owner.interfaces.each do |int_t|
95
+ if int_t.is_a?(String) && int_t == type.graphql_name
96
+ new_interfaces << type
97
+ elsif int_t.is_a?(LateBoundType) && int_t.graphql_name == type.graphql_name
98
+ new_interfaces << type
99
+ else
100
+ # Don't re-add proper interface definitions,
101
+ # they were probably already added, maybe with options.
102
+ end
103
+ end
104
+ owner.implements(*new_interfaces)
105
+ new_interfaces.each do |int|
106
+ pt = @possible_types[int.graphql_name] ||= []
107
+ if !pt.include?(owner) && owner.is_a?(Class)
108
+ pt << owner
109
+ end
110
+ end
111
+ end
112
+ when nil
113
+ # It's a root type
114
+ @types[type.graphql_name] = type
115
+ when GraphQL::Schema::Field, GraphQL::Schema::Argument
116
+ orig_type = owner.type
117
+ # Apply list/non-null wrapper as needed
118
+ if orig_type.respond_to?(:of_type)
119
+ transforms = []
120
+ while (orig_type.respond_to?(:of_type))
121
+ if orig_type.kind.non_null?
122
+ transforms << :to_non_null_type
123
+ elsif orig_type.kind.list?
124
+ transforms << :to_list_type
125
+ else
126
+ raise "Invariant: :of_type isn't non-null or list"
127
+ end
128
+ orig_type = orig_type.of_type
129
+ end
130
+ transforms.reverse_each { |t| type = type.public_send(t) }
131
+ end
132
+ owner.type = type
133
+ else
134
+ raise "Unexpected update: #{owner.inspect} #{type.inspect}"
135
+ end
136
+ end
137
+
138
+ def add_type(type, owner:, late_types:, path:)
139
+ if type.respond_to?(:metadata) && type.metadata.is_a?(Hash)
140
+ type_class = type.metadata[:type_class]
141
+ if type_class.nil?
142
+ raise ArgumentError, "Can't add legacy type: #{type} (#{type.class})"
143
+ else
144
+ type = type_class
145
+ end
146
+ elsif type.is_a?(String) || type.is_a?(GraphQL::Schema::LateBoundType)
147
+ late_types << [owner, type]
148
+ return
149
+ end
150
+
151
+ if owner.is_a?(Class) && owner < GraphQL::Schema::Union
152
+ um = @union_memberships[type.graphql_name] ||= []
153
+ um << owner
154
+ end
155
+
156
+ if (prev_type = get_local_type(type.graphql_name)) && prev_type == type
157
+ # No need to re-visit
158
+ elsif type.is_a?(Class) && type < GraphQL::Schema::Directive
159
+ @directives << type
160
+ type.all_argument_definitions.each do |arg|
161
+ arg_type = arg.type.unwrap
162
+ references_to(arg_type, from: arg)
163
+ add_type(arg_type, owner: arg, late_types: late_types, path: path + [arg.graphql_name])
164
+ if arg.default_value?
165
+ @arguments_with_default_values << arg
166
+ end
167
+ end
168
+ else
169
+ prev_type = @types[type.graphql_name]
170
+ if prev_type.nil?
171
+ @types[type.graphql_name] = type
172
+ elsif prev_type.is_a?(Array)
173
+ prev_type << type
174
+ else
175
+ @types[type.graphql_name] = [prev_type, type]
176
+ end
177
+
178
+ add_directives_from(type)
179
+ if type.kind.fields?
180
+ type.all_field_definitions.each do |field|
181
+ name = field.graphql_name
182
+ field_type = field.type.unwrap
183
+ references_to(field_type, from: field)
184
+ field_path = path + [name]
185
+ add_type(field_type, owner: field, late_types: late_types, path: field_path)
186
+ add_directives_from(field)
187
+ field.all_argument_definitions.each do |arg|
188
+ add_directives_from(arg)
189
+ arg_type = arg.type.unwrap
190
+ references_to(arg_type, from: arg)
191
+ add_type(arg_type, owner: arg, late_types: late_types, path: field_path + [arg.graphql_name])
192
+ if arg.default_value?
193
+ @arguments_with_default_values << arg
194
+ end
195
+ end
196
+ end
197
+ end
198
+ if type.kind.input_object?
199
+ type.all_argument_definitions.each do |arg|
200
+ add_directives_from(arg)
201
+ arg_type = arg.type.unwrap
202
+ references_to(arg_type, from: arg)
203
+ add_type(arg_type, owner: arg, late_types: late_types, path: path + [arg.graphql_name])
204
+ if arg.default_value?
205
+ @arguments_with_default_values << arg
206
+ end
207
+ end
208
+ end
209
+ if type.kind.union?
210
+ @possible_types[type.graphql_name] = type.all_possible_types
211
+ type.all_possible_types.each do |t|
212
+ add_type(t, owner: type, late_types: late_types, path: path + ["possible_types"])
213
+ end
214
+ end
215
+ if type.kind.interface?
216
+ type.orphan_types.each do |t|
217
+ add_type(t, owner: type, late_types: late_types, path: path + ["orphan_types"])
218
+ end
219
+ end
220
+ if type.kind.object?
221
+ possible_types_for_this_name = @possible_types[type.graphql_name] ||= []
222
+ possible_types_for_this_name << type
223
+ end
224
+
225
+ if type.kind.object? || type.kind.interface?
226
+ type.interface_type_memberships.each do |interface_type_membership|
227
+ case interface_type_membership
228
+ when Schema::TypeMembership
229
+ interface_type = interface_type_membership.abstract_type
230
+ # We can get these now; we'll have to get late-bound types later
231
+ if interface_type.is_a?(Module) && type.is_a?(Class)
232
+ implementers = @possible_types[interface_type.graphql_name] ||= []
233
+ implementers << type
234
+ end
235
+ when String, Schema::LateBoundType
236
+ interface_type = interface_type_membership
237
+ else
238
+ raise ArgumentError, "Invariant: unexpected type membership for #{type.graphql_name}: #{interface_type_membership.class} (#{interface_type_membership.inspect})"
239
+ end
240
+ add_type(interface_type, owner: type, late_types: late_types, path: path + ["implements"])
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end
@@ -2,10 +2,6 @@
2
2
  module GraphQL
3
3
  class Schema
4
4
  class Argument
5
- if !String.method_defined?(:-@)
6
- using GraphQL::StringDedupBackport
7
- end
8
-
9
5
  include GraphQL::Schema::Member::CachedGraphQLDefinition
10
6
  include GraphQL::Schema::Member::AcceptsDefinition
11
7
  include GraphQL::Schema::Member::HasPath
@@ -41,7 +37,7 @@ module GraphQL
41
37
  # @param arg_name [Symbol]
42
38
  # @param type_expr
43
39
  # @param desc [String]
44
- # @param required [Boolean] if true, this argument is non-null; if false, this argument is nullable
40
+ # @param required [Boolean, :nullable] if true, this argument is non-null; if false, this argument is nullable. If `:nullable`, then the argument must be provided, though it may be `null`.
45
41
  # @param description [String]
46
42
  # @param default_value [Object]
47
43
  # @param as [Symbol] Override the keyword name when passed to a method
@@ -52,12 +48,12 @@ module GraphQL
52
48
  # @param directives [Hash{Class => Hash}]
53
49
  # @param deprecation_reason [String]
54
50
  # @param validates [Hash, nil] Options for building validators, if any should be applied
55
- def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NO_DEFAULT, as: nil, from_resolver: false, camelize: true, prepare: nil, method_access: true, owner:, validates: nil, directives: nil, deprecation_reason: nil, &definition_block)
51
+ 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: NO_DEFAULT, as: nil, from_resolver: false, camelize: true, prepare: nil, method_access: true, owner:, validates: nil, directives: nil, deprecation_reason: nil, &definition_block)
56
52
  arg_name ||= name
57
53
  @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
58
54
  @type_expr = type_expr || type
59
55
  @description = desc || description
60
- @null = !required
56
+ @null = required != true
61
57
  @default_value = default_value
62
58
  @owner = owner
63
59
  @as = as
@@ -76,6 +72,9 @@ module GraphQL
76
72
  end
77
73
 
78
74
  self.validates(validates)
75
+ if required == :nullable
76
+ self.owner.validates(required: { argument: arg_name })
77
+ end
79
78
 
80
79
  if definition_block
81
80
  if definition_block.arity == 1
@@ -86,6 +85,10 @@ module GraphQL
86
85
  end
87
86
  end
88
87
 
88
+ def inspect
89
+ "#<#{self.class} #{path}: #{type.to_type_signature}#{description ? " @description=#{description.inspect}" : ""}>"
90
+ end
91
+
89
92
  # @return [Object] the value used when the client doesn't provide a value for this argument
90
93
  attr_reader :default_value
91
94
 
@@ -147,20 +150,15 @@ module GraphQL
147
150
  end
148
151
  end
149
152
  elsif as_type.kind.input_object?
150
- as_type.arguments.each do |_name, input_obj_arg|
151
- input_obj_arg = input_obj_arg.type_class
152
- # TODO: this skips input objects whose values were alread replaced with application objects.
153
- # See: https://github.com/rmosolgo/graphql-ruby/issues/2633
154
- if value.respond_to?(:key?) && value.key?(input_obj_arg.keyword) && !input_obj_arg.authorized?(obj, value[input_obj_arg.keyword], ctx)
155
- return false
156
- end
157
- end
153
+ return as_type.authorized?(obj, value, ctx)
158
154
  end
159
155
  # None of the early-return conditions were activated,
160
156
  # so this is authorized.
161
157
  true
162
158
  end
163
159
 
160
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
161
+
164
162
  def to_graphql
165
163
  argument = GraphQL::Argument.new
166
164
  argument.name = @name
@@ -240,50 +238,40 @@ module GraphQL
240
238
  def coerce_into_values(parent_object, values, context, argument_values)
241
239
  arg_name = graphql_name
242
240
  arg_key = keyword
243
- has_value = false
244
241
  default_used = false
242
+
245
243
  if values.key?(arg_name)
246
- has_value = true
247
244
  value = values[arg_name]
248
245
  elsif values.key?(arg_key)
249
- has_value = true
250
246
  value = values[arg_key]
251
247
  elsif default_value?
252
- has_value = true
253
248
  value = default_value
254
249
  default_used = true
250
+ else
251
+ # no value at all
252
+ owner.validate_directive_argument(self, nil)
253
+ return
255
254
  end
256
255
 
257
- if has_value
258
- loaded_value = nil
259
- coerced_value = context.schema.error_handler.with_error_handling(context) do
260
- type.coerce_input(value, context)
261
- end
256
+ loaded_value = nil
257
+ coerced_value = context.schema.error_handler.with_error_handling(context) do
258
+ type.coerce_input(value, context)
259
+ end
262
260
 
263
- # TODO this should probably be inside after_lazy
261
+ # If this isn't lazy, then the block returns eagerly and assigns the result here
262
+ # If it _is_ lazy, then we write the lazy to the hash, then update it later
263
+ argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |resolved_coerced_value|
264
264
  if loads && !from_resolver?
265
- loaded_value = if type.list?
266
- loaded_values = coerced_value.map { |val| owner.load_application_object(self, loads, val, context) }
267
- context.schema.after_any_lazies(loaded_values) { |result| result }
268
- else
269
- context.query.with_error_handling do
270
- owner.load_application_object(self, loads, coerced_value, context)
271
- end
265
+ loaded_value = context.query.with_error_handling do
266
+ load_and_authorize_value(owner, coerced_value, context)
272
267
  end
273
268
  end
274
269
 
275
- coerced_value = if loaded_value
276
- loaded_value
277
- else
278
- coerced_value
279
- end
280
-
281
- # If this isn't lazy, then the block returns eagerly and assigns the result here
282
- # If it _is_ lazy, then we write the lazy to the hash, then update it later
283
- argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |coerced_value|
284
- owner.validate_directive_argument(self, coerced_value)
270
+ maybe_loaded_value = loaded_value || resolved_coerced_value
271
+ context.schema.after_lazy(maybe_loaded_value) do |resolved_loaded_value|
272
+ owner.validate_directive_argument(self, resolved_loaded_value)
285
273
  prepared_value = context.schema.error_handler.with_error_handling(context) do
286
- prepare_value(parent_object, coerced_value, context: context)
274
+ prepare_value(parent_object, resolved_loaded_value, context: context)
287
275
  end
288
276
 
289
277
  # TODO code smell to access such a deeply-nested constant in a distant module
@@ -293,9 +281,79 @@ module GraphQL
293
281
  default_used: default_used,
294
282
  )
295
283
  end
284
+ end
285
+ end
286
+
287
+ def load_and_authorize_value(load_method_owner, coerced_value, context)
288
+ if coerced_value.nil?
289
+ return nil
290
+ end
291
+ arg_load_method = "load_#{keyword}"
292
+ if load_method_owner.respond_to?(arg_load_method)
293
+ custom_loaded_value = if load_method_owner.is_a?(Class)
294
+ load_method_owner.public_send(arg_load_method, coerced_value, context)
295
+ else
296
+ load_method_owner.public_send(arg_load_method, coerced_value)
297
+ end
298
+ context.schema.after_lazy(custom_loaded_value) do |custom_value|
299
+ if loads
300
+ if type.list?
301
+ loaded_values = custom_value.each_with_index.map { |custom_val, idx|
302
+ id = coerced_value[idx]
303
+ load_method_owner.authorize_application_object(self, id, context, custom_val)
304
+ }
305
+ context.schema.after_any_lazies(loaded_values, &:itself)
306
+ else
307
+ load_method_owner.authorize_application_object(self, coerced_value, context, custom_loaded_value)
308
+ end
309
+ else
310
+ custom_value
311
+ end
312
+ end
313
+ elsif loads
314
+ if type.list?
315
+ loaded_values = coerced_value.map { |val| load_method_owner.load_and_authorize_application_object(self, val, context) }
316
+ context.schema.after_any_lazies(loaded_values, &:itself)
317
+ else
318
+ load_method_owner.load_and_authorize_application_object(self, coerced_value, context)
319
+ end
296
320
  else
297
- # has_value is false
298
- owner.validate_directive_argument(self, nil)
321
+ coerced_value
322
+ end
323
+ end
324
+
325
+ # @api private
326
+ def validate_default_value
327
+ coerced_default_value = begin
328
+ # This is weird, but we should accept single-item default values for list-type arguments.
329
+ # If we used `coerce_isolated_input` below, it would do this for us, but it's not really
330
+ # the right thing here because we expect default values in application format (Ruby values)
331
+ # not GraphQL format (scalar values).
332
+ #
333
+ # But I don't think Schema::List#coerce_result should apply wrapping to single-item lists.
334
+ prepped_default_value = if default_value.nil?
335
+ nil
336
+ elsif (type.kind.list? || (type.kind.non_null? && type.of_type.list?)) && !default_value.respond_to?(:map)
337
+ [default_value]
338
+ else
339
+ default_value
340
+ end
341
+
342
+ type.coerce_isolated_result(prepped_default_value) unless prepped_default_value.nil?
343
+ rescue GraphQL::Schema::Enum::UnresolvedValueError
344
+ # It raises this, which is helpful at runtime, but not here...
345
+ default_value
346
+ end
347
+ res = type.valid_isolated_input?(coerced_default_value)
348
+ if !res
349
+ raise InvalidDefaultValueError.new(self)
350
+ end
351
+ end
352
+
353
+ class InvalidDefaultValueError < GraphQL::Error
354
+ def initialize(argument)
355
+ message = "`#{argument.path}` has an invalid default value: `#{argument.default_value.inspect}` isn't accepted by `#{argument.type.to_type_signature}`; update the default value or the argument type."
356
+ super(message)
299
357
  end
300
358
  end
301
359
 
@@ -3,12 +3,7 @@ require "graphql/schema/build_from_definition/resolve_map"
3
3
 
4
4
  module GraphQL
5
5
  class Schema
6
- # TODO Populate `.directive(...)` from here
7
6
  module BuildFromDefinition
8
- if !String.method_defined?(:-@)
9
- using GraphQL::StringDedupBackport
10
- end
11
-
12
7
  class << self
13
8
  # @see {Schema.from_definition}
14
9
  def from_definition(definition_string, parser: GraphQL.default_parser, **kwargs)
@@ -47,10 +42,16 @@ module GraphQL
47
42
  # _while_ building the schema.
48
43
  # It will dig for a type if it encounters a custom type. This could be a problem if there are cycles.
49
44
  directive_type_resolver = nil
50
- directive_type_resolver = build_resolve_type(GraphQL::Schema::BUILT_IN_TYPES, directives, ->(type_name) {
45
+ directive_type_resolver = build_resolve_type(types, directives, ->(type_name) {
51
46
  types[type_name] ||= begin
52
47
  defn = document.definitions.find { |d| d.respond_to?(:name) && d.name == type_name }
53
- build_definition_from_node(defn, directive_type_resolver, default_resolve)
48
+ if defn
49
+ build_definition_from_node(defn, directive_type_resolver, default_resolve)
50
+ elsif (built_in_defn = GraphQL::Schema::BUILT_IN_TYPES[type_name])
51
+ built_in_defn
52
+ else
53
+ raise "No definition for #{type_name.inspect} found in schema document or built-in types. Add a definition for it or remove it."
54
+ end
54
55
  end
55
56
  })
56
57
 
@@ -388,6 +389,11 @@ module GraphQL
388
389
  include GraphQL::Schema::Interface
389
390
  graphql_name(interface_type_definition.name)
390
391
  description(interface_type_definition.description)
392
+ interface_type_definition.interfaces.each do |interface_name|
393
+ "Implements: #{interface_type_definition} -> #{interface_name}"
394
+ interface_defn = type_resolver.call(interface_name)
395
+ implements(interface_defn)
396
+ end
391
397
  ast_node(interface_type_definition)
392
398
  builder.build_directives(self, interface_type_definition, type_resolver)
393
399
 
@@ -42,7 +42,7 @@ module GraphQL
42
42
  GraphQL::Schema::Directive::INLINE_FRAGMENT
43
43
  )
44
44
 
45
- argument :flag, String, required: true,
45
+ argument :flag, String,
46
46
  description: "The name of the feature to check before continuing"
47
47
 
48
48
  # Implement the Directive API
@@ -35,7 +35,7 @@ module GraphQL
35
35
  GraphQL::Schema::Directive::INPUT_FIELD_DEFINITION,
36
36
  )
37
37
 
38
- argument :by, [String], "Flags to check for this schema member", required: true
38
+ argument :by, [String], "Flags to check for this schema member"
39
39
 
40
40
  module VisibleByFlag
41
41
  def self.included(schema_class)
@@ -44,7 +44,7 @@ module GraphQL
44
44
 
45
45
  def visible?(context)
46
46
  if dir = self.directives.find { |d| d.is_a?(Flagged) }
47
- relevant_flags = (f = context[:flags]) && dir.arguments[:by] & f
47
+ relevant_flags = (f = context[:flags]) && dir.arguments[:by] & f # rubocop:disable Development/ContextIsPassedCop -- definition-related
48
48
  relevant_flags && relevant_flags.any? && super
49
49
  else
50
50
  super
@@ -11,7 +11,7 @@ module GraphQL
11
11
  GraphQL::Schema::Directive::INLINE_FRAGMENT
12
12
  )
13
13
 
14
- argument :if, Boolean, required: true,
14
+ argument :if, Boolean,
15
15
  description: "Included when true."
16
16
 
17
17
  default_directive true
@@ -11,7 +11,7 @@ module GraphQL
11
11
  GraphQL::Schema::Directive::INLINE_FRAGMENT
12
12
  )
13
13
 
14
- argument :if, Boolean, required: true,
14
+ argument :if, Boolean,
15
15
  description: "Skipped when true."
16
16
 
17
17
  default_directive true