lib/graphql/stitching/composer/validate_interfaces.rb in graphql-stitching-0.3.1 vs lib/graphql/stitching/composer/validate_interfaces.rb in graphql-stitching-0.3.2
- old
+ new
@@ -2,22 +2,50 @@
module GraphQL
module Stitching
class Composer::ValidateInterfaces < Composer::BaseValidator
+ # For each composed interface, check the interface against each possible type
+ # to assure that intersecting fields have compatible types, structures, and nullability.
+ # Verifies compatibility of types that inherit interface contracts through merging.
def perform(supergraph, composer)
- # @todo
- # Validate all supergraph interface fields
- # match possible types in all locations...
- # - Traverse supergraph types (supergraph.types)
- # - For each interface (.kind.interface?), get possible types (Util.get_possible_types)
- # - For each possible type, traverse type candidates (composer.subschema_types_by_name_and_location)
- # - For each type candidate, compare interface fields to type candidate fields
- # - For each type candidate field that matches an interface field...
- # - Named types must match
- # - List structures must match
- # - Nullabilities must be >= interface field
- # - It's OKAY if a type candidate does not implement the full interface
+ supergraph.schema.types.each do |type_name, interface_type|
+ next unless interface_type.kind.interface?
+
+ supergraph.schema.possible_types(interface_type).each do |possible_type|
+ interface_type.fields.each do |field_name, interface_field|
+ # graphql-ruby will dynamically apply interface fields on a type implementation,
+ # so check the delegation map to assure that all materialized fields have resolver locations.
+ unless supergraph.locations_by_type_and_field[possible_type.graphql_name][field_name]&.any?
+ raise Composer::ValidationError, "Type #{possible_type.graphql_name} does not implement a `#{field_name}` field in any location, "\
+ "which is required by interface #{interface_type.graphql_name}."
+ end
+
+ intersecting_field = possible_type.fields[field_name]
+ interface_type_structure = Util.flatten_type_structure(interface_field.type)
+ possible_type_structure = Util.flatten_type_structure(intersecting_field.type)
+
+ if possible_type_structure.length != interface_type_structure.length
+ raise Composer::ValidationError, "Incompatible list structures between field #{possible_type.graphql_name}.#{field_name} of type "\
+ "#{intersecting_field.type.to_type_signature} and interface #{interface_type.graphql_name}.#{field_name} of type #{interface_field.type.to_type_signature}."
+ end
+
+ interface_type_structure.each_with_index do |interface_struct, index|
+ possible_struct = possible_type_structure[index]
+
+ if possible_struct[:name] != interface_struct[:name]
+ raise Composer::ValidationError, "Incompatible named types between field #{possible_type.graphql_name}.#{field_name} of type "\
+ "#{intersecting_field.type.to_type_signature} and interface #{interface_type.graphql_name}.#{field_name} of type #{interface_field.type.to_type_signature}."
+ end
+
+ if possible_struct[:null] && !interface_struct[:null]
+ raise Composer::ValidationError, "Incompatible nullability between field #{possible_type.graphql_name}.#{field_name} of type "\
+ "#{intersecting_field.type.to_type_signature} and interface #{interface_type.graphql_name}.#{field_name} of type #{interface_field.type.to_type_signature}."
+ end
+ end
+ end
+ end
+ end
end
end
end
end