graphql 1.8.0.pre3 → 1.8.0.pre4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2a150383d3c4ffcaf126e675d8e844f93c4d2244
4
- data.tar.gz: 76b98bf791afc9ca8cd0eb9caf9798005d058169
3
+ metadata.gz: ef7a7ef6e77dbf150ac668f99f293298e3764e9d
4
+ data.tar.gz: 702a3a337b3c53a813bd699637a93c985f7f5bf0
5
5
  SHA512:
6
- metadata.gz: 4850a2d2c7872936c2ef09e016e6da4d6afba9bc554f76f08cc7f747d38e336e183ce201f184053364c468241b25442483ac83eab4e9bb63da2a74f19a67a202
7
- data.tar.gz: 853775ca411cab71eae69b917071ebb0c03c61d1a4557d38ff8fb4410b05da3e34dad381098ba41417b03e39114d6e1ba36aab441fc3297fffb356d0222d1b73
6
+ metadata.gz: b9e377b6787dda4fe97be9687545adb6f0fd9625df8f3233ee069ff70d96e660b3768bb58ef5b7ddbb330df3322dbe1aba23b13af53cd661dc69fe8e5d61a9e4
7
+ data.tar.gz: 628e7b0c786292b2009ce08b799c8c9db340a289ca3d3ac59b1cc9f454639cd321a327cefb9e77954311a1bd5994ffdd4ed5bd5f0715bbd38b5118345b861dfa
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './upgrader/member'
4
- require_relative './upgrader/schema'
5
3
 
6
4
  module GraphQL
7
5
  class Railtie < Rails::Railtie
8
6
  rake_tasks do
7
+ # Defer this so that you only need the `parser` gem when you _run_ the upgrader
8
+ def load_upgraders
9
+ require_relative './upgrader/member'
10
+ require_relative './upgrader/schema'
11
+ end
12
+
9
13
  namespace :graphql do
10
14
  task :upgrade, [:dir] do |t, args|
11
15
  unless (dir = args[:dir])
@@ -82,7 +86,7 @@ module GraphQL
82
86
 
83
87
  task :schema, [:schema_file] do |t, args|
84
88
  schema_file = args.schema_file
85
-
89
+ load_upgraders
86
90
  upgrader = GraphQL::Upgrader::Schema.new File.read(schema_file)
87
91
 
88
92
  puts "- Transforming schema #{schema_file}"
@@ -91,7 +95,7 @@ module GraphQL
91
95
 
92
96
  task :member, [:member_file] do |t, args|
93
97
  member_file = args.member_file
94
-
98
+ load_upgraders
95
99
  upgrader = GraphQL::Upgrader::Member.new File.read(member_file)
96
100
  next unless upgrader.upgradeable?
97
101
 
@@ -33,20 +33,15 @@ module GraphQL
33
33
  def value(graphql_name, description = nil, value: nil, deprecation_reason: nil)
34
34
  graphql_name = graphql_name.to_s
35
35
  value ||= graphql_name
36
- own_values << Value.new(graphql_name, description, value, deprecation_reason)
36
+ own_values[graphql_name] = Value.new(graphql_name, description, value, deprecation_reason)
37
37
  nil
38
38
  end
39
39
 
40
- # @return [Array<GraphQL::Schema::Enum::Value>]
40
+ # @return [Hash<String => GraphQL::Schema::Enum::Value>] Possible values of this enum, keyed by name
41
41
  def values
42
- all_values = own_values
43
- inherited_values = superclass <= GraphQL::Schema::Enum ? superclass.values : []
44
- inherited_values.each do |inherited_v|
45
- if all_values.none? { |v| v.name == inherited_v.name }
46
- all_values << inherited_v
47
- end
48
- end
49
- all_values
42
+ inherited_values = superclass <= GraphQL::Schema::Enum ? superclass.values : {}
43
+ # Local values take precedence over inherited ones
44
+ inherited_values.merge(own_values)
50
45
  end
51
46
 
52
47
  # @return [GraphQL::EnumType]
@@ -55,7 +50,7 @@ module GraphQL
55
50
  enum_type.name = graphql_name
56
51
  enum_type.description = description
57
52
  enum_type.introspection = introspection
58
- values.each do |val|
53
+ values.each do |name, val|
59
54
  enum_value = GraphQL::EnumType::EnumValue.new
60
55
  enum_value.name = val.name
61
56
  enum_value.description = val.description
@@ -69,7 +64,7 @@ module GraphQL
69
64
  private
70
65
 
71
66
  def own_values
72
- @own_values ||= []
67
+ @own_values ||= {}
73
68
  end
74
69
  end
75
70
  end
@@ -11,9 +11,12 @@ module GraphQL
11
11
  attr_reader :name
12
12
 
13
13
  # @return [String]
14
- attr_reader :description
14
+ attr_accessor :description
15
15
 
16
- def initialize(name, return_type_expr = nil, desc = nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, connection: nil, max_page_size: nil, resolve: nil, introspection: false, extras: [], &args_block)
16
+ # @return [Hash{String => GraphQL::Schema::Argument}]
17
+ attr_reader :arguments
18
+
19
+ def initialize(name, return_type_expr = nil, desc = nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, connection: nil, max_page_size: nil, resolve: nil, introspection: false, extras: [], &definition_block)
17
20
  if !(field || function)
18
21
  if return_type_expr.nil?
19
22
  raise ArgumentError, "missing positional argument `type`"
@@ -37,11 +40,29 @@ module GraphQL
37
40
  @method = method
38
41
  @return_type_expr = return_type_expr
39
42
  @return_type_null = null
40
- @args_block = args_block
41
43
  @connection = connection
42
44
  @max_page_size = max_page_size
43
45
  @introspection = introspection
44
46
  @extras = extras
47
+ @arguments = {}
48
+
49
+ if definition_block
50
+ instance_eval(&definition_block)
51
+ end
52
+ end
53
+
54
+ # This is the `argument(...)` DSL for class-based field definitons
55
+ def argument(*args)
56
+ arg_defn = self.class.argument_class.new(*args)
57
+ arguments[arg_defn.name] = arg_defn
58
+ end
59
+
60
+ def description(text = nil)
61
+ if text
62
+ @description = text
63
+ else
64
+ @description
65
+ end
45
66
  end
46
67
 
47
68
  # @return [GraphQL::Field]
@@ -93,17 +114,20 @@ module GraphQL
93
114
  field_defn.connection_max_page_size = @max_page_size
94
115
  field_defn.introspection = @introspection
95
116
 
96
- field_proxy = FieldProxy.new(field_defn, argument_class: self.class.argument_class)
97
117
  # apply this first, so it can be overriden below
98
118
  if connection
99
- field_proxy.argument :after, "String", "Returns the elements in the list that come after the specified global ID.", required: false
100
- field_proxy.argument :before, "String", "Returns the elements in the list that come before the specified global ID.", required: false
101
- field_proxy.argument :first, "Int", "Returns the first _n_ elements from the list.", required: false
102
- field_proxy.argument :last, "Int", "Returns the last _n_ elements from the list.", required: false
119
+ # TODO: this could be a bit weird, because these fields won't be present
120
+ # after initialization, only in the `to_graphql` response.
121
+ # This calculation _could_ be moved up if need be.
122
+ argument :after, "String", "Returns the elements in the list that come after the specified global ID.", required: false
123
+ argument :before, "String", "Returns the elements in the list that come before the specified global ID.", required: false
124
+ argument :first, "Int", "Returns the first _n_ elements from the list.", required: false
125
+ argument :last, "Int", "Returns the last _n_ elements from the list.", required: false
103
126
  end
104
127
 
105
- if @args_block
106
- field_proxy.instance_eval(&@args_block)
128
+ @arguments.each do |name, defn|
129
+ arg_graphql = defn.to_graphql
130
+ field_defn.arguments[arg_graphql.name] = arg_graphql
107
131
  end
108
132
 
109
133
  field_defn
@@ -120,32 +144,6 @@ module GraphQL
120
144
  end
121
145
  end
122
146
  end
123
-
124
-
125
- # This object exists only to be `instance_eval`'d
126
- # when the `field(...)` method is called with a block.
127
- # This object receives that block.
128
- class FieldProxy
129
- def initialize(field, argument_class:)
130
- @field = field
131
- @argument_class = argument_class
132
- end
133
-
134
- # This is the `argument(...)` DSL for class-based field definitons
135
- def argument(*args)
136
- arg = @argument_class.new(*args)
137
- graphql_arg = arg.graphql_definition
138
- @field.arguments[graphql_arg.name] = graphql_arg
139
- end
140
-
141
- def description(text)
142
- if @field.description
143
- fail "You're overriding the description of #{@field.name} in the provided block!"
144
- else
145
- @field.description = text
146
- end
147
- end
148
- end
149
147
  end
150
148
  end
151
149
  end
@@ -6,6 +6,7 @@ module GraphQL
6
6
  class DynamicResolve
7
7
  def initialize(method_name:, connection:, extras:)
8
8
  @method_name = method_name
9
+ @method_sym = method_name.to_sym
9
10
  @connection = connection
10
11
  @extras = extras
11
12
  end
@@ -15,8 +16,9 @@ module GraphQL
15
16
  public_send_field(obj, @method_name, args, ctx)
16
17
  elsif obj.object.respond_to?(@method_name)
17
18
  public_send_field(obj.object, @method_name, args, ctx)
18
- elsif obj.is_a?(Hash)
19
- obj[@method_name]
19
+ elsif obj.object.is_a?(Hash)
20
+ inner_object = obj.object
21
+ inner_object[@method_name] || inner_object[@method_sym]
20
22
  else
21
23
  raise <<-ERR
22
24
  Failed to implement #{ctx.irep_node.owner_type.name}.#{ctx.field.name}, tried:
@@ -19,34 +19,30 @@ module GraphQL
19
19
 
20
20
  def argument(*args)
21
21
  argument = GraphQL::Schema::Argument.new(*args)
22
- own_arguments << argument
23
22
  arg_name = argument.graphql_definition.name
23
+ own_arguments[arg_name] = argument
24
24
  # Add a method access
25
25
  define_method(Member::BuildType.underscore(arg_name)) do
26
26
  @arguments.public_send(arg_name)
27
27
  end
28
28
  end
29
29
 
30
+ # @return [Hash<String => GraphQL::Schema::Argument] Input fields on this input object, keyed by name.
30
31
  def arguments
31
- all_arguments = own_arguments
32
- inherited_arguments = (superclass <= GraphQL::Schema::InputObject ? superclass.arguments : [])
33
- inherited_arguments.each do |inherited_a|
34
- if all_arguments.none? { |a| a.name == inherited_a.name }
35
- all_arguments << inherited_a
36
- end
37
- end
38
- all_arguments
32
+ inherited_arguments = (superclass <= GraphQL::Schema::InputObject ? superclass.arguments : {})
33
+ # Local definitions override inherited ones
34
+ inherited_arguments.merge(own_arguments)
39
35
  end
40
36
 
41
37
  def own_arguments
42
- @own_arguments ||= []
38
+ @own_arguments ||= {}
43
39
  end
44
40
 
45
41
  def to_graphql
46
42
  type_defn = GraphQL::InputObjectType.new
47
43
  type_defn.name = graphql_name
48
44
  type_defn.description = description
49
- arguments.each do |arg|
45
+ arguments.each do |name, arg|
50
46
  type_defn.arguments[arg.graphql_definition.name] = arg.graphql_definition
51
47
  end
52
48
  # Make a reference to a classic-style Arguments class
@@ -20,7 +20,7 @@ module GraphQL
20
20
  type_defn = GraphQL::InterfaceType.new
21
21
  type_defn.name = graphql_name
22
22
  type_defn.description = description
23
- fields.each do |field_inst|
23
+ fields.each do |field_name, field_inst|
24
24
  field_defn = field_inst.graphql_definition
25
25
  type_defn.fields[field_defn.name] = field_defn
26
26
  end
@@ -4,6 +4,8 @@ module GraphQL
4
4
  class Member
5
5
  # @api private
6
6
  module BuildType
7
+ LIST_TYPE_ERROR = "Use an array of [T] or [T, null: true] for list types; other arrays are not supported"
8
+
7
9
  module_function
8
10
  # @param type_expr [String, Class, GraphQL::BaseType]
9
11
  # @return [GraphQL::BaseType]
@@ -46,12 +48,21 @@ module GraphQL
46
48
  when GraphQL::BaseType, GraphQL::Schema::LateBoundType
47
49
  type_expr
48
50
  when Array
49
- if type_expr.length != 1
50
- raise "Use an array of length = 1 for list types; other arrays are not supported"
51
+ case type_expr.length
52
+ when 1
53
+ list_type = true
54
+ # List members are required by default
55
+ parse_type(type_expr.first, null: false)
56
+ when 2
57
+ inner_type, nullable_option = type_expr
58
+ if nullable_option.keys != [:null] || nullable_option.values != [true]
59
+ raise ArgumentError, LIST_TYPE_ERROR
60
+ end
61
+ list_type = true
62
+ parse_type(inner_type, null: true)
63
+ else
64
+ raise ArgumentError, LIST_TYPE_ERROR
51
65
  end
52
- list_type = true
53
- # List members are required by default
54
- parse_type(type_expr.first, null: false)
55
66
  when Class
56
67
  if Class < GraphQL::Schema::Member
57
68
  type_expr.graphql_definition
@@ -13,25 +13,18 @@ module GraphQL
13
13
  nil
14
14
  end
15
15
 
16
- # @return [Array<GraphQL::Schema::Field>] Fields on this object, including inherited fields
16
+ # @return [Hash<String => GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields
17
17
  def fields
18
- all_fields = own_fields
19
- inherited_fields = (superclass.is_a?(HasFields) ? superclass.fields : [])
20
- # Remove any inherited fields which were overridden on this class:
21
- inherited_fields.each do |inherited_f|
22
- if all_fields.none? {|f| f.name == inherited_f.name}
23
- all_fields << inherited_f
24
- end
25
- end
26
- all_fields
18
+ inherited_fields = (superclass.is_a?(HasFields) ? superclass.fields : {})
19
+ # Local overrides take precedence over inherited fields
20
+ inherited_fields.merge(own_fields)
27
21
  end
28
22
 
29
23
  # Register this field with the class, overriding a previous one if needed
30
24
  # @param field_defn [GraphQL::Schema::Field]
31
25
  # @return [void]
32
26
  def add_field(field_defn)
33
- own_fields.reject! {|f| f.name == field_defn.name}
34
- own_fields << field_defn
27
+ own_fields[field_defn.name] = field_defn
35
28
  nil
36
29
  end
37
30
 
@@ -48,7 +41,7 @@ module GraphQL
48
41
 
49
42
  # @return [Array<GraphQL::Schema::Field>] Fields defined on this class _specifically_, not parent classes
50
43
  def own_fields
51
- @own_fields ||= []
44
+ @own_fields ||= {}
52
45
  end
53
46
  end
54
47
  end
@@ -17,7 +17,7 @@ module GraphQL
17
17
  new_interfaces.each do |int|
18
18
  if int.is_a?(Class) && int < GraphQL::Schema::Interface
19
19
  # Add the graphql field defns
20
- int.fields.each do |field|
20
+ int.fields.each do |_name, field|
21
21
  add_field(field)
22
22
  end
23
23
  # And call the implemented hook
@@ -47,7 +47,7 @@ module GraphQL
47
47
  obj_type.interfaces = interfaces
48
48
  obj_type.introspection = introspection
49
49
 
50
- fields.each do |field_inst|
50
+ fields.each do |field_name, field_inst|
51
51
  field_defn = field_inst.to_graphql
52
52
  obj_type.fields[field_defn.name] = field_defn
53
53
  end
@@ -95,12 +95,18 @@ module GraphQL
95
95
  # Remove newlines -- normalize the text for processing
96
96
  class RemoveNewlinesTransform
97
97
  def apply(input_text)
98
- input_text.gsub(/(?<field>(?:field|connection|argument).*?,)\n(\s*)(?<next_line>(:?"|field)(.*))/) do
99
- field = $~[:field].chomp
100
- next_line = $~[:next_line]
101
-
102
- "#{field} #{next_line}"
98
+ keep_looking = true
99
+ while keep_looking do
100
+ keep_looking = false
101
+ input_text = input_text.gsub(/(?<field>(?:field|connection|argument).*?,)\n(\s*)(?<next_line>.*)/) do
102
+ keep_looking = true
103
+ field = $~[:field].chomp
104
+ next_line = $~[:next_line]
105
+
106
+ "#{field} #{next_line}"
107
+ end
103
108
  end
109
+ input_text
104
110
  end
105
111
  end
106
112
 
@@ -141,7 +147,7 @@ module GraphQL
141
147
  ) do
142
148
  field = $~[:field]
143
149
  block_contents = $~[:block_contents]
144
- kwarg_value = $~[:kwarg_value]
150
+ kwarg_value = $~[:kwarg_value].strip
145
151
 
146
152
  "#{field}, #{@kwarg}: #{kwarg_value} do#{block_contents}"
147
153
  end
@@ -155,6 +161,28 @@ module GraphQL
155
161
  end
156
162
  end
157
163
 
164
+ # Find a keyword whose value is a string or symbol,
165
+ # and if the value is equivalent to the field name,
166
+ # remove the keyword altogether.
167
+ class RemoveRedundantKwargTransform < Transform
168
+ def initialize(kwarg:)
169
+ @kwarg = kwarg
170
+ @finder_pattern = /(field|connection|argument) :(?<name>[a-zA-Z_0-9]*).*#{@kwarg}: ['":](?<kwarg_value>[a-zA-Z_0-9]+)['"]?/
171
+ end
172
+
173
+ def apply(input_text)
174
+ if input_text =~ @finder_pattern
175
+ field_name = $~[:name]
176
+ kwarg_value = $~[:kwarg_value]
177
+ if field_name == kwarg_value
178
+ # It's redundant, remove it
179
+ input_text = input_text.sub(/, #{@kwarg}: ['":]#{kwarg_value}['"]?/, "")
180
+ end
181
+ end
182
+ input_text
183
+ end
184
+ end
185
+
158
186
  # Take camelized field names and convert them to underscore case.
159
187
  # (They'll be automatically camelized later.)
160
188
  class UnderscoreizeFieldNameTransform < Transform
@@ -196,7 +224,13 @@ module GraphQL
196
224
 
197
225
  input_text.match(/(?<field_type>input_field|field|connection|argument) :(?<name>[a-zA-Z_0-9_]*)/)
198
226
  field_name = $~[:name]
199
- field_ast = Parser::CurrentRuby.parse(input_text)
227
+ begin
228
+ field_ast = Parser::CurrentRuby.parse(input_text)
229
+ rescue Parser::SyntaxError
230
+ puts "Error text:"
231
+ puts input_text
232
+ raise
233
+ end
200
234
  processor = ResolveProcProcessor.new
201
235
  processor.process(field_ast)
202
236
  proc_body = input_text[processor.proc_start..processor.proc_end]
@@ -288,9 +322,14 @@ module GraphQL
288
322
  ) do
289
323
  indent = $~[:indent]
290
324
  interfaces = $~[:interfaces].split(',').map(&:strip).reject(&:empty?)
291
- interfaces.map do |interface|
292
- "#{indent}implements #{interface}"
293
- end.join
325
+ # Preserve leading newlines before the `interfaces ...`
326
+ # call, but don't re-insert them between `implements` calls.
327
+ extra_leading_newlines = "\n" * (indent[/^\n*/].length - 1)
328
+ indent = indent.sub(/^\n*/m, "")
329
+ interfaces_calls = interfaces
330
+ .map { |interface| "\n#{indent}implements #{interface}" }
331
+ .join
332
+ extra_leading_newlines + interfaces_calls
294
333
  end
295
334
  end
296
335
  end
@@ -298,23 +337,27 @@ module GraphQL
298
337
  class UpdateMethodSignatureTransform < Transform
299
338
  def apply(input_text)
300
339
  input_text.scan(/(?:input_field|field|connection|argument) .*$/).each do |field|
301
- matches = /(?<field_type>input_field|field|connection|argument) :(?<name>[a-zA-Z_0-9_]*)?, (?<return_type>[^,]*)(?<remainder>.*)/.match(field)
340
+ matches = /(?<field_type>input_field|field|connection|argument) :(?<name>[a-zA-Z_0-9_]*)?, (?<return_type>([A-Za-z\[\]\.\!_0-9]|::|-> ?\{ ?| ?\})+)(?<remainder>( |,|$).*)/.match(field)
302
341
  if matches
303
342
  name = matches[:name]
304
343
  return_type = matches[:return_type]
305
344
  remainder = matches[:remainder]
306
345
  field_type = matches[:field_type]
307
-
308
- # This is a small bug in the regex. Ideally the `do` part would only be in the remainder.
309
- with_block = remainder.gsub!(/\ do$/, '') || return_type.gsub!(/\ do$/, '')
346
+ with_block = remainder.gsub!(/\ do$/, '')
310
347
 
311
348
  remainder.gsub! /,$/, ''
312
349
  remainder.gsub! /^,/, ''
313
350
  remainder.chomp!
314
351
 
315
- has_bang = !(return_type.gsub! '!', '')
316
- return_type = normalize_type_expression(return_type)
317
- return_type = return_type.gsub ',', ''
352
+ if return_type
353
+ non_nullable = return_type.gsub! '!', ''
354
+ nullable = !non_nullable
355
+ return_type = normalize_type_expression(return_type)
356
+ return_type = return_type.gsub ',', ''
357
+ else
358
+ non_nullable = nil
359
+ nullable = nil
360
+ end
318
361
 
319
362
  input_text.sub!(field) do
320
363
  is_argument = ['argument', 'input_field'].include?(field_type)
@@ -325,15 +368,15 @@ module GraphQL
325
368
  end
326
369
 
327
370
  if is_argument
328
- if has_bang
371
+ if nullable
329
372
  f += ', required: false'
330
- else
373
+ elsif non_nullable
331
374
  f += ', required: true'
332
375
  end
333
376
  else
334
- if has_bang
377
+ if nullable
335
378
  f += ', null: true'
336
- else
379
+ elsif non_nullable
337
380
  f += ', null: false'
338
381
  end
339
382
  end
@@ -363,17 +406,29 @@ module GraphQL
363
406
 
364
407
  # Remove redundant newlines, which may have trailing spaces
365
408
  # Remove double newline after `do`
409
+ # Remove double newline before `end`
366
410
  class RemoveExcessWhitespaceTransform < Transform
367
411
  def apply(input_text)
368
412
  input_text
369
- .gsub(/\n{3,}/m, "\n")
370
- .gsub(/do\n\n/m, "do\n")
413
+ .gsub(/\n{3,}/m, "\n\n")
414
+ .gsub(/do\n{2,}/m, "do\n")
415
+ .gsub(/\n{2,}(\s*)end/m, "\n\\1end")
416
+ end
417
+ end
418
+
419
+ # Skip this file if you see any `field`
420
+ # helpers with `null: true` or `null: false` keywords,
421
+ # because it's already been transformed
422
+ class SkipOnNullKeyword
423
+ def skip?(input_text)
424
+ input_text =~ /field.*null: (true|false)/
371
425
  end
372
426
  end
373
427
 
374
428
  class Member
375
- def initialize(member, type_transforms: DEFAULT_TYPE_TRANSFORMS, field_transforms: DEFAULT_FIELD_TRANSFORMS, clean_up_transforms: DEFAULT_CLEAN_UP_TRANSFORMS)
429
+ def initialize(member, skip: SkipOnNullKeyword, type_transforms: DEFAULT_TYPE_TRANSFORMS, field_transforms: DEFAULT_FIELD_TRANSFORMS, clean_up_transforms: DEFAULT_CLEAN_UP_TRANSFORMS)
376
430
  @member = member
431
+ @skip = skip
377
432
  @type_transforms = type_transforms
378
433
  @field_transforms = field_transforms
379
434
  @clean_up_transforms = clean_up_transforms
@@ -390,7 +445,11 @@ module GraphQL
390
445
  PositionalTypeArgTransform,
391
446
  ConfigurationToKwargTransform.new(kwarg: "property"),
392
447
  ConfigurationToKwargTransform.new(kwarg: "description"),
448
+ ConfigurationToKwargTransform.new(kwarg: "deprecation_reason"),
449
+ ConfigurationToKwargTransform.new(kwarg: "hash_key"),
393
450
  PropertyToMethodTransform,
451
+ RemoveRedundantKwargTransform.new(kwarg: "hash_key"),
452
+ RemoveRedundantKwargTransform.new(kwarg: "method"),
394
453
  UnderscoreizeFieldNameTransform,
395
454
  ResolveProcToMethodTransform,
396
455
  UpdateMethodSignatureTransform,
@@ -402,8 +461,14 @@ module GraphQL
402
461
  ]
403
462
 
404
463
  def upgrade
464
+ type_source = @member.dup
465
+ should_skip = @skip.new.skip?(type_source)
466
+ # return the unmodified code
467
+ if should_skip
468
+ return type_source
469
+ end
405
470
  # Transforms on type defn code:
406
- type_source = apply_transforms(@member.dup, @type_transforms)
471
+ type_source = apply_transforms(type_source, @type_transforms)
407
472
  # Transforms on each field:
408
473
  field_sources = find_fields(type_source)
409
474
  field_sources.each do |field_source|
@@ -464,6 +529,10 @@ module GraphQL
464
529
  # - use `gsub` after performing the transformation.
465
530
  field_sources.uniq!
466
531
  field_sources
532
+ rescue Parser::SyntaxError
533
+ puts "Error Source:"
534
+ puts type_source
535
+ raise
467
536
  end
468
537
 
469
538
  class FieldFinder < Parser::AST::Processor
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.8.0.pre3"
3
+ VERSION = "1.8.0.pre4"
4
4
  end
@@ -8,7 +8,7 @@ module Platform
8
8
 
9
9
  scopeless_tokens_as_minimum
10
10
 
11
- # Test multiline interfaces
11
+
12
12
  interfaces [
13
13
  Interfaces::A,
14
14
  Interfaces::B,
@@ -7,7 +7,6 @@ module Platform
7
7
 
8
8
  scopeless_tokens_as_minimum
9
9
 
10
- # Test multiline interfaces
11
10
  implements Interfaces::A
12
11
  implements Interfaces::B
13
12
 
@@ -12,11 +12,14 @@ module Platform
12
12
  interfaces [GraphQL::Relay::Node.interface]
13
13
 
14
14
  field :f1, !Objects::O1, "The x being y."
15
- field :f2, !Enums::E1, "x for the y.", property: :field_2
15
+ field :f2, !Enums::E1, "x for the y.",
16
+ property: :field_2
16
17
  field :f3, Enums::E2, "x for y."
17
18
  field :details, types.String, "Details."
18
19
 
19
20
  field :f4, !Objects::O2, "x as a y inside the z." do
21
+ argument :a1, !Inputs::I1
22
+
20
23
  resolve ->(obj_x, arguments, context) do
21
24
  Class1.new(
22
25
  a: Class2.new(
@@ -30,6 +33,21 @@ module Platform
30
33
  )
31
34
  end
32
35
  end
36
+
37
+ field :f5, -> { !types.String } do
38
+ description "The thing"
39
+ property :custom_property
40
+ visibility :custom_value
41
+ end
42
+
43
+ field :f6, -> { !types.String } do
44
+ description "The thing"
45
+ property :custom_property
46
+ visibility :custom_value
47
+ end
48
+
49
+ field :f7, field: SomeField
50
+ field :f8, function: SomeFunction
33
51
  end
34
52
  end
35
53
  end
@@ -16,9 +16,11 @@ module Platform
16
16
  field :f3, Enums::E2, "x for y.", null: true
17
17
  field :details, String, "Details.", null: true
18
18
 
19
- field :f4, Objects::O2, "x as a y inside the z.", null: false
19
+ field :f4, Objects::O2, "x as a y inside the z.", null: false do
20
+ argument :a1, Inputs::I1, required: true
21
+ end
20
22
 
21
- def f4
23
+ def f4(**arguments)
22
24
  Class1.new(
23
25
  a: Class2.new(
24
26
  b: @object.b_1,
@@ -30,6 +32,13 @@ module Platform
30
32
  )
31
33
  )
32
34
  end
35
+
36
+ field :f5, String, visibility: :custom_value, method: :custom_property, description: "The thing", null: false
37
+
38
+ field :f6, String, visibility: :custom_value, method: :custom_property, description: "The thing", null: false
39
+
40
+ field :f7, field: SomeField
41
+ field :f8, function: SomeFunction
33
42
  end
34
43
  end
35
44
  end
@@ -256,8 +256,9 @@ TABLE
256
256
  }'
257
257
  res = Jazz::Schema.execute(query_str, context: { magic_key: :ignored, normal_key: "normal_value" })
258
258
  expected_values = ["custom_method", "magic_value", "normal_value"]
259
+ expected_values_with_nil = expected_values + [nil]
259
260
  assert_equal expected_values, res["data"]["inspectContext"]
260
- assert_equal expected_values, res["data"]["find"]["inspectContext"]
261
+ assert_equal expected_values_with_nil, res["data"]["find"]["inspectContext"]
261
262
  end
262
263
  end
263
264
  end
@@ -21,7 +21,7 @@ describe GraphQL::Schema::Enum do
21
21
  # values were inherited without modifying the parent
22
22
  assert_equal 6, enum.values.size
23
23
  assert_equal 7, new_enum.values.size
24
- perc_value = new_enum.values.find { |v| v.name == "PERCUSSION" }
24
+ perc_value = new_enum.values["PERCUSSION"]
25
25
  assert_equal "new description", perc_value.description
26
26
  end
27
27
  end
@@ -4,7 +4,7 @@ require "spec_helper"
4
4
  describe GraphQL::Schema::Field do
5
5
  describe "graphql definition" do
6
6
  let(:object_class) { Jazz::Query }
7
- let(:field) { object_class.fields.find { |f| f.name == "inspect_input" } }
7
+ let(:field) { object_class.fields["inspect_input"] }
8
8
 
9
9
  it "uses the argument class" do
10
10
  arg_defn = field.graphql_definition.arguments.values.first
@@ -15,18 +15,18 @@ describe GraphQL::Schema::Field do
15
15
  assert_equal 'inspectInput', field.graphql_definition.name
16
16
  end
17
17
 
18
- describe "description in block" do
19
- it "will raise if description is defined both in the argument and in the block" do
20
- assert_raises RuntimeError, "You're overriding the description of shouldRaise in the provided block!" do
21
- Class.new(Jazz::BaseObject) do
22
- graphql_name "JustAName"
18
+ it "accepts a block for definition" do
19
+ object = Class.new(Jazz::BaseObject) do
20
+ graphql_name "JustAName"
23
21
 
24
- field :should_raise, Jazz::Key, "this should not raise", null: true do
25
- description "This should raise"
26
- end
27
- end.to_graphql
22
+ field :test, String, null: true do
23
+ argument :test, String, required: true
24
+ description "A Description."
28
25
  end
29
- end
26
+ end.to_graphql
27
+
28
+ assert_equal "test", object.fields["test"].arguments["test"].name
29
+ assert_equal "A Description.", object.fields["test"].description
30
30
  end
31
31
  end
32
32
  end
@@ -24,9 +24,9 @@ describe GraphQL::Schema::Object do
24
24
  # inherited interfaces are present
25
25
  assert_equal 2, new_object_class.interfaces.size
26
26
  # The new field is present
27
- assert new_object_class.fields.find { |f| f.name == "newField" }
27
+ assert new_object_class.fields.key?("newField")
28
28
  # The overridden field is present:
29
- name_field = new_object_class.fields.find { |f| f.name == "name" }
29
+ name_field = new_object_class.fields["name"]
30
30
  assert_equal "The new description", name_field.description
31
31
  end
32
32
 
@@ -42,6 +42,23 @@ describe GraphQL::Schema::Object do
42
42
  end
43
43
  end
44
44
 
45
+ describe "wrapping a Hash" do
46
+ it "automatically looks up symbol and string keys" do
47
+ query_str = <<-GRAPHQL
48
+ {
49
+ hashyEnsemble {
50
+ name
51
+ musicians { name }
52
+ }
53
+ }
54
+ GRAPHQL
55
+ res = Jazz::Schema.execute(query_str)
56
+ ensemble = res["data"]["hashyEnsemble"]
57
+ assert_equal "The Grateful Dead", ensemble["name"]
58
+ assert_equal ["Jerry Garcia"], ensemble["musicians"].map { |m| m["name"] }
59
+ end
60
+ end
61
+
45
62
  describe ".to_graphql_type" do
46
63
  let(:obj_type) { Jazz::Ensemble.to_graphql }
47
64
  it "returns a matching GraphQL::ObjectType" do
@@ -17,17 +17,52 @@ describe GraphQL::Upgrader::Member do
17
17
  end
18
18
  end
19
19
 
20
- it 'upgrades the property definition to method' do
21
- old = %{field :name, String, property: :name}
22
- new = %{field :name, String, method: :name, null: true}
20
+ describe "property / method upgrade" do
21
+ it 'upgrades the property definition to method' do
22
+ old = %{field :name, String, property: :full_name}
23
+ new = %{field :name, String, method: :full_name, null: true}
23
24
 
24
- assert_equal new, upgrade(old)
25
+ assert_equal new, upgrade(old)
26
+ end
27
+
28
+ it 'upgrades the property definition in a block to method' do
29
+ old = %{field :name, String do\n property :full_name\nend}
30
+ new = %{field :name, String, method: :full_name, null: true}
31
+ assert_equal new, upgrade(old)
32
+ end
33
+
34
+ it "removes property when redundant" do
35
+ old = %{field :name, String do\n property "name" \nend}
36
+ new = %{field :name, String, null: true}
37
+ assert_equal new, upgrade(old)
38
+
39
+ old = %{field :name, String, property: :name}
40
+ new = %{field :name, String, null: true}
41
+ assert_equal new, upgrade(old)
42
+
43
+ end
25
44
  end
26
45
 
27
- it 'upgrades the property definition in a block to method' do
28
- old = %{field :name, String do\n property :name\nend}
29
- new = %{field :name, String, method: :name, null: true}
30
- assert_equal new, upgrade(old)
46
+ describe "hash_key" do
47
+ it "it moves configuration to kwarg" do
48
+ old = %{field :name, String do\n hash_key :full_name\nend}
49
+ new = %{field :name, String, hash_key: :full_name, null: true}
50
+ assert_equal new, upgrade(old)
51
+ end
52
+
53
+ it "removes it if it's redundant" do
54
+ old = %{field :name, String do\n hash_key :name\nend}
55
+ new = %{field :name, String, null: true}
56
+ assert_equal new, upgrade(old)
57
+
58
+ old = %{field :name, String, hash_key: :name}
59
+ new = %{field :name, String, null: true}
60
+ assert_equal new, upgrade(old)
61
+
62
+ old = %{field :name, String do\n hash_key "name"\nend}
63
+ new = %{field :name, String, null: true}
64
+ assert_equal new, upgrade(old)
65
+ end
31
66
  end
32
67
 
33
68
  describe 'name' do
@@ -224,8 +259,8 @@ describe GraphQL::Upgrader::Member do
224
259
  new = %{field :name, String, null: false}
225
260
  assert_equal new, upgrade(old)
226
261
 
227
- old = %{field :name, !types.String, "description", method: :name}
228
- new = %{field :name, String, "description", method: :name, null: false}
262
+ old = %{field :name, !types.String, "description", method: :name_full}
263
+ new = %{field :name, String, "description", method: :name_full, null: false}
229
264
  assert_equal new, upgrade(old)
230
265
 
231
266
  old = %{field :name, -> { !types.String }}
@@ -347,8 +382,7 @@ describe GraphQL::Upgrader::Member do
347
382
  property: :example_field?
348
383
  }
349
384
  new = %{
350
- field :is_example_field, Boolean, null: true
351
- method: :example_field?
385
+ field :is_example_field, Boolean, method: :example_field?, null: true
352
386
  }
353
387
 
354
388
  assert_equal new, upgrade(old)
@@ -362,8 +396,7 @@ describe GraphQL::Upgrader::Member do
362
396
  property: :example_connections
363
397
  }
364
398
  new = %{
365
- field :example_connection, ExampleConnectionType, null: true, connection: true
366
- method: :example_connections
399
+ field :example_connection, ExampleConnectionType, method: :example_connections, null: true, connection: true
367
400
  }
368
401
 
369
402
  assert_equal new, upgrade(old)
@@ -416,19 +449,26 @@ describe GraphQL::Upgrader::Member do
416
449
  }
417
450
 
418
451
  type_transforms.unshift(ActiveRecordTypeToClassTransform)
419
- upgrader = GraphQL::Upgrader::Member.new(original_text, type_transforms: type_transforms)
452
+ field_transforms = GraphQL::Upgrader::Member::DEFAULT_FIELD_TRANSFORMS
453
+ field_transforms.unshift(GraphQL::Upgrader::ConfigurationToKwargTransform.new(kwarg: "visibility"))
454
+ upgrader = GraphQL::Upgrader::Member.new(original_text, type_transforms: type_transforms, field_transforms: field_transforms)
420
455
  upgrader.upgrade
421
456
  end
422
457
 
423
458
  original_files = Dir.glob("spec/fixtures/upgrader/*.original.rb")
424
459
  original_files.each do |original_file|
425
460
  transformed_file = original_file.sub(".original.", ".transformed.")
461
+ original_text = File.read(original_file)
462
+ expected_text = File.read(transformed_file)
426
463
  it "transforms #{original_file} -> #{transformed_file}" do
427
- original_text = File.read(original_file)
428
- expected_text = File.read(transformed_file)
429
464
  transformed_text = custom_upgrade(original_text)
430
465
  assert_equal(expected_text, transformed_text)
431
466
  end
467
+
468
+ it "is idempotent on #{transformed_file}" do
469
+ retransformed_text = custom_upgrade(expected_text)
470
+ assert_equal(expected_text, retransformed_text)
471
+ end
432
472
  end
433
473
  end
434
474
  end
data/spec/support/jazz.rb CHANGED
@@ -201,13 +201,15 @@ module Jazz
201
201
  description "An object played in order to produce music"
202
202
  end
203
203
  field :favorite_key, Key, null: true
204
- field :inspect_context, [String], null: false
204
+ # Test lists with nullable members:
205
+ field :inspect_context, [String, null: true], null: false
205
206
 
206
207
  def inspect_context
207
208
  [
208
209
  @context.custom_method,
209
210
  @context[:magic_key],
210
- @context[:normal_key]
211
+ @context[:normal_key],
212
+ nil,
211
213
  ]
212
214
  end
213
215
  end
@@ -272,6 +274,7 @@ module Jazz
272
274
  # For asserting that the object is initialized once:
273
275
  field :object_id, Integer, null: false
274
276
  field :inspect_context, [String], null: false
277
+ field :hashyEnsemble, Ensemble, null: false
275
278
 
276
279
  def ensembles
277
280
  Models.data["Ensemble"]
@@ -317,6 +320,17 @@ module Jazz
317
320
  @context[:normal_key]
318
321
  ]
319
322
  end
323
+
324
+ def hashy_ensemble
325
+ # Both string and symbol keys are supported:
326
+
327
+ {
328
+ name: "The Grateful Dead",
329
+ "musicians" => [
330
+ OpenStruct.new(name: "Jerry Garcia"),
331
+ ],
332
+ }
333
+ end
320
334
  end
321
335
 
322
336
  class EnsembleInput < GraphQL::Schema::InputObject
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0.pre3
4
+ version: 1.8.0.pre4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-12 00:00:00.000000000 Z
11
+ date: 2018-01-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips