use indexmap::IndexSet;
use tracing::trace;
use crate::error::CompositionError;
use crate::error::FederationError;
use crate::merger::merge::Merger;
use crate::schema::position::HasAppliedDirectives;
use crate::schema::position::InterfaceTypeDefinitionPosition;
use crate::schema::position::ObjectOrInterfaceTypeDefinitionPosition;
use crate::schema::position::ObjectTypeDefinitionPosition;
use crate::schema::position::TypeDefinitionPosition;
use crate::utils::human_readable::human_readable_types;
impl Merger {
fn validate_interface_keys(
&mut self,
dest: &InterfaceTypeDefinitionPosition,
) -> Result<bool, FederationError> {
trace!("Validating interface keys");
let supergraph_implementations = self.merged.possible_runtime_types(dest.clone().into())?;
let mut has_key = false;
for subgraph in self.subgraphs.iter() {
if !subgraph.schema().is_interface(&dest.type_name) {
continue;
}
let Some(key_directive_name) = subgraph.key_directive_name()? else {
continue;
};
let interface_pos: TypeDefinitionPosition = dest.clone().into();
let keys = interface_pos.get_applied_directives(subgraph.schema(), &key_directive_name);
has_key = has_key || !keys.is_empty();
let federation_spec_definition = subgraph.metadata().federation_spec_definition();
let Some(resolvable_key) = keys.iter().find(|key| {
federation_spec_definition
.key_directive_arguments(key)
.map(|args| args.resolvable)
.unwrap_or(true) }) else {
continue;
};
let implementations_in_subgraph = subgraph
.schema()
.possible_runtime_types(dest.clone().into())?;
let missing_implementations: IndexSet<_> = supergraph_implementations
.difference(&implementations_in_subgraph)
.collect();
let subgraph_name = &subgraph.name;
if !missing_implementations.is_empty() {
self.error_reporter.add_error(CompositionError::InterfaceKeyMissingImplementationType {
message: format!("[{subgraph_name}] Interface type \"{dest}\" has a resolvable key ({}) in subgraph \"{subgraph_name}\" but that subgraph is missing some of the supergraph implementation types of \"{dest}\". Subgraph \"{subgraph_name}\" should define {} (and have {} implement \"{dest}\").",
resolvable_key.serialize(),
human_readable_types(missing_implementations.iter().map(|impl_type| &impl_type.type_name)),
if missing_implementations.len() > 1 { "them" } else { "it" },
)
});
}
}
Ok(has_key)
}
fn validate_interface_objects(
&mut self,
dest: &InterfaceTypeDefinitionPosition,
) -> Result<(), FederationError> {
trace!("Validating interface objects");
let supergraph_implementations = self.merged.possible_runtime_types(dest.clone().into())?;
for subgraph in self.subgraphs.iter() {
let ty_as_obj = ObjectTypeDefinitionPosition {
type_name: dest.type_name.clone(),
};
if !subgraph.is_interface_object_type(&ty_as_obj.into()) {
continue;
}
let subgraph_name = &subgraph.name;
let defined_implementations: IndexSet<_> = supergraph_implementations
.iter()
.filter(|implementation| implementation.get(subgraph.schema().schema()).is_ok())
.collect::<IndexSet<_>>();
if !defined_implementations.is_empty() {
self.error_reporter.add_error(CompositionError::InterfaceObjectUsageError {
message: format!("[{subgraph_name}] Interface type \"{dest}\" is defined as an @interfaceObject in subgraph \"{subgraph_name}\" so that subgraph should not define any of the implementation types of \"{dest}\", but it defines {}",
human_readable_types(defined_implementations.iter().map(|impl_type| &impl_type.type_name)),
)
});
}
}
Ok(())
}
pub(crate) fn merge_interface(
&mut self,
itf: InterfaceTypeDefinitionPosition,
) -> Result<(), FederationError> {
let has_key = self.validate_interface_keys(&itf)?;
self.validate_interface_objects(&itf)?;
let added = self.add_fields_shallow(itf.clone())?;
for (dest_field, subgraph_fields) in added {
if !has_key {
let subgraph_types = self
.subgraphs
.iter()
.enumerate()
.map(|(idx, subgraph)| {
let maybe_ty: Option<ObjectOrInterfaceTypeDefinitionPosition> = subgraph
.schema()
.get_type(itf.type_name.clone())
.ok()
.and_then(|ty| ty.try_into().ok());
(idx, maybe_ty)
})
.collect();
self.hint_on_inconsistent_value_type_field(
&subgraph_types,
&ObjectOrInterfaceTypeDefinitionPosition::Interface(itf.clone()),
&dest_field,
)?;
}
let merge_context = self.validate_override(&subgraph_fields, &dest_field)?;
trace!("Merging interface field {}", dest_field.field_name());
self.merge_field(&subgraph_fields, &dest_field, &merge_context)?;
}
Ok(())
}
}