graphql 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graph_ql/{types → definition_helpers}/argument_definer.rb +0 -0
  3. data/lib/graph_ql/definition_helpers/definable.rb +18 -0
  4. data/lib/graph_ql/{types → definition_helpers}/field_definer.rb +0 -0
  5. data/lib/graph_ql/definition_helpers/forwardable.rb +10 -0
  6. data/lib/graph_ql/definition_helpers/non_null_with_bang.rb +9 -0
  7. data/lib/graph_ql/definition_helpers/string_named_hash.rb +12 -0
  8. data/lib/graph_ql/{types → definition_helpers}/type_definer.rb +1 -0
  9. data/lib/graph_ql/directive.rb +9 -5
  10. data/lib/graph_ql/directives/directive_chain.rb +1 -1
  11. data/lib/graph_ql/field.rb +1 -5
  12. data/lib/graph_ql/parser/transform.rb +1 -3
  13. data/lib/graph_ql/query.rb +14 -3
  14. data/lib/graph_ql/query/arguments.rb +6 -5
  15. data/lib/graph_ql/query/field_resolution_strategy.rb +3 -3
  16. data/lib/graph_ql/{types → scalars}/boolean_type.rb +0 -0
  17. data/lib/graph_ql/{types → scalars}/float_type.rb +1 -1
  18. data/lib/graph_ql/scalars/id_type.rb +6 -0
  19. data/lib/graph_ql/{types → scalars}/int_type.rb +0 -0
  20. data/lib/graph_ql/{types → scalars}/scalar_type.rb +0 -4
  21. data/lib/graph_ql/{types → scalars}/string_type.rb +0 -0
  22. data/lib/graph_ql/static_validation.rb +7 -10
  23. data/lib/graph_ql/static_validation/all_rules.rb +23 -0
  24. data/lib/graph_ql/static_validation/literal_validator.rb +0 -3
  25. data/lib/graph_ql/static_validation/{argument_literals_are_compatible.rb → rules/argument_literals_are_compatible.rb} +0 -0
  26. data/lib/graph_ql/static_validation/{arguments_are_defined.rb → rules/arguments_are_defined.rb} +5 -0
  27. data/lib/graph_ql/static_validation/{directives_are_defined.rb → rules/directives_are_defined.rb} +0 -0
  28. data/lib/graph_ql/static_validation/{fields_are_defined_on_type.rb → rules/fields_are_defined_on_type.rb} +0 -0
  29. data/lib/graph_ql/static_validation/{fields_have_appropriate_selections.rb → rules/fields_have_appropriate_selections.rb} +0 -0
  30. data/lib/graph_ql/static_validation/{fields_will_merge.rb → rules/fields_will_merge.rb} +13 -5
  31. data/lib/graph_ql/static_validation/rules/fragment_spreads_are_possible.rb +50 -0
  32. data/lib/graph_ql/static_validation/{fragment_types_exist.rb → rules/fragment_types_exist.rb} +0 -0
  33. data/lib/graph_ql/static_validation/rules/fragments_are_finite.rb +23 -0
  34. data/lib/graph_ql/static_validation/rules/fragments_are_on_composite_types.rb +27 -0
  35. data/lib/graph_ql/static_validation/{fragments_are_used.rb → rules/fragments_are_used.rb} +0 -0
  36. data/lib/graph_ql/static_validation/{required_arguments_are_present.rb → rules/required_arguments_are_present.rb} +0 -0
  37. data/lib/graph_ql/static_validation/rules/variable_default_values_are_correctly_typed.rb +24 -0
  38. data/lib/graph_ql/static_validation/rules/variable_usages_are_allowed.rb +47 -0
  39. data/lib/graph_ql/static_validation/rules/variables_are_input_types.rb +27 -0
  40. data/lib/graph_ql/static_validation/rules/variables_are_used_and_defined.rb +31 -0
  41. data/lib/graph_ql/static_validation/type_stack.rb +13 -1
  42. data/lib/graph_ql/static_validation/validator.rb +14 -18
  43. data/lib/graph_ql/type_kinds.rb +8 -4
  44. data/lib/graph_ql/{enum.rb → types/enum.rb} +7 -6
  45. data/lib/graph_ql/types/input_object_type.rb +3 -10
  46. data/lib/graph_ql/{interface.rb → types/interface.rb} +0 -4
  47. data/lib/graph_ql/types/list_type.rb +0 -4
  48. data/lib/graph_ql/types/non_null_type.rb +0 -4
  49. data/lib/graph_ql/types/object_type.rb +28 -13
  50. data/lib/graph_ql/{union.rb → types/union.rb} +0 -4
  51. data/lib/graph_ql/version.rb +1 -1
  52. data/lib/graphql.rb +9 -40
  53. data/readme.md +26 -20
  54. data/spec/graph_ql/directive_spec.rb +4 -4
  55. data/spec/graph_ql/introspection/directive_type_spec.rb +2 -2
  56. data/spec/graph_ql/introspection/schema_type_spec.rb +3 -2
  57. data/spec/graph_ql/introspection/type_type_spec.rb +7 -7
  58. data/spec/graph_ql/parser/parser_spec.rb +6 -2
  59. data/spec/graph_ql/query_spec.rb +26 -8
  60. data/spec/graph_ql/scalars/id_type_spec.rb +24 -0
  61. data/spec/graph_ql/schema/type_reducer_spec.rb +1 -0
  62. data/spec/graph_ql/schema/type_validator_spec.rb +1 -1
  63. data/spec/graph_ql/static_validation/{argument_literals_are_compatible_spec.rb → rules/argument_literals_are_compatible_spec.rb} +1 -1
  64. data/spec/graph_ql/static_validation/{arguments_are_defined_spec.rb → rules/arguments_are_defined_spec.rb} +1 -1
  65. data/spec/graph_ql/static_validation/{directives_are_defined_spec.rb → rules/directives_are_defined_spec.rb} +1 -1
  66. data/spec/graph_ql/static_validation/{fields_are_defined_on_type_spec.rb → rules/fields_are_defined_on_type_spec.rb} +1 -1
  67. data/spec/graph_ql/static_validation/{fields_have_appropriate_selections_spec.rb → rules/fields_have_appropriate_selections_spec.rb} +1 -1
  68. data/spec/graph_ql/static_validation/{fields_will_merge_spec.rb → rules/fields_will_merge_spec.rb} +1 -1
  69. data/spec/graph_ql/static_validation/rules/fragment_spreads_are_possible_spec.rb +46 -0
  70. data/spec/graph_ql/static_validation/{fragment_types_exist_spec.rb → rules/fragment_types_exist_spec.rb} +1 -1
  71. data/spec/graph_ql/static_validation/rules/fragments_are_finite_spec.rb +43 -0
  72. data/spec/graph_ql/static_validation/rules/fragments_are_on_composite_types_spec.rb +48 -0
  73. data/spec/graph_ql/static_validation/{fragments_are_used_spec.rb → rules/fragments_are_used_spec.rb} +1 -1
  74. data/spec/graph_ql/static_validation/{required_arguments_are_present_spec.rb → rules/required_arguments_are_present_spec.rb} +1 -1
  75. data/spec/graph_ql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +43 -0
  76. data/spec/graph_ql/static_validation/rules/variable_usages_are_allowed_spec.rb +45 -0
  77. data/spec/graph_ql/static_validation/rules/variables_are_input_types_spec.rb +36 -0
  78. data/spec/graph_ql/static_validation/rules/variables_are_used_and_defined_spec.rb +44 -0
  79. data/spec/graph_ql/static_validation/type_stack_spec.rb +2 -2
  80. data/spec/graph_ql/static_validation/validator_spec.rb +44 -5
  81. data/spec/graph_ql/types/enum_spec.rb +10 -0
  82. data/spec/graph_ql/{interface_spec.rb → types/interface_spec.rb} +1 -1
  83. data/spec/graph_ql/types/object_type_spec.rb +13 -0
  84. data/spec/graph_ql/{union_spec.rb → types/union_spec.rb} +0 -0
  85. data/spec/support/dummy_app.rb +7 -6
  86. data/spec/support/dummy_data.rb +3 -3
  87. metadata +74 -46
  88. data/lib/graph_ql/types/non_null_with_bang.rb +0 -5
  89. data/spec/graph_ql/enum_spec.rb +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 45c3f35aae5e941e7bad2109eb9405e6afc081cc
4
- data.tar.gz: 3cd3d8dd85a88e341552d0709aff11361937d157
3
+ metadata.gz: 9cf6add0a41fbbf194c90bd12da917467ae612d7
4
+ data.tar.gz: b0e7b7f03eb0e6a9def2f7402f3349e69d07ec33
5
5
  SHA512:
6
- metadata.gz: 274f08f78112de84f94dcb9fcdcc62da63a36a29000aad2055cf5e353327bb9ba3f673fe3e83c3a3c6405b360847f17104ab97c66a08323400f6df3f27f6044a
7
- data.tar.gz: 82f47b290d9779b1214bb59857edb079369932d08686a8ac1af47c15ab1f3e549e4c8f9f269947836fe2093fc49badc6986e397466cb2d1010520bc3a765e7ff
6
+ metadata.gz: f5d0f097d277cdd8e40976ce87ef5d57edf365c2fadf821de5c90d7dad9f9d7819a21291c6b01a83ab05e1d7850617bbbe0382c492776b04e5fa619e186e8caa
7
+ data.tar.gz: ee252ea72993ee9c07ce9d69f6f25c0b6d13e9f7f2bf7cbbb419d8d2b01f48a9c7e57310c6cc05e051c833ee72b42494399bd5252813a4641d1ba3e88be17d16
@@ -0,0 +1,18 @@
1
+ # Define attributes which can be assigned & read, or
2
+ # "defined", by passing the new value as an argument
3
+ #
4
+ # @example defining an object's name
5
+ # object.name("New name")
6
+ #
7
+ module GraphQL::Definable
8
+ def attr_definable(*names)
9
+ attr_accessor(*names)
10
+ names.each do |name|
11
+ ivar_name = "@#{name}".to_sym
12
+ define_method(name) do |new_value=nil|
13
+ new_value && self.instance_variable_set(ivar_name, new_value)
14
+ instance_variable_get(ivar_name)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ # Get `delegate` like Rails has
2
+ module GraphQL::Forwardable
3
+ def delegate(*methods, to:)
4
+ methods.each do |method_name|
5
+ define_method(method_name) do |*args|
6
+ self.public_send(to).public_send(method_name, *args)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ # Wrap the object in NonNullType in response to `!`
2
+ # @example required Int type
3
+ # !GraphQL::INT_TYPE
4
+ #
5
+ module GraphQL::NonNullWithBang
6
+ def !
7
+ GraphQL::NonNullType.new(of_type: self)
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ # Accepts a hash with symbol keys.
2
+ # - convert keys to strings
3
+ # - if the value responds to `name=`, then assign the hash key as `name`
4
+ class GraphQL::StringNamedHash
5
+ attr_reader :to_h
6
+ def initialize(input_hash)
7
+ @to_h = input_hash
8
+ .reduce({}) { |memo, (key, value)| memo[key.to_s] = value; memo }
9
+ # Set the name of the value based on its key
10
+ @to_h.each {|k, v| v.respond_to?("name=") && v.name = k }
11
+ end
12
+ end
@@ -5,6 +5,7 @@ class GraphQL::TypeDefiner
5
5
  def String; GraphQL::STRING_TYPE; end
6
6
  def Float; GraphQL::FLOAT_TYPE; end
7
7
  def Boolean; GraphQL::BOOLEAN_TYPE; end
8
+ def ID; GraphQL::ID_TYPE; end
8
9
 
9
10
  def [](type)
10
11
  GraphQL::ListType.new(of_type: type)
@@ -1,4 +1,7 @@
1
- class GraphQL::Directive < GraphQL::ObjectType
1
+ class GraphQL::Directive
2
+ extend GraphQL::Definable
3
+ attr_definable :on, :arguments, :name, :description
4
+
2
5
  LOCATIONS = [
3
6
  ON_OPERATION = :on_operation?,
4
7
  ON_FRAGMENT = :on_fragment?,
@@ -8,7 +11,6 @@ class GraphQL::Directive < GraphQL::ObjectType
8
11
  define_method(location) { self.on.include?(location) }
9
12
  end
10
13
 
11
- attr_definable :on, :arguments
12
14
 
13
15
  def initialize
14
16
  @arguments = {}
@@ -27,12 +29,14 @@ class GraphQL::Directive < GraphQL::ObjectType
27
29
 
28
30
  def arguments(new_arguments=nil)
29
31
  if !new_arguments.nil?
30
- @arguments = new_arguments
31
- .reduce({}) {|memo, (k, v)| memo[k.to_s] = v; memo}
32
- .each { |k, v| v.respond_to?("name=") && v.name = k}
32
+ @arguments = GraphQL::StringNamedHash.new(new_arguments).to_h
33
33
  end
34
34
  @arguments
35
35
  end
36
+
37
+ def to_s
38
+ "<GraphQL::Directive #{name}>"
39
+ end
36
40
  end
37
41
 
38
42
  require 'graph_ql/directives/directive_chain'
@@ -24,7 +24,7 @@ class GraphQL::DirectiveChain
24
24
  @result = block.call
25
25
  else
26
26
  applicable_directives.map do |(ast_directive, directive)|
27
- args = GraphQL::Query::Arguments.new(ast_directive.arguments, operation_resolver.variables).to_h
27
+ args = GraphQL::Query::Arguments.new(ast_directive.arguments, directive.arguments, operation_resolver.variables).to_h
28
28
  @result = directive.resolve(args, block)
29
29
  end
30
30
  @result ||= {}
@@ -16,11 +16,7 @@ class GraphQL::Field
16
16
  end
17
17
 
18
18
  def arguments=(new_arguments)
19
- stringified_arguments = new_arguments
20
- .reduce({}) { |memo, (key, value)| memo[key.to_s] = value; memo }
21
- # Set the name from its context on this type:
22
- stringified_arguments.each {|k, v| v.respond_to?("name=") && v.name = k }
23
- @arguments = stringified_arguments
19
+ @arguments = GraphQL::StringNamedHash.new(new_arguments).to_h
24
20
  end
25
21
 
26
22
 
@@ -1,9 +1,7 @@
1
1
  # {Transform} is a [parslet](http://kschiess.github.io/parslet/) transform for for turning the AST into objects in {GraphQL::Nodes} objects.
2
2
  class GraphQL::Transform < Parslet::Transform
3
3
  # Get syntax classes by shallow name:
4
- def self.const_missing(constant_name)
5
- GraphQL::Nodes.const_get(constant_name)
6
- end
4
+ include GraphQL::Nodes
7
5
 
8
6
  def self.optional_sequence(name)
9
7
  rule(name => simple(:val)) { [] }
@@ -9,7 +9,7 @@ class GraphQL::Query
9
9
  @schema = schema
10
10
  @debug = debug
11
11
  @query_string = query_string
12
- @context = context
12
+ @context = Context.new(context)
13
13
  @params = params
14
14
  @validate = validate
15
15
  @fragments = {}
@@ -48,8 +48,7 @@ class GraphQL::Query
48
48
  def execute
49
49
  @operations.reduce({}) do |memo, (name, operation)|
50
50
  resolver = OperationResolver.new(operation, self)
51
- memo[name] = resolver.result
52
- memo
51
+ memo.merge(resolver.result)
53
52
  end
54
53
  end
55
54
 
@@ -62,6 +61,18 @@ class GraphQL::Query
62
61
  end
63
62
  end
64
63
  end
64
+
65
+ # Expose some query-specific info to field resolve functions.
66
+ # It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`.
67
+ class Context
68
+ def initialize(arbitrary_hash)
69
+ @arbitrary_hash = arbitrary_hash
70
+ end
71
+
72
+ def [](key)
73
+ @arbitrary_hash[key]
74
+ end
75
+ end
65
76
  end
66
77
 
67
78
  require 'graph_ql/query/arguments'
@@ -1,9 +1,10 @@
1
1
  # Creates a plain hash out of arguments, looking up variables if necessary
2
2
  class GraphQL::Query::Arguments
3
3
  attr_reader :to_h
4
- def initialize(ast_arguments, variables)
4
+ def initialize(ast_arguments, argument_hash, variables)
5
5
  @to_h = ast_arguments.reduce({}) do |memo, arg|
6
- value = reduce_value(arg.value, variables)
6
+ arg_defn = argument_hash[arg.name]
7
+ value = reduce_value(arg.value, arg_defn, variables)
7
8
  memo[arg.name] = value
8
9
  memo
9
10
  end
@@ -11,13 +12,13 @@ class GraphQL::Query::Arguments
11
12
 
12
13
  private
13
14
 
14
- def reduce_value(value, variables)
15
+ def reduce_value(value, arg_defn, variables)
15
16
  if value.is_a?(GraphQL::Nodes::VariableIdentifier)
16
17
  value = variables[value.name]
17
18
  elsif value.is_a?(GraphQL::Nodes::Enum)
18
- value = value.name
19
+ value = arg_defn.type.coerce(value.name)
19
20
  elsif value.is_a?(GraphQL::Nodes::InputObject)
20
- value = self.class.new(value.pairs, variables).to_h
21
+ value = self.class.new(value.pairs, arg_defn.type.input_fields, variables).to_h
21
22
  else
22
23
  value
23
24
  end
@@ -2,9 +2,9 @@ class GraphQL::Query::FieldResolutionStrategy
2
2
  attr_reader :result, :result_value
3
3
 
4
4
  def initialize(ast_field, parent_type, target, operation_resolver)
5
- arguments = GraphQL::Query::Arguments.new(ast_field.arguments, operation_resolver.variables).to_h
6
5
  field_name = ast_field.name
7
6
  field = parent_type.fields[field_name] || raise("No field found on #{parent_type.name} '#{parent_type}' for '#{field_name}'")
7
+ arguments = GraphQL::Query::Arguments.new(ast_field.arguments, field.arguments, operation_resolver.variables).to_h
8
8
  value = field.resolve(target, arguments, operation_resolver.context)
9
9
  if value.nil?
10
10
  @result_value = value
@@ -40,9 +40,9 @@ class GraphQL::Query::FieldResolutionStrategy
40
40
  attr_reader :result
41
41
  def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
42
42
  wrapped_type = field_type.of_type
43
- resolved_type = wrapped_type.kind.resolve(wrapped_type, value)
44
- strategy_class = GraphQL::Query::FieldResolutionStrategy.get_strategy_for_kind(resolved_type.kind)
45
43
  @result = value.map do |item|
44
+ resolved_type = wrapped_type.kind.resolve(wrapped_type, item)
45
+ strategy_class = GraphQL::Query::FieldResolutionStrategy.get_strategy_for_kind(resolved_type.kind)
46
46
  inner_strategy = strategy_class.new(item, resolved_type, target, parent_type, ast_field, operation_resolver)
47
47
  inner_strategy.result
48
48
  end
File without changes
@@ -1,6 +1,6 @@
1
1
  GraphQL::FLOAT_TYPE = GraphQL::ScalarType.new do |t|
2
2
  t.name "Float"
3
3
  def t.coerce(value)
4
- value.to_f
4
+ value.respond_to?(:to_f) ? value.to_f : nil
5
5
  end
6
6
  end
@@ -0,0 +1,6 @@
1
+ GraphQL::ID_TYPE = GraphQL::ScalarType.new do |t|
2
+ t.name "ID"
3
+ def t.coerce(value)
4
+ value.to_s
5
+ end
6
+ end
File without changes
@@ -2,8 +2,4 @@ class GraphQL::ScalarType < GraphQL::ObjectType
2
2
  def kind
3
3
  GraphQL::TypeKinds::SCALAR
4
4
  end
5
-
6
- def to_s
7
- "<GraphQL::ScalarType #{name} >"
8
- end
9
5
  end
File without changes
@@ -3,16 +3,13 @@ end
3
3
 
4
4
  require 'graph_ql/static_validation/message'
5
5
  require 'graph_ql/static_validation/arguments_validator'
6
-
7
- require 'graph_ql/static_validation/argument_literals_are_compatible'
8
- require 'graph_ql/static_validation/arguments_are_defined'
9
- require 'graph_ql/static_validation/required_arguments_are_present'
10
- require 'graph_ql/static_validation/fragment_types_exist'
11
- require 'graph_ql/static_validation/directives_are_defined'
12
- require 'graph_ql/static_validation/fields_are_defined_on_type'
13
- require 'graph_ql/static_validation/fields_have_appropriate_selections'
14
- require 'graph_ql/static_validation/fields_will_merge'
15
- require 'graph_ql/static_validation/fragments_are_used'
16
6
  require 'graph_ql/static_validation/type_stack'
17
7
  require 'graph_ql/static_validation/validator'
18
8
  require 'graph_ql/static_validation/literal_validator'
9
+
10
+ rules_glob = File.expand_path("../static_validation/rules/*.rb", __FILE__)
11
+ Dir.glob(rules_glob).each do |file|
12
+ require(file)
13
+ end
14
+
15
+ require 'graph_ql/static_validation/all_rules'
@@ -0,0 +1,23 @@
1
+ # Default rules for {GraphQL::StaticValidation::Validator}
2
+ #
3
+ # Order is important here. Some validators return {GraphQL::Visitor::SKIP}
4
+ # which stops the visit on that node. That way it doesn't try to find fields on types that
5
+ # don't exist, etc.
6
+ GraphQL::StaticValidation::ALL_RULES = [
7
+ GraphQL::StaticValidation::DirectivesAreDefined,
8
+ GraphQL::StaticValidation::FragmentsAreFinite,
9
+ GraphQL::StaticValidation::FragmentTypesExist,
10
+ GraphQL::StaticValidation::FragmentsAreOnCompositeTypes,
11
+ GraphQL::StaticValidation::FragmentSpreadsArePossible,
12
+ GraphQL::StaticValidation::FragmentsAreUsed,
13
+ GraphQL::StaticValidation::FieldsAreDefinedOnType,
14
+ GraphQL::StaticValidation::FieldsWillMerge,
15
+ GraphQL::StaticValidation::FieldsHaveAppropriateSelections,
16
+ GraphQL::StaticValidation::ArgumentsAreDefined,
17
+ GraphQL::StaticValidation::ArgumentLiteralsAreCompatible,
18
+ GraphQL::StaticValidation::RequiredArgumentsArePresent,
19
+ GraphQL::StaticValidation::VariablesAreInputTypes,
20
+ GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTyped,
21
+ GraphQL::StaticValidation::VariablesAreUsedAndDefined,
22
+ GraphQL::StaticValidation::VariableUsagesAreAllowed,
23
+ ]
@@ -1,8 +1,5 @@
1
1
  # Test whether `ast_value` is a valid input for `type`
2
2
  class GraphQL::StaticValidation::LiteralValidator
3
- include GraphQL::StaticValidation::Message::MessageHelper
4
-
5
- attr_reader :errors
6
3
  def validate(ast_value, type)
7
4
  if type.kind.non_null?
8
5
  (!ast_value.nil?) && validate(ast_value, type.of_type)
@@ -1,10 +1,15 @@
1
1
  class GraphQL::StaticValidation::ArgumentsAreDefined < GraphQL::StaticValidation::ArgumentsValidator
2
2
  def validate_node(node, defn, context)
3
+ skip = nil
4
+
3
5
  node.arguments.each do |argument|
4
6
  argument_defn = defn.arguments[argument.name]
5
7
  if argument_defn.nil?
6
8
  context.errors << message("#{node.class.name.split("::").last} '#{node.name}' doesn't accept argument #{argument.name}", node)
9
+ skip = GraphQL::Visitor::SKIP
7
10
  end
8
11
  end
12
+
13
+ skip
9
14
  end
10
15
  end
@@ -10,7 +10,7 @@ class GraphQL::StaticValidation::FieldsWillMerge
10
10
  }
11
11
  visitor[GraphQL::Nodes::Document].leave << -> (node, parent) {
12
12
  has_selections.each { |node|
13
- field_map = gather_fields_by_name(node.selections, {}, context)
13
+ field_map = gather_fields_by_name(node.selections, {}, [], context)
14
14
  find_conflicts(field_map, context)
15
15
  }
16
16
  }
@@ -23,18 +23,26 @@ class GraphQL::StaticValidation::FieldsWillMerge
23
23
  comparison = FieldDefinitionComparison.new(name, ast_fields)
24
24
  context.errors.push(*comparison.errors)
25
25
 
26
- subfield_map = ast_fields.reduce({}) do |memo, defn|
27
- gather_fields_by_name(defn.selections, memo, context)
26
+
27
+ subfield_map = {}
28
+ visited_fragments = []
29
+ ast_fields.each do |defn|
30
+ gather_fields_by_name(defn.selections, subfield_map, visited_fragments, context)
28
31
  end
29
32
  find_conflicts(subfield_map, context)
30
33
  end
31
34
  end
32
35
 
33
- def gather_fields_by_name(fields, field_map, context)
36
+ def gather_fields_by_name(fields, field_map, visited_fragments, context)
34
37
  fields.each do |field|
35
38
  if field.is_a?(GraphQL::Nodes::InlineFragment)
36
39
  next_fields = field.selections
37
40
  elsif field.is_a?(GraphQL::Nodes::FragmentSpread)
41
+ if visited_fragments.include?(field.name)
42
+ next
43
+ else
44
+ visited_fragments << field.name
45
+ end
38
46
  fragment = context.fragments[field.name]
39
47
  next_fields = fragment.selections
40
48
  else
@@ -43,7 +51,7 @@ class GraphQL::StaticValidation::FieldsWillMerge
43
51
  field_map[name_in_selection].push(field)
44
52
  next_fields = []
45
53
  end
46
- gather_fields_by_name(next_fields, field_map, context)
54
+ gather_fields_by_name(next_fields, field_map, visited_fragments, context)
47
55
  end
48
56
  field_map
49
57
  end
@@ -0,0 +1,50 @@
1
+ class GraphQL::StaticValidation::FragmentSpreadsArePossible
2
+ include GraphQL::StaticValidation::Message::MessageHelper
3
+
4
+ def validate(context)
5
+
6
+ context.visitor[GraphQL::Nodes::InlineFragment] << -> (node, parent) {
7
+ fragment_parent = context.object_types[-2]
8
+ fragment_child = context.object_types.last
9
+ validate_fragment_in_scope(fragment_parent, fragment_child, node, context)
10
+ }
11
+
12
+ spreads_to_validate = []
13
+
14
+ context.visitor[GraphQL::Nodes::FragmentSpread] << -> (node, parent) {
15
+ fragment_parent = context.object_types.last
16
+ spreads_to_validate << [node, fragment_parent]
17
+ }
18
+
19
+ context.visitor[GraphQL::Nodes::Document].leave << -> (node, parent) {
20
+ spreads_to_validate.each do |spread_values|
21
+ node, fragment_parent = spread_values
22
+ fragment_child_name = context.fragments[node.name].type
23
+ fragment_child = context.schema.types[fragment_child_name]
24
+ validate_fragment_in_scope(fragment_parent, fragment_child, node, context)
25
+ end
26
+ }
27
+ end
28
+
29
+ private
30
+
31
+ def validate_fragment_in_scope(parent_type, child_type, node, context)
32
+ intersecting_types = get_possible_types(parent_type) & get_possible_types(child_type)
33
+ if intersecting_types.none?
34
+ name = node.respond_to?(:name) ? " #{node.name}" : ""
35
+ context.errors << message("Fragment#{name} on #{child_type.name} can't be spread inside #{parent_type.name}", node)
36
+ end
37
+ end
38
+
39
+ def get_possible_types(type)
40
+ if type.kind.wraps?
41
+ get_possible_types(type.of_type)
42
+ elsif type.kind.object?
43
+ [type]
44
+ elsif type.kind.resolves?
45
+ type.possible_types
46
+ else
47
+ []
48
+ end
49
+ end
50
+ end