graphql 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graph_ql/directive.rb +9 -5
  3. data/lib/graph_ql/directives/include_directive.rb +2 -2
  4. data/lib/graph_ql/directives/skip_directive.rb +2 -2
  5. data/lib/graph_ql/enum.rb +5 -8
  6. data/lib/graph_ql/field.rb +50 -0
  7. data/lib/graph_ql/interface.rb +4 -0
  8. data/lib/graph_ql/introspection/arguments_field.rb +2 -2
  9. data/lib/graph_ql/introspection/directive_type.rb +10 -10
  10. data/lib/graph_ql/introspection/enum_value_type.rb +11 -8
  11. data/lib/graph_ql/introspection/enum_values_field.rb +5 -5
  12. data/lib/graph_ql/introspection/field_type.rb +14 -10
  13. data/lib/graph_ql/introspection/fields_field.rb +4 -4
  14. data/lib/graph_ql/introspection/input_fields_field.rb +3 -3
  15. data/lib/graph_ql/introspection/input_value_type.rb +8 -8
  16. data/lib/graph_ql/introspection/interfaces_field.rb +5 -0
  17. data/lib/graph_ql/introspection/of_type_field.rb +3 -9
  18. data/lib/graph_ql/introspection/possible_types_field.rb +3 -10
  19. data/lib/graph_ql/introspection/schema_type.rb +8 -14
  20. data/lib/graph_ql/introspection/type_kind_enum.rb +1 -1
  21. data/lib/graph_ql/introspection/type_type.rb +17 -19
  22. data/lib/graph_ql/introspection/typename_field.rb +15 -0
  23. data/lib/graph_ql/parser.rb +9 -0
  24. data/lib/graph_ql/parser/nodes.rb +17 -7
  25. data/lib/graph_ql/parser/parser.rb +7 -7
  26. data/lib/graph_ql/parser/transform.rb +23 -20
  27. data/lib/graph_ql/parser/visitor.rb +29 -13
  28. data/lib/graph_ql/query.rb +39 -16
  29. data/lib/graph_ql/query/field_resolution_strategy.rb +15 -11
  30. data/lib/graph_ql/query/type_resolver.rb +4 -2
  31. data/lib/graph_ql/repl.rb +1 -1
  32. data/lib/graph_ql/schema.rb +19 -7
  33. data/lib/graph_ql/schema/each_item_validator.rb +12 -0
  34. data/lib/graph_ql/schema/field_validator.rb +13 -0
  35. data/lib/graph_ql/schema/implementation_validator.rb +21 -0
  36. data/lib/graph_ql/schema/schema_validator.rb +10 -0
  37. data/lib/graph_ql/schema/type_reducer.rb +6 -6
  38. data/lib/graph_ql/schema/type_validator.rb +47 -0
  39. data/lib/graph_ql/static_validation.rb +18 -0
  40. data/lib/graph_ql/static_validation/argument_literals_are_compatible.rb +13 -0
  41. data/lib/graph_ql/static_validation/arguments_are_defined.rb +10 -0
  42. data/lib/graph_ql/static_validation/arguments_validator.rb +16 -0
  43. data/lib/graph_ql/static_validation/directives_are_defined.rb +18 -0
  44. data/lib/graph_ql/static_validation/fields_are_defined_on_type.rb +29 -0
  45. data/lib/graph_ql/static_validation/fields_have_appropriate_selections.rb +31 -0
  46. data/lib/graph_ql/static_validation/fields_will_merge.rb +93 -0
  47. data/lib/graph_ql/static_validation/fragment_types_exist.rb +24 -0
  48. data/lib/graph_ql/static_validation/fragments_are_used.rb +30 -0
  49. data/lib/graph_ql/static_validation/literal_validator.rb +27 -0
  50. data/lib/graph_ql/static_validation/message.rb +29 -0
  51. data/lib/graph_ql/static_validation/required_arguments_are_present.rb +13 -0
  52. data/lib/graph_ql/static_validation/type_stack.rb +87 -0
  53. data/lib/graph_ql/static_validation/validator.rb +48 -0
  54. data/lib/graph_ql/type_kinds.rb +50 -12
  55. data/lib/graph_ql/types/argument_definer.rb +7 -0
  56. data/lib/graph_ql/types/boolean_type.rb +3 -3
  57. data/lib/graph_ql/types/field_definer.rb +19 -0
  58. data/lib/graph_ql/types/float_type.rb +3 -3
  59. data/lib/graph_ql/types/input_object_type.rb +4 -0
  60. data/lib/graph_ql/types/input_value.rb +1 -1
  61. data/lib/graph_ql/types/int_type.rb +4 -4
  62. data/lib/graph_ql/types/list_type.rb +5 -1
  63. data/lib/graph_ql/types/non_null_type.rb +4 -0
  64. data/lib/graph_ql/types/object_type.rb +9 -20
  65. data/lib/graph_ql/types/scalar_type.rb +4 -0
  66. data/lib/graph_ql/types/string_type.rb +3 -3
  67. data/lib/graph_ql/types/type_definer.rb +5 -9
  68. data/lib/graph_ql/union.rb +6 -17
  69. data/lib/graph_ql/version.rb +1 -1
  70. data/lib/graphql.rb +58 -78
  71. data/readme.md +80 -7
  72. data/spec/graph_ql/interface_spec.rb +15 -1
  73. data/spec/graph_ql/introspection/directive_type_spec.rb +2 -2
  74. data/spec/graph_ql/introspection/schema_type_spec.rb +2 -1
  75. data/spec/graph_ql/introspection/type_type_spec.rb +16 -1
  76. data/spec/graph_ql/parser/parser_spec.rb +3 -1
  77. data/spec/graph_ql/parser/transform_spec.rb +12 -2
  78. data/spec/graph_ql/parser/visitor_spec.rb +13 -5
  79. data/spec/graph_ql/query_spec.rb +25 -13
  80. data/spec/graph_ql/schema/field_validator_spec.rb +21 -0
  81. data/spec/graph_ql/schema/type_reducer_spec.rb +2 -2
  82. data/spec/graph_ql/schema/type_validator_spec.rb +54 -0
  83. data/spec/graph_ql/static_validation/argument_literals_are_compatible_spec.rb +41 -0
  84. data/spec/graph_ql/static_validation/arguments_are_defined_spec.rb +40 -0
  85. data/spec/graph_ql/static_validation/directives_are_defined_spec.rb +33 -0
  86. data/spec/graph_ql/static_validation/fields_are_defined_on_type_spec.rb +59 -0
  87. data/spec/graph_ql/static_validation/fields_have_appropriate_selections_spec.rb +30 -0
  88. data/spec/graph_ql/{validations → static_validation}/fields_will_merge_spec.rb +24 -17
  89. data/spec/graph_ql/static_validation/fragment_types_exist_spec.rb +38 -0
  90. data/spec/graph_ql/static_validation/fragments_are_used_spec.rb +24 -0
  91. data/spec/graph_ql/static_validation/required_arguments_are_present_spec.rb +41 -0
  92. data/spec/graph_ql/static_validation/type_stack_spec.rb +35 -0
  93. data/spec/graph_ql/static_validation/validator_spec.rb +28 -0
  94. data/spec/graph_ql/types/object_type_spec.rb +1 -1
  95. data/spec/graph_ql/union_spec.rb +1 -14
  96. data/spec/support/dummy_app.rb +75 -53
  97. metadata +53 -31
  98. data/lib/graph_ql/fields/abstract_field.rb +0 -37
  99. data/lib/graph_ql/fields/access_field.rb +0 -24
  100. data/lib/graph_ql/fields/field.rb +0 -34
  101. data/lib/graph_ql/types/abstract_type.rb +0 -14
  102. data/lib/graph_ql/validations/fields_are_defined_on_type.rb +0 -44
  103. data/lib/graph_ql/validations/fields_will_merge.rb +0 -80
  104. data/lib/graph_ql/validations/fragments_are_used.rb +0 -24
  105. data/lib/graph_ql/validator.rb +0 -29
  106. data/spec/graph_ql/validations/fields_are_defined_on_type_spec.rb +0 -28
  107. data/spec/graph_ql/validations/fragments_are_used_spec.rb +0 -28
  108. data/spec/graph_ql/validator_spec.rb +0 -24
@@ -1,20 +1,19 @@
1
1
  class GraphQL::Schema
2
- extend ActiveSupport::Autoload
3
- autoload(:TypeReducer)
4
2
  DIRECTIVES = [GraphQL::SkipDirective, GraphQL::IncludeDirective]
5
3
 
6
- attr_reader :query, :mutation, :directives
4
+ attr_reader :query, :mutation, :directives, :static_validator
7
5
  def initialize(query:, mutation:)
8
6
  # Add fields to this query root for introspection:
9
7
  query.fields = query.fields.merge({
10
- "__type" => GraphQL::Field.new do |f|
8
+ "__type" => GraphQL::Field.new do |f, type, field, arg|
11
9
  f.description("A type in the GraphQL system")
12
- f.type(!GraphQL::TypeType)
13
- f.resolve -> (o, a, c) { self.types[a["name"]] || raise("No type found in schema for '#{a["name"]}'") }
10
+ f.arguments({name: arg.build(type: !type.String)})
11
+ f.type(!GraphQL::Introspection::TypeType)
12
+ f.resolve -> (o, a, c) { self.types[a["name"]] }
14
13
  end,
15
14
  "__schema" => GraphQL::Field.new do |f|
16
15
  f.description("This GraphQL schema")
17
- f.type(!GraphQL::SchemaType)
16
+ f.type(!GraphQL::Introspection::SchemaType)
18
17
  f.resolve -> (o, a, c) { self }
19
18
  end
20
19
  })
@@ -22,9 +21,22 @@ class GraphQL::Schema
22
21
  @query = query
23
22
  @mutation = mutation
24
23
  @directives = DIRECTIVES.reduce({}) { |m, d| m[d.name] = d; m }
24
+ @static_validator = GraphQL::StaticValidation::Validator.new(schema: self)
25
+
26
+ errors = SchemaValidator.new.validate(self)
27
+ if errors.any?
28
+ raise("Schema is invalid: \n#{errors.join("\n")}")
29
+ end
25
30
  end
26
31
 
27
32
  def types
28
33
  @types ||= TypeReducer.new(query, {}).result
29
34
  end
30
35
  end
36
+
37
+ require 'graph_ql/schema/each_item_validator'
38
+ require 'graph_ql/schema/field_validator'
39
+ require 'graph_ql/schema/implementation_validator'
40
+ require 'graph_ql/schema/schema_validator'
41
+ require 'graph_ql/schema/type_reducer'
42
+ require 'graph_ql/schema/type_validator'
@@ -0,0 +1,12 @@
1
+ class GraphQL::Schema::EachItemValidator
2
+ def initialize(errors)
3
+ @errors = errors
4
+ end
5
+
6
+ def validate(items, as:, must_be:)
7
+ invalid_items = items.select {|k| !yield(k) }
8
+ if invalid_items.any?
9
+ @errors << "#{as} must be #{must_be}, but some aren't: #{invalid_items.map(&:to_s).join(", ")}"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ class GraphQL::Schema::FieldValidator
2
+ def validate(field, errors)
3
+ implementation = GraphQL::Schema::ImplementationValidator.new(field, as: "Field", errors: errors)
4
+ implementation.must_respond_to(:name)
5
+ implementation.must_respond_to(:type)
6
+ implementation.must_respond_to(:description)
7
+ implementation.must_respond_to(:arguments) do |arguments|
8
+ validator = GraphQL::Schema::EachItemValidator.new(errors)
9
+ validator.validate(arguments.keys, as: "#{field.name}.arguments keys", must_be: "Strings") { |k| k.is_a?(String) }
10
+ end
11
+ implementation.must_respond_to(:deprecation_reason)
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ # A helper to ensure `object` implements the concept `as`
2
+ class GraphQL::Schema::ImplementationValidator
3
+ attr_reader :object, :errors, :implementation_as
4
+ def initialize(object, as:, errors:)
5
+ @object = object
6
+ @implementation_as = as
7
+ @errors = errors
8
+ end
9
+
10
+ # Ensure the object responds to `method_name`.
11
+ # If `block_given?`, yield the return value of that method
12
+ # If provided, use `as` in the error message, overriding class-level `as`.
13
+ def must_respond_to(method_name, args: [], as: nil)
14
+ if !object.respond_to?(method_name)
15
+ local_as = as || implementation_as
16
+ errors << "#{object.to_s} must respond to ##{method_name}(#{args.join(", ")}) to be a #{local_as}"
17
+ elsif block_given?
18
+ yield(object.public_send(method_name))
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,10 @@
1
+ class GraphQL::Schema::SchemaValidator
2
+ def validate(schema)
3
+ errors = []
4
+ schema.types.each do |name, type|
5
+ type_validator = GraphQL::Schema::TypeValidator.new
6
+ type_validator.validate(type, errors)
7
+ end
8
+ errors
9
+ end
10
+ end
@@ -1,6 +1,6 @@
1
+ # Starting from a given type, discover other types in the system by
2
+ # traversing that type's fields, possible_types, etc
1
3
  class GraphQL::Schema::TypeReducer
2
- FIELDS_TYPE_KINDS = [GraphQL::TypeKinds::OBJECT, GraphQL::TypeKinds::INTERFACE]
3
- POSSIBLE_TYPES_TYPE_KINDS = [GraphQL::TypeKinds::INTERFACE, GraphQL::TypeKinds::UNION]
4
4
  attr_reader :type, :result
5
5
  def initialize(type, existing_type_hash)
6
6
  if [GraphQL::TypeKinds::NON_NULL, GraphQL::TypeKinds::LIST].include?(type.kind)
@@ -17,20 +17,20 @@ class GraphQL::Schema::TypeReducer
17
17
 
18
18
  def find_types(type, type_hash)
19
19
  type_hash[type.name] = type
20
- if FIELDS_TYPE_KINDS.include?(type.kind)
20
+ if type.kind.fields?
21
21
  type.fields.each do |name, field|
22
22
  type_hash.merge!(reduce_type(field.type, type_hash))
23
23
  field.arguments.each do |name, argument|
24
- type_hash.merge!(reduce_type(argument[:type], type_hash))
24
+ type_hash.merge!(reduce_type(argument.type, type_hash))
25
25
  end
26
26
  end
27
27
  end
28
- if type.kind == GraphQL::TypeKinds::OBJECT
28
+ if type.kind.object?
29
29
  type.interfaces.each do |interface|
30
30
  type_hash.merge!(reduce_type(interface, type_hash))
31
31
  end
32
32
  end
33
- if POSSIBLE_TYPES_TYPE_KINDS.include?(type.kind)
33
+ if type.kind.resolves?
34
34
  type.possible_types.each do |possible_type|
35
35
  type_hash.merge!(reduce_type(possible_type, type_hash))
36
36
  end
@@ -0,0 +1,47 @@
1
+ class GraphQL::Schema::TypeValidator
2
+ def validate(type, errors)
3
+ implementation = GraphQL::Schema::ImplementationValidator.new(type, as: "Type", errors: errors)
4
+ implementation.must_respond_to(:name)
5
+ implementation.must_respond_to(:kind)
6
+ type_name = type.name
7
+ kind_name = type.kind.name
8
+
9
+ implementation.must_respond_to(:description, as: kind_name)
10
+ each_item_validator = GraphQL::Schema::EachItemValidator.new(errors)
11
+
12
+ if type.kind.fields?
13
+ field_validator = GraphQL::Schema::FieldValidator.new
14
+ implementation.must_respond_to(:fields, as: kind_name) do |fields|
15
+ each_item_validator.validate(fields.keys, as: "#{type.name}.fields keys", must_be: "Strings") { |k| k.is_a?(String) }
16
+
17
+ fields.values.each do |field|
18
+ field_validator.validate(field, errors)
19
+ end
20
+ end
21
+ end
22
+
23
+ if type.kind.resolves?
24
+ implementation.must_respond_to(:resolve_type)
25
+ implementation.must_respond_to(:possible_types, as: kind_name) do |possible_types|
26
+ each_item_validator.validate(possible_types, as: "#{type_name}.possible_types", must_be: "objects") { |t| t.kind.object? }
27
+ end
28
+ end
29
+
30
+ if type.kind.object?
31
+ implementation.must_respond_to(:interfaces, as: kind_name) do |interfaces|
32
+ each_item_validator.validate(interfaces, as: "#{type_name}.interfaces", must_be: "interfaces") { |t| t.kind.interface? }
33
+ end
34
+ end
35
+
36
+ if type.kind.input_object?
37
+ implementation.must_respond_to(:input_fields, as: kind_name)
38
+ end
39
+
40
+ if type.kind.union?
41
+ union_types = type.possible_types
42
+ if union_types.length < 2
43
+ errors << "Union #{type_name} must be defined with 2 or more types, not #{union_types.length}"
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,18 @@
1
+ module GraphQL::StaticValidation
2
+ end
3
+
4
+ require 'graph_ql/static_validation/message'
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
+ require 'graph_ql/static_validation/type_stack'
17
+ require 'graph_ql/static_validation/validator'
18
+ require 'graph_ql/static_validation/literal_validator'
@@ -0,0 +1,13 @@
1
+ class GraphQL::StaticValidation::ArgumentLiteralsAreCompatible < GraphQL::StaticValidation::ArgumentsValidator
2
+ def validate_node(node, defn, context)
3
+ args_with_literals = node.arguments.select {|a| !a.value.is_a?(GraphQL::Nodes::VariableIdentifier)}
4
+ validator = GraphQL::StaticValidation::LiteralValidator.new
5
+ args_with_literals.each do |arg|
6
+ arg_defn = defn.arguments[arg.name]
7
+ valid = validator.validate(arg.value, arg_defn.type)
8
+ if !valid
9
+ context.errors << message("#{arg.name} on #{node.class.name.split("::").last} '#{node.name}' has an invalid value", node)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ class GraphQL::StaticValidation::ArgumentsAreDefined < GraphQL::StaticValidation::ArgumentsValidator
2
+ def validate_node(node, defn, context)
3
+ node.arguments.each do |argument|
4
+ argument_defn = defn.arguments[argument.name]
5
+ if argument_defn.nil?
6
+ context.errors << message("#{node.class.name.split("::").last} '#{node.name}' doesn't accept argument #{argument.name}", node)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ # Implement validate_node
2
+ class GraphQL::StaticValidation::ArgumentsValidator
3
+ include GraphQL::StaticValidation::Message::MessageHelper
4
+
5
+ def validate(context)
6
+ visitor = context.visitor
7
+ visitor[GraphQL::Nodes::Field] << -> (node, parent) {
8
+ field_defn = context.field_definition
9
+ validate_node(node, field_defn, context)
10
+ }
11
+ visitor[GraphQL::Nodes::Directive] << -> (node, parent) {
12
+ directive_defn = context.schema.directives[node.name]
13
+ validate_node(node, directive_defn, context)
14
+ }
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ class GraphQL::StaticValidation::DirectivesAreDefined
2
+ include GraphQL::StaticValidation::Message::MessageHelper
3
+
4
+ def validate(context)
5
+ directive_names = context.schema.directives.keys
6
+ context.visitor[GraphQL::Nodes::Directive] << -> (node, parent) {
7
+ validate_directive(node, directive_names, context.errors)
8
+ }
9
+ end
10
+
11
+ private
12
+
13
+ def validate_directive(ast_directive, directive_names, errors)
14
+ if !directive_names.include?(ast_directive.name)
15
+ errors << message("Directive @#{ast_directive.name} is not defined", ast_directive)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,29 @@
1
+ class GraphQL::StaticValidation::FieldsAreDefinedOnType
2
+ include GraphQL::StaticValidation::Message::MessageHelper
3
+
4
+ IS_FIELD = Proc.new {|f| f.is_a?(GraphQL::Nodes::Field) }
5
+
6
+ def validate(context)
7
+ visitor = context.visitor
8
+ visitor[GraphQL::Nodes::Field] << -> (node, parent) {
9
+ parent_type = context.object_types[-2]
10
+ parent_type = parent_type.kind.unwrap(parent_type)
11
+ validate_field(context.errors, node, parent_type, parent)
12
+ }
13
+ end
14
+
15
+ private
16
+
17
+ def validate_field(errors, ast_field, parent_type, parent)
18
+ if parent_type.kind.union?
19
+ errors << message("Selections can't be made directly on unions (see selections on #{parent_type.name})", parent)
20
+ return GraphQL::Visitor::SKIP
21
+ end
22
+
23
+ field = parent_type.fields[ast_field.name]
24
+ if field.nil?
25
+ errors << message("Field '#{ast_field.name}' doesn't exist on type '#{parent_type.name}'", parent)
26
+ return GraphQL::Visitor::SKIP
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ # Scalars _can't_ have selections
2
+ # Objects _must_ have selections
3
+ class GraphQL::StaticValidation::FieldsHaveAppropriateSelections
4
+ include GraphQL::StaticValidation::Message::MessageHelper
5
+
6
+ def validate(context)
7
+ context.visitor[GraphQL::Nodes::Field] << -> (node, parent) {
8
+ field_defn = context.field_definition
9
+ validate_field_selections(node, field_defn, context.errors)
10
+ }
11
+ end
12
+
13
+ private
14
+
15
+ def validate_field_selections(ast_field, field_defn, errors)
16
+ resolved_type = field_defn.type.kind.unwrap(field_defn.type)
17
+
18
+ if resolved_type.kind.scalar? && ast_field.selections.any?
19
+ 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)
20
+ elsif resolved_type.kind.object? && ast_field.selections.none?
21
+ error = message("Objects must have selections (field '#{ast_field.name}' returns #{resolved_type.name} but has no selections)", ast_field)
22
+ else
23
+ error = nil
24
+ end
25
+
26
+ if !error.nil?
27
+ errors << error
28
+ GraphQL::Visitor::SKIP
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,93 @@
1
+ class GraphQL::StaticValidation::FieldsWillMerge
2
+ def validate(context)
3
+ fragments = {}
4
+ has_selections = []
5
+ visitor = context.visitor
6
+ visitor[GraphQL::Nodes::OperationDefinition] << -> (node, parent) {
7
+ if node.selections.any?
8
+ has_selections << node
9
+ end
10
+ }
11
+ visitor[GraphQL::Nodes::Document].leave << -> (node, parent) {
12
+ has_selections.each { |node|
13
+ field_map = gather_fields_by_name(node.selections, {}, context)
14
+ find_conflicts(field_map, context)
15
+ }
16
+ }
17
+ end
18
+
19
+ private
20
+
21
+ def find_conflicts(field_map, context)
22
+ field_map.each do |name, ast_fields|
23
+ comparison = FieldDefinitionComparison.new(name, ast_fields)
24
+ context.errors.push(*comparison.errors)
25
+
26
+ subfield_map = ast_fields.reduce({}) do |memo, defn|
27
+ gather_fields_by_name(defn.selections, memo, context)
28
+ end
29
+ find_conflicts(subfield_map, context)
30
+ end
31
+ end
32
+
33
+ def gather_fields_by_name(fields, field_map, context)
34
+ fields.each do |field|
35
+ if field.is_a?(GraphQL::Nodes::InlineFragment)
36
+ next_fields = field.selections
37
+ elsif field.is_a?(GraphQL::Nodes::FragmentSpread)
38
+ fragment = context.fragments[field.name]
39
+ next_fields = fragment.selections
40
+ else
41
+ name_in_selection = field.alias || field.name
42
+ field_map[name_in_selection] ||= []
43
+ field_map[name_in_selection].push(field)
44
+ next_fields = []
45
+ end
46
+ gather_fields_by_name(next_fields, field_map, context)
47
+ end
48
+ field_map
49
+ end
50
+
51
+ # Compare two field definitions, add errors to the list if there are any
52
+ class FieldDefinitionComparison
53
+ include GraphQL::StaticValidation::Message::MessageHelper
54
+ NAMED_VALUES = [GraphQL::Nodes::Enum, GraphQL::Nodes::VariableIdentifier]
55
+ attr_reader :errors
56
+ def initialize(name, defs)
57
+ errors = []
58
+
59
+ names = defs.map(&:name).uniq
60
+ if names.length != 1
61
+ errors << message("Field '#{name}' has a field conflict: #{names.join(" or ")}?", defs.first)
62
+ end
63
+
64
+ args = defs.map { |defn| reduce_list(defn.arguments)}.uniq
65
+ if args.length != 1
66
+ errors << message("Field '#{name}' has an argument conflict: #{args.map {|a| JSON.dump(a) }.join(" or ")}?", defs.first)
67
+ end
68
+
69
+ directive_names = defs.map { |defn| defn.directives.map(&:name) }.uniq
70
+ if directive_names.length != 1
71
+ errors << message("Field '#{name}' has a directive conflict: #{directive_names.map {|names| "[#{names.join(", ")}]"}.join(" or ")}?", defs.first)
72
+ end
73
+
74
+ directive_args = defs.map {|defn| defn.directives.map {|d| reduce_list(d.arguments) } }.uniq
75
+ if directive_args.length != 1
76
+ errors << message("Field '#{name}' has a directive argument conflict: #{directive_args.map {|args| JSON.dump(args)}.join(" or ")}?", defs.first)
77
+ end
78
+
79
+ @errors = errors
80
+ end
81
+
82
+ private
83
+
84
+ # Turn AST tree into a hash
85
+ # can't look up args, the names just have to match
86
+ def reduce_list(args)
87
+ args.reduce({}) do |memo, a|
88
+ memo[a.name] = NAMED_VALUES.include?(a.value.class) ? a.value.name : a.value
89
+ memo
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,24 @@
1
+ class GraphQL::StaticValidation::FragmentTypesExist
2
+ include GraphQL::StaticValidation::Message::MessageHelper
3
+
4
+ FRAGMENTS_ON_TYPES = [
5
+ GraphQL::Nodes::FragmentDefinition,
6
+ GraphQL::Nodes::InlineFragment,
7
+ ]
8
+
9
+ def validate(context)
10
+ FRAGMENTS_ON_TYPES.each do |node_class|
11
+ context.visitor[node_class] << -> (node, parent) { validate_type_exists(node, context) }
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def validate_type_exists(node, context)
18
+ type = context.schema.types[node.type]
19
+ if type.nil?
20
+ context.errors << message("No such type #{node.type}, so it can't be a fragment condition", node)
21
+ GraphQL::Visitor::SKIP
22
+ end
23
+ end
24
+ end