use super::*;
pub(super) fn is_entity_interface(
subgraphs: &subgraphs::Subgraphs,
mut definitions: impl Iterator<Item = subgraphs::DefinitionId>,
) -> bool {
let Some(definition_id) = definitions.find(|def| {
let subgraph_id = subgraphs.at(*def).subgraph_id;
subgraphs.at(subgraph_id).federation_spec.is_apollo_v2()
}) else {
return false;
};
let definition = &subgraphs.at(definition_id);
match definition.kind {
DefinitionKind::Object => definition.directives.interface_object(subgraphs),
DefinitionKind::Interface => definition_id.keys(subgraphs).next().is_some(),
_ => false,
}
}
pub(crate) fn merge_entity_interface_definitions<'a>(
ctx: &mut Context<'a>,
first: DefinitionView<'a>,
definitions: &[DefinitionView<'a>],
) {
let interface_name = first.name;
let interface_defs = || {
definitions.iter().filter(|def| {
def.kind == DefinitionKind::Interface
&& ctx
.subgraphs
.at(ctx.subgraphs.at(def.id).subgraph_id)
.federation_spec
.is_apollo_v2()
})
};
let mut interfaces = interface_defs();
let Some(interface_def) = interfaces.next() else {
ctx.diagnostics.push_fatal(format!(
"The entity interface `{}` is not defined as an interface in any subgraph.",
ctx.subgraphs[first.name]
));
return;
};
if interfaces.next().is_some() {
let all_implementers: BTreeSet<_> = interface_defs()
.flat_map(|interface| {
interface
.subgraph_id
.interface_implementers(ctx.subgraphs, interface_name)
.map(|def_id| ctx.subgraphs.at(def_id).name)
})
.collect();
for interface in interface_defs() {
let implementers: BTreeSet<_> = interface
.subgraph_id
.interface_implementers(ctx.subgraphs, interface_name)
.map(|def_id| ctx.subgraphs.at(def_id).name)
.collect();
if implementers != all_implementers {
let subgraph = ctx.subgraphs.at(interface.subgraph_id);
let subgraph_name = ctx.subgraphs[subgraph.name].as_ref();
let interface_name = ctx.subgraphs[interface_name].as_ref();
let implementer_names = all_implementers
.difference(&implementers)
.map(|id| ctx.subgraphs[*id].as_ref())
.join(", ");
ctx.diagnostics.push_fatal(format!(
r#"[{subgraph_name}]: Interface type "{interface_name}" has a resolvable key in subgraph "{subgraph_name}" but that subgraph is missing some of the supergraph implementation types of "{interface_name}". Subgraph "{subgraph_name}" should define types {implementer_names}."#
));
}
if interface.directives.interface_object(ctx.subgraphs) {
ctx.diagnostics.push_fatal(format!(
"[{}] The @interfaceObject directive is not valid on interfaces (on `{}`).",
ctx.subgraphs[ctx.subgraphs.at(interface.subgraph_id).name],
ctx.subgraphs[interface_name],
));
}
}
}
let description = interface_def.description.map(|d| ctx.subgraphs[d].as_ref());
let interface_name = ctx.insert_string(interface_name);
let directives = collect_composed_directives(definitions.iter().map(|def| def.directives), ctx);
let interface_id = ctx.insert_interface(interface_name, description, directives);
let Some(expected_key) = interface_def.id.keys(ctx.subgraphs).next() else {
ctx.diagnostics.push_fatal(format!(
"The entity interface `{}` is missing a key in the `{}` subgraph.",
ctx.subgraphs[first.name],
ctx.subgraphs[ctx.subgraphs.at(interface_def.subgraph_id).name],
));
return;
};
ctx.insert_interface_resolvable_key(interface_id, expected_key, false);
for definition in definitions.iter().filter(|def| def.kind == DefinitionKind::Object) {
if !definition.directives.interface_object(ctx.subgraphs) {
let definition_name = ctx.subgraphs[definition.name].as_ref();
ctx.diagnostics.push_fatal(format!(
"`{definition_name}` is an entity interface but the object type `{definition_name}` is missing the @interfaceObject directive in the `{}` subgraph.",
ctx.subgraphs[ctx.subgraphs.at(definition.subgraph_id).name],
));
}
match definition.id.keys(ctx.subgraphs).next() {
None => {
ctx.diagnostics.push_fatal(format!(
"The object type `{}` is annotated with @interfaceObject but missing a key in the `{}` subgraph.",
ctx.subgraphs[first.name],
ctx.subgraphs[ctx.subgraphs.at(definition.subgraph_id).name]
));
}
Some(key) if key.fields() == expected_key.fields() => (),
Some(_) => {
ctx.diagnostics.push_fatal(format!(
"[{}] The object type `{}` is annotated with @interfaceObject but has a different key than the entity interface `{}`.",
ctx.subgraphs[ctx.subgraphs.at(definition.subgraph_id).name],
ctx.subgraphs[definition.name],
ctx.subgraphs[interface_def.name],
));
}
}
for entity_key in definition.id.keys(ctx.subgraphs).filter(|key| key.is_resolvable()) {
ctx.insert_interface_resolvable_key(interface_id, entity_key, true);
}
}
let fields = object::compose_fields(ctx, definitions, interface_name);
let fields_to_add: Vec<(subgraphs::StringId, _)> = fields
.into_iter()
.map(|mut field| {
ctx.insert_field(field.clone());
field.directives = vec![ir::Directive::JoinEntityInterfaceField];
(field.field_name, field)
})
.collect();
for object_id in interface_def
.subgraph_id
.interface_implementers(ctx.subgraphs, first.name)
{
let object = ctx.subgraphs.at(object_id);
match object.id.keys(ctx.subgraphs).next() {
Some(key) if key.selection_set == expected_key.fields() => (),
Some(_) => ctx.diagnostics.push_fatal(format!(
"[{}] The object type `{}` implements the entity interface `{}` but does not have the same key. The key must match exactly.",
&ctx[ctx.subgraphs.at(object.subgraph_id).name],
&ctx[object.name],
ctx.subgraphs[first.name],
)),
None => ctx.diagnostics.push_fatal(format!(
"[{}] The object type `{}` is annotated with @interfaceObject but missing a key.",
&ctx[ctx.subgraphs.at(object.subgraph_id).name],
&ctx[object.name],
)),
}
let object_name = ctx.insert_string(object.name);
let fields_to_add = fields_to_add
.iter()
.filter(|(name, _)| object.id.field_by_name(ctx.subgraphs, *name).is_none())
.map(|(_, field_ir)| field_ir);
for field_ir in fields_to_add {
let mut field_ir = field_ir.clone();
field_ir.parent_definition_name = object_name;
ctx.insert_field(field_ir);
}
}
}