graphql 1.8.0.pre9 → 1.8.0.pre10

Sign up to get free protection for your applications and to get access to all the features.
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