graphql 0.17.2 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +1 -0
  3. data/lib/graphql/analysis/query_depth.rb +1 -1
  4. data/lib/graphql/base_type.rb +25 -1
  5. data/lib/graphql/define.rb +2 -0
  6. data/lib/graphql/define/assign_connection.rb +11 -0
  7. data/lib/graphql/define/assign_global_id_field.rb +11 -0
  8. data/lib/graphql/define/assign_object_field.rb +21 -20
  9. data/lib/graphql/define/defined_object_proxy.rb +2 -2
  10. data/lib/graphql/define/instance_definable.rb +13 -3
  11. data/lib/graphql/field.rb +1 -1
  12. data/lib/graphql/language/generation.rb +57 -6
  13. data/lib/graphql/language/lexer.rb +434 -212
  14. data/lib/graphql/language/lexer.rl +18 -0
  15. data/lib/graphql/language/nodes.rb +75 -0
  16. data/lib/graphql/language/parser.rb +853 -341
  17. data/lib/graphql/language/parser.y +114 -17
  18. data/lib/graphql/query.rb +15 -1
  19. data/lib/graphql/relay.rb +13 -0
  20. data/lib/graphql/relay/array_connection.rb +80 -0
  21. data/lib/graphql/relay/base_connection.rb +138 -0
  22. data/lib/graphql/relay/connection_field.rb +54 -0
  23. data/lib/graphql/relay/connection_type.rb +25 -0
  24. data/lib/graphql/relay/edge.rb +22 -0
  25. data/lib/graphql/relay/edge_type.rb +14 -0
  26. data/lib/graphql/relay/global_id_resolve.rb +15 -0
  27. data/lib/graphql/relay/global_node_identification.rb +124 -0
  28. data/lib/graphql/relay/mutation.rb +146 -0
  29. data/lib/graphql/relay/page_info.rb +13 -0
  30. data/lib/graphql/relay/relation_connection.rb +98 -0
  31. data/lib/graphql/schema.rb +3 -0
  32. data/lib/graphql/schema/printer.rb +12 -2
  33. data/lib/graphql/static_validation/message.rb +9 -5
  34. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  35. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  36. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  37. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +7 -7
  38. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
  39. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -5
  40. data/lib/graphql/static_validation/rules/fields_will_merge.rb +6 -6
  41. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +17 -9
  42. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
  43. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +1 -1
  44. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  45. data/lib/graphql/static_validation/rules/fragments_are_used.rb +17 -6
  46. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  47. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +2 -2
  48. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +5 -5
  49. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  50. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +12 -11
  51. data/lib/graphql/static_validation/type_stack.rb +33 -2
  52. data/lib/graphql/static_validation/validation_context.rb +5 -0
  53. data/lib/graphql/version.rb +1 -1
  54. data/readme.md +16 -4
  55. data/spec/graphql/analysis/analyze_query_spec.rb +31 -2
  56. data/spec/graphql/analysis/query_complexity_spec.rb +24 -0
  57. data/spec/graphql/argument_spec.rb +1 -1
  58. data/spec/graphql/define/instance_definable_spec.rb +9 -0
  59. data/spec/graphql/field_spec.rb +1 -1
  60. data/spec/graphql/internal_representation/rewrite_spec.rb +3 -3
  61. data/spec/graphql/language/generation_spec.rb +25 -4
  62. data/spec/graphql/language/parser_spec.rb +116 -1
  63. data/spec/graphql/query_spec.rb +10 -0
  64. data/spec/graphql/relay/array_connection_spec.rb +164 -0
  65. data/spec/graphql/relay/connection_type_spec.rb +37 -0
  66. data/spec/graphql/relay/global_node_identification_spec.rb +149 -0
  67. data/spec/graphql/relay/mutation_spec.rb +55 -0
  68. data/spec/graphql/relay/page_info_spec.rb +106 -0
  69. data/spec/graphql/relay/relation_connection_spec.rb +348 -0
  70. data/spec/graphql/schema/printer_spec.rb +8 -0
  71. data/spec/graphql/schema/reduce_types_spec.rb +1 -1
  72. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +12 -6
  73. data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +8 -4
  74. data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +4 -2
  75. data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +4 -2
  76. data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +7 -2
  77. data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +4 -2
  78. data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +6 -3
  79. data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +5 -3
  80. data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +4 -2
  81. data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +5 -2
  82. data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +10 -2
  83. data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +6 -3
  84. data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +8 -4
  85. data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +8 -4
  86. data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +6 -3
  87. data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +6 -3
  88. data/spec/spec_helper.rb +7 -0
  89. data/spec/support/dairy_app.rb +11 -10
  90. data/spec/support/star_wars_data.rb +65 -58
  91. data/spec/support/star_wars_schema.rb +192 -54
  92. metadata +84 -2
@@ -26,6 +26,9 @@ module GraphQL
26
26
  :mutation_execution_strategy,
27
27
  :subscription_execution_strategy
28
28
 
29
+ # @return [GraphQL::Relay::GlobalNodeIdentification] the node identification instance for this schema, when using Relay
30
+ attr_accessor :node_identification
31
+
29
32
  # @return [Array<#call>] Middlewares suitable for MiddlewareChain, applied to fields during execution
30
33
  attr_reader :middleware
31
34
 
@@ -28,7 +28,17 @@ module GraphQL
28
28
 
29
29
  def print_filtered_schema(schema, type_filter)
30
30
  types = schema.types.values.select{ |type| type_filter.call(type) }.sort_by(&:name)
31
- types.map{ |type| print_type(type) }.join("\n\n")
31
+ type_definitions = types.map{ |type| print_type(type) }
32
+
33
+ [print_schema_definition(schema)].concat(type_definitions).join("\n\n")
34
+ end
35
+
36
+ def print_schema_definition(schema)
37
+ operations = [:query, :mutation, :subscription].map do |operation_type|
38
+ object_type = schema.public_send(operation_type)
39
+ " #{operation_type}: #{object_type.name}\n" if object_type
40
+ end.compact.join
41
+ "schema {\n#{operations}}"
32
42
  end
33
43
 
34
44
  BUILTIN_SCALARS = Set.new(["String", "Boolean", "Int", "Float", "ID"])
@@ -122,7 +132,7 @@ module GraphQL
122
132
 
123
133
  class UnionPrinter
124
134
  def self.print(type)
125
- "union #{type.name} = #{type.possible_types.map(&:to_s).join(" | ")}\n}"
135
+ "union #{type.name} = #{type.possible_types.map(&:to_s).join(" | ")}"
126
136
  end
127
137
  end
128
138
 
@@ -6,23 +6,27 @@ module GraphQL
6
6
  # Convenience for validators
7
7
  module MessageHelper
8
8
  # Error `message` is located at `node`
9
- def message(message, node)
10
- GraphQL::StaticValidation::Message.new(message, line: node.line, col: node.col)
9
+ def message(message, node, context: nil, path: nil)
10
+ path ||= context.path
11
+ GraphQL::StaticValidation::Message.new(message, line: node.line, col: node.col, path: path)
11
12
  end
12
13
  end
13
- attr_reader :message, :line, :col
14
14
 
15
- def initialize(message, line: nil, col: nil)
15
+ attr_reader :message, :line, :col, :path
16
+
17
+ def initialize(message, line: nil, col: nil, path: [])
16
18
  @message = message
17
19
  @line = line
18
20
  @col = col
21
+ @path = path
19
22
  end
20
23
 
21
24
  # A hash representation of this Message
22
25
  def to_h
23
26
  {
24
27
  "message" => message,
25
- "locations" => locations
28
+ "locations" => locations,
29
+ "path" => path,
26
30
  }
27
31
  end
28
32
 
@@ -10,7 +10,7 @@ module GraphQL
10
10
  if !valid
11
11
  kind_of_node = node_type(parent)
12
12
  error_arg_name = parent_name(parent, defn)
13
- context.errors << message("Argument '#{node.name}' on #{kind_of_node} '#{error_arg_name}' has an invalid value", parent)
13
+ context.errors << message("Argument '#{node.name}' on #{kind_of_node} '#{error_arg_name}' has an invalid value", parent, context: context)
14
14
  end
15
15
  end
16
16
  end
@@ -6,7 +6,7 @@ module GraphQL
6
6
  if argument_defn.nil?
7
7
  kind_of_node = node_type(parent)
8
8
  error_arg_name = parent_name(parent, defn)
9
- context.errors << message("#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'", parent)
9
+ context.errors << message("#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'", parent, context: context)
10
10
  GraphQL::Language::Visitor::SKIP
11
11
  else
12
12
  nil
@@ -6,15 +6,15 @@ module GraphQL
6
6
  def validate(context)
7
7
  directive_names = context.schema.directives.keys
8
8
  context.visitor[GraphQL::Language::Nodes::Directive] << -> (node, parent) {
9
- validate_directive(node, directive_names, context.errors)
9
+ validate_directive(node, directive_names, context)
10
10
  }
11
11
  end
12
12
 
13
13
  private
14
14
 
15
- def validate_directive(ast_directive, directive_names, errors)
15
+ def validate_directive(ast_directive, directive_names, context)
16
16
  if !directive_names.include?(ast_directive.name)
17
- errors << message("Directive @#{ast_directive.name} is not defined", ast_directive)
17
+ context.errors << message("Directive @#{ast_directive.name} is not defined", ast_directive, context: context)
18
18
  end
19
19
  end
20
20
  end
@@ -8,7 +8,7 @@ module GraphQL
8
8
  directives = context.schema.directives
9
9
 
10
10
  context.visitor[Nodes::Directive] << -> (node, parent) {
11
- validate_location(node, parent, directives, context.errors)
11
+ validate_location(node, parent, directives, context)
12
12
  }
13
13
  end
14
14
 
@@ -33,25 +33,25 @@ module GraphQL
33
33
 
34
34
  SIMPLE_LOCATION_NODES = SIMPLE_LOCATIONS.keys
35
35
 
36
- def validate_location(ast_directive, ast_parent, directives, errors)
36
+ def validate_location(ast_directive, ast_parent, directives, context)
37
37
  directive_defn = directives[ast_directive.name]
38
38
  case ast_parent
39
39
  when Nodes::OperationDefinition
40
40
  required_location = GraphQL::Directive.const_get(ast_parent.operation_type.upcase)
41
- assert_includes_location(directive_defn, ast_directive, required_location, errors)
41
+ assert_includes_location(directive_defn, ast_directive, required_location, context)
42
42
  when *SIMPLE_LOCATION_NODES
43
43
  required_location = SIMPLE_LOCATIONS[ast_parent.class]
44
- assert_includes_location(directive_defn, ast_directive, required_location, errors)
44
+ assert_includes_location(directive_defn, ast_directive, required_location, context)
45
45
  else
46
- errors << message("Directives can't be applied to #{ast_parent.class.name}s", ast_directive)
46
+ context.errors << message("Directives can't be applied to #{ast_parent.class.name}s", ast_directive, context: context)
47
47
  end
48
48
  end
49
49
 
50
- def assert_includes_location(directive_defn, directive_ast, required_location, errors)
50
+ def assert_includes_location(directive_defn, directive_ast, required_location, context)
51
51
  if !directive_defn.locations.include?(required_location)
52
52
  location_name = LOCATION_MESSAGE_NAMES[required_location]
53
53
  allowed_location_names = directive_defn.locations.map { |loc| LOCATION_MESSAGE_NAMES[loc] }
54
- errors << message("'@#{directive_defn.name}' can't be applied to #{location_name} (allowed: #{allowed_location_names.join(", ")})", directive_ast)
54
+ context.errors << message("'@#{directive_defn.name}' can't be applied to #{location_name} (allowed: #{allowed_location_names.join(", ")})", directive_ast, context: context)
55
55
  end
56
56
  end
57
57
  end
@@ -9,21 +9,21 @@ module GraphQL
9
9
  return if context.skip_field?(node.name)
10
10
  parent_type = context.object_types[-2]
11
11
  parent_type = parent_type.unwrap
12
- validate_field(context.errors, node, parent_type, parent)
12
+ validate_field(context, node, parent_type, parent)
13
13
  }
14
14
  end
15
15
 
16
16
  private
17
17
 
18
- def validate_field(errors, ast_field, parent_type, parent)
18
+ def validate_field(context, ast_field, parent_type, parent)
19
19
  if parent_type.kind.union?
20
- errors << message("Selections can't be made directly on unions (see selections on #{parent_type.name})", parent)
20
+ context.errors << message("Selections can't be made directly on unions (see selections on #{parent_type.name})", parent, context: context)
21
21
  return GraphQL::Language::Visitor::SKIP
22
22
  end
23
23
 
24
24
  field = parent_type.get_field(ast_field.name)
25
25
  if field.nil?
26
- errors << message("Field '#{ast_field.name}' doesn't exist on type '#{parent_type.name}'", parent)
26
+ context.errors << message("Field '#{ast_field.name}' doesn't exist on type '#{parent_type.name}'", parent, context: context)
27
27
  return GraphQL::Language::Visitor::SKIP
28
28
  end
29
29
  end
@@ -9,25 +9,25 @@ module GraphQL
9
9
  context.visitor[GraphQL::Language::Nodes::Field] << -> (node, parent) {
10
10
  return if context.skip_field?(node.name)
11
11
  field_defn = context.field_definition
12
- validate_field_selections(node, field_defn, context.errors)
12
+ validate_field_selections(node, field_defn, context)
13
13
  }
14
14
  end
15
15
 
16
16
  private
17
17
 
18
- def validate_field_selections(ast_field, field_defn, errors)
18
+ def validate_field_selections(ast_field, field_defn, context)
19
19
  resolved_type = field_defn.type.unwrap
20
20
 
21
21
  if resolved_type.kind.scalar? && ast_field.selections.any?
22
- error = message("Selections can't be made on scalars (field '#{ast_field.name}' returns #{resolved_type.name} but has selections [#{ast_field.selections.map(&:name).join(", ")}])", ast_field)
22
+ error = message("Selections can't be made on scalars (field '#{ast_field.name}' returns #{resolved_type.name} but has selections [#{ast_field.selections.map(&:name).join(", ")}])", ast_field, context: context)
23
23
  elsif resolved_type.kind.object? && ast_field.selections.none?
24
- error = message("Objects must have selections (field '#{ast_field.name}' returns #{resolved_type.name} but has no selections)", ast_field)
24
+ error = message("Objects must have selections (field '#{ast_field.name}' returns #{resolved_type.name} but has no selections)", ast_field, context: context)
25
25
  else
26
26
  error = nil
27
27
  end
28
28
 
29
29
  if !error.nil?
30
- errors << error
30
+ context.errors << error
31
31
  GraphQL::Language::Visitor::SKIP
32
32
  end
33
33
  end
@@ -22,7 +22,7 @@ module GraphQL
22
22
 
23
23
  def find_conflicts(field_map, context)
24
24
  field_map.each do |name, ast_fields|
25
- comparison = FieldDefinitionComparison.new(name, ast_fields)
25
+ comparison = FieldDefinitionComparison.new(name, ast_fields, context)
26
26
  context.errors.push(*comparison.errors)
27
27
 
28
28
 
@@ -63,27 +63,27 @@ module GraphQL
63
63
  include GraphQL::StaticValidation::Message::MessageHelper
64
64
  NAMED_VALUES = [GraphQL::Language::Nodes::Enum, GraphQL::Language::Nodes::VariableIdentifier]
65
65
  attr_reader :errors
66
- def initialize(name, defs)
66
+ def initialize(name, defs, context)
67
67
  errors = []
68
68
 
69
69
  names = defs.map(&:name).uniq
70
70
  if names.length != 1
71
- errors << message("Field '#{name}' has a field conflict: #{names.join(" or ")}?", defs.first)
71
+ errors << message("Field '#{name}' has a field conflict: #{names.join(" or ")}?", defs.first, context: context)
72
72
  end
73
73
 
74
74
  args = defs.map { |defn| reduce_list(defn.arguments)}.uniq
75
75
  if args.length != 1
76
- errors << message("Field '#{name}' has an argument conflict: #{args.map {|a| JSON.dump(a) }.join(" or ")}?", defs.first)
76
+ errors << message("Field '#{name}' has an argument conflict: #{args.map {|a| JSON.dump(a) }.join(" or ")}?", defs.first, context: context)
77
77
  end
78
78
 
79
79
  directive_names = defs.map { |defn| defn.directives.map(&:name) }.uniq
80
80
  if directive_names.length != 1
81
- errors << message("Field '#{name}' has a directive conflict: #{directive_names.map {|names| "[#{names.join(", ")}]"}.join(" or ")}?", defs.first)
81
+ errors << message("Field '#{name}' has a directive conflict: #{directive_names.map {|names| "[#{names.join(", ")}]"}.join(" or ")}?", defs.first, context: context)
82
82
  end
83
83
 
84
84
  directive_args = defs.map {|defn| defn.directives.map {|d| reduce_list(d.arguments) } }.uniq
85
85
  if directive_args.length != 1
86
- errors << message("Field '#{name}' has a directive argument conflict: #{directive_args.map {|args| JSON.dump(args)}.join(" or ")}?", defs.first)
86
+ errors << message("Field '#{name}' has a directive argument conflict: #{directive_args.map {|args| JSON.dump(args)}.join(" or ")}?", defs.first, context: context)
87
87
  end
88
88
 
89
89
  @errors = errors
@@ -9,7 +9,7 @@ module GraphQL
9
9
  fragment_parent = context.object_types[-2]
10
10
  fragment_child = context.object_types.last
11
11
  if fragment_child
12
- validate_fragment_in_scope(fragment_parent, fragment_child, node, context)
12
+ validate_fragment_in_scope(fragment_parent, fragment_child, node, context, context.path)
13
13
  end
14
14
  }
15
15
 
@@ -17,26 +17,25 @@ module GraphQL
17
17
 
18
18
  context.visitor[GraphQL::Language::Nodes::FragmentSpread] << -> (node, parent) {
19
19
  fragment_parent = context.object_types.last
20
- spreads_to_validate << [node, fragment_parent]
20
+ spreads_to_validate << FragmentSpread.new(node: node, parent_type: fragment_parent, path: context.path)
21
21
  }
22
22
 
23
- context.visitor[GraphQL::Language::Nodes::Document].leave << -> (node, parent) {
24
- spreads_to_validate.each do |spread_values|
25
- node, fragment_parent = spread_values
26
- fragment_child_name = context.fragments[node.name].type
23
+ context.visitor[GraphQL::Language::Nodes::Document].leave << -> (doc_node, parent) {
24
+ spreads_to_validate.each do |frag_spread|
25
+ fragment_child_name = context.fragments[frag_spread.node.name].type
27
26
  fragment_child = context.schema.types[fragment_child_name]
28
- validate_fragment_in_scope(fragment_parent, fragment_child, node, context)
27
+ validate_fragment_in_scope(frag_spread.parent_type, fragment_child, frag_spread.node, context, frag_spread.path)
29
28
  end
30
29
  }
31
30
  end
32
31
 
33
32
  private
34
33
 
35
- def validate_fragment_in_scope(parent_type, child_type, node, context)
34
+ def validate_fragment_in_scope(parent_type, child_type, node, context, path)
36
35
  intersecting_types = get_possible_types(parent_type, context.schema) & get_possible_types(child_type, context.schema)
37
36
  if intersecting_types.none?
38
37
  name = node.respond_to?(:name) ? " #{node.name}" : ""
39
- context.errors << message("Fragment#{name} on #{child_type.name} can't be spread inside #{parent_type.name}", node)
38
+ context.errors << message("Fragment#{name} on #{child_type.name} can't be spread inside #{parent_type.name}", node, path: path)
40
39
  end
41
40
  end
42
41
 
@@ -51,6 +50,15 @@ module GraphQL
51
50
  []
52
51
  end
53
52
  end
53
+
54
+ class FragmentSpread
55
+ attr_reader :node, :parent_type, :path
56
+ def initialize(node:, parent_type:, path:)
57
+ @node = node
58
+ @parent_type = parent_type
59
+ @path = path
60
+ end
61
+ end
54
62
  end
55
63
  end
56
64
  end
@@ -20,7 +20,7 @@ module GraphQL
20
20
  return unless node.type
21
21
  type = context.schema.types.fetch(node.type, nil)
22
22
  if type.nil?
23
- context.errors << message("No such type #{node.type}, so it can't be a fragment condition", node)
23
+ context.errors << message("No such type #{node.type}, so it can't be a fragment condition", node, context: context)
24
24
  GraphQL::Language::Visitor::SKIP
25
25
  end
26
26
  end
@@ -6,7 +6,7 @@ module GraphQL
6
6
  def validate(context)
7
7
  context.visitor[GraphQL::Language::Nodes::FragmentDefinition] << -> (node, parent) {
8
8
  if has_nested_spread(node, [], context)
9
- context.errors << message("Fragment #{node.name} contains an infinite loop", node)
9
+ context.errors << message("Fragment #{node.name} contains an infinite loop", node, context: context)
10
10
  end
11
11
  }
12
12
  end
@@ -23,7 +23,7 @@ module GraphQL
23
23
  return unless type_name
24
24
  type_def = context.schema.types[type_name]
25
25
  if type_def.nil? || !type_def.kind.composite?
26
- context.errors << message("Invalid fragment on type #{type_name} (must be Union, Interface or Object)", node)
26
+ context.errors << message("Invalid fragment on type #{type_name} (must be Union, Interface or Object)", node, context: context)
27
27
  GraphQL::Language::Visitor::SKIP
28
28
  end
29
29
  end
@@ -9,35 +9,46 @@ module GraphQL
9
9
  defined_fragments = []
10
10
 
11
11
  v[GraphQL::Language::Nodes::Document] << -> (node, parent) {
12
- defined_fragments = node.definitions.select { |defn| defn.is_a?(GraphQL::Language::Nodes::FragmentDefinition) }
12
+ defined_fragments = node.definitions
13
+ .select { |defn| defn.is_a?(GraphQL::Language::Nodes::FragmentDefinition) }
14
+ .map { |node| FragmentInstance.new(node: node, path: context.path) }
13
15
  }
14
16
 
15
17
  v[GraphQL::Language::Nodes::FragmentSpread] << -> (node, parent) {
16
- used_fragments << node
18
+ used_fragments << FragmentInstance.new(node: node, path: context.path)
17
19
  if defined_fragments.none? { |defn| defn.name == node.name }
18
20
  GraphQL::Language::Visitor::SKIP
19
21
  end
20
22
  }
21
- v[GraphQL::Language::Nodes::Document].leave << -> (node, parent) { add_errors(context.errors, used_fragments, defined_fragments) }
23
+ v[GraphQL::Language::Nodes::Document].leave << -> (node, parent) { add_errors(context, used_fragments, defined_fragments) }
22
24
  end
23
25
 
24
26
  private
25
27
 
26
- def add_errors(errors, used_fragments, defined_fragments)
28
+ def add_errors(context, used_fragments, defined_fragments)
27
29
  undefined_fragments = find_difference(used_fragments, defined_fragments.map(&:name))
28
30
  undefined_fragments.each do |fragment|
29
- errors << message("Fragment #{fragment.name} was used, but not defined", fragment)
31
+ context.errors << message("Fragment #{fragment.name} was used, but not defined", fragment.node, path: fragment.path)
30
32
  end
31
33
 
32
34
  unused_fragments = find_difference(defined_fragments, used_fragments.map(&:name))
33
35
  unused_fragments.each do |fragment|
34
- errors << message("Fragment #{fragment.name} was defined, but not used", fragment)
36
+ context.errors << message("Fragment #{fragment.name} was defined, but not used", fragment.node, path: fragment.path)
35
37
  end
36
38
  end
37
39
 
38
40
  def find_difference(fragments, allowed_fragment_names)
39
41
  fragments.select {|f| !allowed_fragment_names.include?(f.name) }
40
42
  end
43
+
44
+ class FragmentInstance
45
+ attr_reader :name, :node, :path
46
+ def initialize(node:, path:)
47
+ @node = node
48
+ @name = node.name
49
+ @path = path
50
+ end
51
+ end
41
52
  end
42
53
  end
43
54
  end
@@ -30,7 +30,7 @@ module GraphQL
30
30
 
31
31
  missing_names = required_argument_names - present_argument_names
32
32
  if missing_names.any?
33
- context.errors << message("#{ast_node.class.name.split("::").last} '#{ast_node.name}' is missing required arguments: #{missing_names.join(", ")}", ast_node)
33
+ context.errors << message("#{ast_node.class.name.split("::").last} '#{ast_node.name}' is missing required arguments: #{missing_names.join(", ")}", ast_node, context: context)
34
34
  end
35
35
  end
36
36
  end
@@ -15,11 +15,11 @@ module GraphQL
15
15
  def validate_default_value(node, literal_validator, context)
16
16
  value = node.default_value
17
17
  if node.type.is_a?(GraphQL::Language::Nodes::NonNullType)
18
- context.errors << message("Non-null variable $#{node.name} can't have a default value", node)
18
+ context.errors << message("Non-null variable $#{node.name} can't have a default value", node, context: context)
19
19
  else
20
20
  type = context.schema.type_from_ast(node.type)
21
21
  if !literal_validator.validate(value, type)
22
- context.errors << message("Default value for $#{node.name} doesn't match type #{type}", node)
22
+ context.errors << message("Default value for $#{node.name} doesn't match type #{type}", node, context: context)
23
23
  end
24
24
  end
25
25
  end
@@ -44,11 +44,11 @@ module GraphQL
44
44
  arg_inner_type = arg_defn_type.unwrap
45
45
 
46
46
  if var_inner_type != arg_inner_type
47
- context.errors << create_error("Type mismatch", var_type, ast_var, arg_defn, arg_node)
47
+ context.errors << create_error("Type mismatch", var_type, ast_var, arg_defn, arg_node, context)
48
48
  elsif list_dimension(var_type) != list_dimension(arg_defn_type)
49
- context.errors << create_error("List dimension mismatch", var_type, ast_var, arg_defn, arg_node)
49
+ context.errors << create_error("List dimension mismatch", var_type, ast_var, arg_defn, arg_node, context)
50
50
  elsif !non_null_levels_match(arg_defn_type, var_type)
51
- context.errors << create_error("Nullability mismatch", var_type, ast_var, arg_defn, arg_node)
51
+ context.errors << create_error("Nullability mismatch", var_type, ast_var, arg_defn, arg_node, context)
52
52
  end
53
53
  end
54
54
 
@@ -62,8 +62,8 @@ module GraphQL
62
62
  end
63
63
  end
64
64
 
65
- def create_error(error_message, var_type, ast_var, arg_defn, arg_node)
66
- message("#{error_message} on variable $#{ast_var.name} and argument #{arg_node.name} (#{var_type.to_s} / #{arg_defn.type.to_s})", arg_node)
65
+ def create_error(error_message, var_type, ast_var, arg_defn, arg_node, context)
66
+ message("#{error_message} on variable $#{ast_var.name} and argument #{arg_node.name} (#{var_type.to_s} / #{arg_defn.type.to_s})", arg_node, context: context)
67
67
  end
68
68
 
69
69
  def list_dimension(type)
@@ -15,7 +15,7 @@ module GraphQL
15
15
  type_name = get_type_name(node.type)
16
16
  type = context.schema.types[type_name]
17
17
  if !type.kind.input?
18
- context.errors << message("#{type.name} isn't a valid input type (on $#{node.name})", node)
18
+ context.errors << message("#{type.name} isn't a valid input type (on $#{node.name})", node, context: context)
19
19
  end
20
20
  end
21
21