use std::fmt;
use std::sync::LazyLock;
use apollo_compiler::Name;
use apollo_compiler::Node;
use apollo_compiler::ast;
use apollo_compiler::collections::IndexMap;
use apollo_compiler::collections::IndexSet;
use itertools::Itertools;
use super::FederationSchema;
use crate::error::FederationError;
use crate::error::SingleFederationError;
use crate::schema::position::CompositeTypeDefinitionPosition;
use crate::schema::position::DirectiveArgumentDefinitionPosition;
use crate::schema::position::DirectiveTargetPosition;
use crate::schema::position::EnumTypeDefinitionPosition;
use crate::schema::position::EnumValueDefinitionPosition;
use crate::schema::position::HasAppliedDirectives;
use crate::schema::position::InputObjectFieldDefinitionPosition;
use crate::schema::position::InputObjectTypeDefinitionPosition;
use crate::schema::position::InterfaceFieldArgumentDefinitionPosition;
use crate::schema::position::InterfaceFieldDefinitionPosition;
use crate::schema::position::InterfaceTypeDefinitionPosition;
use crate::schema::position::ObjectFieldArgumentDefinitionPosition;
use crate::schema::position::ObjectFieldDefinitionPosition;
use crate::schema::position::ObjectOrInterfaceFieldDefinitionPosition;
use crate::schema::position::ObjectTypeDefinitionPosition;
use crate::schema::position::ScalarTypeDefinitionPosition;
use crate::schema::position::SchemaDefinitionPosition;
use crate::schema::position::SchemaRootDefinitionPosition;
use crate::schema::position::UnionTypeDefinitionPosition;
use crate::schema::position::UnionTypenameFieldDefinitionPosition;
#[derive(Clone, Default)]
pub(crate) struct Referencers {
pub(crate) scalar_types: IndexMap<Name, ScalarTypeReferencers>,
pub(crate) object_types: IndexMap<Name, ObjectTypeReferencers>,
pub(crate) interface_types: IndexMap<Name, InterfaceTypeReferencers>,
pub(crate) union_types: IndexMap<Name, UnionTypeReferencers>,
pub(crate) enum_types: IndexMap<Name, EnumTypeReferencers>,
pub(crate) input_object_types: IndexMap<Name, InputObjectTypeReferencers>,
pub(crate) directives: IndexMap<Name, DirectiveReferencers>,
}
impl fmt::Debug for Referencers {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut parts = Vec::new();
for (name, refs) in &self.scalar_types {
let all_refs = refs
.object_fields
.iter()
.map(|p| p.to_string())
.chain(refs.object_field_arguments.iter().map(|p| p.to_string()))
.chain(refs.interface_fields.iter().map(|p| p.to_string()))
.chain(refs.interface_field_arguments.iter().map(|p| p.to_string()))
.chain(refs.union_fields.iter().map(|p| p.to_string()))
.chain(refs.input_object_fields.iter().map(|p| p.to_string()))
.chain(refs.directive_arguments.iter().map(|p| p.to_string()))
.filter(|s| !s.contains("__"))
.join(", ");
if !all_refs.is_empty() {
parts.push(format!("{name}: [{all_refs}]"));
}
}
for (name, refs) in &self.object_types {
let all_refs = refs
.schema_roots
.iter()
.map(|p| p.to_string())
.chain(refs.object_fields.iter().map(|p| p.to_string()))
.chain(refs.interface_fields.iter().map(|p| p.to_string()))
.chain(refs.union_types.iter().map(|p| p.to_string()))
.filter(|s| !s.contains("__"))
.join(", ");
if !all_refs.is_empty() {
parts.push(format!("{name}: [{all_refs}]"));
}
}
for (name, refs) in &self.interface_types {
let all_refs = refs
.object_types
.iter()
.map(|p| p.to_string())
.chain(refs.object_fields.iter().map(|p| p.to_string()))
.chain(refs.interface_types.iter().map(|p| p.to_string()))
.chain(refs.interface_fields.iter().map(|p| p.to_string()))
.filter(|s| !s.contains("__"))
.join(", ");
if !all_refs.is_empty() {
parts.push(format!("{name}: [{all_refs}]"));
}
}
for (name, refs) in &self.union_types {
let all_refs = refs
.object_fields
.iter()
.map(|p| p.to_string())
.chain(refs.interface_fields.iter().map(|p| p.to_string()))
.filter(|s| !s.contains("__"))
.join(", ");
if !all_refs.is_empty() {
parts.push(format!("{name}: [{all_refs}]"));
}
}
for (name, refs) in &self.enum_types {
let all_refs = refs
.object_fields
.iter()
.map(|p| p.to_string())
.chain(refs.object_field_arguments.iter().map(|p| p.to_string()))
.chain(refs.interface_fields.iter().map(|p| p.to_string()))
.chain(refs.interface_field_arguments.iter().map(|p| p.to_string()))
.chain(refs.input_object_fields.iter().map(|p| p.to_string()))
.chain(refs.directive_arguments.iter().map(|p| p.to_string()))
.filter(|s| !s.contains("__"))
.join(", ");
if !all_refs.is_empty() {
parts.push(format!("{name}: [{all_refs}]"));
}
}
for (name, refs) in &self.input_object_types {
let all_refs = refs
.object_field_arguments
.iter()
.map(|p| p.to_string())
.chain(refs.interface_field_arguments.iter().map(|p| p.to_string()))
.chain(refs.input_object_fields.iter().map(|p| p.to_string()))
.chain(refs.directive_arguments.iter().map(|p| p.to_string()))
.filter(|s| !s.contains("__"))
.join(", ");
if !all_refs.is_empty() {
parts.push(format!("{name}: [{all_refs}]"));
}
}
for (name, refs) in &self.directives {
let all_refs = refs
.iter()
.map(|p| p.to_string())
.filter(|s| !s.contains("__"))
.join(", ");
if !all_refs.is_empty() {
parts.push(format!("{name}: [{all_refs}]"));
}
}
if f.alternate() {
write!(f, "{{\n {}\n}}", parts.join(",\n "))
} else {
write!(f, "{{{}}}", parts.join(", "))
}
}
}
impl Referencers {
pub(crate) fn contains_type_name(&self, name: &str) -> bool {
self.scalar_types.contains_key(name)
|| self.object_types.contains_key(name)
|| self.interface_types.contains_key(name)
|| self.union_types.contains_key(name)
|| self.enum_types.contains_key(name)
|| self.input_object_types.contains_key(name)
}
pub(crate) fn get_interface_type(
&self,
name: &str,
) -> Result<&InterfaceTypeReferencers, FederationError> {
self.interface_types.get(name).ok_or_else(|| {
SingleFederationError::Internal {
message: "Interface type referencers unexpectedly missing type".to_owned(),
}
.into()
})
}
pub(crate) fn get_directive(&self, name: &str) -> &DirectiveReferencers {
self.directives
.get(name)
.unwrap_or_else(|| &EMPTY_REFERENCERS)
}
pub(crate) fn get_directive_applications<'schema>(
&self,
schema: &'schema FederationSchema,
name: &Name,
) -> impl Iterator<Item = (DirectiveTargetPosition, &'schema Node<ast::Directive>)> {
let directive_referencers = self.get_directive(name);
directive_referencers.iter().flat_map(|pos| {
pos.get_applied_directives(schema, name)
.into_iter()
.map(move |directive_application| (pos.clone(), directive_application))
})
}
pub(crate) fn rename_object_type(&mut self, old_name: &Name, new_name: &Name) {
for (_scalar_name, scalar_refs) in self.scalar_types.iter_mut() {
Self::update_object_field_positions(&mut scalar_refs.object_fields, old_name, new_name);
Self::update_object_field_argument_positions(
&mut scalar_refs.object_field_arguments,
old_name,
new_name,
);
}
for (_object_name, object_refs) in self.object_types.iter_mut() {
Self::update_object_field_positions(&mut object_refs.object_fields, old_name, new_name);
}
for (_interface_name, interface_refs) in self.interface_types.iter_mut() {
Self::update_object_field_positions(
&mut interface_refs.object_fields,
old_name,
new_name,
);
}
for (_union_name, union_refs) in self.union_types.iter_mut() {
Self::update_object_field_positions(&mut union_refs.object_fields, old_name, new_name);
}
for (_enum_name, enum_refs) in self.enum_types.iter_mut() {
Self::update_object_field_positions(&mut enum_refs.object_fields, old_name, new_name);
Self::update_object_field_argument_positions(
&mut enum_refs.object_field_arguments,
old_name,
new_name,
);
}
for (_input_name, input_refs) in self.input_object_types.iter_mut() {
Self::update_object_field_argument_positions(
&mut input_refs.object_field_arguments,
old_name,
new_name,
);
}
for (_directive_name, directive_refs) in self.directives.iter_mut() {
Self::update_object_type_positions(
&mut directive_refs.object_types,
old_name,
new_name,
);
Self::update_object_field_positions(
&mut directive_refs.object_fields,
old_name,
new_name,
);
Self::update_object_field_argument_positions(
&mut directive_refs.object_field_arguments,
old_name,
new_name,
);
}
}
fn update_object_type_positions(
types: &mut IndexSet<ObjectTypeDefinitionPosition>,
old_type_name: &Name,
new_type_name: &Name,
) {
let updated_types: Vec<_> = types
.iter()
.filter(|t| &t.type_name == old_type_name)
.map(|_| ObjectTypeDefinitionPosition {
type_name: new_type_name.clone(),
})
.collect();
types.retain(|t| &t.type_name != old_type_name);
types.extend(updated_types);
}
fn update_object_field_positions(
fields: &mut IndexSet<ObjectFieldDefinitionPosition>,
old_type_name: &Name,
new_type_name: &Name,
) {
let updated_fields: Vec<_> = fields
.iter()
.filter(|f| &f.type_name == old_type_name)
.map(|f| ObjectFieldDefinitionPosition {
type_name: new_type_name.clone(),
field_name: f.field_name.clone(),
})
.collect();
fields.retain(|f| &f.type_name != old_type_name);
fields.extend(updated_fields);
}
fn update_object_field_argument_positions(
arguments: &mut IndexSet<ObjectFieldArgumentDefinitionPosition>,
old_type_name: &Name,
new_type_name: &Name,
) {
let updated_arguments: Vec<_> = arguments
.iter()
.filter(|a| &a.type_name == old_type_name)
.map(|a| ObjectFieldArgumentDefinitionPosition {
type_name: new_type_name.clone(),
field_name: a.field_name.clone(),
argument_name: a.argument_name.clone(),
})
.collect();
arguments.retain(|a| &a.type_name != old_type_name);
arguments.extend(updated_arguments);
}
pub(crate) fn rename_interface_type(&mut self, old_name: &Name, new_name: &Name) {
for (_scalar_name, scalar_refs) in self.scalar_types.iter_mut() {
Self::update_interface_field_positions(
&mut scalar_refs.interface_fields,
old_name,
new_name,
);
Self::update_interface_field_argument_positions(
&mut scalar_refs.interface_field_arguments,
old_name,
new_name,
);
}
for (_object_name, object_refs) in self.object_types.iter_mut() {
Self::update_interface_field_positions(
&mut object_refs.interface_fields,
old_name,
new_name,
);
}
for (_interface_name, interface_refs) in self.interface_types.iter_mut() {
Self::update_interface_field_positions(
&mut interface_refs.interface_fields,
old_name,
new_name,
);
}
for (_union_name, union_refs) in self.union_types.iter_mut() {
Self::update_interface_field_positions(
&mut union_refs.interface_fields,
old_name,
new_name,
);
}
for (_enum_name, enum_refs) in self.enum_types.iter_mut() {
Self::update_interface_field_positions(
&mut enum_refs.interface_fields,
old_name,
new_name,
);
Self::update_interface_field_argument_positions(
&mut enum_refs.interface_field_arguments,
old_name,
new_name,
);
}
for (_input_name, input_refs) in self.input_object_types.iter_mut() {
Self::update_interface_field_argument_positions(
&mut input_refs.interface_field_arguments,
old_name,
new_name,
);
}
for (_directive_name, directive_refs) in self.directives.iter_mut() {
Self::update_interface_field_positions(
&mut directive_refs.interface_fields,
old_name,
new_name,
);
Self::update_interface_field_argument_positions(
&mut directive_refs.interface_field_arguments,
old_name,
new_name,
);
}
}
fn update_interface_field_positions(
fields: &mut IndexSet<InterfaceFieldDefinitionPosition>,
old_type_name: &Name,
new_type_name: &Name,
) {
let updated_fields: Vec<_> = fields
.iter()
.filter(|f| &f.type_name == old_type_name)
.map(|f| InterfaceFieldDefinitionPosition {
type_name: new_type_name.clone(),
field_name: f.field_name.clone(),
})
.collect();
fields.retain(|f| &f.type_name != old_type_name);
fields.extend(updated_fields);
}
fn update_interface_field_argument_positions(
arguments: &mut IndexSet<InterfaceFieldArgumentDefinitionPosition>,
old_type_name: &Name,
new_type_name: &Name,
) {
let updated_arguments: Vec<_> = arguments
.iter()
.filter(|a| &a.type_name == old_type_name)
.map(|a| InterfaceFieldArgumentDefinitionPosition {
type_name: new_type_name.clone(),
field_name: a.field_name.clone(),
argument_name: a.argument_name.clone(),
})
.collect();
arguments.retain(|a| &a.type_name != old_type_name);
arguments.extend(updated_arguments);
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct ScalarTypeReferencers {
pub(crate) object_fields: IndexSet<ObjectFieldDefinitionPosition>,
pub(crate) object_field_arguments: IndexSet<ObjectFieldArgumentDefinitionPosition>,
pub(crate) interface_fields: IndexSet<InterfaceFieldDefinitionPosition>,
pub(crate) interface_field_arguments: IndexSet<InterfaceFieldArgumentDefinitionPosition>,
pub(crate) union_fields: IndexSet<UnionTypenameFieldDefinitionPosition>,
pub(crate) input_object_fields: IndexSet<InputObjectFieldDefinitionPosition>,
pub(crate) directive_arguments: IndexSet<DirectiveArgumentDefinitionPosition>,
}
impl ScalarTypeReferencers {
pub(crate) fn len(&self) -> usize {
self.object_fields.len()
+ self.object_field_arguments.len()
+ self.interface_fields.len()
+ self.interface_field_arguments.len()
+ self.union_fields.len()
+ self.input_object_fields.len()
+ self.directive_arguments.len()
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct ObjectTypeReferencers {
pub(crate) schema_roots: IndexSet<SchemaRootDefinitionPosition>,
pub(crate) object_fields: IndexSet<ObjectFieldDefinitionPosition>,
pub(crate) interface_fields: IndexSet<InterfaceFieldDefinitionPosition>,
pub(crate) union_types: IndexSet<UnionTypeDefinitionPosition>,
}
impl ObjectTypeReferencers {
pub(crate) fn len(&self) -> usize {
self.schema_roots.len()
+ self.object_fields.len()
+ self.interface_fields.len()
+ self.union_types.len()
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct InterfaceTypeReferencers {
pub(crate) object_types: IndexSet<ObjectTypeDefinitionPosition>,
pub(crate) object_fields: IndexSet<ObjectFieldDefinitionPosition>,
pub(crate) interface_types: IndexSet<InterfaceTypeDefinitionPosition>,
pub(crate) interface_fields: IndexSet<InterfaceFieldDefinitionPosition>,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct UnionTypeReferencers {
pub(crate) object_fields: IndexSet<ObjectFieldDefinitionPosition>,
pub(crate) interface_fields: IndexSet<InterfaceFieldDefinitionPosition>,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct EnumTypeReferencers {
pub(crate) object_fields: IndexSet<ObjectFieldDefinitionPosition>,
pub(crate) object_field_arguments: IndexSet<ObjectFieldArgumentDefinitionPosition>,
pub(crate) interface_fields: IndexSet<InterfaceFieldDefinitionPosition>,
pub(crate) interface_field_arguments: IndexSet<InterfaceFieldArgumentDefinitionPosition>,
pub(crate) input_object_fields: IndexSet<InputObjectFieldDefinitionPosition>,
pub(crate) directive_arguments: IndexSet<DirectiveArgumentDefinitionPosition>,
}
impl EnumTypeReferencers {
pub(crate) fn len(&self) -> usize {
self.object_fields.len()
+ self.object_field_arguments.len()
+ self.interface_fields.len()
+ self.interface_field_arguments.len()
+ self.input_object_fields.len()
+ self.directive_arguments.len()
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct InputObjectTypeReferencers {
pub(crate) object_field_arguments: IndexSet<ObjectFieldArgumentDefinitionPosition>,
pub(crate) interface_field_arguments: IndexSet<InterfaceFieldArgumentDefinitionPosition>,
pub(crate) input_object_fields: IndexSet<InputObjectFieldDefinitionPosition>,
pub(crate) directive_arguments: IndexSet<DirectiveArgumentDefinitionPosition>,
}
impl InputObjectTypeReferencers {
pub(crate) fn len(&self) -> usize {
self.object_field_arguments.len()
+ self.interface_field_arguments.len()
+ self.input_object_fields.len()
+ self.directive_arguments.len()
}
}
static EMPTY_REFERENCERS: LazyLock<DirectiveReferencers> =
LazyLock::new(DirectiveReferencers::default);
#[derive(Debug, Clone, Default)]
pub(crate) struct DirectiveReferencers {
pub(crate) schema: Option<SchemaDefinitionPosition>,
pub(crate) scalar_types: IndexSet<ScalarTypeDefinitionPosition>,
pub(crate) object_types: IndexSet<ObjectTypeDefinitionPosition>,
pub(crate) object_fields: IndexSet<ObjectFieldDefinitionPosition>,
pub(crate) object_field_arguments: IndexSet<ObjectFieldArgumentDefinitionPosition>,
pub(crate) interface_types: IndexSet<InterfaceTypeDefinitionPosition>,
pub(crate) interface_fields: IndexSet<InterfaceFieldDefinitionPosition>,
pub(crate) interface_field_arguments: IndexSet<InterfaceFieldArgumentDefinitionPosition>,
pub(crate) union_types: IndexSet<UnionTypeDefinitionPosition>,
pub(crate) enum_types: IndexSet<EnumTypeDefinitionPosition>,
pub(crate) enum_values: IndexSet<EnumValueDefinitionPosition>,
pub(crate) input_object_types: IndexSet<InputObjectTypeDefinitionPosition>,
pub(crate) input_object_fields: IndexSet<InputObjectFieldDefinitionPosition>,
pub(crate) directive_arguments: IndexSet<DirectiveArgumentDefinitionPosition>,
}
impl DirectiveReferencers {
pub(crate) fn object_or_interface_fields(
&self,
) -> impl Iterator<Item = ObjectOrInterfaceFieldDefinitionPosition> {
self.object_fields
.iter()
.map(|pos| ObjectOrInterfaceFieldDefinitionPosition::Object(pos.clone()))
.chain(
self.interface_fields
.iter()
.map(|pos| ObjectOrInterfaceFieldDefinitionPosition::Interface(pos.clone())),
)
}
pub(crate) fn composite_type_positions(
&self,
) -> impl Iterator<Item = CompositeTypeDefinitionPosition> {
self.object_types
.iter()
.map(|t| CompositeTypeDefinitionPosition::from(t.clone()))
.chain(self.interface_types.iter().map(|t| t.clone().into()))
.chain(self.union_types.iter().map(|t| t.clone().into()))
}
pub(crate) fn extend(&mut self, other: &Self) {
if let Some(schema) = &other.schema {
self.schema = Some(schema.clone());
}
self.scalar_types.extend(other.scalar_types.iter().cloned());
self.object_types.extend(other.object_types.iter().cloned());
self.object_fields
.extend(other.object_fields.iter().cloned());
self.object_field_arguments
.extend(other.object_field_arguments.iter().cloned());
self.interface_types
.extend(other.interface_types.iter().cloned());
self.interface_fields
.extend(other.interface_fields.iter().cloned());
self.interface_field_arguments
.extend(other.interface_field_arguments.iter().cloned());
self.union_types.extend(other.union_types.iter().cloned());
self.enum_types.extend(other.enum_types.iter().cloned());
self.enum_values.extend(other.enum_values.iter().cloned());
self.input_object_types
.extend(other.input_object_types.iter().cloned());
self.input_object_fields
.extend(other.input_object_fields.iter().cloned());
self.directive_arguments
.extend(other.directive_arguments.iter().cloned());
}
pub(crate) fn iter(&self) -> impl Iterator<Item = DirectiveTargetPosition> {
let schema = self
.schema
.iter()
.cloned()
.map(DirectiveTargetPosition::Schema);
let scalar_types = self
.scalar_types
.iter()
.cloned()
.map(DirectiveTargetPosition::ScalarType);
let object_types = self
.object_types
.iter()
.cloned()
.map(DirectiveTargetPosition::ObjectType);
let object_fields = self
.object_fields
.iter()
.cloned()
.map(DirectiveTargetPosition::ObjectField);
let object_field_arguments = self
.object_field_arguments
.iter()
.cloned()
.map(DirectiveTargetPosition::ObjectFieldArgument);
let interface_types = self
.interface_types
.iter()
.cloned()
.map(DirectiveTargetPosition::InterfaceType);
let interface_fields = self
.interface_fields
.iter()
.cloned()
.map(DirectiveTargetPosition::InterfaceField);
let interface_field_arguments = self
.interface_field_arguments
.iter()
.cloned()
.map(DirectiveTargetPosition::InterfaceFieldArgument);
let union_types = self
.union_types
.iter()
.cloned()
.map(DirectiveTargetPosition::UnionType);
let enum_types = self
.enum_types
.iter()
.cloned()
.map(DirectiveTargetPosition::EnumType);
let enum_values = self
.enum_values
.iter()
.cloned()
.map(DirectiveTargetPosition::EnumValue);
let input_object_types = self
.input_object_types
.iter()
.cloned()
.map(DirectiveTargetPosition::InputObjectType);
let input_object_fields = self
.input_object_fields
.iter()
.cloned()
.map(DirectiveTargetPosition::InputObjectField);
let directive_arguments = self
.directive_arguments
.iter()
.cloned()
.map(DirectiveTargetPosition::DirectiveArgument);
schema
.chain(scalar_types)
.chain(object_types)
.chain(object_fields)
.chain(object_field_arguments)
.chain(interface_types)
.chain(interface_fields)
.chain(interface_field_arguments)
.chain(union_types)
.chain(enum_types)
.chain(enum_values)
.chain(input_object_types)
.chain(input_object_fields)
.chain(directive_arguments)
}
}