1use std::fmt::Display;
2use std::fmt::Formatter;
3use std::hash::Hash;
4use std::ops::Deref;
5use std::ops::DerefMut;
6use std::sync::Arc;
7
8use apollo_compiler::Name;
9use apollo_compiler::Node;
10use apollo_compiler::collections::IndexMap;
11use apollo_compiler::collections::IndexSet;
12use apollo_compiler::schema::NamedType;
13use apollo_compiler::schema::Type;
14use petgraph::Direction;
15use petgraph::graph::DiGraph;
16use petgraph::graph::EdgeIndex;
17use petgraph::graph::EdgeReference;
18use petgraph::graph::NodeIndex;
19use petgraph::visit::EdgeRef;
20
21use crate::ensure;
22use crate::error::FederationError;
23use crate::error::SingleFederationError;
24use crate::internal_error;
25use crate::operation::Field;
26use crate::operation::InlineFragment;
27use crate::operation::SelectionSet;
28use crate::schema::ValidFederationSchema;
29use crate::schema::field_set::parse_field_set;
30use crate::schema::position::CompositeTypeDefinitionPosition;
31use crate::schema::position::FieldDefinitionPosition;
32use crate::schema::position::InterfaceFieldDefinitionPosition;
33use crate::schema::position::ObjectFieldArgumentDefinitionPosition;
34use crate::schema::position::ObjectTypeDefinitionPosition;
35use crate::schema::position::OutputTypeDefinitionPosition;
36use crate::schema::position::SchemaRootDefinitionKind;
37use crate::utils::FallibleIterator;
38
39pub mod build_query_graph;
40pub(crate) mod condition_resolver;
41pub(crate) mod graph_path;
42pub mod output;
43pub(crate) mod path_tree;
44
45pub use build_query_graph::build_federated_query_graph;
46pub use build_query_graph::build_supergraph_api_query_graph;
47use graph_path::operation::OpGraphPathContext;
48use graph_path::operation::OpGraphPathTrigger;
49use graph_path::operation::OpPathElement;
50
51use crate::query_graph::condition_resolver::ConditionResolution;
52use crate::query_graph::condition_resolver::ConditionResolver;
53use crate::query_graph::graph_path::ExcludedConditions;
54use crate::query_graph::graph_path::ExcludedDestinations;
55use crate::query_plan::QueryPlanCost;
56use crate::query_plan::query_planning_traversal::non_local_selections_estimation;
57
58#[derive(Debug, Clone, PartialEq, Eq, Hash)]
59pub(crate) struct QueryGraphNode {
60 pub(crate) type_: QueryGraphNodeType,
62 pub(crate) source: Arc<str>,
65 pub(crate) has_reachable_cross_subgraph_edges: bool,
67 pub(crate) provide_id: Option<u32>,
75 pub(crate) root_kind: Option<SchemaRootDefinitionKind>,
77}
78
79impl QueryGraphNode {
80 pub(crate) fn is_root_node(&self) -> bool {
81 matches!(self.type_, QueryGraphNodeType::FederatedRootType(_))
82 }
83}
84
85impl Display for QueryGraphNode {
86 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
87 write!(f, "{}({})", self.type_, self.source)?;
88 if let Some(provide_id) = self.provide_id {
89 write!(f, "-{provide_id}")?;
90 }
91 if self.is_root_node() {
92 write!(f, "*")?;
93 }
94 Ok(())
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From, derive_more::IsVariant)]
99pub(crate) enum QueryGraphNodeType {
100 SchemaType(OutputTypeDefinitionPosition),
101 FederatedRootType(SchemaRootDefinitionKind),
102}
103
104impl Display for QueryGraphNodeType {
105 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
106 match self {
107 QueryGraphNodeType::SchemaType(pos) => pos.fmt(f),
108 QueryGraphNodeType::FederatedRootType(root_kind) => {
109 write!(f, "[{root_kind}]")
110 }
111 }
112 }
113}
114
115impl TryFrom<QueryGraphNodeType> for CompositeTypeDefinitionPosition {
116 type Error = FederationError;
117
118 fn try_from(value: QueryGraphNodeType) -> Result<Self, Self::Error> {
119 match value {
120 QueryGraphNodeType::SchemaType(ty) => Ok(ty.try_into()?),
121 QueryGraphNodeType::FederatedRootType(_) => Err(FederationError::internal(format!(
122 r#"Type "{value}" was unexpectedly not a composite type"#
123 ))),
124 }
125 }
126}
127
128impl TryFrom<QueryGraphNodeType> for ObjectTypeDefinitionPosition {
129 type Error = FederationError;
130
131 fn try_from(value: QueryGraphNodeType) -> Result<Self, Self::Error> {
132 match value {
133 QueryGraphNodeType::SchemaType(ty) => Ok(ty.try_into()?),
134 QueryGraphNodeType::FederatedRootType(_) => Err(FederationError::internal(format!(
135 r#"Type "{value}" was unexpectedly not an object type"#
136 ))),
137 }
138 }
139}
140
141#[derive(Debug, PartialEq, Clone)]
144pub struct ContextCondition {
145 context: String,
146 subgraph_name: Arc<str>,
147 selection: String,
150 types_with_context_set: IndexSet<CompositeTypeDefinitionPosition>,
151 argument_name: Name,
154 argument_coordinate: ObjectFieldArgumentDefinitionPosition,
156 argument_type: Node<Type>,
159}
160
161#[derive(Debug, PartialEq, Clone)]
162pub(crate) struct QueryGraphEdge {
163 pub(crate) transition: QueryGraphEdgeTransition,
167 pub(crate) conditions: Option<Arc<SelectionSet>>,
179 pub(crate) override_condition: Option<OverrideCondition>,
185 pub(crate) required_contexts: Vec<ContextCondition>,
188}
189
190impl QueryGraphEdge {
191 pub(crate) fn new(
192 transition: QueryGraphEdgeTransition,
193 conditions: Option<Arc<SelectionSet>>,
194 override_condition: Option<OverrideCondition>,
195 ) -> Self {
196 Self {
197 transition,
198 conditions,
199 override_condition,
200 required_contexts: Vec::new(),
201 }
202 }
203
204 fn satisfies_override_conditions(&self, conditions_to_check: &OverrideConditions) -> bool {
205 if let Some(override_condition) = &self.override_condition {
206 override_condition.check(conditions_to_check)
207 } else {
208 true
209 }
210 }
211}
212
213impl Display for QueryGraphEdge {
214 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
215 if matches!(
216 self.transition,
217 QueryGraphEdgeTransition::SubgraphEnteringTransition
218 ) && self.conditions.is_none()
219 {
220 return Ok(());
221 }
222
223 match (&self.override_condition, &self.conditions) {
224 (Some(override_condition), Some(conditions)) => write!(
225 f,
226 "{}, {} ⊢ {}",
227 conditions, override_condition, self.transition
228 ),
229 (Some(override_condition), None) => {
230 write!(f, "{} ⊢ {}", override_condition, self.transition)
231 }
232 (None, Some(conditions)) => write!(f, "{} ⊢ {}", conditions, self.transition),
233 _ => self.transition.fmt(f),
234 }
235 }
236}
237
238#[derive(Debug, Clone, PartialEq, Eq, Hash)]
239pub(crate) struct OverrideCondition {
240 pub(crate) label: Arc<str>,
241 pub(crate) condition: bool,
242}
243
244impl OverrideCondition {
245 pub(crate) fn check(&self, override_conditions: &OverrideConditions) -> bool {
246 override_conditions.get(&self.label) == Some(&self.condition)
247 }
248}
249
250#[derive(Debug, Clone, Default)]
256pub(crate) struct OverrideConditions(IndexMap<Arc<str>, bool>);
257
258impl Deref for OverrideConditions {
259 type Target = IndexMap<Arc<str>, bool>;
260
261 fn deref(&self) -> &Self::Target {
262 &self.0
263 }
264}
265
266impl DerefMut for OverrideConditions {
267 fn deref_mut(&mut self) -> &mut Self::Target {
268 &mut self.0
269 }
270}
271
272impl OverrideConditions {
273 pub(crate) fn new(graph: &QueryGraph, enabled_conditions: &IndexSet<String>) -> Self {
274 Self(
275 graph
276 .override_condition_labels
277 .iter()
278 .map(|label| (label.clone(), enabled_conditions.contains(label.as_ref())))
279 .collect(),
280 )
281 }
282}
283
284impl Display for OverrideCondition {
285 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
286 write!(f, "{} = {}", self.label, self.condition)
287 }
288}
289
290#[derive(Debug, Clone, PartialEq, Eq, Hash)]
294pub(crate) enum QueryGraphEdgeTransition {
295 FieldCollection {
297 source: Arc<str>,
299 field_definition_position: FieldDefinitionPosition,
301 is_part_of_provides: bool,
303 },
304 Downcast {
308 source: Arc<str>,
310 from_type_position: CompositeTypeDefinitionPosition,
313 to_type_position: CompositeTypeDefinitionPosition,
315 },
316 KeyResolution,
320 RootTypeResolution {
325 root_kind: SchemaRootDefinitionKind,
327 },
328 SubgraphEnteringTransition,
333 InterfaceObjectFakeDownCast {
340 source: Arc<str>,
342 from_type_position: CompositeTypeDefinitionPosition,
345 to_type_name: Name,
347 },
348}
349
350impl QueryGraphEdgeTransition {
351 pub(crate) fn collect_operation_elements(&self) -> bool {
352 match self {
353 QueryGraphEdgeTransition::FieldCollection { .. } => true,
354 QueryGraphEdgeTransition::Downcast { .. } => true,
355 QueryGraphEdgeTransition::KeyResolution => false,
356 QueryGraphEdgeTransition::RootTypeResolution { .. } => false,
357 QueryGraphEdgeTransition::SubgraphEnteringTransition => false,
358 QueryGraphEdgeTransition::InterfaceObjectFakeDownCast { .. } => true,
359 }
360 }
361
362 pub(crate) fn matches_supergraph_transition(
363 &self,
364 other: &Self,
365 ) -> Result<bool, FederationError> {
366 ensure!(
367 other.collect_operation_elements(),
368 "Supergraphs shouldn't have a transition that doesn't collect elements; got {}",
369 other,
370 );
371 Ok(match self {
372 QueryGraphEdgeTransition::FieldCollection {
373 field_definition_position,
374 ..
375 } => {
376 let QueryGraphEdgeTransition::FieldCollection {
377 field_definition_position: other_field_definition_position,
378 ..
379 } = other
380 else {
381 return Ok(false);
382 };
383 field_definition_position.field_name()
384 == other_field_definition_position.field_name()
385 }
386 QueryGraphEdgeTransition::Downcast {
387 to_type_position, ..
388 } => {
389 let QueryGraphEdgeTransition::Downcast {
390 to_type_position: other_to_type_position,
391 ..
392 } = other
393 else {
394 return Ok(false);
395 };
396 to_type_position.type_name() == other_to_type_position.type_name()
397 }
398 QueryGraphEdgeTransition::InterfaceObjectFakeDownCast { to_type_name, .. } => {
399 let QueryGraphEdgeTransition::InterfaceObjectFakeDownCast {
400 to_type_name: other_to_type_name,
401 ..
402 } = other
403 else {
404 return Ok(false);
405 };
406 to_type_name == other_to_type_name
407 }
408 _ => false,
409 })
410 }
411}
412
413impl Display for QueryGraphEdgeTransition {
414 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
415 match self {
416 QueryGraphEdgeTransition::FieldCollection {
417 field_definition_position,
418 ..
419 } => {
420 write!(f, "{}", field_definition_position.field_name())
421 }
422 QueryGraphEdgeTransition::Downcast {
423 to_type_position, ..
424 } => {
425 write!(f, "... on {}", to_type_position.type_name())
426 }
427 QueryGraphEdgeTransition::KeyResolution => {
428 write!(f, "key()")
429 }
430 QueryGraphEdgeTransition::RootTypeResolution { root_kind } => {
431 write!(f, "{root_kind}()")
432 }
433 QueryGraphEdgeTransition::SubgraphEnteringTransition => {
434 write!(f, "∅")
435 }
436 QueryGraphEdgeTransition::InterfaceObjectFakeDownCast { to_type_name, .. } => {
437 write!(f, "... on {to_type_name}")
438 }
439 }
440 }
441}
442
443#[derive(Debug)]
444pub struct QueryGraph {
445 current_source: Arc<str>,
450 graph: DiGraph<QueryGraphNode, QueryGraphEdge>,
453 sources: IndexMap<Arc<str>, ValidFederationSchema>,
457 subgraphs_by_name: IndexMap<Arc<str>, ValidFederationSchema>,
461 supergraph_schema: Option<ValidFederationSchema>,
463 types_to_nodes_by_source: IndexMap<Arc<str>, IndexMap<NamedType, IndexSet<NodeIndex>>>,
467 root_kinds_to_nodes_by_source:
469 IndexMap<Arc<str>, IndexMap<SchemaRootDefinitionKind, NodeIndex>>,
470 non_trivial_followup_edges: IndexMap<EdgeIndex, Vec<EdgeIndex>>,
493 arguments_to_context_ids_by_source:
500 IndexMap<Arc<str>, IndexMap<ObjectFieldArgumentDefinitionPosition, Name>>,
501 override_condition_labels: IndexSet<Arc<str>>,
502 non_local_selection_metadata: non_local_selections_estimation::QueryGraphMetadata,
505}
506
507impl QueryGraph {
508 pub(crate) fn name(&self) -> &Arc<str> {
509 &self.current_source
510 }
511
512 pub(crate) fn graph(&self) -> &DiGraph<QueryGraphNode, QueryGraphEdge> {
513 &self.graph
514 }
515
516 pub(crate) fn override_condition_labels(&self) -> &IndexSet<Arc<str>> {
517 &self.override_condition_labels
518 }
519
520 pub(crate) fn supergraph_schema(&self) -> Result<ValidFederationSchema, FederationError> {
521 self.supergraph_schema
522 .clone()
523 .ok_or_else(|| internal_error!("Supergraph schema unexpectedly missing"))
524 }
525
526 pub(crate) fn node_weight(&self, node: NodeIndex) -> Result<&QueryGraphNode, FederationError> {
527 self.graph
528 .node_weight(node)
529 .ok_or_else(|| internal_error!("Node unexpectedly missing"))
530 }
531
532 fn node_weight_mut(&mut self, node: NodeIndex) -> Result<&mut QueryGraphNode, FederationError> {
533 self.graph
534 .node_weight_mut(node)
535 .ok_or_else(|| internal_error!("Node unexpectedly missing"))
536 }
537
538 pub(crate) fn edge_weight(&self, edge: EdgeIndex) -> Result<&QueryGraphEdge, FederationError> {
539 self.graph
540 .edge_weight(edge)
541 .ok_or_else(|| internal_error!("Edge unexpectedly missing"))
542 }
543
544 fn edge_weight_mut(&mut self, edge: EdgeIndex) -> Result<&mut QueryGraphEdge, FederationError> {
545 self.graph
546 .edge_weight_mut(edge)
547 .ok_or_else(|| internal_error!("Edge unexpectedly missing"))
548 }
549
550 pub(crate) fn edge_head_weight(
551 &self,
552 edge: EdgeIndex,
553 ) -> Result<&QueryGraphNode, FederationError> {
554 let (head_id, _) = self.edge_endpoints(edge)?;
555 self.node_weight(head_id)
556 }
557
558 pub(crate) fn edge_endpoints(
559 &self,
560 edge: EdgeIndex,
561 ) -> Result<(NodeIndex, NodeIndex), FederationError> {
562 self.graph
563 .edge_endpoints(edge)
564 .ok_or_else(|| internal_error!("Edge unexpectedly missing"))
565 }
566
567 pub(crate) fn schema(&self) -> Result<&ValidFederationSchema, FederationError> {
568 self.schema_by_source(&self.current_source)
569 }
570
571 pub(crate) fn schema_by_source(
572 &self,
573 source: &str,
574 ) -> Result<&ValidFederationSchema, FederationError> {
575 self.sources
576 .get(source)
577 .ok_or_else(|| internal_error!(r#"Schema for "{source}" unexpectedly missing"#))
578 }
579
580 pub(crate) fn subgraph_schemas(&self) -> &IndexMap<Arc<str>, ValidFederationSchema> {
581 &self.subgraphs_by_name
582 }
583
584 pub(crate) fn subgraphs(&self) -> impl Iterator<Item = (&Arc<str>, &ValidFederationSchema)> {
585 self.subgraphs_by_name.iter()
586 }
587
588 pub(crate) fn nodes_for_type(
590 &self,
591 name: &Name,
592 ) -> Result<&IndexSet<NodeIndex>, FederationError> {
593 self.types_to_nodes()?
594 .get(name)
595 .ok_or_else(|| internal_error!("No nodes unexpectedly found for type"))
596 }
597
598 pub(crate) fn types_to_nodes(
599 &self,
600 ) -> Result<&IndexMap<NamedType, IndexSet<NodeIndex>>, FederationError> {
601 self.types_to_nodes_by_source(&self.current_source)
602 }
603
604 pub(super) fn types_to_nodes_by_source(
605 &self,
606 source: &str,
607 ) -> Result<&IndexMap<NamedType, IndexSet<NodeIndex>>, FederationError> {
608 self.types_to_nodes_by_source.get(source).ok_or_else(|| {
609 SingleFederationError::Internal {
610 message: "Types-to-nodes map unexpectedly missing".to_owned(),
611 }
612 .into()
613 })
614 }
615
616 fn types_to_nodes_mut(
617 &mut self,
618 ) -> Result<&mut IndexMap<NamedType, IndexSet<NodeIndex>>, FederationError> {
619 self.types_to_nodes_by_source
620 .get_mut(&self.current_source)
621 .ok_or_else(|| {
622 SingleFederationError::Internal {
623 message: "Types-to-nodes map unexpectedly missing".to_owned(),
624 }
625 .into()
626 })
627 }
628
629 pub(crate) fn root_kinds_to_nodes(
630 &self,
631 ) -> Result<&IndexMap<SchemaRootDefinitionKind, NodeIndex>, FederationError> {
632 self.root_kinds_to_nodes_by_source
633 .get(&self.current_source)
634 .ok_or_else(|| {
635 SingleFederationError::Internal {
636 message: "Root-kinds-to-nodes map unexpectedly missing".to_owned(),
637 }
638 .into()
639 })
640 }
641
642 pub(crate) fn root_kinds_to_nodes_by_source(
643 &self,
644 source: &str,
645 ) -> Result<&IndexMap<SchemaRootDefinitionKind, NodeIndex>, FederationError> {
646 self.root_kinds_to_nodes_by_source
647 .get(source)
648 .ok_or_else(|| {
649 SingleFederationError::Internal {
650 message: "Root-kinds-to-nodes map unexpectedly missing".to_owned(),
651 }
652 .into()
653 })
654 }
655
656 fn root_kinds_to_nodes_mut(
657 &mut self,
658 ) -> Result<&mut IndexMap<SchemaRootDefinitionKind, NodeIndex>, FederationError> {
659 self.root_kinds_to_nodes_by_source
660 .get_mut(&self.current_source)
661 .ok_or_else(|| {
662 SingleFederationError::Internal {
663 message: "Root-kinds-to-nodes map unexpectedly missing".to_owned(),
664 }
665 .into()
666 })
667 }
668
669 pub(crate) fn context_id_by_source_and_argument(
670 &self,
671 source: &str,
672 argument: &ObjectFieldArgumentDefinitionPosition,
673 ) -> Result<&Name, FederationError> {
674 self.arguments_to_context_ids_by_source
675 .get(source)
676 .and_then(|r| r.get(argument))
677 .ok_or_else(|| {
678 internal_error!("context ID unexpectedly missing for @fromContext argument")
679 })
680 }
681
682 pub(crate) fn is_context_used(&self) -> bool {
683 !self.arguments_to_context_ids_by_source.is_empty()
684 }
685
686 pub(crate) fn non_local_selection_metadata(
687 &self,
688 ) -> &non_local_selections_estimation::QueryGraphMetadata {
689 &self.non_local_selection_metadata
690 }
691
692 pub(crate) fn out_edges_with_federation_self_edges(
696 &self,
697 node: NodeIndex,
698 ) -> Vec<EdgeReference<'_, QueryGraphEdge>> {
699 Self::sorted_edges(self.graph.edges_directed(node, Direction::Outgoing))
700 }
701
702 pub(crate) fn out_edges(&self, node: NodeIndex) -> Vec<EdgeReference<'_, QueryGraphEdge>> {
705 Self::sorted_edges(self.graph.edges_directed(node, Direction::Outgoing).filter(
706 |edge_ref| {
707 !(edge_ref.source() == edge_ref.target()
708 && matches!(
709 edge_ref.weight().transition,
710 QueryGraphEdgeTransition::KeyResolution
711 | QueryGraphEdgeTransition::RootTypeResolution { .. }
712 ))
713 },
714 ))
715 }
716
717 fn sorted_edges<'graph>(
729 edges: impl Iterator<Item = EdgeReference<'graph, QueryGraphEdge>>,
730 ) -> Vec<EdgeReference<'graph, QueryGraphEdge>> {
731 let mut edges: Vec<_> = edges.collect();
732 edges.sort_by_key(|e| -> EdgeIndex { e.id() });
733 edges
734 }
735
736 pub(crate) fn is_terminal(&self, node: NodeIndex) -> bool {
737 self.graph.edges_directed(node, Direction::Outgoing).count() == 0
738 }
739
740 pub(crate) fn is_self_key_or_root_edge(
741 &self,
742 edge: EdgeIndex,
743 ) -> Result<bool, FederationError> {
744 let edge_weight = self.edge_weight(edge)?;
745 let (head, tail) = self.edge_endpoints(edge)?;
746 let head_weight = self.node_weight(head)?;
747 let tail_weight = self.node_weight(tail)?;
748 Ok(head_weight.source == tail_weight.source
749 && matches!(
750 edge_weight.transition,
751 QueryGraphEdgeTransition::KeyResolution
752 | QueryGraphEdgeTransition::RootTypeResolution { .. }
753 ))
754 }
755
756 pub(crate) fn has_satisfiable_direct_key_edge(
758 &self,
759 from_node: NodeIndex,
760 to_subgraph: &str,
761 condition_resolver: &mut impl ConditionResolver,
762 max_cost: QueryPlanCost,
763 ) -> Result<bool, FederationError> {
764 for edge_ref in self.out_edges(from_node) {
765 let edge_weight = edge_ref.weight();
766 if !matches!(
767 edge_weight.transition,
768 QueryGraphEdgeTransition::KeyResolution
769 ) {
770 continue;
771 }
772
773 let tail = edge_ref.target();
774 let tail_weight = self.node_weight(tail)?;
775 if tail_weight.source.as_ref() != to_subgraph {
776 continue;
777 }
778
779 let condition_resolution = condition_resolver.resolve(
780 edge_ref.id(),
781 &OpGraphPathContext::default(),
782 &ExcludedDestinations::default(),
783 &ExcludedConditions::default(),
784 None,
785 )?;
786 let ConditionResolution::Satisfied { cost, .. } = condition_resolution else {
787 continue;
788 };
789
790 if cost <= max_cost {
792 return Ok(true);
793 }
794 }
795 Ok(false)
796 }
797
798 pub(crate) fn locally_satisfiable_key(
799 &self,
800 edge_index: EdgeIndex,
801 ) -> Result<Option<SelectionSet>, FederationError> {
802 let edge_head = self.edge_head_weight(edge_index)?;
803 let QueryGraphNodeType::SchemaType(type_position) = &edge_head.type_ else {
804 return Err(FederationError::internal(
805 "Unable to compute locally_satisfiable_key. Edge head was unexpectedly pointing to a federated root type",
806 ));
807 };
808 let Some(subgraph_schema) = self.sources.get(&edge_head.source) else {
809 return Err(FederationError::internal(format!(
810 "Could not find subgraph source {}",
811 edge_head.source
812 )));
813 };
814 let Some(metadata) = subgraph_schema.subgraph_metadata() else {
815 return Err(FederationError::internal(format!(
816 "Could not find federation metadata for source {}",
817 edge_head.source
818 )));
819 };
820 let key_directive_definition = metadata
821 .federation_spec_definition()
822 .key_directive_definition(subgraph_schema)?;
823 let external_metadata = metadata.external_metadata();
824 let composite_type_position: CompositeTypeDefinitionPosition =
825 type_position.clone().try_into()?;
826 let type_ = composite_type_position.get(subgraph_schema.schema())?;
827 type_
828 .directives()
829 .get_all(&key_directive_definition.name)
830 .map(|key| {
831 metadata
832 .federation_spec_definition()
833 .key_directive_arguments(key)
834 })
835 .and_then(|key_value| {
836 parse_field_set(
837 subgraph_schema,
838 composite_type_position.type_name().clone(),
839 key_value.fields,
840 true,
841 )
842 })
843 .find_ok(|selection| !external_metadata.selects_any_external_field(selection))
844 }
845
846 pub(crate) fn edge_for_field(
847 &self,
848 node: NodeIndex,
849 field: &Field,
850 override_conditions: &OverrideConditions,
851 ) -> Option<EdgeIndex> {
852 let mut candidates = self.out_edges(node).into_iter().filter_map(|edge_ref| {
853 let edge_weight = edge_ref.weight();
854 let QueryGraphEdgeTransition::FieldCollection {
855 field_definition_position,
856 ..
857 } = &edge_weight.transition
858 else {
859 return None;
860 };
861
862 if !edge_weight.satisfies_override_conditions(override_conditions) {
863 return None;
864 }
865
866 if field.field_position.field_name() == field_definition_position.field_name() {
869 Some(edge_ref.id())
870 } else {
871 None
872 }
873 });
874 if let Some(candidate) = candidates.next() {
875 debug_assert!(
879 candidates.next().is_none(),
880 "Unexpectedly found multiple candidates",
881 );
882 Some(candidate)
883 } else {
884 None
885 }
886 }
887
888 pub(crate) fn edge_for_inline_fragment(
889 &self,
890 node: NodeIndex,
891 inline_fragment: &InlineFragment,
892 ) -> Option<EdgeIndex> {
893 let Some(type_condition_pos) = &inline_fragment.type_condition_position else {
894 return None;
896 };
897 let mut candidates = self.out_edges(node).into_iter().filter_map(|edge_ref| {
898 let edge_weight = edge_ref.weight();
899 let QueryGraphEdgeTransition::Downcast {
900 to_type_position, ..
901 } = &edge_weight.transition
902 else {
903 return None;
904 };
905 if type_condition_pos.type_name() == to_type_position.type_name() {
909 Some(edge_ref.id())
910 } else {
911 None
912 }
913 });
914 if let Some(candidate) = candidates.next() {
915 debug_assert!(
919 candidates.next().is_none(),
920 "Unexpectedly found multiple candidates",
921 );
922 Some(candidate)
923 } else {
924 None
925 }
926 }
927
928 pub(crate) fn edge_for_op_graph_path_trigger(
929 &self,
930 node: NodeIndex,
931 op_graph_path_trigger: &OpGraphPathTrigger,
932 override_conditions: &OverrideConditions,
933 ) -> Option<Option<EdgeIndex>> {
934 let OpGraphPathTrigger::OpPathElement(op_path_element) = op_graph_path_trigger else {
935 return None;
936 };
937 match op_path_element {
938 OpPathElement::Field(field) => self
939 .edge_for_field(node, field, override_conditions)
940 .map(Some),
941 OpPathElement::InlineFragment(inline_fragment) => {
942 if inline_fragment.type_condition_position.is_some() {
943 self.edge_for_inline_fragment(node, inline_fragment)
944 .map(Some)
945 } else {
946 Some(None)
947 }
948 }
949 }
950 }
951
952 pub(crate) fn edge_for_transition_graph_path_trigger(
953 &self,
954 node: NodeIndex,
955 transition_graph_path_trigger: &QueryGraphEdgeTransition,
956 override_conditions: &OverrideConditions,
957 ) -> Result<Option<EdgeIndex>, FederationError> {
958 for edge_ref in self.out_edges(node) {
959 let edge_weight = edge_ref.weight();
960 if edge_weight
961 .transition
962 .matches_supergraph_transition(transition_graph_path_trigger)?
963 && edge_weight.satisfies_override_conditions(override_conditions)
964 {
965 return Ok(Some(edge_ref.id()));
966 }
967 }
968 Ok(None)
969 }
970
971 pub(crate) fn advance_possible_runtime_types(
975 &self,
976 possible_runtime_types: &IndexSet<ObjectTypeDefinitionPosition>,
977 edge: Option<EdgeIndex>,
978 ) -> Result<IndexSet<ObjectTypeDefinitionPosition>, FederationError> {
979 let Some(edge) = edge else {
980 return Ok(possible_runtime_types.clone());
981 };
982
983 let edge_weight = self.edge_weight(edge)?;
984 let (_, tail) = self.edge_endpoints(edge)?;
985 let tail_weight = self.node_weight(tail)?;
986 let QueryGraphNodeType::SchemaType(tail_type_pos) = &tail_weight.type_ else {
987 return Err(FederationError::internal(
988 "Unexpectedly encountered federation root node as tail node.",
989 ));
990 };
991 match &edge_weight.transition {
992 QueryGraphEdgeTransition::FieldCollection {
993 source,
994 field_definition_position,
995 ..
996 } => {
997 if CompositeTypeDefinitionPosition::try_from(tail_type_pos.clone()).is_err() {
998 return Ok(IndexSet::default());
999 }
1000 let schema = self.schema_by_source(source)?;
1001 let mut new_possible_runtime_types = IndexSet::default();
1002 for possible_runtime_type in possible_runtime_types {
1003 let field_pos =
1004 possible_runtime_type.field(field_definition_position.field_name().clone());
1005 let Some(field) = field_pos.try_get(schema.schema()) else {
1006 continue;
1007 };
1008 let field_type_pos: CompositeTypeDefinitionPosition = schema
1009 .get_type(field.ty.inner_named_type().clone())?
1010 .try_into()?;
1011 new_possible_runtime_types
1012 .extend(schema.possible_runtime_types(field_type_pos)?);
1013 }
1014 Ok(new_possible_runtime_types)
1015 }
1016 QueryGraphEdgeTransition::Downcast {
1017 source,
1018 to_type_position,
1019 ..
1020 } => Ok(self
1021 .schema_by_source(source)?
1022 .possible_runtime_types(to_type_position.clone())?
1023 .intersection(possible_runtime_types)
1024 .cloned()
1025 .collect()),
1026 QueryGraphEdgeTransition::KeyResolution => {
1027 let tail_type_pos: CompositeTypeDefinitionPosition =
1028 tail_type_pos.clone().try_into()?;
1029 Ok(self
1030 .schema_by_source(&tail_weight.source)?
1031 .possible_runtime_types(tail_type_pos)?)
1032 }
1033 QueryGraphEdgeTransition::RootTypeResolution { .. } => {
1034 let OutputTypeDefinitionPosition::Object(tail_type_pos) = tail_type_pos.clone()
1035 else {
1036 return Err(FederationError::internal(
1037 "Unexpectedly encountered non-object root operation type.",
1038 ));
1039 };
1040 Ok(IndexSet::from_iter([tail_type_pos]))
1041 }
1042 QueryGraphEdgeTransition::SubgraphEnteringTransition => {
1043 let OutputTypeDefinitionPosition::Object(tail_type_pos) = tail_type_pos.clone()
1044 else {
1045 return Err(FederationError::internal(
1046 "Unexpectedly encountered non-object root operation type.",
1047 ));
1048 };
1049 Ok(IndexSet::from_iter([tail_type_pos]))
1050 }
1051 QueryGraphEdgeTransition::InterfaceObjectFakeDownCast { .. } => {
1052 Ok(possible_runtime_types.clone())
1053 }
1054 }
1055 }
1056
1057 pub(crate) fn get_locally_satisfiable_key(
1061 &self,
1062 node_index: NodeIndex,
1063 ) -> Result<Option<SelectionSet>, FederationError> {
1064 let node = self.node_weight(node_index)?;
1065 let type_name = match &node.type_ {
1066 QueryGraphNodeType::SchemaType(ty) => {
1067 CompositeTypeDefinitionPosition::try_from(ty.clone())?
1068 }
1069 QueryGraphNodeType::FederatedRootType(_) => {
1070 return Err(FederationError::internal(format!(
1071 "get_locally_satisfiable_key must be called on a composite type, got {}",
1072 node.type_
1073 )));
1074 }
1075 };
1076 let schema = self.schema_by_source(&node.source)?;
1077 let Some(metadata) = schema.subgraph_metadata() else {
1078 return Err(FederationError::internal(format!(
1079 "Could not find subgraph metadata for source {}",
1080 node.source
1081 )));
1082 };
1083 let key_directive_definition = metadata
1084 .federation_spec_definition()
1085 .key_directive_definition(schema)?;
1086
1087 let ty = type_name.get(schema.schema())?;
1088
1089 ty.directives()
1090 .get_all(&key_directive_definition.name)
1091 .filter_map(|key| {
1092 key.specified_argument_by_name("fields")
1093 .and_then(|arg| arg.as_str())
1094 })
1095 .map(|value| parse_field_set(schema, ty.name().clone(), value, true))
1096 .find_ok(|selection| {
1097 !metadata
1098 .external_metadata()
1099 .selects_any_external_field(selection)
1100 })
1101 }
1102
1103 pub(crate) fn is_cross_subgraph_edge(&self, edge: EdgeIndex) -> Result<bool, FederationError> {
1104 let (head, tail) = self.edge_endpoints(edge)?;
1105 let head_weight = self.node_weight(head)?;
1106 let tail_weight = self.node_weight(tail)?;
1107 Ok(head_weight.source != tail_weight.source)
1108 }
1109
1110 pub(crate) fn is_provides_edge(&self, edge: EdgeIndex) -> Result<bool, FederationError> {
1111 let edge_weight = self.edge_weight(edge)?;
1112 let QueryGraphEdgeTransition::FieldCollection {
1113 is_part_of_provides,
1114 ..
1115 } = &edge_weight.transition
1116 else {
1117 return Ok(false);
1118 };
1119 Ok(*is_part_of_provides)
1120 }
1121
1122 pub(crate) fn has_an_implementation_with_provides(
1123 &self,
1124 source: &Arc<str>,
1125 interface_field_definition_position: InterfaceFieldDefinitionPosition,
1126 ) -> Result<bool, FederationError> {
1127 let schema = self.schema_by_source(source)?;
1128 let Some(metadata) = schema.subgraph_metadata() else {
1129 return Err(FederationError::internal(format!(
1130 "Interface should have come from a federation subgraph {source}"
1131 )));
1132 };
1133
1134 let provides_directive_definition = metadata
1135 .federation_spec_definition()
1136 .provides_directive_definition(schema)?;
1137
1138 Ok(schema
1139 .possible_runtime_types(interface_field_definition_position.parent().into())?
1140 .into_iter()
1141 .map(|object_type_definition_position| {
1142 let field_pos = object_type_definition_position
1143 .field(interface_field_definition_position.field_name.clone());
1144 field_pos.get(schema.schema())
1145 })
1146 .ok_and_any(|field| field.directives.has(&provides_directive_definition.name))?)
1147 }
1148}