graphql 1.8.0.pre9 → 1.8.0.pre10

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/argument.rb +1 -0
  3. data/lib/graphql/base_type.rb +2 -0
  4. data/lib/graphql/compatibility/query_parser_specification.rb +110 -0
  5. data/lib/graphql/deprecated_dsl.rb +15 -3
  6. data/lib/graphql/directive.rb +1 -0
  7. data/lib/graphql/enum_type.rb +2 -0
  8. data/lib/graphql/execution/multiplex.rb +1 -1
  9. data/lib/graphql/field.rb +2 -0
  10. data/lib/graphql/introspection/entry_points.rb +2 -2
  11. data/lib/graphql/introspection/schema_field.rb +1 -1
  12. data/lib/graphql/introspection/type_by_name_field.rb +1 -1
  13. data/lib/graphql/language/parser.rb +25 -25
  14. data/lib/graphql/language/parser.y +7 -7
  15. data/lib/graphql/query/arguments.rb +12 -0
  16. data/lib/graphql/relay/mutation/instrumentation.rb +1 -1
  17. data/lib/graphql/relay/mutation/resolve.rb +5 -1
  18. data/lib/graphql/schema.rb +5 -1
  19. data/lib/graphql/schema/argument.rb +1 -0
  20. data/lib/graphql/schema/build_from_definition.rb +60 -18
  21. data/lib/graphql/schema/enum.rb +1 -0
  22. data/lib/graphql/schema/enum_value.rb +1 -0
  23. data/lib/graphql/schema/field.rb +44 -31
  24. data/lib/graphql/schema/field/dynamic_resolve.rb +4 -8
  25. data/lib/graphql/schema/input_object.rb +30 -19
  26. data/lib/graphql/schema/interface.rb +12 -5
  27. data/lib/graphql/schema/member.rb +10 -0
  28. data/lib/graphql/schema/member/build_type.rb +3 -1
  29. data/lib/graphql/schema/member/has_arguments.rb +50 -0
  30. data/lib/graphql/schema/member/has_fields.rb +1 -1
  31. data/lib/graphql/schema/member/instrumentation.rb +4 -4
  32. data/lib/graphql/schema/mutation.rb +195 -0
  33. data/lib/graphql/schema/object.rb +4 -5
  34. data/lib/graphql/schema/relay_classic_mutation.rb +85 -0
  35. data/lib/graphql/schema/scalar.rb +1 -0
  36. data/lib/graphql/schema/traversal.rb +1 -1
  37. data/lib/graphql/schema/union.rb +1 -0
  38. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  39. data/lib/graphql/unresolved_type_error.rb +3 -2
  40. data/lib/graphql/upgrader/member.rb +194 -19
  41. data/lib/graphql/version.rb +1 -1
  42. data/spec/fixtures/upgrader/delete_project.original.rb +28 -0
  43. data/spec/fixtures/upgrader/delete_project.transformed.rb +27 -0
  44. data/spec/fixtures/upgrader/increment_count.original.rb +59 -0
  45. data/spec/fixtures/upgrader/increment_count.transformed.rb +50 -0
  46. data/spec/graphql/execution/multiplex_spec.rb +1 -1
  47. data/spec/graphql/language/parser_spec.rb +0 -74
  48. data/spec/graphql/query/serial_execution/value_resolution_spec.rb +2 -8
  49. data/spec/graphql/query_spec.rb +26 -0
  50. data/spec/graphql/relay/mutation_spec.rb +2 -2
  51. data/spec/graphql/schema/build_from_definition_spec.rb +59 -0
  52. data/spec/graphql/schema/field_spec.rb +24 -0
  53. data/spec/graphql/schema/input_object_spec.rb +1 -0
  54. data/spec/graphql/schema/interface_spec.rb +4 -1
  55. data/spec/graphql/schema/mutation_spec.rb +99 -0
  56. data/spec/graphql/schema/relay_classic_mutation_spec.rb +28 -0
  57. data/spec/support/jazz.rb +25 -1
  58. data/spec/support/star_wars/schema.rb +17 -27
  59. metadata +17 -2
@@ -10,7 +10,7 @@ module GraphQL
10
10
  # By using an instrumention, we can apply our wrapper _last_,
11
11
  # giving users access to the original resolve function in earlier instrumentation.
12
12
  def self.instrument(type, field)
13
- if field.mutation
13
+ if field.mutation.is_a?(GraphQL::Relay::Mutation) || (field.mutation.is_a?(Class) && field.mutation < GraphQL::Schema::RelayClassicMutation)
14
14
  new_resolve = Mutation::Resolve.new(field.mutation, field.resolve_proc)
15
15
  new_lazy_resolve = Mutation::Resolve.new(field.mutation, field.lazy_resolve_proc)
16
16
  field.redefine(resolve: new_resolve, lazy_resolve: new_lazy_resolve)
@@ -10,7 +10,8 @@ module GraphQL
10
10
  def initialize(mutation, resolve)
11
11
  @mutation = mutation
12
12
  @resolve = resolve
13
- @wrap_result = mutation.has_generated_return_type?
13
+ @wrap_result = mutation.is_a?(GraphQL::Relay::Mutation) && mutation.has_generated_return_type?
14
+ @class_based = mutation.is_a?(Class)
14
15
  end
15
16
 
16
17
  def call(obj, args, ctx)
@@ -44,6 +45,9 @@ module GraphQL
44
45
  end
45
46
 
46
47
  @mutation.result_class.new(client_mutation_id: args[:input][:clientMutationId], result: mutation_result)
48
+ elsif @class_based
49
+ mutation_result[:client_mutation_id] = args[:input][:client_mutation_id]
50
+ mutation_result
47
51
  else
48
52
  mutation_result
49
53
  end
@@ -27,6 +27,8 @@ require "graphql/schema/enum"
27
27
  require "graphql/schema/field"
28
28
  require "graphql/schema/input_object"
29
29
  require "graphql/schema/interface"
30
+ require "graphql/schema/mutation"
31
+ require "graphql/schema/relay_classic_mutation"
30
32
  require "graphql/schema/object"
31
33
  require "graphql/schema/scalar"
32
34
  require "graphql/schema/union"
@@ -96,7 +98,9 @@ module GraphQL
96
98
  :orphan_types, :directives,
97
99
  :query_analyzers, :multiplex_analyzers, :instrumenters, :lazy_methods,
98
100
  :cursor_encoder,
99
- :raise_definition_error, :introspection_namespace
101
+ :ast_node,
102
+ :raise_definition_error,
103
+ :introspection_namespace
100
104
 
101
105
  # Single, long-lived instance of the provided subscriptions class, if there is one.
102
106
  # @return [GraphQL::Subscriptions]
@@ -48,6 +48,7 @@ module GraphQL
48
48
  Member::BuildType.parse_type(@type_expr, null: @null)
49
49
  }
50
50
  argument.description = @description
51
+ argument.metadata[:type_class] = self
51
52
  if NO_DEFAULT != @default_value
52
53
  argument.default_value = @default_value
53
54
  end
@@ -107,6 +107,8 @@ module GraphQL
107
107
  directives directives.values
108
108
  end
109
109
 
110
+ schema.ast_node = schema_definition if schema_definition
111
+
110
112
  schema
111
113
  end
112
114
 
@@ -117,18 +119,26 @@ module GraphQL
117
119
  NullScalarCoerce = ->(val, _ctx) { val }
118
120
 
119
121
  def build_enum_type(enum_type_definition, type_resolver)
120
- GraphQL::EnumType.define(
122
+ enum = GraphQL::EnumType.define(
121
123
  name: enum_type_definition.name,
122
124
  description: enum_type_definition.description,
123
125
  values: enum_type_definition.values.map do |enum_value_definition|
124
- EnumType::EnumValue.define(
126
+ value = EnumType::EnumValue.define(
125
127
  name: enum_value_definition.name,
126
128
  value: enum_value_definition.name,
127
129
  deprecation_reason: build_deprecation_reason(enum_value_definition.directives),
128
130
  description: enum_value_definition.description,
129
131
  )
132
+
133
+ value.ast_node = enum_value_definition
134
+
135
+ value
130
136
  end
131
137
  )
138
+
139
+ enum.ast_node = enum_type_definition
140
+
141
+ enum
132
142
  end
133
143
 
134
144
  def build_deprecation_reason(directives)
@@ -148,6 +158,8 @@ module GraphQL
148
158
  coerce: NullScalarCoerce,
149
159
  )
150
160
 
161
+ scalar_type.ast_node = scalar_type_definition
162
+
151
163
  if default_resolve.respond_to?(:coerce_input)
152
164
  scalar_type = scalar_type.redefine(
153
165
  coerce_input: ->(val, ctx) { default_resolve.coerce_input(scalar_type, val, ctx) },
@@ -159,11 +171,15 @@ module GraphQL
159
171
  end
160
172
 
161
173
  def build_union_type(union_type_definition, type_resolver)
162
- GraphQL::UnionType.define(
174
+ union = GraphQL::UnionType.define(
163
175
  name: union_type_definition.name,
164
176
  description: union_type_definition.description,
165
177
  possible_types: union_type_definition.types.map{ |type_name| type_resolver.call(type_name) },
166
178
  )
179
+
180
+ union.ast_node = union_type_definition
181
+
182
+ union
167
183
  end
168
184
 
169
185
  def build_object_type(object_type_definition, type_resolver, default_resolve:)
@@ -175,14 +191,20 @@ module GraphQL
175
191
  fields: Hash[build_fields(object_type_definition.fields, type_resolver, default_resolve: typed_resolve_fn)],
176
192
  interfaces: object_type_definition.interfaces.map{ |interface_name| type_resolver.call(interface_name) },
177
193
  )
194
+ type_def.ast_node = object_type_definition
195
+ type_def
178
196
  end
179
197
 
180
198
  def build_input_object_type(input_object_type_definition, type_resolver)
181
- GraphQL::InputObjectType.define(
199
+ input = GraphQL::InputObjectType.define(
182
200
  name: input_object_type_definition.name,
183
201
  description: input_object_type_definition.description,
184
202
  arguments: Hash[build_input_arguments(input_object_type_definition, type_resolver)],
185
203
  )
204
+
205
+ input.ast_node = input_object_type_definition
206
+
207
+ input
186
208
  end
187
209
 
188
210
  def build_default_value(default_value)
@@ -208,25 +230,33 @@ module GraphQL
208
230
  kwargs[:default_value] = build_default_value(input_argument.default_value)
209
231
  end
210
232
 
233
+ argument = GraphQL::Argument.define(
234
+ name: input_argument.name,
235
+ type: type_resolver.call(input_argument.type),
236
+ description: input_argument.description,
237
+ **kwargs,
238
+ )
239
+
240
+ argument.ast_node = input_object_type_definition
241
+
211
242
  [
212
243
  input_argument.name,
213
- GraphQL::Argument.define(
214
- name: input_argument.name,
215
- type: type_resolver.call(input_argument.type),
216
- description: input_argument.description,
217
- **kwargs,
218
- )
244
+ argument
219
245
  ]
220
246
  end
221
247
  end
222
248
 
223
249
  def build_directive(directive_definition, type_resolver)
224
- GraphQL::Directive.define(
250
+ directive = GraphQL::Directive.define(
225
251
  name: directive_definition.name,
226
252
  description: directive_definition.description,
227
253
  arguments: Hash[build_directive_arguments(directive_definition, type_resolver)],
228
254
  locations: directive_definition.locations.map(&:to_sym),
229
255
  )
256
+
257
+ directive.ast_node = directive_definition
258
+
259
+ directive
230
260
  end
231
261
 
232
262
  def build_directive_arguments(directive_definition, type_resolver)
@@ -237,24 +267,32 @@ module GraphQL
237
267
  kwargs[:default_value] = build_default_value(directive_argument.default_value)
238
268
  end
239
269
 
270
+ argument = GraphQL::Argument.define(
271
+ name: directive_argument.name,
272
+ type: type_resolver.call(directive_argument.type),
273
+ description: directive_argument.description,
274
+ **kwargs,
275
+ )
276
+
277
+ argument.ast_node = directive_argument
278
+
240
279
  [
241
280
  directive_argument.name,
242
- GraphQL::Argument.define(
243
- name: directive_argument.name,
244
- type: type_resolver.call(directive_argument.type),
245
- description: directive_argument.description,
246
- **kwargs,
247
- )
281
+ argument
248
282
  ]
249
283
  end
250
284
  end
251
285
 
252
286
  def build_interface_type(interface_type_definition, type_resolver)
253
- GraphQL::InterfaceType.define(
287
+ interface = GraphQL::InterfaceType.define(
254
288
  name: interface_type_definition.name,
255
289
  description: interface_type_definition.description,
256
290
  fields: Hash[build_fields(interface_type_definition.fields, type_resolver, default_resolve: nil)],
257
291
  )
292
+
293
+ interface.ast_node = interface_type_definition
294
+
295
+ interface
258
296
  end
259
297
 
260
298
  def build_fields(field_definitions, type_resolver, default_resolve:)
@@ -273,6 +311,8 @@ module GraphQL
273
311
  **kwargs,
274
312
  )
275
313
 
314
+ arg.ast_node = argument
315
+
276
316
  [argument.name, arg]
277
317
  end]
278
318
 
@@ -285,6 +325,8 @@ module GraphQL
285
325
  deprecation_reason: build_deprecation_reason(field_definition.directives),
286
326
  )
287
327
 
328
+ field.ast_node = field_definition
329
+
288
330
  type_name = resolve_type_name(field_definition.type)
289
331
  field.connection = type_name.end_with?("Connection")
290
332
  [field_definition.name, field]
@@ -53,6 +53,7 @@ module GraphQL
53
53
  values.each do |name, val|
54
54
  enum_type.add_value(val.to_graphql)
55
55
  end
56
+ enum_type.metadata[:type_class] = self
56
57
  enum_type
57
58
  end
58
59
 
@@ -66,6 +66,7 @@ module GraphQL
66
66
  enum_value.description = @description
67
67
  enum_value.value = @value
68
68
  enum_value.deprecation_reason = @deprecation_reason
69
+ enum_value.metadata[:type_class] = self
69
70
  enum_value
70
71
  end
71
72
  end
@@ -7,6 +7,7 @@ module GraphQL
7
7
  class Field
8
8
  include GraphQL::Schema::Member::CachedGraphQLDefinition
9
9
  include GraphQL::Schema::Member::AcceptsDefinition
10
+ include GraphQL::Schema::Member::HasArguments
10
11
 
11
12
  # @return [String]
12
13
  attr_reader :name
@@ -14,15 +15,17 @@ module GraphQL
14
15
  # @return [String]
15
16
  attr_accessor :description
16
17
 
17
- # @return [Hash{String => GraphQL::Schema::Argument}]
18
- attr_reader :arguments
19
-
20
18
  # @return [Symbol]
21
19
  attr_reader :method
22
20
 
23
21
  # @return [Class] The type that this field belongs to
24
22
  attr_reader :owner
25
23
 
24
+ # @return [Class, nil] The mutation this field was derived from, if there is one
25
+ def mutation
26
+ @mutation || @mutation_class
27
+ end
28
+
26
29
  # @param name [Symbol] The underscore-cased version of this field name (will be camelized for the GraphQL API)
27
30
  # @param return_type_expr [Class, GraphQL::BaseType, Array] The return type of this field
28
31
  # @param desc [String] Field description
@@ -36,17 +39,23 @@ module GraphQL
36
39
  # @param max_page_size [Integer] For connections, the maximum number of items to return from this field
37
40
  # @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
38
41
  # @param resolve [<#call(obj, args, ctx)>] **deprecated** for compatibility with <1.8.0
39
- # @param field [GraphQL::Field] **deprecated** for compatibility with <1.8.0
42
+ # @param field [GraphQL::Field, GraphQL::Schema::Field] **deprecated** for compatibility with <1.8.0
40
43
  # @param function [GraphQL::Function] **deprecated** for compatibility with <1.8.0
44
+ # @param mutation [Class] A {Schema::Mutation} class for serving this field
45
+ # @param mutation_class [Class] (Private) A {Schema::Mutation} which this field was derived from.
46
+ # @param arguments [{String=>GraphQL::Schema::Arguments}] Arguments for this field (may be added in the block, also)
41
47
  # @param camelize [Boolean] If true, the field name will be camelized when building the schema
42
48
  # @param complexity [Numeric] When provided, set the complexity for this field
43
- def initialize(name, return_type_expr = nil, desc = nil, owner:, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, connection: nil, max_page_size: nil, resolve: nil, introspection: false, hash_key: nil, camelize: true, complexity: 1, extras: [], &definition_block)
49
+ def initialize(name, return_type_expr = nil, desc = nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, connection: nil, max_page_size: nil, resolve: nil, introspection: false, hash_key: nil, camelize: true, complexity: 1, extras: [], mutation: nil, mutation_class: nil, arguments: {}, &definition_block)
44
50
  if (field || function) && desc.nil? && return_type_expr.is_a?(String)
45
51
  # The return type should be copied from `field` or `function`, and the second positional argument is the description
46
52
  desc = return_type_expr
47
53
  return_type_expr = nil
48
54
  end
49
- if !(field || function)
55
+ if mutation && (return_type_expr || desc || description || function || field || null || deprecation_reason || method || resolve || introspection || hash_key)
56
+ raise ArgumentError, "when keyword `mutation:` is present, all arguments are ignored, please remove them"
57
+ end
58
+ if !(field || function || mutation)
50
59
  if return_type_expr.nil?
51
60
  raise ArgumentError, "missing positional argument `type`"
52
61
  end
@@ -54,15 +63,19 @@ module GraphQL
54
63
  raise ArgumentError, "missing keyword argument null:"
55
64
  end
56
65
  end
57
- if (field || function || resolve) && extras.any?
58
- raise ArgumentError, "keyword `extras:` may only be used with method-based resolve, please remove `field:`, `function:`, or `resolve:`"
66
+ if (field || function || resolve || resolve) && extras.any?
67
+ raise ArgumentError, "keyword `extras:` may only be used with method-based resolve, please remove `field:`, `function:`, `resolve:`, or `mutation:`"
59
68
  end
60
69
  @name = name.to_s
61
70
  if description && desc
62
71
  raise ArgumentError, "Provide description as a positional argument or `description:` keyword, but not both (#{desc.inspect}, #{description.inspect})"
63
72
  end
64
73
  @description = description || desc
65
- @field = field
74
+ if field.is_a?(GraphQL::Schema::Field)
75
+ @field_instance = field
76
+ else
77
+ @field = field
78
+ end
66
79
  @function = function
67
80
  @resolve = resolve
68
81
  @deprecation_reason = deprecation_reason
@@ -78,8 +91,11 @@ module GraphQL
78
91
  @max_page_size = max_page_size
79
92
  @introspection = introspection
80
93
  @extras = extras
81
- @arguments = {}
82
94
  @camelize = camelize
95
+ @mutation = mutation
96
+ @mutation_class = mutation_class
97
+ # Override the default from HasArguments
98
+ @own_arguments = arguments
83
99
  @owner = owner
84
100
 
85
101
  if definition_block
@@ -87,13 +103,6 @@ module GraphQL
87
103
  end
88
104
  end
89
105
 
90
- # This is the `argument(...)` DSL for class-based field definitons
91
- def argument(*args, **kwargs, &block)
92
- kwargs[:owner] = self
93
- arg_defn = self.class.argument_class.new(*args, **kwargs, &block)
94
- arguments[arg_defn.name] = arg_defn
95
- end
96
-
97
106
  def description(text = nil)
98
107
  if text
99
108
  @description = text
@@ -123,6 +132,14 @@ module GraphQL
123
132
 
124
133
  # @return [GraphQL::Field]
125
134
  def to_graphql
135
+ # this field was previously defined and passed here, so delegate to it
136
+ if @field_instance
137
+ return @field_instance.to_graphql
138
+ elsif @mutation
139
+ field_inst = @mutation.graphql_field
140
+ return field_inst.to_graphql
141
+ end
142
+
126
143
  method_name = @method || @hash_key || Member::BuildType.underscore(@name)
127
144
 
128
145
  field_defn = if @field
@@ -138,7 +155,11 @@ module GraphQL
138
155
  return_type_name = Member::BuildType.to_type_name(@return_type_expr)
139
156
  connection = @connection.nil? ? return_type_name.end_with?("Connection") : @connection
140
157
  field_defn.type = -> {
141
- Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
158
+ begin
159
+ Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
160
+ rescue
161
+ raise ArgumentError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: #{$!.message}", $!.backtrace
162
+ end
142
163
  }
143
164
  elsif @connection.nil? && (@field || @function)
144
165
  return_type_name = Member::BuildType.to_type_name(field_defn.type)
@@ -155,6 +176,10 @@ module GraphQL
155
176
  field_defn.deprecation_reason = @deprecation_reason
156
177
  end
157
178
 
179
+ if @mutation_class
180
+ field_defn.mutation = @mutation_class
181
+ end
182
+
158
183
  field_defn.resolve = if @resolve || @function || @field
159
184
  prev_resolve = @resolve || field_defn.resolve_proc
160
185
  UnwrappedResolve.new(inner_resolve: prev_resolve)
@@ -182,25 +207,13 @@ module GraphQL
182
207
  argument :last, "Int", "Returns the last _n_ elements from the list.", required: false
183
208
  end
184
209
 
185
- @arguments.each do |name, defn|
210
+ arguments.each do |name, defn|
186
211
  arg_graphql = defn.to_graphql
187
212
  field_defn.arguments[arg_graphql.name] = arg_graphql
188
213
  end
189
214
 
190
215
  field_defn
191
216
  end
192
-
193
- private
194
-
195
- class << self
196
- def argument_class(new_arg_class = nil)
197
- if new_arg_class
198
- @argument_class = new_arg_class
199
- else
200
- @argument_class || GraphQL::Schema::Argument
201
- end
202
- end
203
- end
204
217
  end
205
218
  end
206
219
  end
@@ -14,18 +14,18 @@ module GraphQL
14
14
  def call(obj, args, ctx)
15
15
  if obj.respond_to?(@method_name)
16
16
  public_send_field(obj, @method_name, args, ctx)
17
- elsif obj.object.respond_to?(@method_name)
18
- public_send_field(obj.object, @method_name, args, ctx)
19
17
  elsif obj.object.is_a?(Hash)
20
18
  inner_object = obj.object
21
19
  inner_object[@method_name] || inner_object[@method_sym]
20
+ elsif obj.object.respond_to?(@method_name)
21
+ public_send_field(obj.object, @method_name, args, ctx)
22
22
  else
23
23
  raise <<-ERR
24
24
  Failed to implement #{ctx.irep_node.owner_type.name}.#{ctx.field.name}, tried:
25
25
 
26
26
  - `#{obj.class}##{@method_name}`, which did not exist
27
27
  - `#{obj.object.class}##{@method_name}`, which did not exist
28
- - Looking up hash key `#{@method_name.inspect}` on `#{obj}`, but it wasn't a Hash
28
+ - Looking up hash key `#{@method_name.inspect}` on `#{obj.object}`, but it wasn't a Hash
29
29
 
30
30
  To implement this field, define one of the methods above (and check for typos)
31
31
  ERR
@@ -39,11 +39,7 @@ ERR
39
39
  def public_send_field(obj, method_name, graphql_args, field_ctx)
40
40
  if graphql_args.any? || @extras.any?
41
41
  # Splat the GraphQL::Arguments to Ruby keyword arguments
42
- ruby_kwargs = {}
43
-
44
- graphql_args.keys.each do |key|
45
- ruby_kwargs[Schema::Member::BuildType.underscore(key).to_sym] = graphql_args[key]
46
- end
42
+ ruby_kwargs = graphql_args.to_kwargs
47
43
 
48
44
  if @connection
49
45
  # Remove pagination args before passing it to a user method