graphql 1.8.0.pre10 → 1.8.0.pre11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +5 -5
  2. data/lib/generators/graphql/install_generator.rb +14 -8
  3. data/lib/graphql.rb +1 -24
  4. data/lib/graphql/backtrace.rb +1 -1
  5. data/lib/graphql/deprecated_dsl.rb +2 -2
  6. data/lib/graphql/execution/execute.rb +6 -0
  7. data/lib/graphql/execution/lazy/lazy_method_map.rb +1 -1
  8. data/lib/graphql/introspection/base_object.rb +1 -0
  9. data/lib/graphql/language/document_from_schema_definition.rb +4 -1
  10. data/lib/graphql/language/nodes.rb +5 -2
  11. data/lib/graphql/language/parser.rb +288 -288
  12. data/lib/graphql/language/parser.y +1 -1
  13. data/lib/graphql/language/printer.rb +12 -2
  14. data/lib/graphql/non_null_type.rb +1 -1
  15. data/lib/graphql/query.rb +1 -1
  16. data/lib/graphql/query/arguments.rb +1 -1
  17. data/lib/graphql/query/context.rb +2 -2
  18. data/lib/graphql/query/null_context.rb +1 -1
  19. data/lib/graphql/query/result.rb +1 -1
  20. data/lib/graphql/query/variables.rb +21 -3
  21. data/lib/graphql/relay.rb +1 -0
  22. data/lib/graphql/relay/mongo_relation_connection.rb +40 -0
  23. data/lib/graphql/scalar_type.rb +14 -2
  24. data/lib/graphql/schema.rb +17 -1
  25. data/lib/graphql/schema/argument.rb +39 -7
  26. data/lib/graphql/schema/enum.rb +7 -0
  27. data/lib/graphql/schema/field.rb +145 -39
  28. data/lib/graphql/schema/finder.rb +4 -4
  29. data/lib/graphql/schema/input_object.rb +13 -2
  30. data/lib/graphql/schema/interface.rb +50 -16
  31. data/lib/graphql/schema/list.rb +28 -0
  32. data/lib/graphql/schema/member.rb +8 -106
  33. data/lib/graphql/schema/member/accepts_definition.rb +58 -24
  34. data/lib/graphql/schema/member/base_dsl_methods.rb +96 -0
  35. data/lib/graphql/schema/member/build_type.rb +15 -9
  36. data/lib/graphql/schema/member/cached_graphql_definition.rb +26 -0
  37. data/lib/graphql/schema/member/graphql_type_names.rb +21 -0
  38. data/lib/graphql/schema/member/has_arguments.rb +1 -1
  39. data/lib/graphql/schema/member/has_fields.rb +91 -8
  40. data/lib/graphql/schema/member/type_system_helpers.rb +34 -0
  41. data/lib/graphql/schema/middleware_chain.rb +5 -1
  42. data/lib/graphql/schema/mutation.rb +24 -12
  43. data/lib/graphql/schema/non_null.rb +34 -0
  44. data/lib/graphql/schema/object.rb +24 -11
  45. data/lib/graphql/schema/relay_classic_mutation.rb +14 -11
  46. data/lib/graphql/schema/rescue_middleware.rb +8 -7
  47. data/lib/graphql/schema/scalar.rb +9 -2
  48. data/lib/graphql/schema/union.rb +4 -0
  49. data/lib/graphql/static_validation/definition_dependencies.rb +1 -1
  50. data/lib/graphql/static_validation/literal_validator.rb +16 -4
  51. data/lib/graphql/static_validation/validation_context.rb +1 -1
  52. data/lib/graphql/subscriptions.rb +90 -16
  53. data/lib/graphql/upgrader/member.rb +27 -89
  54. data/lib/graphql/version.rb +1 -1
  55. data/spec/dummy/app/channels/graphql_channel.rb +1 -1
  56. data/spec/dummy/log/test.log +206 -0
  57. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/-x/-xYZjAnuuzgR79fcznLTQtSdh6AARxu8FcQ_J6p7L3U.cache +0 -0
  58. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/13/13HiV12xyoQvT-1L39ZzLwMZxjyaGMiENmfw7f-QTIc.cache +0 -0
  59. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/3W/3Wtf5pCWdqq0AB-iB0Y9uUNrTkruRxIEf1XFn_BETU0.cache +1 -0
  60. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/5i/5iguGafb4hOn8262Kn8Q37ogNN9MxxQKGKNzHAzUcvI.cache +1 -0
  61. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/8m/8mj2T6yy847Mc2Z7k3Xzh8O91hhVJt3NrPe8ASNDlIA.cache +1 -0
  62. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/DT/DTQyMpr4ABZYQetsdRJ5A7S4jf1r3ie4FGOR7GZBNSs.cache +3 -0
  63. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Dq/DqJ5_yJPrP5iLlOQyTQsjAVI5FE5LCVDkED0f7GgsSo.cache +3 -0
  64. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/F8/F8MUNRzORGFgr329fNM0xLaoWCXdv3BIalT7dsvLfjs.cache +0 -0
  65. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/KB/KB07ZaKNC5uXJ7TjLi-WqnY6g7dq8wWp_8N3HNjBNxg.cache +0 -0
  66. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Rw/RwDuCV-XpnCtjNkvhpJfBuxXMk0b5AD3L9eR6M-wcy0.cache +3 -0
  67. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/UL/ULdjhhb0bRuqmaG7XSZlFYzGYCXTDnqZuJBTWRlzqgw.cache +0 -0
  68. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Up/UpPNgh0yYoUsyMDh5zWqe_U6qJIyTC6-dxMMAs1vvlM.cache +1 -0
  69. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Wg/Wguh-szFGTI1gaL6npYwPekMXflugRei7F_mOyRucXg.cache +0 -0
  70. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/X-/X-khLYMA9mqFRPg3zAi86mREDxpKl4bdKYp3uF6WHos.cache +0 -0
  71. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/bi/BIkdhfxsezxM4q-HZ4oCNTq97WEJTigcq0tpX2cDvbY.cache +0 -0
  72. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/ff/FfxmA4CMHQZT7exx0G7NS1Wpcnny0vzp-Jhc2H36bp8.cache +1 -0
  73. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/gE/gEiiG4GZNy_djEjK2pHm_NgA-gyhLZhdQvo0Yt96GqE.cache +0 -0
  74. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/gn/gnA9ZSqpjccNL2m8pe_jBvY6SinXlCzXDWyop83Od8s.cache +1 -0
  75. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/lO/lOAan3cMwCE_Hli6gsDML88xFNfn0nxPmvrSkW7eEOw.cache +1 -0
  76. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/m1/M1pv8MJEPLXGLvS8QxVh3DSO9cI4mRt5FHFWdrvUj6o.cache +2 -0
  77. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/m7/m77qH7ZqH0_0SmwJbiKGDd-aLau1Dav847DC6ge46zY.cache +1 -0
  78. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/sj/sjRjnjRB37lH2vrgtkdJ8Cz84__IJ978IuKTM7HcztI.cache +0 -0
  79. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/um/um1JrirR4hJhK-1rE-HywlyCi5ibgxHVrReiujZBWJM.cache +1 -0
  80. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/v4/v4fwVytD7ITcE0_GDbslZEYud8a5Okm85fV1o7SDl6g.cache +0 -0
  81. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/v_/v_0PAQt0iipQjFP5zjgkkk9Stnpf4VzvnMv67d1Keuw.cache +1 -0
  82. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/wd/wdT9U4MKxe1PyqNjVuCKMpCl3dxGCIRJIlwUTfh2DQU.cache +1 -0
  83. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/xI/xIaxut_fEIhKBDqljTNwYaADK9kj3gG0ESrfHs-5_og.cache +3 -0
  84. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/y0/y0SJOqIx2fn1SKqOkAihsQow0trRJrSIyAswufVuoA8.cache +0 -0
  85. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/zg/zgpzeaX-KZErHyGJ1aBH3ZusweNXMneVZule88XsIJI.cache +1 -0
  86. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/zy/zYFltDy-8VC-uKq2BVEiJJyYXNFvVzAKuMlR3ZIYZsk.cache +0 -0
  87. data/spec/dummy/tmp/screenshots/failures_test_it_handles_subscriptions.png +0 -0
  88. data/spec/fixtures/upgrader/photo.original.rb +10 -0
  89. data/spec/fixtures/upgrader/photo.transformed.rb +12 -0
  90. data/spec/fixtures/upgrader/starrable.original.rb +4 -1
  91. data/spec/fixtures/upgrader/starrable.transformed.rb +25 -22
  92. data/spec/fixtures/upgrader/subscribable.transformed.rb +32 -33
  93. data/spec/graphql/execution_error_spec.rb +18 -0
  94. data/spec/graphql/introspection/schema_type_spec.rb +1 -0
  95. data/spec/graphql/object_type_spec.rb +2 -1
  96. data/spec/graphql/query/variables_spec.rb +41 -0
  97. data/spec/graphql/relay/mongo_relation_connection_spec.rb +474 -0
  98. data/spec/graphql/schema/argument_spec.rb +65 -9
  99. data/spec/graphql/schema/build_from_definition_spec.rb +4 -2
  100. data/spec/graphql/schema/enum_spec.rb +1 -1
  101. data/spec/graphql/schema/field_spec.rb +79 -4
  102. data/spec/graphql/schema/input_object_spec.rb +56 -0
  103. data/spec/graphql/schema/instrumentation_spec.rb +4 -3
  104. data/spec/graphql/schema/interface_spec.rb +40 -25
  105. data/spec/graphql/schema/member/accepts_definition_spec.rb +38 -11
  106. data/spec/graphql/schema/member/has_fields_spec.rb +129 -0
  107. data/spec/graphql/schema/member/type_system_helpers_spec.rb +63 -0
  108. data/spec/graphql/schema/mutation_spec.rb +48 -0
  109. data/spec/graphql/schema/object_spec.rb +34 -8
  110. data/spec/graphql/schema/relay_classic_mutation_spec.rb +10 -0
  111. data/spec/graphql/schema/rescue_middleware_spec.rb +11 -0
  112. data/spec/graphql/schema/scalar_spec.rb +55 -0
  113. data/spec/graphql/subscriptions_spec.rb +31 -4
  114. data/spec/graphql/upgrader/member_spec.rb +14 -6
  115. data/spec/spec_helper.rb +7 -0
  116. data/spec/support/dummy/schema.rb +8 -0
  117. data/spec/support/jazz.rb +89 -19
  118. data/spec/support/star_trek/data.rb +109 -0
  119. data/spec/support/star_trek/schema.rb +388 -0
  120. metadata +80 -7
  121. data/lib/graphql/schema/field/dynamic_resolve.rb +0 -70
  122. data/lib/graphql/schema/field/unwrapped_resolve.rb +0 -20
  123. data/lib/graphql/schema/member/list_type_proxy.rb +0 -25
  124. data/lib/graphql/schema/member/non_null_type_proxy.rb +0 -25
@@ -23,7 +23,21 @@ module GraphQL
23
23
  # The payload should always include this field
24
24
  field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.", null: true)
25
25
 
26
+
27
+ # Override {GraphQL::Schema::Mutation#resolve_mutation} to
28
+ # delete `client_mutation_id` from the kwargs.
29
+ def resolve_mutation(kwargs)
30
+ # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are.
31
+ kwargs.delete(:client_mutation_id)
32
+ super
33
+ end
34
+
26
35
  class << self
36
+ def inherited(base)
37
+ base.null(true)
38
+ super
39
+ end
40
+
27
41
  # The base class for generated input object types
28
42
  # @param new_class [Class] The base class to use for generating input object definitions
29
43
  # @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject})
@@ -68,17 +82,6 @@ module GraphQL
68
82
  argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
69
83
  end
70
84
  end
71
-
72
- # Override {GraphQL::Schema::Mutation.resolve_field} to
73
- # delete `client_mutation_id` from the kwargs.
74
- def resolve_field(obj, args, ctx)
75
- mutation = self.new(object: obj, arguments: args, context: ctx.query.context)
76
- kwargs = args.to_kwargs
77
- # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are.
78
- kwargs.delete(:client_mutation_id)
79
- extras.each { |e| kwargs[e] = ctx.public_send(e) }
80
- mutation.resolve(**kwargs)
81
- end
82
85
  end
83
86
  end
84
87
  end
@@ -41,13 +41,14 @@ module GraphQL
41
41
  private
42
42
 
43
43
  def attempt_rescue(err)
44
- handler = rescue_table[err.class]
45
- if handler
46
- message = handler.call(err)
47
- GraphQL::ExecutionError.new(message)
48
- else
49
- raise(err)
50
- end
44
+ rescue_table.each { |klass, handler|
45
+ if klass.is_a?(Class) && err.is_a?(klass) && handler
46
+ message = handler.call(err)
47
+ return GraphQL::ExecutionError.new(message)
48
+ end
49
+ }
50
+
51
+ raise(err)
51
52
  end
52
53
  end
53
54
  end
@@ -5,12 +5,15 @@ module GraphQL
5
5
  extend GraphQL::Schema::Member::AcceptsDefinition
6
6
 
7
7
  class << self
8
+ extend Forwardable
9
+ def_delegators :graphql_definition, :coerce_isolated_input, :coerce_isolated_result
10
+
8
11
  def coerce_input(val, ctx)
9
- raise NotImplementedError, "#{self.name}.coerce_input(val, ctx) must prepare GraphQL input (#{val.inspect}) for Ruby processing"
12
+ val
10
13
  end
11
14
 
12
15
  def coerce_result(val, ctx)
13
- raise NotImplementedError, "#{self.name}.coerce_result(val, ctx) must prepare Ruby value (#{val.inspect}) for GraphQL response"
16
+ val
14
17
  end
15
18
 
16
19
  def to_graphql
@@ -22,6 +25,10 @@ module GraphQL
22
25
  type_defn.metadata[:type_class] = self
23
26
  type_defn
24
27
  end
28
+
29
+ def kind
30
+ GraphQL::TypeKinds::SCALAR
31
+ end
25
32
  end
26
33
  end
27
34
  end
@@ -26,6 +26,10 @@ module GraphQL
26
26
  type_defn.metadata[:type_class] = self
27
27
  type_defn
28
28
  end
29
+
30
+ def kind
31
+ GraphQL::TypeKinds::UNION
32
+ end
29
33
  end
30
34
  end
31
35
  end
@@ -103,7 +103,7 @@ module GraphQL
103
103
  end
104
104
 
105
105
  class NodeWithPath
106
- extend GraphQL::Delegate
106
+ extend Forwardable
107
107
  attr_reader :node, :path
108
108
  def initialize(node, path)
109
109
  @node = node
@@ -16,23 +16,35 @@ module GraphQL
16
16
  elsif type.kind.list?
17
17
  item_type = type.of_type
18
18
  ensure_array(ast_value).all? { |val| validate(val, item_type) }
19
- elsif type.kind.scalar? && !ast_value.is_a?(GraphQL::Language::Nodes::AbstractNode) && !ast_value.is_a?(Array)
19
+ elsif ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
20
+ true
21
+ elsif type.kind.scalar? && constant_scalar?(ast_value)
20
22
  type.valid_input?(ast_value, @context)
21
23
  elsif type.kind.enum? && ast_value.is_a?(GraphQL::Language::Nodes::Enum)
22
24
  type.valid_input?(ast_value.name, @context)
23
25
  elsif type.kind.input_object? && ast_value.is_a?(GraphQL::Language::Nodes::InputObject)
24
26
  required_input_fields_are_present(type, ast_value) &&
25
27
  present_input_field_values_are_valid(type, ast_value)
26
- elsif ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
27
- true
28
28
  else
29
29
  false
30
30
  end
31
31
  end
32
32
 
33
-
34
33
  private
35
34
 
35
+ # The GraphQL grammar supports variables embedded within scalars but graphql.js
36
+ # doesn't support it so we won't either for simplicity
37
+ def constant_scalar?(ast_value)
38
+ if ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
39
+ false
40
+ elsif ast_value.is_a?(Array)
41
+ ast_value.all? { |element| constant_scalar?(element) }
42
+ elsif ast_value.is_a?(GraphQL::Language::Nodes::InputObject)
43
+ ast_value.arguments.all? { |arg| constant_scalar?(arg.value) }
44
+ else
45
+ true
46
+ end
47
+ end
36
48
 
37
49
  def required_input_fields_are_present(type, ast_node)
38
50
  required_field_names = @warden.arguments(type)
@@ -12,7 +12,7 @@ module GraphQL
12
12
  # It also provides limited access to the {TypeStack} instance,
13
13
  # which tracks state as you climb in and out of different fields.
14
14
  class ValidationContext
15
- extend GraphQL::Delegate
15
+ extend Forwardable
16
16
 
17
17
  attr_reader :query, :schema,
18
18
  :document, :errors, :visitor,
@@ -9,6 +9,13 @@ end
9
9
 
10
10
  module GraphQL
11
11
  class Subscriptions
12
+ # Raised when either:
13
+ # - the triggered `event_name` doesn't match a field in the schema; or
14
+ # - one or more arguments don't match the field arguments
15
+ class InvalidTriggerError < GraphQL::Error
16
+ end
17
+
18
+ # @see {Subscriptions#initialize} for options, concrete implementations may add options.
12
19
  def self.use(defn, options = {})
13
20
  schema = defn.target
14
21
  options[:schema] = schema
@@ -19,8 +26,9 @@ module GraphQL
19
26
  nil
20
27
  end
21
28
 
22
- def initialize(kwargs)
23
- @schema = kwargs[:schema]
29
+ # @param schema [Class] the GraphQL schema this manager belongs to
30
+ def initialize(schema:, **rest)
31
+ @schema = schema
24
32
  end
25
33
 
26
34
  # Fetch subscriptions matching this field + arguments pair
@@ -31,31 +39,35 @@ module GraphQL
31
39
  # @param scope [Symbol, String]
32
40
  # @return [void]
33
41
  def trigger(event_name, args, object, scope: nil)
42
+ event_name = event_name.to_s
43
+
44
+ # Try with the verbatim input first:
34
45
  field = @schema.get_field("Subscription", event_name)
35
- if !field
36
- raise "No subscription matching trigger: #{event_name}"
37
- end
38
46
 
39
- # Normalize symbol-keyed args to strings
40
- if args.any?
41
- stringified_args = {}
42
- args.each { |k, v| stringified_args[k.to_s] = v }
43
- args = stringified_args
47
+ if field.nil?
48
+ # And if it wasn't found, normalize it:
49
+ normalized_event_name = normalize_name(event_name)
50
+ field = @schema.get_field("Subscription", normalized_event_name)
51
+ if field.nil?
52
+ raise InvalidTriggerError, "No subscription matching trigger: #{event_name} (looked for #{@schema.subscription.graphql_name}.#{normalized_event_name})"
53
+ end
54
+ else
55
+ # Since we found a field, the original input was already normalized
56
+ normalized_event_name = event_name
44
57
  end
45
58
 
59
+ # Normalize symbol-keyed args to strings, try camelizing them
60
+ normalized_args = normalize_arguments(normalized_event_name, field, args)
61
+
46
62
  event = Subscriptions::Event.new(
47
- name: event_name,
48
- arguments: args,
63
+ name: normalized_event_name,
64
+ arguments: normalized_args,
49
65
  field: field,
50
66
  scope: scope,
51
67
  )
52
68
  execute_all(event, object)
53
69
  end
54
70
 
55
- def initialize(schema:, **rest)
56
- @schema = schema
57
- end
58
-
59
71
  # `event` was triggered on `object`, and `subscription_id` was subscribed,
60
72
  # so it should be updated.
61
73
  #
@@ -139,5 +151,67 @@ module GraphQL
139
151
  def build_id
140
152
  SecureRandom.uuid
141
153
  end
154
+
155
+ # Convert a user-provided event name or argument
156
+ # to the equivalent in GraphQL.
157
+ #
158
+ # By default, it converts the identifier to camelcase.
159
+ # Override this in a subclass to change the transformation.
160
+ #
161
+ # @param event_or_arg_name [String, Symbol]
162
+ # @return [String]
163
+ def normalize_name(event_or_arg_name)
164
+ Schema::Member::BuildType.camelize(event_or_arg_name.to_s)
165
+ end
166
+
167
+ private
168
+
169
+ # Recursively normalize `args` as belonging to `arg_owner`:
170
+ # - convert symbols to strings,
171
+ # - if needed, camelize the string (using {#normalize_name})
172
+ # @param arg_owner [GraphQL::Field, GraphQL::BaseType]
173
+ # @param args [Hash, Array, Any] some GraphQL input value to coerce as `arg_owner`
174
+ # @return [Any] normalized arguments value
175
+ def normalize_arguments(event_name, arg_owner, args)
176
+ case arg_owner
177
+ when GraphQL::Field, GraphQL::InputObjectType
178
+ normalized_args = {}
179
+ missing_arg_names = []
180
+ args.each do |k, v|
181
+ arg_name = k.to_s
182
+ arg_defn = arg_owner.arguments[arg_name]
183
+ if arg_defn
184
+ normalized_arg_name = arg_name
185
+ else
186
+ normalized_arg_name = normalize_name(arg_name)
187
+ arg_defn = arg_owner.arguments[normalized_arg_name]
188
+ end
189
+
190
+ if arg_defn
191
+ normalized_args[normalized_arg_name] = normalize_arguments(event_name, arg_defn.type, v)
192
+ else
193
+ # Couldn't find a matching argument definition
194
+ missing_arg_names << arg_name
195
+ end
196
+ end
197
+
198
+ if missing_arg_names.any?
199
+ arg_owner_name = if arg_owner.is_a?(GraphQL::Field)
200
+ "Subscription.#{arg_owner.name}"
201
+ else
202
+ arg_owner.to_s
203
+ end
204
+ raise InvalidTriggerError, "Can't trigger Subscription.#{event_name}, received undefined arguments: #{missing_arg_names.join(", ")}. (Should match arguments of #{arg_owner_name}.)"
205
+ end
206
+
207
+ normalized_args
208
+ when GraphQL::ListType
209
+ args.map { |a| normalize_arguments(event_name, arg_owner.of_type, a) }
210
+ when GraphQL::NonNullType
211
+ normalize_arguments(event_name, arg_owner.of_type, args)
212
+ else
213
+ args
214
+ end
215
+ end
142
216
  end
143
217
  end
@@ -97,13 +97,18 @@ module GraphQL
97
97
  # Turns `{X} = GraphQL::{Y}Type.define do` into `class {X} < Types::Base{Y}`.
98
98
  class TypeDefineToClassTransform < Transform
99
99
  # @param base_class_pattern [String] Replacement pattern for the base class name. Use this if your base classes have nonstandard names.
100
- def initialize(base_class_pattern: "Types::Base\\2")
101
- @find_pattern = /([a-zA-Z_0-9:]*) = GraphQL::#{GRAPHQL_TYPES}Type\.define do/
102
- @replace_pattern = "class \\1 < #{base_class_pattern}"
100
+ def initialize(base_class_pattern: "Types::Base\\3")
101
+ @find_pattern = /( *)([a-zA-Z_0-9:]*) = GraphQL::#{GRAPHQL_TYPES}Type\.define do/
102
+ @replace_pattern = "\\1class \\2 < #{base_class_pattern}"
103
+ @interface_replace_pattern = "\\1module \\2\n\\1 include #{base_class_pattern}"
103
104
  end
104
105
 
105
106
  def apply(input_text)
106
- input_text.sub(@find_pattern, @replace_pattern)
107
+ if input_text.include?("GraphQL::InterfaceType.define")
108
+ input_text.sub(@find_pattern, @interface_replace_pattern)
109
+ else
110
+ input_text.sub(@find_pattern, @replace_pattern)
111
+ end
107
112
  end
108
113
  end
109
114
 
@@ -124,7 +129,12 @@ module GraphQL
124
129
  # Or, if it is not redundant, move it to `graphql_name "Something"`.
125
130
  class NameTransform < Transform
126
131
  def apply(transformable)
127
- if (matches = transformable.match(/class (?<type_name>[a-zA-Z_0-9:]*) </))
132
+ last_type_defn = transformable
133
+ .split("\n")
134
+ .select { |line| line.include?("class ") || line.include?("module ")}
135
+ .last
136
+
137
+ if last_type_defn && (matches = last_type_defn.match(/(class|module) (?<type_name>[a-zA-Z_0-9:]*)( <|$)/))
128
138
  type_name = matches[:type_name]
129
139
  # Get the name without any prefixes or suffixes
130
140
  type_name_without_the_type_part = type_name.split('::').last.gsub(/Type$/, '')
@@ -151,7 +161,8 @@ module GraphQL
151
161
  keep_looking = true
152
162
  while keep_looking do
153
163
  keep_looking = false
154
- input_text = input_text.gsub(/(?<field>(?:field|input_field|return_field|connection|argument)(?:\(.*|.*,))\n\s*(?<next_line>.+)/) do
164
+ # Find the `field` call (or other method), and an open paren, but not a close paren, or a comma between arguments
165
+ input_text = input_text.gsub(/(?<field>(?:field|input_field|return_field|connection|argument)(?:\([^)]*|.*,))\n\s*(?<next_line>.+)/) do
155
166
  keep_looking = true
156
167
  field = $~[:field].chomp
157
168
  next_line = $~[:next_line]
@@ -167,8 +178,8 @@ module GraphQL
167
178
  class RemoveMethodParensTransform < Transform
168
179
  def apply(input_text)
169
180
  input_text.sub(
170
- /(field|input_field|return_field|connection|argument)\( *(.*?) *\) */,
171
- '\1 \2'
181
+ /(field|input_field|return_field|connection|argument)\( *(.*?) *\)( *)/,
182
+ '\1 \2\3'
172
183
  )
173
184
  end
174
185
  end
@@ -475,7 +486,7 @@ module GraphQL
475
486
 
476
487
  class ResolveProcToMethodTransform < Transform
477
488
  def apply(input_text)
478
- if input_text =~ /resolve ->/
489
+ if input_text =~ /resolve\(? ?->/
479
490
  # - Find the proc literal
480
491
  # - Get the three argument names (obj, arg, ctx)
481
492
  # - Get the proc body
@@ -572,7 +583,8 @@ module GraphQL
572
583
  def on_block(node)
573
584
  send_node, args_node, body_node = node.children
574
585
  _receiver, method_name, _send_args_node = *send_node
575
- if method_name == :lambda
586
+ # Assume that the first three-argument proc we enter is the resolve
587
+ if method_name == :lambda && args_node.children.size == 3 && @proc_arg_names.nil?
576
588
  source_exp = body_node.loc.expression
577
589
  @proc_arg_names = args_node.children.map { |arg_node| arg_node.children[0].to_s }
578
590
  @proc_start = source_exp.begin.begin_pos
@@ -705,81 +717,6 @@ module GraphQL
705
717
  end
706
718
  end
707
719
 
708
- class MoveInterfaceMethodsToImplementationTransform < Transform
709
- def initialize(interface_file_pattern: /<.*Interface/)
710
- @interface_file_pattern = interface_file_pattern
711
- end
712
-
713
- def apply(input_text)
714
- # See if it its an interface file with any instance methods
715
- if input_text =~ @interface_file_pattern && input_text =~ /\n *def (?!(self|[A-Z]))/
716
- # Extract the method bodies and figure out where the module should be inserted
717
- method_bodies = []
718
- processor = apply_processor(input_text, InterfaceMethodProcessor.new)
719
- processor.methods.each do |(begin_pos, end_pos)|
720
- # go all the way back to the newline so we get whitespace too
721
- while input_text[begin_pos] != "\n" && begin_pos >= 0
722
- begin_pos -= 1
723
- end
724
- # Tuck away the method body, and remove it from here
725
- method_body = input_text[begin_pos..end_pos]
726
- method_bodies << method_body
727
- end
728
- # How far are these method bodies indented? The module will be this indented
729
- leading_indent = method_bodies.first[/\n +/][1..-1]
730
- # Increase the indent since it will be in a nested module
731
- indented_method_bodies = method_bodies.map {|m| m.gsub("\n", "\n ").rstrip }
732
- # Build the ruby module definition
733
- module_body = "\n\n#{leading_indent}module Implementation#{indented_method_bodies.join("\n")}\n#{leading_indent}end"
734
-
735
- # find the `end` of the class definition, and put the module before it
736
- _class_start, class_end = processor.class_definition
737
- # This might target the newline _after_ `end`, but we don't want that one
738
- if input_text[class_end] == "\n"
739
- class_end -= 1
740
- end
741
-
742
- while input_text[class_end] != "\n" && class_end > 0
743
- class_end -= 1
744
- end
745
-
746
- input_text.insert(class_end, module_body)
747
-
748
- # Do the replacement _after_ identifying the bodies,
749
- # otherwise the offsets get messed up
750
- method_bodies.each do |method_body|
751
- input_text.sub!(method_body, "")
752
- end
753
- end
754
- input_text
755
- end
756
-
757
- # Find the beginning and end of each method def,
758
- # so that we can move it wholesale
759
- class InterfaceMethodProcessor < Parser::AST::Processor
760
- attr_reader :methods, :class_definition
761
- def initialize
762
- @class_definition = nil
763
- @methods = []
764
- super
765
- end
766
-
767
- def on_def(node)
768
- start = node.loc.expression.begin_pos
769
- finish = node.loc.expression.end_pos
770
- @methods << [start, finish]
771
- super(node)
772
- end
773
-
774
- def on_class(node)
775
- @class_definition = [
776
- node.loc.expression.begin_pos,
777
- node.loc.expression.end_pos
778
- ]
779
- super(node)
780
- end
781
- end
782
- end
783
720
  # Skip this file if you see any `field`
784
721
  # helpers with `null: true` or `null: false` keywords
785
722
  # or `argument` helpers with `required:` keywords,
@@ -829,7 +766,6 @@ module GraphQL
829
766
  ]
830
767
 
831
768
  DEFAULT_CLEAN_UP_TRANSFORMS = [
832
- MoveInterfaceMethodsToImplementationTransform,
833
769
  RemoveExcessWhitespaceTransform,
834
770
  RemoveEmptyBlocksTransform,
835
771
  ]
@@ -892,8 +828,8 @@ module GraphQL
892
828
  # For each of the locations we found, extract the text for that definition.
893
829
  # The text will be transformed independently,
894
830
  # then the transformed text will replace the original text.
895
- finder.locations.each do |category, locs|
896
- locs.each do |name, (starting_idx, ending_idx)|
831
+ FieldFinder::DEFINITION_METHODS.each do |def_method|
832
+ finder.locations[def_method].each do |name, (starting_idx, ending_idx)|
897
833
  field_source = type_source[starting_idx..ending_idx]
898
834
  field_sources << field_source
899
835
  end
@@ -913,7 +849,9 @@ module GraphQL
913
849
 
914
850
  class FieldFinder < Parser::AST::Processor
915
851
  # These methods are definition DSLs which may accept a block,
916
- # each of these definitions is passed for transformation in its own right
852
+ # each of these definitions is passed for transformation in its own right.
853
+ # `field` and `connection` take priority. In fact, they upgrade their
854
+ # own arguments, so those upgrades turn out to be no-ops.
917
855
  DEFINITION_METHODS = [:field, :connection, :input_field, :return_field, :argument]
918
856
  attr_reader :locations
919
857