graphql 0.1.0 → 0.2.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 (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