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(
366 &self,
367 other: &Self,
368 ) -> Result<bool, FederationError> {
369 ensure!(
370 other.collect_operation_elements(),
371 "Supergraphs shouldn't have a transition that doesn't collect elements; got {}",
372 other,
373 );
374
375 match (self, other) {
376 (
377 QueryGraphEdgeTransition::FieldCollection {
378 field_definition_position,
379 ..
380 },
381 QueryGraphEdgeTransition::FieldCollection {
382 field_definition_position: other_field_definition_position,
383 ..
384 },
385 ) => Ok(field_definition_position.field_name()
386 == other_field_definition_position.field_name()),
387 (
388 QueryGraphEdgeTransition::Downcast {
389 to_type_position, ..
390 },
391 QueryGraphEdgeTransition::Downcast {
392 to_type_position: other_to_type_position,
393 ..
394 },
395 ) => Ok(to_type_position.type_name() == other_to_type_position.type_name()),
396 (
400 QueryGraphEdgeTransition::InterfaceObjectFakeDownCast { to_type_name, .. },
401 QueryGraphEdgeTransition::Downcast {
402 to_type_position: other_to_type_position,
403 ..
404 },
405 ) => Ok(to_type_name == other_to_type_position.type_name()),
406 _ => Ok(false),
407 }
408 }
409}
410
411impl Display for QueryGraphEdgeTransition {
412 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
413 match self {
414 QueryGraphEdgeTransition::FieldCollection {
415 field_definition_position,
416 ..
417 } => {
418 write!(f, "{}", field_definition_position.field_name())
419 }
420 QueryGraphEdgeTransition::Downcast {
421 to_type_position, ..
422 } => {
423 write!(f, "... on {}", to_type_position.type_name())
424 }
425 QueryGraphEdgeTransition::KeyResolution => {
426 write!(f, "key()")
427 }
428 QueryGraphEdgeTransition::RootTypeResolution { root_kind } => {
429 write!(f, "{root_kind}()")
430 }
431 QueryGraphEdgeTransition::SubgraphEnteringTransition => {
432 write!(f, "∅")
433 }
434 QueryGraphEdgeTransition::InterfaceObjectFakeDownCast { to_type_name, .. } => {
435 write!(f, "... on {to_type_name}")
436 }
437 }
438 }
439}
440
441#[derive(Debug)]
442pub struct QueryGraph {
443 current_source: Arc<str>,
448 graph: DiGraph<QueryGraphNode, QueryGraphEdge>,
451 sources: IndexMap<Arc<str>, ValidFederationSchema>,
455 subgraphs_by_name: IndexMap<Arc<str>, ValidFederationSchema>,
459 supergraph_schema: Option<ValidFederationSchema>,
461 types_to_nodes_by_source: IndexMap<Arc<str>, IndexMap<NamedType, IndexSet<NodeIndex>>>,
465 root_kinds_to_nodes_by_source:
467 IndexMap<Arc<str>, IndexMap<SchemaRootDefinitionKind, NodeIndex>>,
468 non_trivial_followup_edges: IndexMap<EdgeIndex, Vec<EdgeIndex>>,
491 arguments_to_context_ids_by_source:
498 IndexMap<Arc<str>, IndexMap<ObjectFieldArgumentDefinitionPosition, Name>>,
499 override_condition_labels: IndexSet<Arc<str>>,
500 non_local_selection_metadata: non_local_selections_estimation::QueryGraphMetadata,
503}
504
505impl QueryGraph {
506 pub(crate) fn name(&self) -> &Arc<str> {
507 &self.current_source
508 }
509
510 pub(crate) fn graph(&self) -> &DiGraph<QueryGraphNode, QueryGraphEdge> {
511 &self.graph
512 }
513
514 pub(crate) fn override_condition_labels(&self) -> &IndexSet<Arc<str>> {
515 &self.override_condition_labels
516 }
517
518 pub(crate) fn supergraph_schema(&self) -> Result<ValidFederationSchema, FederationError> {
519 self.supergraph_schema
520 .clone()
521 .ok_or_else(|| internal_error!("Supergraph schema unexpectedly missing"))
522 }
523
524 pub(crate) fn node_weight(&self, node: NodeIndex) -> Result<&QueryGraphNode, FederationError> {
525 self.graph
526 .node_weight(node)
527 .ok_or_else(|| internal_error!("Node unexpectedly missing"))
528 }
529
530 fn node_weight_mut(&mut self, node: NodeIndex) -> Result<&mut QueryGraphNode, FederationError> {
531 self.graph
532 .node_weight_mut(node)
533 .ok_or_else(|| internal_error!("Node unexpectedly missing"))
534 }
535
536 pub(crate) fn edge_weight(&self, edge: EdgeIndex) -> Result<&QueryGraphEdge, FederationError> {
537 self.graph
538 .edge_weight(edge)
539 .ok_or_else(|| internal_error!("Edge unexpectedly missing"))
540 }
541
542 fn edge_weight_mut(&mut self, edge: EdgeIndex) -> Result<&mut QueryGraphEdge, FederationError> {
543 self.graph
544 .edge_weight_mut(edge)
545 .ok_or_else(|| internal_error!("Edge unexpectedly missing"))
546 }
547
548 pub(crate) fn edge_head_weight(
549 &self,
550 edge: EdgeIndex,
551 ) -> Result<&QueryGraphNode, FederationError> {
552 let (head_id, _) = self.edge_endpoints(edge)?;
553 self.node_weight(head_id)
554 }
555
556 pub(crate) fn edge_endpoints(
557 &self,
558 edge: EdgeIndex,
559 ) -> Result<(NodeIndex, NodeIndex), FederationError> {
560 self.graph
561 .edge_endpoints(edge)
562 .ok_or_else(|| internal_error!("Edge unexpectedly missing"))
563 }
564
565 pub(crate) fn schema(&self) -> Result<&ValidFederationSchema, FederationError> {
566 self.schema_by_source(&self.current_source)
567 }
568
569 pub(crate) fn schema_by_source(
570 &self,
571 source: &str,
572 ) -> Result<&ValidFederationSchema, FederationError> {
573 self.sources
574 .get(source)
575 .ok_or_else(|| internal_error!(r#"Schema for "{source}" unexpectedly missing"#))
576 }
577
578 pub(crate) fn subgraph_schemas(&self) -> &IndexMap<Arc<str>, ValidFederationSchema> {
579 &self.subgraphs_by_name
580 }
581
582 pub(crate) fn subgraphs(&self) -> impl Iterator<Item = (&Arc<str>, &ValidFederationSchema)> {
583 self.subgraphs_by_name.iter()
584 }
585
586 pub(crate) fn nodes_for_type(
588 &self,
589 name: &Name,
590 ) -> Result<&IndexSet<NodeIndex>, FederationError> {
591 self.types_to_nodes()?
592 .get(name)
593 .ok_or_else(|| internal_error!("No nodes unexpectedly found for type"))
594 }
595
596 pub(crate) fn types_to_nodes(
597 &self,
598 ) -> Result<&IndexMap<NamedType, IndexSet<NodeIndex>>, FederationError> {
599 self.types_to_nodes_by_source(&self.current_source)
600 }
601
602 pub(super) fn types_to_nodes_by_source(
603 &self,
604 source: &str,
605 ) -> Result<&IndexMap<NamedType, IndexSet<NodeIndex>>, FederationError> {
606 self.types_to_nodes_by_source.get(source).ok_or_else(|| {
607 SingleFederationError::Internal {
608 message: "Types-to-nodes map unexpectedly missing".to_owned(),
609 }
610 .into()
611 })
612 }
613
614 fn types_to_nodes_mut(
615 &mut self,
616 ) -> Result<&mut IndexMap<NamedType, IndexSet<NodeIndex>>, FederationError> {
617 self.types_to_nodes_by_source
618 .get_mut(&self.current_source)
619 .ok_or_else(|| {
620 SingleFederationError::Internal {
621 message: "Types-to-nodes map unexpectedly missing".to_owned(),
622 }
623 .into()
624 })
625 }
626
627 pub(crate) fn root_kinds_to_nodes(
628 &self,
629 ) -> Result<&IndexMap<SchemaRootDefinitionKind, NodeIndex>, FederationError> {
630 self.root_kinds_to_nodes_by_source
631 .get(&self.current_source)
632 .ok_or_else(|| {
633 SingleFederationError::Internal {
634 message: "Root-kinds-to-nodes map unexpectedly missing".to_owned(),
635 }
636 .into()
637 })
638 }
639
640 pub(crate) fn root_kinds_to_nodes_by_source(
641 &self,
642 source: &str,
643 ) -> Result<&IndexMap<SchemaRootDefinitionKind, NodeIndex>, FederationError> {
644 self.root_kinds_to_nodes_by_source
645 .get(source)
646 .ok_or_else(|| {
647 SingleFederationError::Internal {
648 message: "Root-kinds-to-nodes map unexpectedly missing".to_owned(),
649 }
650 .into()
651 })
652 }
653
654 fn root_kinds_to_nodes_mut(
655 &mut self,
656 ) -> Result<&mut IndexMap<SchemaRootDefinitionKind, NodeIndex>, FederationError> {
657 self.root_kinds_to_nodes_by_source
658 .get_mut(&self.current_source)
659 .ok_or_else(|| {
660 SingleFederationError::Internal {
661 message: "Root-kinds-to-nodes map unexpectedly missing".to_owned(),
662 }
663 .into()
664 })
665 }
666
667 pub(crate) fn context_id_by_source_and_argument(
668 &self,
669 source: &str,
670 argument: &ObjectFieldArgumentDefinitionPosition,
671 ) -> Result<&Name, FederationError> {
672 self.arguments_to_context_ids_by_source
673 .get(source)
674 .and_then(|r| r.get(argument))
675 .ok_or_else(|| {
676 internal_error!("context ID unexpectedly missing for @fromContext argument")
677 })
678 }
679
680 pub(crate) fn is_context_used(&self) -> bool {
681 !self.arguments_to_context_ids_by_source.is_empty()
682 }
683
684 pub(crate) fn non_local_selection_metadata(
685 &self,
686 ) -> &non_local_selections_estimation::QueryGraphMetadata {
687 &self.non_local_selection_metadata
688 }
689
690 pub(crate) fn out_edges_with_federation_self_edges(
694 &self,
695 node: NodeIndex,
696 ) -> Vec<EdgeReference<'_, QueryGraphEdge>> {
697 Self::sorted_edges(self.graph.edges_directed(node, Direction::Outgoing))
698 }
699
700 pub(crate) fn out_edges(&self, node: NodeIndex) -> Vec<EdgeReference<'_, QueryGraphEdge>> {
703 Self::sorted_edges(self.graph.edges_directed(node, Direction::Outgoing).filter(
704 |edge_ref| {
705 !(edge_ref.source() == edge_ref.target()
706 && matches!(
707 edge_ref.weight().transition,
708 QueryGraphEdgeTransition::KeyResolution
709 | QueryGraphEdgeTransition::RootTypeResolution { .. }
710 ))
711 },
712 ))
713 }
714
715 fn sorted_edges<'graph>(
727 edges: impl Iterator<Item = EdgeReference<'graph, QueryGraphEdge>>,
728 ) -> Vec<EdgeReference<'graph, QueryGraphEdge>> {
729 let mut edges: Vec<_> = edges.collect();
730 edges.sort_by_key(|e| -> EdgeIndex { e.id() });
731 edges
732 }
733
734 pub(crate) fn is_terminal(&self, node: NodeIndex) -> bool {
735 self.graph.edges_directed(node, Direction::Outgoing).count() == 0
736 }
737
738 pub(crate) fn is_self_key_or_root_edge(
739 &self,
740 edge: EdgeIndex,
741 ) -> Result<bool, FederationError> {
742 let edge_weight = self.edge_weight(edge)?;
743 let (head, tail) = self.edge_endpoints(edge)?;
744 let head_weight = self.node_weight(head)?;
745 let tail_weight = self.node_weight(tail)?;
746 Ok(head_weight.source == tail_weight.source
747 && matches!(
748 edge_weight.transition,
749 QueryGraphEdgeTransition::KeyResolution
750 | QueryGraphEdgeTransition::RootTypeResolution { .. }
751 ))
752 }
753
754 pub(crate) fn has_satisfiable_direct_key_edge(
756 &self,
757 from_node: NodeIndex,
758 to_subgraph: &str,
759 condition_resolver: &mut impl ConditionResolver,
760 max_cost: QueryPlanCost,
761 ) -> Result<bool, FederationError> {
762 for edge_ref in self.out_edges(from_node) {
763 let edge_weight = edge_ref.weight();
764 if !matches!(
765 edge_weight.transition,
766 QueryGraphEdgeTransition::KeyResolution
767 ) {
768 continue;
769 }
770
771 let tail = edge_ref.target();
772 let tail_weight = self.node_weight(tail)?;
773 if tail_weight.source.as_ref() != to_subgraph {
774 continue;
775 }
776
777 let condition_resolution = condition_resolver.resolve(
778 edge_ref.id(),
779 &OpGraphPathContext::default(),
780 &ExcludedDestinations::default(),
781 &ExcludedConditions::default(),
782 None,
783 )?;
784 let ConditionResolution::Satisfied { cost, .. } = condition_resolution else {
785 continue;
786 };
787
788 if cost <= max_cost {
790 return Ok(true);
791 }
792 }
793 Ok(false)
794 }
795
796 pub(crate) fn locally_satisfiable_key(
797 &self,
798 edge_index: EdgeIndex,
799 ) -> Result<Option<SelectionSet>, FederationError> {
800 let edge_head = self.edge_head_weight(edge_index)?;
801 let QueryGraphNodeType::SchemaType(type_position) = &edge_head.type_ else {
802 return Err(FederationError::internal(
803 "Unable to compute locally_satisfiable_key. Edge head was unexpectedly pointing to a federated root type",
804 ));
805 };
806 let Some(subgraph_schema) = self.sources.get(&edge_head.source) else {
807 return Err(FederationError::internal(format!(
808 "Could not find subgraph source {}",
809 edge_head.source
810 )));
811 };
812 let Some(metadata) = subgraph_schema.subgraph_metadata() else {
813 return Err(FederationError::internal(format!(
814 "Could not find federation metadata for source {}",
815 edge_head.source
816 )));
817 };
818 let key_directive_definition = metadata
819 .federation_spec_definition()
820 .key_directive_definition(subgraph_schema)?;
821 let external_metadata = metadata.external_metadata();
822 let composite_type_position: CompositeTypeDefinitionPosition =
823 type_position.clone().try_into()?;
824 let type_ = composite_type_position.get(subgraph_schema.schema())?;
825 type_
826 .directives()
827 .get_all(&key_directive_definition.name)
828 .map(|key| {
829 metadata
830 .federation_spec_definition()
831 .key_directive_arguments(key)
832 })
833 .and_then(|key_value| {
834 parse_field_set(
835 subgraph_schema,
836 composite_type_position.type_name().clone(),
837 key_value.fields,
838 true,
839 )
840 })
841 .find_ok(|selection| !external_metadata.selects_any_external_field(selection))
842 }
843
844 pub(crate) fn edge_for_field(
845 &self,
846 node: NodeIndex,
847 field: &Field,
848 override_conditions: &OverrideConditions,
849 ) -> Option<EdgeIndex> {
850 let mut candidates = self.out_edges(node).into_iter().filter_map(|edge_ref| {
851 let edge_weight = edge_ref.weight();
852 let QueryGraphEdgeTransition::FieldCollection {
853 field_definition_position,
854 ..
855 } = &edge_weight.transition
856 else {
857 return None;
858 };
859
860 if !edge_weight.satisfies_override_conditions(override_conditions) {
861 return None;
862 }
863
864 if field.field_position.field_name() == field_definition_position.field_name() {
867 Some(edge_ref.id())
868 } else {
869 None
870 }
871 });
872 if let Some(candidate) = candidates.next() {
873 debug_assert!(
877 candidates.next().is_none(),
878 "Unexpectedly found multiple candidates",
879 );
880 Some(candidate)
881 } else {
882 None
883 }
884 }
885
886 pub(crate) fn edge_for_inline_fragment(
887 &self,
888 node: NodeIndex,
889 inline_fragment: &InlineFragment,
890 ) -> Option<EdgeIndex> {
891 let Some(type_condition_pos) = &inline_fragment.type_condition_position else {
892 return None;
894 };
895 let mut candidates = self.out_edges(node).into_iter().filter_map(|edge_ref| {
896 let edge_weight = edge_ref.weight();
897 let QueryGraphEdgeTransition::Downcast {
898 to_type_position, ..
899 } = &edge_weight.transition
900 else {
901 return None;
902 };
903 if type_condition_pos.type_name() == to_type_position.type_name() {
907 Some(edge_ref.id())
908 } else {
909 None
910 }
911 });
912 if let Some(candidate) = candidates.next() {
913 debug_assert!(
917 candidates.next().is_none(),
918 "Unexpectedly found multiple candidates",
919 );
920 Some(candidate)
921 } else {
922 None
923 }
924 }
925
926 pub(crate) fn edge_for_op_graph_path_trigger(
927 &self,
928 node: NodeIndex,
929 op_graph_path_trigger: &OpGraphPathTrigger,
930 override_conditions: &OverrideConditions,
931 ) -> Option<Option<EdgeIndex>> {
932 let OpGraphPathTrigger::OpPathElement(op_path_element) = op_graph_path_trigger else {
933 return None;
934 };
935 match op_path_element {
936 OpPathElement::Field(field) => self
937 .edge_for_field(node, field, override_conditions)
938 .map(Some),
939 OpPathElement::InlineFragment(inline_fragment) => {
940 if inline_fragment.type_condition_position.is_some() {
941 self.edge_for_inline_fragment(node, inline_fragment)
942 .map(Some)
943 } else {
944 Some(None)
945 }
946 }
947 }
948 }
949
950 pub(crate) fn edge_for_transition_graph_path_trigger(
951 &self,
952 node: NodeIndex,
953 transition_graph_path_trigger: &QueryGraphEdgeTransition,
954 override_conditions: &OverrideConditions,
955 ) -> Result<Option<EdgeIndex>, FederationError> {
956 for edge_ref in self.out_edges(node) {
957 let edge_weight = edge_ref.weight();
958 if edge_weight
959 .transition
960 .matches_supergraph_transition(transition_graph_path_trigger)?
961 && edge_weight.satisfies_override_conditions(override_conditions)
962 {
963 return Ok(Some(edge_ref.id()));
964 }
965 }
966 Ok(None)
967 }
968
969 pub(crate) fn advance_possible_runtime_types(
973 &self,
974 possible_runtime_types: &IndexSet<ObjectTypeDefinitionPosition>,
975 edge: Option<EdgeIndex>,
976 ) -> Result<IndexSet<ObjectTypeDefinitionPosition>, FederationError> {
977 let Some(edge) = edge else {
978 return Ok(possible_runtime_types.clone());
979 };
980
981 let edge_weight = self.edge_weight(edge)?;
982 let (_, tail) = self.edge_endpoints(edge)?;
983 let tail_weight = self.node_weight(tail)?;
984 let QueryGraphNodeType::SchemaType(tail_type_pos) = &tail_weight.type_ else {
985 return Err(FederationError::internal(
986 "Unexpectedly encountered federation root node as tail node.",
987 ));
988 };
989 match &edge_weight.transition {
990 QueryGraphEdgeTransition::FieldCollection {
991 source,
992 field_definition_position,
993 ..
994 } => {
995 if CompositeTypeDefinitionPosition::try_from(tail_type_pos.clone()).is_err() {
996 return Ok(IndexSet::default());
997 }
998 let schema = self.schema_by_source(source)?;
999 let mut new_possible_runtime_types = IndexSet::default();
1000 for possible_runtime_type in possible_runtime_types {
1001 let field_pos =
1002 possible_runtime_type.field(field_definition_position.field_name().clone());
1003 let Some(field) = field_pos.try_get(schema.schema()) else {
1004 continue;
1005 };
1006 let field_type_pos: CompositeTypeDefinitionPosition = schema
1007 .get_type(field.ty.inner_named_type().clone())?
1008 .try_into()?;
1009 new_possible_runtime_types
1010 .extend(schema.possible_runtime_types(field_type_pos)?);
1011 }
1012 Ok(new_possible_runtime_types)
1013 }
1014 QueryGraphEdgeTransition::Downcast {
1015 source,
1016 to_type_position,
1017 ..
1018 } => Ok(self
1019 .schema_by_source(source)?
1020 .possible_runtime_types(to_type_position.clone())?
1021 .intersection(possible_runtime_types)
1022 .cloned()
1023 .collect()),
1024 QueryGraphEdgeTransition::KeyResolution => {
1025 let tail_type_pos: CompositeTypeDefinitionPosition =
1026 tail_type_pos.clone().try_into()?;
1027 Ok(self
1028 .schema_by_source(&tail_weight.source)?
1029 .possible_runtime_types(tail_type_pos)?)
1030 }
1031 QueryGraphEdgeTransition::RootTypeResolution { .. } => {
1032 let OutputTypeDefinitionPosition::Object(tail_type_pos) = tail_type_pos.clone()
1033 else {
1034 return Err(FederationError::internal(
1035 "Unexpectedly encountered non-object root operation type.",
1036 ));
1037 };
1038 Ok(IndexSet::from_iter([tail_type_pos]))
1039 }
1040 QueryGraphEdgeTransition::SubgraphEnteringTransition => {
1041 let OutputTypeDefinitionPosition::Object(tail_type_pos) = tail_type_pos.clone()
1042 else {
1043 return Err(FederationError::internal(
1044 "Unexpectedly encountered non-object root operation type.",
1045 ));
1046 };
1047 Ok(IndexSet::from_iter([tail_type_pos]))
1048 }
1049 QueryGraphEdgeTransition::InterfaceObjectFakeDownCast { .. } => {
1050 Ok(possible_runtime_types.clone())
1051 }
1052 }
1053 }
1054
1055 pub(crate) fn get_locally_satisfiable_key(
1059 &self,
1060 node_index: NodeIndex,
1061 ) -> Result<Option<SelectionSet>, FederationError> {
1062 let node = self.node_weight(node_index)?;
1063 let type_name = match &node.type_ {
1064 QueryGraphNodeType::SchemaType(ty) => {
1065 CompositeTypeDefinitionPosition::try_from(ty.clone())?
1066 }
1067 QueryGraphNodeType::FederatedRootType(_) => {
1068 return Err(FederationError::internal(format!(
1069 "get_locally_satisfiable_key must be called on a composite type, got {}",
1070 node.type_
1071 )));
1072 }
1073 };
1074 let schema = self.schema_by_source(&node.source)?;
1075 let Some(metadata) = schema.subgraph_metadata() else {
1076 return Err(FederationError::internal(format!(
1077 "Could not find subgraph metadata for source {}",
1078 node.source
1079 )));
1080 };
1081 let key_directive_definition = metadata
1082 .federation_spec_definition()
1083 .key_directive_definition(schema)?;
1084
1085 let ty = type_name.get(schema.schema())?;
1086
1087 ty.directives()
1088 .get_all(&key_directive_definition.name)
1089 .filter_map(|key| {
1090 key.specified_argument_by_name("fields")
1091 .and_then(|arg| arg.as_str())
1092 })
1093 .map(|value| parse_field_set(schema, ty.name().clone(), value, true))
1094 .find_ok(|selection| {
1095 !metadata
1096 .external_metadata()
1097 .selects_any_external_field(selection)
1098 })
1099 }
1100
1101 pub(crate) fn is_cross_subgraph_edge(&self, edge: EdgeIndex) -> Result<bool, FederationError> {
1102 let (head, tail) = self.edge_endpoints(edge)?;
1103 let head_weight = self.node_weight(head)?;
1104 let tail_weight = self.node_weight(tail)?;
1105 Ok(head_weight.source != tail_weight.source)
1106 }
1107
1108 pub(crate) fn is_provides_edge(&self, edge: EdgeIndex) -> Result<bool, FederationError> {
1109 let edge_weight = self.edge_weight(edge)?;
1110 let QueryGraphEdgeTransition::FieldCollection {
1111 is_part_of_provides,
1112 ..
1113 } = &edge_weight.transition
1114 else {
1115 return Ok(false);
1116 };
1117 Ok(*is_part_of_provides)
1118 }
1119
1120 pub(crate) fn has_an_implementation_with_provides(
1121 &self,
1122 source: &Arc<str>,
1123 interface_field_definition_position: InterfaceFieldDefinitionPosition,
1124 ) -> Result<bool, FederationError> {
1125 let schema = self.schema_by_source(source)?;
1126 let Some(metadata) = schema.subgraph_metadata() else {
1127 return Err(FederationError::internal(format!(
1128 "Interface should have come from a federation subgraph {source}"
1129 )));
1130 };
1131
1132 let provides_directive_definition = metadata
1133 .federation_spec_definition()
1134 .provides_directive_definition(schema)?;
1135
1136 Ok(schema
1137 .possible_runtime_types(interface_field_definition_position.parent().into())?
1138 .into_iter()
1139 .map(|object_type_definition_position| {
1140 let field_pos = object_type_definition_position
1141 .field(interface_field_definition_position.field_name.clone());
1142 field_pos.get(schema.schema())
1143 })
1144 .ok_and_any(|field| field.directives.has(&provides_directive_definition.name))?)
1145 }
1146}