use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::hash::Hash;
use apollo_compiler::Name;
use apollo_compiler::Schema;
use apollo_compiler::ast::FieldDefinition;
use apollo_compiler::ast::NamedType;
use apollo_compiler::schema::Component;
use crate::connectors::schema_type_ref::SchemaTypeRef;
use crate::error::FederationError;
use crate::schema::position::ObjectOrInterfaceFieldDirectivePosition;
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub(crate) struct ObjectTypeDefinitionDirectivePosition {
pub(super) type_name: Name,
pub(super) directive_name: Name,
pub(super) directive_index: usize,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub(crate) enum ConnectorPosition {
Field(ObjectOrInterfaceFieldDirectivePosition),
Type(ObjectTypeDefinitionDirectivePosition),
}
impl ConnectorPosition {
pub(crate) fn element<'s>(
&self,
schema: &'s Schema,
) -> Result<ConnectedElement<'s>, FederationError> {
match self {
Self::Field(pos) => Ok(ConnectedElement::Field {
parent_type: SchemaTypeRef::new(schema, pos.field.parent().type_name())
.ok_or_else(|| {
FederationError::internal("Parent type for connector not found")
})?,
field_def: pos.field.get(schema).map_err(|_| {
FederationError::internal("Field definition for connector not found")
})?,
parent_category: if self.on_query_type(schema) {
ObjectCategory::Query
} else if self.on_mutation_type(schema) {
ObjectCategory::Mutation
} else {
ObjectCategory::Other
},
}),
Self::Type(pos) => Ok(ConnectedElement::Type {
type_ref: SchemaTypeRef::new(schema, &pos.type_name)
.ok_or_else(|| FederationError::internal("Type for connector not found"))?,
}),
}
}
pub(crate) fn parent_type_name(&self) -> Option<Name> {
match self {
ConnectorPosition::Field(pos) => Some(pos.field.type_name().clone()),
ConnectorPosition::Type(_) => None,
}
}
pub(crate) fn base_type_name(&self, schema: &Schema) -> Option<NamedType> {
match self {
ConnectorPosition::Field(_) => self
.field_definition(schema)
.map(|field| field.ty.inner_named_type().clone()),
ConnectorPosition::Type(pos) => Some(pos.type_name.clone()),
}
}
pub(crate) fn field_definition<'s>(
&self,
schema: &'s Schema,
) -> Option<&'s Component<FieldDefinition>> {
match self {
ConnectorPosition::Field(pos) => pos.field.get(schema).ok(),
ConnectorPosition::Type(_) => None,
}
}
pub(crate) fn coordinate(&self) -> String {
match self {
ConnectorPosition::Field(pos) => format!(
"{}.{}[{}]",
pos.field.type_name(),
pos.field.field_name(),
pos.directive_index,
),
ConnectorPosition::Type(pos) => format!("{}[{}]", pos.type_name, pos.directive_index,),
}
}
pub(crate) fn synthetic_name(&self) -> String {
match self {
ConnectorPosition::Field(pos) => format!(
"{}_{}_{}",
pos.field.type_name(),
pos.field.field_name(),
pos.directive_index,
),
ConnectorPosition::Type(pos) => format!("{}_{}", pos.type_name, pos.directive_index),
}
}
pub(crate) fn simple_name(&self) -> String {
match self {
ConnectorPosition::Field(pos) => {
format!("{}.{}", pos.field.type_name(), pos.field.field_name(),)
}
ConnectorPosition::Type(pos) => format!("{}", pos.type_name),
}
}
pub(super) fn on_root_type(&self, schema: &Schema) -> bool {
self.on_query_type(schema) || self.on_mutation_type(schema)
}
fn on_query_type(&self, schema: &Schema) -> bool {
schema
.schema_definition
.query
.as_ref()
.is_some_and(|query| match self {
ConnectorPosition::Field(pos) => *pos.field.type_name() == query.name,
ConnectorPosition::Type(_) => false,
})
}
fn on_mutation_type(&self, schema: &Schema) -> bool {
schema
.schema_definition
.mutation
.as_ref()
.is_some_and(|mutation| match self {
ConnectorPosition::Field(pos) => *pos.field.type_name() == mutation.name,
ConnectorPosition::Type(_) => false,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ConnectedElement<'schema> {
Field {
parent_type: SchemaTypeRef<'schema>,
field_def: &'schema Component<FieldDefinition>,
parent_category: ObjectCategory,
},
Type {
type_ref: SchemaTypeRef<'schema>,
},
}
impl ConnectedElement<'_> {
pub(super) fn base_type_name(&self) -> NamedType {
match self {
ConnectedElement::Field { field_def, .. } => field_def.ty.inner_named_type().clone(),
ConnectedElement::Type { type_ref } => type_ref.name().clone(),
}
}
pub(super) fn is_root_type(&self, schema: &Schema) -> bool {
self.is_query_type(schema) || self.is_mutation_type(schema)
}
fn is_query_type(&self, schema: &Schema) -> bool {
schema
.schema_definition
.query
.as_ref()
.is_some_and(|query| match self {
ConnectedElement::Field { .. } => false,
ConnectedElement::Type { type_ref } => type_ref.name() == query.name.as_str(),
})
}
fn is_mutation_type(&self, schema: &Schema) -> bool {
schema
.schema_definition
.mutation
.as_ref()
.is_some_and(|mutation| match self {
ConnectedElement::Field { .. } => false,
ConnectedElement::Type { type_ref } => type_ref.name() == mutation.name.as_str(),
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum ObjectCategory {
Query,
Mutation,
Other,
}
impl Display for ConnectedElement<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Field {
parent_type,
field_def,
..
} => write!(f, "{}.{}", parent_type.name(), field_def.name),
Self::Type { type_ref } => write!(f, "{}", type_ref.name()),
}
}
}