use std::fmt;
use std::sync::Arc;
use apollo_compiler::ExecutableDocument;
use apollo_compiler::Name;
use apollo_compiler::Node;
use apollo_compiler::ast;
use apollo_compiler::collections::IndexMap;
use apollo_compiler::collections::IndexSet;
use apollo_compiler::executable::Field;
use apollo_compiler::executable::Fragment;
use apollo_compiler::executable::FragmentMap;
use apollo_compiler::executable::Operation;
use apollo_compiler::executable::Selection;
use apollo_compiler::executable::SelectionSet;
use apollo_compiler::validation::Valid;
use crate::FederationError;
use crate::bail;
use crate::display_helpers;
use crate::ensure;
use crate::internal_error;
use crate::schema::ValidFederationSchema;
use crate::schema::position::CompositeTypeDefinitionPosition;
use crate::schema::position::INTROSPECTION_TYPENAME_FIELD_NAME;
use crate::schema::position::InterfaceTypeDefinitionPosition;
use crate::schema::position::ObjectTypeDefinitionPosition;
use crate::utils::FallibleIterator;
fn vec_sorted_by<T: Clone>(src: &[T], compare: impl Fn(&T, &T) -> std::cmp::Ordering) -> Vec<T> {
let mut sorted = src.to_owned();
sorted.sort_by(&compare);
sorted
}
fn get_interface_implementers<'a>(
interface: &InterfaceTypeDefinitionPosition,
schema: &'a ValidFederationSchema,
) -> Result<&'a IndexSet<ObjectTypeDefinitionPosition>, FederationError> {
Ok(&schema
.referencers()
.get_interface_type(&interface.type_name)?
.object_types)
}
fn runtime_types_implies(
x: &CompositeTypeDefinitionPosition,
y: &CompositeTypeDefinitionPosition,
schema: &ValidFederationSchema,
) -> Result<bool, FederationError> {
use CompositeTypeDefinitionPosition::*;
match (x, y) {
(Object(x), Object(y)) => Ok(x == y),
(Object(object), Union(union)) => {
let union_type = union.get(schema.schema())?;
Ok(union_type.members.contains(&object.type_name))
}
(Union(union), Object(object)) => {
let union_type = union.get(schema.schema())?;
Ok(union_type.members.len() == 1 && union_type.members.contains(&object.type_name))
}
(Object(object), Interface(interface)) => {
let interface_implementers = get_interface_implementers(interface, schema)?;
Ok(interface_implementers.contains(object))
}
(Interface(interface), Object(object)) => {
let interface_implementers = get_interface_implementers(interface, schema)?;
Ok(interface_implementers.len() == 1 && interface_implementers.contains(object))
}
(Union(x), Union(y)) if x == y => Ok(true),
(Union(x), Union(y)) => {
let (x, y) = (x.get(schema.schema())?, y.get(schema.schema())?);
Ok(x.members.is_subset(&y.members))
}
(Interface(x), Interface(y)) if x == y => Ok(true),
(Interface(x), Interface(y)) => {
let x = get_interface_implementers(x, schema)?;
let y = get_interface_implementers(y, schema)?;
Ok(x.is_subset(y))
}
(Union(union), Interface(interface)) => {
let union = union.get(schema.schema())?;
let interface_implementers = get_interface_implementers(interface, schema)?;
Ok(union.members.iter().all(|m| {
let m_ty = ObjectTypeDefinitionPosition::new(m.name.clone());
interface_implementers.contains(&m_ty)
}))
}
(Interface(interface), Union(union)) => {
let interface_implementers = get_interface_implementers(interface, schema)?;
let union = union.get(schema.schema())?;
Ok(interface_implementers
.iter()
.all(|t| union.members.contains(&t.type_name)))
}
}
}
fn get_ground_types(
ty: &CompositeTypeDefinitionPosition,
schema: &ValidFederationSchema,
) -> Result<Vec<ObjectTypeDefinitionPosition>, FederationError> {
let mut result = schema.possible_runtime_types(ty.clone())?;
result.sort_by(|a, b| a.type_name.cmp(&b.type_name));
Ok(result.into_iter().collect())
}
#[derive(Debug, Clone)]
struct DisplayTypeCondition(Vec<CompositeTypeDefinitionPosition>);
impl DisplayTypeCondition {
fn new(ty: CompositeTypeDefinitionPosition) -> Self {
DisplayTypeCondition(vec![ty])
}
fn deduced() -> Self {
DisplayTypeCondition(Vec::new())
}
fn add_type_name(
&self,
name: Name,
schema: &ValidFederationSchema,
) -> Result<Self, FederationError> {
let ty: CompositeTypeDefinitionPosition = schema.get_type(name)?.try_into()?;
if self
.0
.iter()
.fallible_any(|t| runtime_types_implies(t, &ty, schema))?
{
return Ok(self.clone());
}
let mut buf = Vec::new();
for t in &self.0 {
if !runtime_types_implies(&ty, t, schema)? {
buf.push(t.clone());
}
}
buf.push(ty);
buf.sort_by(|a, b| a.type_name().cmp(b.type_name()));
Ok(DisplayTypeCondition(buf))
}
}
#[derive(Debug, Clone)]
pub struct NormalizedTypeCondition {
ground_set: Vec<ObjectTypeDefinitionPosition>,
for_display: DisplayTypeCondition,
}
impl PartialEq for NormalizedTypeCondition {
fn eq(&self, other: &Self) -> bool {
self.ground_set == other.ground_set
}
}
impl Eq for NormalizedTypeCondition {}
impl std::hash::Hash for NormalizedTypeCondition {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.ground_set.hash(state);
}
}
impl NormalizedTypeCondition {
pub(crate) fn from_type_name(
name: Name,
schema: &ValidFederationSchema,
) -> Result<Option<Self>, FederationError> {
let ty: CompositeTypeDefinitionPosition = schema.get_type(name)?.try_into()?;
let ground_set = get_ground_types(&ty, schema)?;
if ground_set.is_empty() {
return Ok(None);
}
Ok(Some(NormalizedTypeCondition {
ground_set,
for_display: DisplayTypeCondition::new(ty),
}))
}
pub(crate) fn from_object_type(ty: &ObjectTypeDefinitionPosition) -> Self {
NormalizedTypeCondition {
ground_set: vec![ty.clone()],
for_display: DisplayTypeCondition::new(ty.clone().into()),
}
}
pub(crate) fn from_object_types(
types: impl Iterator<Item = ObjectTypeDefinitionPosition>,
) -> Result<Self, FederationError> {
let mut ground_set: Vec<_> = types.collect();
if ground_set.is_empty() {
bail!("Unexpected empty type list for from_object_types")
}
ground_set.sort_by(|a, b| a.type_name.cmp(&b.type_name));
Ok(NormalizedTypeCondition {
ground_set,
for_display: DisplayTypeCondition::deduced(),
})
}
pub(crate) fn ground_set(&self) -> &[ObjectTypeDefinitionPosition] {
&self.ground_set
}
pub fn is_named_type(&self, type_name: &Name) -> bool {
let Some((first, rest)) = self.for_display.0.split_first() else {
return false;
};
if rest.is_empty() && first.type_name() == type_name {
return true;
}
let Some((first, rest)) = self.ground_set.split_first() else {
return false;
};
rest.is_empty() && first.type_name == *type_name
}
pub fn is_named_object_type(&self) -> bool {
let Some((display_first, display_rest)) = self.for_display.0.split_first() else {
return false;
};
display_rest.is_empty() && display_first.is_object_type()
}
pub fn implies(&self, other: &Self) -> bool {
self.ground_set.iter().all(|t| other.ground_set.contains(t))
}
}
impl NormalizedTypeCondition {
pub(crate) fn add_type_name(
&self,
name: Name,
schema: &ValidFederationSchema,
) -> Result<Option<Self>, FederationError> {
let other_ty: CompositeTypeDefinitionPosition =
schema.get_type(name.clone())?.try_into()?;
let other_types = get_ground_types(&other_ty, schema)?;
let ground_set: Vec<ObjectTypeDefinitionPosition> = self
.ground_set
.iter()
.filter(|t| other_types.contains(t))
.cloned()
.collect();
if ground_set.is_empty() {
Ok(None)
} else {
let for_display = if ground_set.len() == self.ground_set.len() {
self.for_display.clone()
} else {
self.for_display.add_type_name(name, schema)?
};
Ok(Some(NormalizedTypeCondition {
ground_set,
for_display,
}))
}
}
fn field_type_condition(
&self,
field: &Field,
schema: &ValidFederationSchema,
) -> Result<Option<Self>, FederationError> {
let declared_type = field.ty().inner_named_type();
let mut types = IndexSet::default();
for ty_pos in &self.ground_set {
let ty_def = ty_pos.get(schema.schema())?;
let Some(field_def) = ty_def.fields.get(&field.name) else {
continue;
};
let field_ty = field_def.ty.inner_named_type().clone();
types.insert(field_ty);
}
if types.len() == 1
&& let Some(first) = types.first()
{
return NormalizedTypeCondition::from_type_name(first.clone(), schema);
}
let mut ground_types = IndexSet::default();
for ty in &types {
let pos = schema.get_type(ty.clone())?.try_into()?;
let pos_types = schema.possible_runtime_types(pos)?;
ground_types.extend(pos_types.into_iter());
}
if ground_types.is_empty() {
return Ok(None);
}
if let Some(declared_type_cond) =
NormalizedTypeCondition::from_type_name(declared_type.clone(), schema)?
&& declared_type_cond.ground_set.len() == ground_types.len()
&& declared_type_cond
.ground_set
.iter()
.all(|t| ground_types.contains(t))
{
return Ok(Some(declared_type_cond));
}
Ok(Some(NormalizedTypeCondition::from_object_types(
ground_types.into_iter(),
)?))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Literal {
Pos(Name), Neg(Name), }
impl Literal {
pub fn variable(&self) -> &Name {
match self {
Literal::Pos(name) | Literal::Neg(name) => name,
}
}
pub fn polarity(&self) -> bool {
matches!(self, Literal::Pos(_))
}
}
#[derive(Debug, Clone, Default, Eq)]
pub struct Clause(Vec<Literal>);
impl Clause {
pub fn literals(&self) -> &[Literal] {
&self.0
}
pub fn is_always_true(&self) -> bool {
self.0.is_empty()
}
pub fn implies(&self, other: &Clause) -> bool {
let mut self_variables: IndexMap<Name, bool> = IndexMap::default();
for lit in &self.0 {
self_variables.insert(lit.variable().clone(), lit.polarity());
}
other.0.iter().all(|lit| {
self_variables
.get(lit.variable())
.is_some_and(|pol| *pol == lit.polarity())
})
}
pub fn from_literals(literals: &[Literal]) -> Self {
let variables: IndexMap<Name, bool> = literals
.iter()
.map(|lit| (lit.variable().clone(), lit.polarity()))
.collect();
Self::from_variable_map(&variables)
}
fn from_variable_map(variables: &IndexMap<Name, bool>) -> Self {
let mut buf: Vec<Literal> = variables
.iter()
.map(|(name, polarity)| match polarity {
false => Literal::Neg(name.clone()),
true => Literal::Pos(name.clone()),
})
.collect();
buf.sort_by(|a, b| a.variable().cmp(b.variable()));
Clause(buf)
}
pub fn concatenate(&self, other: &Clause) -> Option<Clause> {
let mut variables: IndexMap<Name, bool> = IndexMap::default();
for lit in &self.0 {
variables.insert(lit.variable().clone(), lit.polarity());
}
for lit in &other.0 {
let var = lit.variable();
let entry = variables.entry(var.clone()).or_insert(lit.polarity());
if *entry != lit.polarity() {
return None; }
}
Some(Self::from_variable_map(&variables))
}
pub fn subtract(&self, other: &Clause) -> Option<Clause> {
let mut other_variables: IndexMap<Name, bool> = IndexMap::default();
for lit in &other.0 {
other_variables.insert(lit.variable().clone(), lit.polarity());
}
let mut variables: IndexMap<Name, bool> = IndexMap::default();
for lit in &self.0 {
let var = lit.variable();
if let Some(pol) = other_variables.get(var) {
if *pol == lit.polarity() {
continue;
} else {
return None;
}
} else {
variables.insert(var.clone(), lit.polarity());
}
}
Some(Self::from_variable_map(&variables))
}
fn add_selection_directives(
&self,
directives: &ast::DirectiveList,
) -> Result<Option<Clause>, FederationError> {
let Some(selection_clause) = boolean_clause_from_directives(directives)? else {
return Ok(None);
};
Ok(self.concatenate(&selection_clause))
}
pub fn concatenate_and_simplify(&self, clause: &Clause) -> Option<(Clause, Clause)> {
let mut all_variables: IndexMap<Name, bool> = IndexMap::default();
for lit in &self.0 {
all_variables.insert(lit.variable().clone(), lit.polarity());
}
let mut added_variables: IndexMap<Name, bool> = IndexMap::default();
for lit in &clause.0 {
let var = lit.variable();
match all_variables.entry(var.clone()) {
indexmap::map::Entry::Occupied(entry) => {
if entry.get() != &lit.polarity() {
return None; }
}
indexmap::map::Entry::Vacant(entry) => {
entry.insert(lit.polarity());
added_variables.insert(var.clone(), lit.polarity());
}
}
}
Some((
Self::from_variable_map(&all_variables),
Self::from_variable_map(&added_variables),
))
}
}
impl PartialEq for Clause {
fn eq(&self, other: &Self) -> bool {
self.0.len() == other.0.len() && self.0.iter().all(|l| other.0.contains(l))
}
}
fn boolean_clause_from_directives(
directives: &ast::DirectiveList,
) -> Result<Option<Clause>, FederationError> {
let mut variables = IndexMap::default(); if let Some(skip) = directives.get("skip") {
let Some(value) = skip.specified_argument_by_name("if") else {
bail!("missing @skip(if:) argument")
};
match value.as_ref() {
ast::Value::Boolean(true) => return Ok(None),
ast::Value::Boolean(_) => {}
ast::Value::Variable(name) => {
variables.insert(name.clone(), false);
}
_ => {
bail!("expected boolean or variable `if` argument, got {value}")
}
}
}
if let Some(include) = directives.get("include") {
let Some(value) = include.specified_argument_by_name("if") else {
bail!("missing @include(if:) argument")
};
match value.as_ref() {
ast::Value::Boolean(false) => return Ok(None),
ast::Value::Boolean(true) => {}
ast::Value::Variable(name) => {
if variables.insert(name.clone(), true) == Some(false) {
return Ok(None);
}
}
_ => {
bail!("expected boolean or variable `if` argument, got {value}")
}
}
}
Ok(Some(Clause::from_variable_map(&variables)))
}
fn normalize_ast_value(v: &mut ast::Value) {
match v {
ast::Value::Object(fields) => {
fields.sort_by(|a, b| a.0.cmp(&b.0));
for (_name, value) in fields {
normalize_ast_value(value.make_mut());
}
}
ast::Value::List(items) => {
for value in items {
normalize_ast_value(value.make_mut());
}
}
_ => (), }
}
fn normalized_arguments(args: &[Node<ast::Argument>]) -> Vec<Node<ast::Argument>> {
let mut args = vec_sorted_by(args, |a, b| a.name.cmp(&b.name));
for arg in &mut args {
normalize_ast_value(arg.make_mut().value.make_mut());
}
args
}
fn remove_conditions_from_directives(directives: &ast::DirectiveList) -> ast::DirectiveList {
directives
.iter()
.filter(|d| d.name != "skip" && d.name != "include")
.cloned()
.collect()
}
pub type FieldSelectionKey = Field;
fn field_selection_key(field: &Field) -> FieldSelectionKey {
Field {
definition: field.definition.clone(),
alias: None, name: field.name.clone(),
arguments: normalized_arguments(&field.arguments),
directives: ast::DirectiveList::default(), selection_set: SelectionSet::new(field.selection_set.ty.clone()), }
}
fn eq_field_selection_key(a: &FieldSelectionKey, b: &FieldSelectionKey) -> bool {
a.name == b.name && a.arguments == b.arguments
}
fn field_display(field: &Field) -> Field {
Field {
definition: field.definition.clone(),
alias: None, name: field.name.clone(),
arguments: field.arguments.clone(),
directives: remove_conditions_from_directives(&field.directives),
selection_set: SelectionSet::new(field.selection_set.ty.clone()), }
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DefinitionVariant {
boolean_clause: Clause,
representative_field: Field,
sub_selection_response_shape: Option<ResponseShape>,
}
impl DefinitionVariant {
pub fn boolean_clause(&self) -> &Clause {
&self.boolean_clause
}
pub fn representative_field(&self) -> &Field {
&self.representative_field
}
pub fn sub_selection_response_shape(&self) -> Option<&ResponseShape> {
self.sub_selection_response_shape.as_ref()
}
pub fn with_updated_clause(&self, boolean_clause: Clause) -> Self {
DefinitionVariant {
boolean_clause,
representative_field: self.representative_field.clone(),
sub_selection_response_shape: self.sub_selection_response_shape.clone(),
}
}
pub fn with_updated_sub_selection_response_shape(&self, new_shape: ResponseShape) -> Self {
DefinitionVariant {
boolean_clause: self.boolean_clause.clone(),
representative_field: self.representative_field.clone(),
sub_selection_response_shape: Some(new_shape),
}
}
pub fn with_updated_fields(
&self,
boolean_clause: Clause,
sub_selection_response_shape: Option<ResponseShape>,
) -> Self {
DefinitionVariant {
boolean_clause,
sub_selection_response_shape,
representative_field: self.representative_field.clone(),
}
}
pub fn new(
boolean_clause: Clause,
representative_field: Field,
sub_selection_response_shape: Option<ResponseShape>,
) -> Self {
DefinitionVariant {
boolean_clause,
representative_field,
sub_selection_response_shape,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct PossibleDefinitionsPerTypeCondition {
field_selection_key: FieldSelectionKey,
conditional_variants: Vec<DefinitionVariant>,
}
impl PossibleDefinitionsPerTypeCondition {
pub fn field_selection_key(&self) -> &FieldSelectionKey {
&self.field_selection_key
}
pub fn conditional_variants(&self) -> &[DefinitionVariant] {
&self.conditional_variants
}
pub fn with_updated_conditional_variants(&self, new_variants: Vec<DefinitionVariant>) -> Self {
PossibleDefinitionsPerTypeCondition {
field_selection_key: self.field_selection_key.clone(),
conditional_variants: new_variants,
}
}
pub fn new(
field_selection_key: FieldSelectionKey,
conditional_variants: Vec<DefinitionVariant>,
) -> Self {
PossibleDefinitionsPerTypeCondition {
field_selection_key,
conditional_variants,
}
}
pub(crate) fn insert_variant(
&mut self,
variant: DefinitionVariant,
) -> Result<(), FederationError> {
for existing in &mut self.conditional_variants {
if existing.boolean_clause == variant.boolean_clause {
match (
&mut existing.sub_selection_response_shape,
variant.sub_selection_response_shape,
) {
(None, None) => {} (Some(existing_rs), Some(ref variant_rs)) => {
existing_rs.merge_with(variant_rs)?;
}
(None, Some(_)) | (Some(_), None) => {
unreachable!("mismatched sub-selection options")
}
}
return Ok(());
}
}
self.conditional_variants.push(variant);
Ok(())
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone)]
pub struct PossibleDefinitions(
IndexMap<NormalizedTypeCondition, PossibleDefinitionsPerTypeCondition>,
);
impl PossibleDefinitions {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn iter(
&self,
) -> impl Iterator<
Item = (
&NormalizedTypeCondition,
&PossibleDefinitionsPerTypeCondition,
),
> {
self.0.iter()
}
pub fn get(
&self,
type_cond: &NormalizedTypeCondition,
) -> Option<&PossibleDefinitionsPerTypeCondition> {
self.0.get(type_cond)
}
pub fn insert(
&mut self,
type_condition: NormalizedTypeCondition,
value: PossibleDefinitionsPerTypeCondition,
) -> bool {
self.0.insert(type_condition, value).is_some()
}
}
impl PossibleDefinitions {
fn insert_possible_definition(
&mut self,
type_conditions: NormalizedTypeCondition,
boolean_clause: Clause, representative_field: Field,
sub_selection_response_shape: Option<ResponseShape>,
) -> Result<(), FederationError> {
let field_selection_key = field_selection_key(&representative_field);
let entry = self.0.entry(type_conditions);
let insert_variant = |per_type_cond: &mut PossibleDefinitionsPerTypeCondition| {
let value = DefinitionVariant {
boolean_clause,
representative_field,
sub_selection_response_shape,
};
per_type_cond.insert_variant(value)
};
match entry {
indexmap::map::Entry::Vacant(e) => {
let empty_per_type_cond = PossibleDefinitionsPerTypeCondition {
field_selection_key,
conditional_variants: vec![],
};
insert_variant(e.insert(empty_per_type_cond))?;
}
indexmap::map::Entry::Occupied(mut e) => {
if !eq_field_selection_key(&e.get().field_selection_key, &field_selection_key) {
return Err(internal_error!(
"field_selection_key was expected to be the same\nexisting: {}\nadding: {}",
e.get().field_selection_key,
field_selection_key,
));
}
insert_variant(e.get_mut())?;
}
};
Ok(())
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ResponseShape {
default_type_condition: Name,
definitions_per_response_key: IndexMap< Name, PossibleDefinitions>,
}
impl ResponseShape {
pub fn default_type_condition(&self) -> &Name {
&self.default_type_condition
}
pub fn is_empty(&self) -> bool {
self.definitions_per_response_key.is_empty()
}
pub fn len(&self) -> usize {
self.definitions_per_response_key.len()
}
pub fn iter(&self) -> impl Iterator<Item = (&Name, &PossibleDefinitions)> {
self.definitions_per_response_key.iter()
}
pub fn get(&self, response_key: &Name) -> Option<&PossibleDefinitions> {
self.definitions_per_response_key.get(response_key)
}
pub fn insert(&mut self, response_key: Name, value: PossibleDefinitions) -> bool {
self.definitions_per_response_key
.insert(response_key, value)
.is_some()
}
pub fn new(default_type_condition: Name) -> Self {
ResponseShape {
default_type_condition,
definitions_per_response_key: IndexMap::default(),
}
}
pub fn merge_with(&mut self, other: &Self) -> Result<(), FederationError> {
for (response_key, other_defs) in &other.definitions_per_response_key {
let value = self
.definitions_per_response_key
.entry(response_key.clone())
.or_default();
for (type_condition, per_type_cond) in &other_defs.0 {
for variant in &per_type_cond.conditional_variants {
value.insert_possible_definition(
type_condition.clone(),
variant.boolean_clause.clone(),
variant.representative_field.clone(),
variant.sub_selection_response_shape.clone(),
)?;
}
}
}
Ok(())
}
}
struct ResponseShapeContext {
schema: ValidFederationSchema,
fragment_defs: Arc<IndexMap<Name, Node<Fragment>>>, parent_type: Name, type_condition: NormalizedTypeCondition, inherited_clause: Clause, current_clause: Clause, skip_introspection: bool, }
impl ResponseShapeContext {
fn process_selection(
&self,
response_shape: &mut ResponseShape,
selection: &Selection,
) -> Result<(), FederationError> {
match selection {
Selection::Field(field) => self.process_field_selection(response_shape, field),
Selection::FragmentSpread(fragment_spread) => {
let fragment_def =
get_fragment_definition(&self.fragment_defs, &fragment_spread.fragment_name)?;
self.process_fragment_selection(
response_shape,
fragment_def.type_condition(),
&fragment_spread.directives,
&fragment_def.selection_set,
)
}
Selection::InlineFragment(inline_fragment) => {
let fragment_type_condition = inline_fragment
.type_condition
.as_ref()
.unwrap_or(&self.parent_type);
self.process_fragment_selection(
response_shape,
fragment_type_condition,
&inline_fragment.directives,
&inline_fragment.selection_set,
)
}
}
}
fn process_field_selection(
&self,
response_shape: &mut ResponseShape,
field: &Node<Field>,
) -> Result<(), FederationError> {
if self.skip_introspection && field.name == *INTROSPECTION_TYPENAME_FIELD_NAME {
return Ok(());
}
if is_introspection_field_name(&field.name) {
return Ok(());
}
let Some(field_clause) = self
.current_clause
.add_selection_directives(&field.directives)?
else {
return Ok(());
};
let Some((inherited_clause, field_clause)) = self
.inherited_clause
.concatenate_and_simplify(&field_clause)
else {
return Ok(());
};
let sub_selection_response_shape: Option<ResponseShape> = if field.selection_set.is_empty()
{
None
} else {
ensure!(
*field.ty().inner_named_type() == field.selection_set.ty,
"internal invariant failure: field's type does not match with its selection set's type"
);
let parent_type = field.selection_set.ty.clone();
self.type_condition
.field_type_condition(field, &self.schema)?
.map(|type_condition| {
let context = ResponseShapeContext {
schema: self.schema.clone(),
fragment_defs: self.fragment_defs.clone(),
parent_type,
type_condition,
inherited_clause,
current_clause: Clause::default(), skip_introspection: false, };
context.process_selection_set(&field.selection_set)
})
.transpose()?
};
let value = response_shape
.definitions_per_response_key
.entry(field.response_key().clone())
.or_default();
value.insert_possible_definition(
self.type_condition.clone(),
field_clause,
field_display(field),
sub_selection_response_shape,
)
}
fn process_fragment_selection(
&self,
response_shape: &mut ResponseShape,
fragment_type_condition: &Name,
directives: &ast::DirectiveList,
selection_set: &SelectionSet,
) -> Result<(), FederationError> {
ensure!(
*fragment_type_condition == selection_set.ty,
"internal invariant failure: fragment's type condition does not match with its selection set's type"
);
let Some(type_condition) = NormalizedTypeCondition::add_type_name(
&self.type_condition,
fragment_type_condition.clone(),
&self.schema,
)?
else {
return Ok(());
};
let Some(current_clause) = self.current_clause.add_selection_directives(directives)? else {
return Ok(());
};
if self.inherited_clause.concatenate(¤t_clause).is_none() {
return Ok(());
}
let context = ResponseShapeContext {
schema: self.schema.clone(),
fragment_defs: self.fragment_defs.clone(),
parent_type: fragment_type_condition.clone(),
type_condition,
inherited_clause: self.inherited_clause.clone(), current_clause,
skip_introspection: self.skip_introspection,
};
context.process_selection_set_within(response_shape, selection_set)
}
fn process_selection_set_within(
&self,
response_shape: &mut ResponseShape,
selection_set: &SelectionSet,
) -> Result<(), FederationError> {
for selection in &selection_set.selections {
self.process_selection(response_shape, selection)?;
}
Ok(())
}
fn process_selection_set(
&self,
selection_set: &SelectionSet,
) -> Result<ResponseShape, FederationError> {
let mut response_shape = ResponseShape::new(selection_set.ty.clone());
self.process_selection_set_within(&mut response_shape, selection_set)?;
Ok(response_shape)
}
}
fn is_introspection_field_name(name: &Name) -> bool {
name == "__schema" || name == "__type"
}
fn get_operation_and_fragment_definitions(
operation_doc: &Valid<ExecutableDocument>,
) -> Result<(Node<Operation>, Arc<FragmentMap>), FederationError> {
let mut op_iter = operation_doc.operations.iter();
let Some(first) = op_iter.next() else {
bail!("Operation not found")
};
if op_iter.next().is_some() {
bail!("Multiple operations are not supported")
}
let fragment_defs = Arc::new(operation_doc.fragments.clone());
Ok((first.clone(), fragment_defs))
}
fn get_fragment_definition<'a>(
fragment_defs: &'a Arc<IndexMap<Name, Node<Fragment>>>,
fragment_name: &Name,
) -> Result<&'a Node<Fragment>, FederationError> {
let fragment_def = fragment_defs
.get(fragment_name)
.ok_or_else(|| internal_error!("Fragment definition not found: {}", fragment_name))?;
Ok(fragment_def)
}
pub fn compute_response_shape_for_operation(
operation_doc: &Valid<ExecutableDocument>,
schema: &ValidFederationSchema,
) -> Result<ResponseShape, FederationError> {
let (operation, fragment_defs) = get_operation_and_fragment_definitions(operation_doc)?;
let parent_type = operation.selection_set.ty.clone();
let Some(type_condition) =
NormalizedTypeCondition::from_type_name(parent_type.clone(), schema)?
else {
bail!("Unexpected empty type condition for the root type: {parent_type}")
};
let context = ResponseShapeContext {
schema: schema.clone(),
fragment_defs,
parent_type,
type_condition,
inherited_clause: Clause::default(), current_clause: Clause::default(), skip_introspection: true, };
context.process_selection_set(&operation.selection_set)
}
pub fn compute_the_root_type_condition_for_operation(
operation_doc: &Valid<ExecutableDocument>,
) -> Result<Name, FederationError> {
let (operation, _) = get_operation_and_fragment_definitions(operation_doc)?;
Ok(operation.selection_set.ty.clone())
}
pub fn compute_response_shape_for_entity_fetch_operation(
operation_doc: &Valid<ExecutableDocument>,
schema: &ValidFederationSchema,
) -> Result<Vec<ResponseShape>, FederationError> {
let (operation, fragment_defs) = get_operation_and_fragment_definitions(operation_doc)?;
let mut sel_iter = operation.selection_set.selections.iter();
let Some(first_selection) = sel_iter.next() else {
bail!("Entity fetch is expected to have at least one selection")
};
if sel_iter.next().is_some() {
bail!("Entity fetch is expected to have exactly one selection")
}
let Selection::Field(field) = first_selection else {
bail!("Entity fetch is expected to have a field selection only")
};
if field.name != crate::subgraph::spec::ENTITIES_QUERY {
bail!("Entity fetch is expected to have a field selection named `_entities`")
}
field
.selection_set
.selections
.iter()
.map(|selection| {
let type_condition = get_fragment_type_condition(&fragment_defs, selection)?;
let Some(normalized_type_condition) =
NormalizedTypeCondition::from_type_name(type_condition.clone(), schema)?
else {
bail!("Unexpected empty type condition for the entity type: {type_condition}")
};
let context = ResponseShapeContext {
schema: schema.clone(),
fragment_defs: fragment_defs.clone(),
parent_type: type_condition.clone(),
type_condition: normalized_type_condition,
inherited_clause: Clause::default(), current_clause: Clause::default(), skip_introspection: false, };
let mut response_shape = ResponseShape::new(type_condition);
context.process_selection(&mut response_shape, selection)?;
Ok(response_shape)
})
.collect()
}
fn get_fragment_type_condition(
fragment_defs: &Arc<FragmentMap>,
selection: &Selection,
) -> Result<Name, FederationError> {
Ok(match selection {
Selection::FragmentSpread(fragment_spread) => {
let fragment_def =
get_fragment_definition(fragment_defs, &fragment_spread.fragment_name)?;
fragment_def.type_condition().clone()
}
Selection::InlineFragment(inline) => {
let Some(type_condition) = &inline.type_condition else {
bail!(
"Expected a type condition on the inline fragment under the `_entities` selection"
)
};
type_condition.clone()
}
_ => bail!("Expected a fragment under the `_entities` selection"),
})
}
pub fn compute_response_shape_for_selection_set(
schema: &ValidFederationSchema,
selection_set: &SelectionSet,
) -> Result<ResponseShape, FederationError> {
let type_condition = &selection_set.ty;
let Some(normalized_type_condition) =
NormalizedTypeCondition::from_type_name(type_condition.clone(), schema)?
else {
bail!("Unexpected empty type condition for field set: {type_condition}")
};
let context = ResponseShapeContext {
schema: schema.clone(),
fragment_defs: Default::default(), parent_type: type_condition.clone(),
type_condition: normalized_type_condition,
inherited_clause: Clause::default(), current_clause: Clause::default(), skip_introspection: false, };
context.process_selection_set(selection_set)
}
impl fmt::Display for DisplayTypeCondition {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.0.is_empty() {
return write!(f, "<deduced>");
}
for (i, cond) in self.0.iter().enumerate() {
if i > 0 {
write!(f, " ∩ ")?;
}
write!(f, "{}", cond.type_name())?;
}
Ok(())
}
}
impl fmt::Display for NormalizedTypeCondition {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.ground_set.is_empty() {
return Err(fmt::Error);
}
write!(f, "{}", self.for_display)?;
if self.for_display.0.len() != 1 {
write!(f, " = {{")?;
for (i, ty) in self.ground_set.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", ty.type_name)?;
}
write!(f, "}}")?;
}
Ok(())
}
}
impl fmt::Display for Clause {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.0.is_empty() {
write!(f, "true")
} else {
for (i, l) in self.0.iter().enumerate() {
if i > 0 {
write!(f, " ∧ ")?;
}
match l {
Literal::Pos(v) => write!(f, "{v}")?,
Literal::Neg(v) => write!(f, "¬{v}")?,
}
}
Ok(())
}
}
}
impl DefinitionVariant {
fn write_indented(&self, state: &mut display_helpers::State<'_, '_>) -> fmt::Result {
let field_display = &self.representative_field;
let boolean_str = if !self.boolean_clause.is_always_true() {
format!(" if {}", self.boolean_clause)
} else {
"".to_string()
};
state.write(format_args!("{field_display} (on <type>){boolean_str}"))?;
if let Some(sub_selection_response_shape) = &self.sub_selection_response_shape {
state.write(" ")?;
sub_selection_response_shape.write_indented(state)?;
}
Ok(())
}
}
impl fmt::Display for DefinitionVariant {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.write_indented(&mut display_helpers::State::new(f))
}
}
impl PossibleDefinitionsPerTypeCondition {
fn has_boolean_conditions(&self) -> bool {
self.conditional_variants.len() > 1
|| self
.conditional_variants
.first()
.is_some_and(|variant| !variant.boolean_clause.is_always_true())
}
fn write_indented(&self, state: &mut display_helpers::State<'_, '_>) -> fmt::Result {
for (i, variant) in self.conditional_variants.iter().enumerate() {
if i > 0 {
state.new_line()?;
}
variant.write_indented(state)?;
}
Ok(())
}
}
impl fmt::Display for PossibleDefinitionsPerTypeCondition {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.write_indented(&mut display_helpers::State::new(f))
}
}
impl PossibleDefinitions {
fn has_type_conditions(&self, default_type_condition: &Name) -> bool {
self.0.len() > 1
|| self.0.first().is_some_and(|(type_condition, _)| {
!type_condition.is_named_type(default_type_condition)
})
}
fn has_multiple_definitions(&self) -> bool {
self.0.len() > 1
|| self
.0
.first()
.is_some_and(|(_, per_type_cond)| per_type_cond.has_boolean_conditions())
}
fn write_indented(&self, state: &mut display_helpers::State<'_, '_>) -> fmt::Result {
let arrow_sym = if self.has_multiple_definitions() {
"-may->"
} else {
"----->"
};
let mut is_first = true;
for (type_condition, per_type_cond) in &self.0 {
for variant in &per_type_cond.conditional_variants {
let field_display = &variant.representative_field;
let type_cond_str = format!(" on {type_condition}");
let boolean_str = if !variant.boolean_clause.is_always_true() {
format!(" if {}", variant.boolean_clause)
} else {
"".to_string()
};
if is_first {
is_first = false;
} else {
state.new_line()?;
}
state.write(format_args!(
"{arrow_sym} {field_display}{type_cond_str}{boolean_str}"
))?;
if let Some(sub_selection_response_shape) = &variant.sub_selection_response_shape {
state.write(" ")?;
sub_selection_response_shape.write_indented(state)?;
}
}
}
Ok(())
}
}
impl fmt::Display for PossibleDefinitions {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.write_indented(&mut display_helpers::State::new(f))
}
}
impl ResponseShape {
fn write_indented(&self, state: &mut display_helpers::State<'_, '_>) -> fmt::Result {
state.write("{")?;
state.indent_no_new_line();
for (response_key, defs) in &self.definitions_per_response_key {
let has_type_cond = defs.has_type_conditions(&self.default_type_condition);
let arrow_sym = if has_type_cond || defs.has_multiple_definitions() {
"-may->"
} else {
"----->"
};
for (type_condition, per_type_cond) in &defs.0 {
for variant in &per_type_cond.conditional_variants {
let field_display = &variant.representative_field;
let type_cond_str = if has_type_cond {
format!(" on {type_condition}")
} else {
"".to_string()
};
let boolean_str = if !variant.boolean_clause.is_always_true() {
format!(" if {}", variant.boolean_clause)
} else {
"".to_string()
};
state.new_line()?;
state.write(format_args!(
"{response_key} {arrow_sym} {field_display}{type_cond_str}{boolean_str}"
))?;
if let Some(sub_selection_response_shape) =
&variant.sub_selection_response_shape
{
state.write(" ")?;
sub_selection_response_shape.write_indented(state)?;
}
}
}
}
state.dedent()?;
state.write("}")
}
}
impl fmt::Display for ResponseShape {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.write_indented(&mut display_helpers::State::new(f))
}
}