graphql 0.18.14 → 0.18.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/query_complexity.rb +15 -6
  3. data/lib/graphql/analysis/query_depth.rb +11 -10
  4. data/lib/graphql/directive.rb +2 -6
  5. data/lib/graphql/directive/include_directive.rb +0 -4
  6. data/lib/graphql/directive/skip_directive.rb +0 -4
  7. data/lib/graphql/execution/directive_checks.rb +22 -13
  8. data/lib/graphql/execution_error.rb +7 -0
  9. data/lib/graphql/internal_representation/node.rb +20 -2
  10. data/lib/graphql/internal_representation/rewrite.rb +66 -20
  11. data/lib/graphql/language/generation.rb +22 -8
  12. data/lib/graphql/language/nodes.rb +48 -20
  13. data/lib/graphql/language/parser.rb +436 -423
  14. data/lib/graphql/language/parser.y +22 -19
  15. data/lib/graphql/language/parser_tests.rb +131 -2
  16. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -0
  17. data/lib/graphql/query/serial_execution/selection_resolution.rb +1 -1
  18. data/lib/graphql/query/serial_execution/value_resolution.rb +4 -1
  19. data/lib/graphql/schema/printer.rb +1 -1
  20. data/lib/graphql/static_validation/message.rb +1 -1
  21. data/lib/graphql/version.rb +1 -1
  22. data/readme.md +10 -12
  23. data/spec/graphql/directive_spec.rb +139 -1
  24. data/spec/graphql/execution_error_spec.rb +63 -3
  25. data/spec/graphql/introspection/type_type_spec.rb +2 -0
  26. data/spec/graphql/language/generation_spec.rb +55 -7
  27. data/spec/graphql/query/executor_spec.rb +4 -2
  28. data/spec/graphql/schema/catchall_middleware_spec.rb +1 -0
  29. data/spec/graphql/schema/printer_spec.rb +1 -1
  30. data/spec/graphql/schema/timeout_middleware_spec.rb +10 -5
  31. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +6 -6
  32. data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +4 -4
  33. data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +2 -2
  34. data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +2 -2
  35. data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +3 -3
  36. data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +2 -2
  37. data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +3 -3
  38. data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +2 -2
  39. data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +2 -2
  40. data/spec/graphql/static_validation/rules/fragments_are_named_spec.rb +1 -1
  41. data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +3 -3
  42. data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +2 -2
  43. data/spec/graphql/static_validation/rules/mutation_root_exists_spec.rb +1 -1
  44. data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +3 -3
  45. data/spec/graphql/static_validation/rules/subscription_root_exists_spec.rb +1 -1
  46. data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +4 -4
  47. data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +4 -4
  48. data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +3 -3
  49. data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +3 -3
  50. data/spec/graphql/static_validation/validator_spec.rb +1 -1
  51. data/spec/support/dairy_app.rb +8 -0
  52. metadata +30 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d8692b90c4aa941fe5a1139719790eb1ba55c92e
4
- data.tar.gz: 84c924ce93a593f26a7419f9ed69e5220380201c
3
+ metadata.gz: ca81d77891fe081da31667ba3d1a8676ff489ff5
4
+ data.tar.gz: e2b4a12d439ce76943470264e60d6007ae7f61fb
5
5
  SHA512:
6
- metadata.gz: 26a2968e15530d07b78b293f41bc103fdc3026036de39fd84e0f48434659d23558d806e67b7d07288cd5eb9b6f08cfbb06e76d809b865d35c70355cd7ad51ae7
7
- data.tar.gz: 9c3401de8f427e468db33e2403b729ec5ccc291b53e95d8d853ef86e28744cfec291c5df778f34c8497149d82f4aa3054ddb0ff8059516e973f0fbc2441e999d
6
+ metadata.gz: d18944b50b350786c710bc9e9ca3f9e463ccc8a6d835fa3b24e2442120e4b41280828ec59ad4e916b5aa3bea5f0566b9fb9bb6a462eaa75dcc7397d9099f5892
7
+ data.tar.gz: 78b22a36ffbed73b1b512c8b351cc55f99a8484139ac71fd1f8f4dc2a9f7682b68c681e5a16d8b7776500b24cd721ac576519566dae87c0bb94c97536651eca6
@@ -18,10 +18,13 @@ module GraphQL
18
18
  # State for the query complexity calcuation:
19
19
  # - `query` is needed for variables, then passed to handler
20
20
  # - `complexities_on_type` holds complexity scores for each type in an IRep node
21
+ # - `skip_depth` increments for each skipped node, then decrements on the way out.
22
+ # While it's greater than `0`, we're visiting a skipped part of the query.
21
23
  def initial_value(query)
22
24
  {
23
25
  query: query,
24
26
  complexities_on_type: [TypeComplexity.new],
27
+ skip_depth: 0,
25
28
  }
26
29
  end
27
30
 
@@ -29,16 +32,22 @@ module GraphQL
29
32
  def call(memo, visit_type, irep_node)
30
33
  if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field)
31
34
  if visit_type == :enter
32
- memo[:complexities_on_type].push(TypeComplexity.new)
35
+ if irep_node.skipped?
36
+ memo[:skip_depth] += 1
37
+ elsif memo[:skip_depth] == 0
38
+ memo[:complexities_on_type].push(TypeComplexity.new)
39
+ end
33
40
  else
34
- type_complexities = memo[:complexities_on_type].pop
35
- own_complexity = if GraphQL::Execution::DirectiveChecks.skip?(irep_node, memo[:query])
36
- 0
41
+ if memo[:skip_depth] > 0
42
+ if irep_node.skipped?
43
+ memo[:skip_depth] -= 1
44
+ end
37
45
  else
46
+ type_complexities = memo[:complexities_on_type].pop
38
47
  child_complexity = type_complexities.max_possible_complexity
39
- get_complexity(irep_node, memo[:query], child_complexity)
48
+ own_complexity = get_complexity(irep_node, memo[:query], child_complexity)
49
+ memo[:complexities_on_type].last.merge(irep_node.definitions, own_complexity)
40
50
  end
41
- memo[:complexities_on_type].last.merge(irep_node.definitions, own_complexity)
42
51
  end
43
52
  end
44
53
  memo
@@ -16,26 +16,27 @@ module GraphQL
16
16
  {
17
17
  max_depth: 0,
18
18
  current_depth: 0,
19
- skip_current_scope: false,
19
+ skip_depth: 0,
20
20
  query: query,
21
21
  }
22
22
  end
23
23
 
24
24
  def call(memo, visit_type, irep_node)
25
25
  if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field)
26
+ # Don't validate introspection fields or skipped nodes
27
+ not_validated_node = GraphQL::Schema::DYNAMIC_FIELDS.include?(irep_node.definition_name) || !irep_node.included?
26
28
  if visit_type == :enter
27
- if GraphQL::Schema::DYNAMIC_FIELDS.include?(irep_node.definition_name)
28
- # Don't validate introspection fields
29
- memo[:skip_current_scope] = true
30
- elsif memo[:skip_current_scope]
31
- # we're inside an introspection query
32
- elsif GraphQL::Execution::DirectiveChecks.include?(irep_node, memo[:query])
29
+ if not_validated_node
30
+ memo[:skip_depth] += 1
31
+ elsif memo[:skip_depth] > 0
32
+ # we're inside an introspection query or skipped node
33
+ else
33
34
  memo[:current_depth] += 1
34
35
  end
35
36
  else
36
- if GraphQL::Schema::DYNAMIC_FIELDS.include?(irep_node.definition_name)
37
- memo[:skip_current_scope] = false
38
- elsif GraphQL::Execution::DirectiveChecks.include?(irep_node, memo[:query])
37
+ if not_validated_node
38
+ memo[:skip_depth] -= 1
39
+ else
39
40
  if memo[:max_depth] < memo[:current_depth]
40
41
  memo[:max_depth] = memo[:current_depth]
41
42
  end
@@ -7,9 +7,9 @@ module GraphQL
7
7
  #
8
8
  class Directive
9
9
  include GraphQL::Define::InstanceDefinable
10
- accepts_definitions :locations, :name, :description, :include_proc, argument: GraphQL::Define::AssignArgument
10
+ accepts_definitions :locations, :name, :description, argument: GraphQL::Define::AssignArgument
11
11
 
12
- lazy_defined_attr_accessor :locations, :arguments, :name, :description, :include_proc
12
+ lazy_defined_attr_accessor :locations, :arguments, :name, :description
13
13
 
14
14
  LOCATIONS = [
15
15
  QUERY = :QUERY,
@@ -25,10 +25,6 @@ module GraphQL
25
25
  @arguments = {}
26
26
  end
27
27
 
28
- def include?(arguments)
29
- include_proc.call(arguments)
30
- end
31
-
32
28
  def to_s
33
29
  "<GraphQL::Directive #{name}>"
34
30
  end
@@ -3,8 +3,4 @@ GraphQL::Directive::IncludeDirective = GraphQL::Directive.define do
3
3
  description "Include this part of the query if `if` is true"
4
4
  locations([GraphQL::Directive::FIELD, GraphQL::Directive::FRAGMENT_SPREAD, GraphQL::Directive::INLINE_FRAGMENT])
5
5
  argument :if, !GraphQL::BOOLEAN_TYPE
6
-
7
- include_proc -> (arguments) {
8
- arguments["if"]
9
- }
10
6
  end
@@ -4,8 +4,4 @@ GraphQL::Directive::SkipDirective = GraphQL::Directive.define do
4
4
  locations([GraphQL::Directive::FIELD, GraphQL::Directive::FRAGMENT_SPREAD, GraphQL::Directive::INLINE_FRAGMENT])
5
5
 
6
6
  argument :if, !GraphQL::BOOLEAN_TYPE
7
-
8
- include_proc -> (arguments) {
9
- !arguments["if"]
10
- }
11
7
  end
@@ -10,22 +10,31 @@ module GraphQL
10
10
 
11
11
  # This covers `@include(if:)` & `@skip(if:)`
12
12
  # @return [Boolean] Should this node be skipped altogether?
13
- def skip?(irep_node, query)
14
- irep_node.directives.each do |directive_node|
15
- if directive_node.name == SKIP || directive_node.name == INCLUDE
16
- directive_defn = directive_node.definitions.first
17
- args = query.arguments_for(directive_node, directive_defn)
18
- if !directive_defn.include?(args)
19
- return true
20
- end
21
- end
22
- end
23
- false
13
+ def skip?(ast_node, query)
14
+ !include?(ast_node, query)
24
15
  end
25
16
 
26
17
  # @return [Boolean] Should this node be included in the query?
27
- def include?(irep_node, query)
28
- !skip?(irep_node, query)
18
+ def include?(directive_irep_nodes, query)
19
+ directive_irep_nodes.each do |directive_irep_node|
20
+ name = directive_irep_node.name
21
+ directive_defn = query.schema.directives[name]
22
+ case name
23
+ when SKIP
24
+ args = query.arguments_for(directive_irep_node, directive_defn)
25
+ if args['if'] == true
26
+ return false
27
+ end
28
+ when INCLUDE
29
+ args = query.arguments_for(directive_irep_node, directive_defn)
30
+ if args['if'] == false
31
+ return false
32
+ end
33
+ else
34
+ # Undefined directive, or one we don't care about
35
+ end
36
+ end
37
+ true
29
38
  end
30
39
  end
31
40
  end
@@ -6,6 +6,10 @@ module GraphQL
6
6
  # @return [GraphQL::Language::Nodes::Field] the field where the error occured
7
7
  attr_accessor :ast_node
8
8
 
9
+ # @return [String] an array describing the JSON-path into the execution
10
+ # response which corresponds to this error.
11
+ attr_accessor :path
12
+
9
13
  def initialize(message, ast_node: nil)
10
14
  @ast_node = ast_node
11
15
  super(message)
@@ -24,6 +28,9 @@ module GraphQL
24
28
  }
25
29
  ]
26
30
  end
31
+ if path
32
+ hash["path"] = path
33
+ end
27
34
  hash
28
35
  end
29
36
  end
@@ -3,7 +3,7 @@ require "set"
3
3
  module GraphQL
4
4
  module InternalRepresentation
5
5
  class Node
6
- def initialize(parent:, ast_node: nil, return_type: nil, name: nil, definition_name: nil, definitions: {}, children: {}, spreads: [], directives: Set.new)
6
+ def initialize(parent:, ast_node: nil, return_type: nil, name: nil, definition_name: nil, definitions: {}, children: {}, spreads: [], directives: Set.new, included: true)
7
7
  # Make sure these are kept in sync with #dup
8
8
  @ast_node = ast_node
9
9
  @return_type = return_type
@@ -14,6 +14,7 @@ module GraphQL
14
14
  @children = children
15
15
  @spreads = spreads
16
16
  @directives = directives
17
+ @included = included
17
18
  end
18
19
 
19
20
  # Note: by the time this gets out of the Rewrite phase, this will be empty -- it's emptied out when fragments are merged back in
@@ -58,6 +59,14 @@ module GraphQL
58
59
  # @return [Array<GraphQL::Query::Node>]
59
60
  attr_reader :children
60
61
 
62
+ # @return [Boolean] false if every field for this node included `@skip(if: true)`
63
+ attr_accessor :included
64
+ alias :included? :included
65
+
66
+ def skipped?
67
+ !@included
68
+ end
69
+
61
70
  # @return [GraphQL::InternalRepresentation::Node] The node which this node is a child of
62
71
  attr_reader :parent
63
72
 
@@ -72,9 +81,18 @@ module GraphQL
72
81
  end
73
82
  end
74
83
 
84
+ def path
85
+ path = parent ? parent.path : []
86
+ path << name if name
87
+ path << @index if @index
88
+ path
89
+ end
90
+
91
+ attr_writer :index
92
+
75
93
  def inspect(indent = 0)
76
94
  own_indent = " " * indent
77
- self_inspect = "#{own_indent}<Node #{name} (#{definition_name}: {#{definitions.keys.join("|")}} -> #{return_type})>"
95
+ self_inspect = "#{own_indent}<Node #{name} #{skipped? ? "(skipped)" : ""}(#{definition_name}: {#{definitions.keys.join("|")}} -> #{return_type})>"
78
96
  if children.any?
79
97
  self_inspect << " {\n#{children.values.map { |n| n.inspect(indent + 2)}.join("\n")}\n#{own_indent}}"
80
98
  end
@@ -24,7 +24,7 @@ module GraphQL
24
24
  # This tracks dependencies from fragment to Node where it was used
25
25
  # { frag_name => [dependent_node, dependent_node]}
26
26
  @fragment_spreads = Hash.new { |h, k| h[k] = []}
27
- # [Nodes::Directive ... ] directive affecting the current scope
27
+ # [[Nodes::Directive ...]] directive affecting the current scope
28
28
  @parent_directives = []
29
29
  end
30
30
 
@@ -55,6 +55,7 @@ module GraphQL
55
55
  name: node_name,
56
56
  definition_name: ast_node.name,
57
57
  parent: parent_node,
58
+ included: false, # may be set to true on leaving the node
58
59
  )
59
60
  end
60
61
  object_type = context.parent_type_definition.unwrap
@@ -64,20 +65,21 @@ module GraphQL
64
65
  }
65
66
 
66
67
  visitor[Nodes::InlineFragment].enter << -> (ast_node, prev_ast_node) {
67
- @parent_directives.push([])
68
+ @parent_directives.push(InlineFragmentDirectives.new)
68
69
  }
69
70
 
70
71
  visitor[Nodes::Directive].enter << -> (ast_node, prev_ast_node) {
71
72
  # It could be a query error where a directive is somewhere it shouldn't be
72
73
  if @parent_directives.any?
73
- @parent_directives.last << Node.new(
74
+ directive_irep_node = Node.new(
74
75
  name: ast_node.name,
75
76
  definition_name: ast_node.name,
76
77
  ast_node: ast_node,
77
- definitions: [context.directive_definition],
78
+ definitions: {context.directive_definition => context.directive_definition},
78
79
  # This isn't used, the directive may have many parents in the case of inline fragment
79
80
  parent: nil,
80
81
  )
82
+ @parent_directives.last.push(directive_irep_node)
81
83
  end
82
84
  }
83
85
 
@@ -88,6 +90,7 @@ module GraphQL
88
90
  parent: parent_node,
89
91
  name: ast_node.name,
90
92
  ast_node: ast_node,
93
+ included: false, # this may be set to true on leaving the node
91
94
  )
92
95
  # The parent node has a reference to the fragment
93
96
  parent_node.spreads.push(spread_node)
@@ -117,8 +120,9 @@ module GraphQL
117
120
  # so that they can be applied to fields when
118
121
  # the fragment is merged in later
119
122
  spread_node = @nodes.pop
120
- spread_node.directives.merge(@parent_directives.flatten)
121
- @parent_directives.pop
123
+ applicable_directives = pop_applicable_directives(@parent_directives)
124
+ spread_node.included ||= GraphQL::Execution::DirectiveChecks.include?(applicable_directives, context.query)
125
+ spread_node.directives.merge(applicable_directives)
122
126
  }
123
127
 
124
128
  visitor[Nodes::FragmentDefinition].leave << -> (ast_node, prev_ast_node) {
@@ -139,8 +143,9 @@ module GraphQL
139
143
  # and record any directives that were visited
140
144
  # during this field & before it (eg, inline fragments)
141
145
  field_node = @nodes.pop
142
- field_node.directives.merge(@parent_directives.flatten)
143
- @parent_directives.pop
146
+ applicable_directives = pop_applicable_directives(@parent_directives)
147
+ field_node.directives.merge(applicable_directives)
148
+ field_node.included ||= GraphQL::Execution::DirectiveChecks.include?(applicable_directives, context.query)
144
149
  }
145
150
 
146
151
  visitor[Nodes::Document].leave << -> (ast_node, prev_ast_node) {
@@ -149,12 +154,14 @@ module GraphQL
149
154
  while fragment_node = @independent_fragments.pop
150
155
  fragment_usages = @fragment_spreads[fragment_node.name]
151
156
  while dependent_node = fragment_usages.pop
152
- # remove self from dependent_node.spreads
153
- rejected_spread_nodes = dependent_node.spreads.select { |spr| spr.name == fragment_node.name }
154
- rejected_spread_nodes.each { |r_node| dependent_node.spreads.delete(r_node) }
157
+ # Find the spreads for this reference
158
+ resolved_spread_nodes = dependent_node.spreads.select { |spr| spr.name == fragment_node.name }
159
+ spread_is_included = resolved_spread_nodes.any?(&:included?)
160
+ # Since we're going to resolve them, remove them from the dependcies
161
+ resolved_spread_nodes.each { |r_node| dependent_node.spreads.delete(r_node) }
155
162
 
156
163
  # resolve the dependency (merge into dependent node)
157
- deep_merge(dependent_node, fragment_node, rejected_spread_nodes.first.directives)
164
+ deep_merge(dependent_node, fragment_node, spread_is_included)
158
165
  owner = dependent_node.owner
159
166
  if owner.ast_node.is_a?(Nodes::FragmentDefinition) && !any_fragment_spreads?(owner)
160
167
  @independent_fragments.push(owner)
@@ -166,27 +173,66 @@ module GraphQL
166
173
 
167
174
  private
168
175
 
169
- # Merge the chilren from `fragment_node` into `parent_node`. Merge `directives` into each of those fields.
170
- def deep_merge(parent_node, fragment_node, directives)
176
+ # Merge the children from `fragment_node` into `parent_node`.
177
+ # This is an implementation of "fragment inlining"
178
+ def deep_merge(parent_node, fragment_node, included)
171
179
  fragment_node.children.each do |name, child_node|
172
- deep_merge_child(parent_node, name, child_node, directives)
180
+ deep_merge_child(parent_node, name, child_node, included)
173
181
  end
174
182
  end
175
183
 
176
- # Merge `node` into `parent_node`'s children, as `name`, applying `extra_directives`
177
- def deep_merge_child(parent_node, name, node, extra_directives)
178
- child_node = parent_node.children[name] ||= node.dup
184
+ # Merge `node` into `parent_node`'s children, as `name`, applying `extra_included`
185
+ # `extra_included` comes from the spread node:
186
+ # - If the spread was included, first-level children should be included if _either_ node was included
187
+ # - If the spread was _not_ included, first-level children should be included if _a pre-existing_ node was included
188
+ # (A copied node should be excluded)
189
+ def deep_merge_child(parent_node, name, node, extra_included)
190
+ child_node = parent_node.children[name]
191
+ previously_included = child_node.nil? ? false : child_node.included?
192
+ next_included = extra_included ? (previously_included || node.included?) : previously_included
193
+
194
+ if child_node.nil?
195
+ child_node = parent_node.children[name] = node.dup
196
+ end
197
+
179
198
  child_node.definitions.merge!(node.definitions)
199
+
200
+ child_node.included = next_included
201
+
202
+
203
+
180
204
  node.children.each do |merge_child_name, merge_child_node|
181
- deep_merge_child(child_node, merge_child_name, merge_child_node, [])
205
+ deep_merge_child(child_node, merge_child_name, merge_child_node, node.included)
182
206
  end
183
- child_node.directives.merge(extra_directives)
184
207
  end
185
208
 
186
209
  # return true if node or _any_ children have a fragment spread
187
210
  def any_fragment_spreads?(node)
188
211
  node.spreads.any? || node.children.any? { |name, node| any_fragment_spreads?(node) }
189
212
  end
213
+
214
+ # pop off own directives,
215
+ # then check the last one to see if it's directives
216
+ # from an inline fragment. If it is, add them in
217
+ # @return [Array<Node>]
218
+ def pop_applicable_directives(directive_stack)
219
+ own_directives = directive_stack.pop
220
+ if directive_stack.last.is_a?(InlineFragmentDirectives)
221
+ own_directives = directive_stack.last + own_directives
222
+ end
223
+ own_directives
224
+ end
225
+
226
+
227
+ # It's an array, but can be identified with `is_a?`
228
+ class InlineFragmentDirectives
229
+ extend Forwardable
230
+ def initialize
231
+ @storage = []
232
+ end
233
+
234
+ def_delegators :@storage, :push, :+
235
+ end
190
236
  end
191
237
  end
192
238
  end
@@ -21,7 +21,9 @@ module GraphQL
21
21
  when Nodes::Argument
22
22
  "#{node.name}: #{generate(node.value)}"
23
23
  when Nodes::Directive
24
- "@#{node.name}(#{node.arguments.map { |a| generate(a) }.join(", ")})"
24
+ out = "@#{node.name}"
25
+ out << "(#{node.arguments.map { |a| generate(a) }.join(", ")})" if node.arguments.any?
26
+ out
25
27
  when Nodes::Enum
26
28
  "#{node.name}"
27
29
  when Nodes::Field
@@ -77,34 +79,46 @@ module GraphQL
77
79
  out << " subscription: #{node.subscription}\n" if node.subscription
78
80
  out << "}"
79
81
  when Nodes::ScalarTypeDefinition
80
- "scalar #{node.name}"
82
+ out = "scalar #{node.name}"
83
+ out << generate_directives(node.directives)
81
84
  when Nodes::ObjectTypeDefinition
82
85
  out = "type #{node.name}"
86
+ out << generate_directives(node.directives)
83
87
  out << " implements " << node.interfaces.join(", ") unless node.interfaces.empty?
84
88
  out << generate_field_definitions(node.fields)
85
89
  when Nodes::InputValueDefinition
86
90
  out = "#{node.name}: #{generate(node.type)}"
87
91
  out << " = #{generate(node.default_value)}" unless node.default_value.nil?
88
- out
92
+ out << generate_directives(node.directives)
89
93
  when Nodes::FieldDefinition
90
94
  out = node.name.dup
91
95
  unless node.arguments.empty?
92
96
  out << "(" << node.arguments.map{ |arg| generate(arg) }.join(", ") << ")"
93
97
  end
94
98
  out << ": #{generate(node.type)}"
99
+ out << generate_directives(node.directives)
95
100
  when Nodes::InterfaceTypeDefinition
96
101
  out = "interface #{node.name}"
102
+ out << generate_directives(node.directives)
97
103
  out << generate_field_definitions(node.fields)
98
104
  when Nodes::UnionTypeDefinition
99
- "union #{node.name} = " + node.types.join(" | ")
105
+ out = "union #{node.name}"
106
+ out << generate_directives(node.directives)
107
+ out << " = " + node.types.join(" | ")
100
108
  when Nodes::EnumTypeDefinition
101
- out = "enum #{node.name} {\n"
109
+ out = "enum #{node.name}#{generate_directives(node.directives)} {\n"
102
110
  node.values.each do |value|
103
- out << " #{value}\n"
111
+ out << generate(value)
104
112
  end
105
113
  out << "}"
114
+ when Nodes::EnumValueDefinition
115
+ out = " #{node.name}"
116
+ out << generate_directives(node.directives)
117
+ out << "\n"
106
118
  when Nodes::InputObjectTypeDefinition
107
- out = "input #{node.name} {\n"
119
+ out = "input #{node.name}"
120
+ out << generate_directives(node.directives)
121
+ out << " {\n"
108
122
  node.fields.each do |field|
109
123
  out << " #{generate(field)}\n"
110
124
  end
@@ -116,7 +130,7 @@ module GraphQL
116
130
  when Array
117
131
  "[#{node.map { |v| generate(v) }.join(", ")}]"
118
132
  when Hash
119
- "{ #{node.map { |k, v| "#{k}: #{generate(v)}" }.join(", ")} }"
133
+ "{#{node.map { |k, v| "#{k}: #{generate(v)}" }.join(", ")}}"
120
134
  else
121
135
  raise TypeError
122
136
  end