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