Skip to main content

apollo_federation/composition/
mod.rs

1mod satisfiability;
2
3use std::sync::Arc;
4use std::vec;
5
6use tracing::instrument;
7
8pub use crate::composition::satisfiability::validate_satisfiability;
9use crate::connectors::Connector;
10use crate::connectors::expand::Connectors;
11use crate::connectors::expand::ExpansionResult;
12use crate::connectors::expand::expand_connectors;
13use crate::error::CompositionError;
14use crate::merger::merge::Merger;
15pub use crate::schema::schema_upgrader::upgrade_subgraphs_if_necessary;
16use crate::schema::validators::root_fields::validate_consistent_root_fields;
17use crate::subgraph::typestate::Expanded;
18use crate::subgraph::typestate::Initial;
19use crate::subgraph::typestate::Subgraph;
20use crate::subgraph::typestate::Validated;
21pub use crate::supergraph::Merged;
22pub use crate::supergraph::Satisfiable;
23pub use crate::supergraph::Supergraph;
24
25/// Mirrors the JS `compose` function.
26#[instrument(skip(subgraphs))]
27pub fn compose(
28    subgraphs: Vec<Subgraph<Initial>>,
29) -> Result<Supergraph<Satisfiable>, Vec<CompositionError>> {
30    tracing::debug!("Expanding subgraphs...");
31    let expanded_subgraphs = expand_subgraphs(subgraphs)?;
32    tracing::debug!("Upgrading subgraphs...");
33    let validated_subgraphs = upgrade_subgraphs_if_necessary(expanded_subgraphs)?;
34
35    tracing::debug!("Pre-merge validations...");
36    pre_merge_validations(&validated_subgraphs)?;
37    tracing::debug!("Merging subgraphs...");
38    let supergraph = merge_subgraphs(validated_subgraphs)?;
39    tracing::debug!("Post-merge validations...");
40    post_merge_validations(&supergraph)?;
41    tracing::debug!("Validating satisfiability...");
42    validate_satisfiability(supergraph)
43}
44
45/// Mirrors the `HybridComposition::compose` from the apollo-composition crate.
46pub fn compose_with_connectors(
47    subgraphs: Vec<Subgraph<Initial>>,
48) -> Result<Supergraph<Satisfiable>, Vec<CompositionError>> {
49    // Pre-expand validation
50    // - These were supposed to be pre-merge validations, but historically FBP performed these
51    //   Rust-based validation, before JS composition.
52    // - Once JS-to-Rust migration is done, we can move these to pre-merge validations.
53    // TODO: (FED-841) Call `validate_cache_tag_directives`
54    // TODO: (FED-855) Call `connectors::validation`, which may change the subgraphs before upgrading.
55
56    tracing::debug!("Expanding subgraphs...");
57    let expanded_subgraphs = expand_subgraphs(subgraphs)?;
58
59    tracing::debug!("Upgrading subgraphs...");
60    let validated_subgraphs = upgrade_subgraphs_if_necessary(expanded_subgraphs)?;
61
62    tracing::debug!("Pre-merge validations...");
63    pre_merge_validations(&validated_subgraphs)?;
64
65    tracing::debug!("Merging subgraphs...");
66    let supergraph = merge_subgraphs(validated_subgraphs)?;
67
68    tracing::debug!("Post-merge validations...");
69    post_merge_validations(&supergraph)?;
70    // TODO: (FED-855) Call `validate_overrides`, which validates the original subgraphs for connectors after merging.
71    // - Once JS-to-Rust migration is done, we may consider to move that to the pre-merge validation step.
72
73    tracing::debug!("Validating satisfiability...");
74    validate_satisfiability_with_connectors(supergraph)
75}
76
77/// Apollo Federation allow subgraphs to specify partial schemas (i.e. "import" directives through
78/// `@link`). This function will update subgraph schemas with all missing federation definitions.
79#[instrument(skip(subgraphs))]
80pub fn expand_subgraphs(
81    subgraphs: Vec<Subgraph<Initial>>,
82) -> Result<Vec<Subgraph<Expanded>>, Vec<CompositionError>> {
83    let mut errors: Vec<CompositionError> = vec![];
84    let expanded: Vec<Subgraph<Expanded>> = subgraphs
85        .into_iter()
86        .map(|s| s.expand_links())
87        .filter_map(|r| r.map_err(|e| errors.extend(e.to_composition_errors())).ok())
88        .collect();
89    if errors.is_empty() {
90        Ok(expanded)
91    } else {
92        Err(errors)
93    }
94}
95
96/// Perform validations that require information about all available subgraphs.
97#[instrument(skip(subgraphs))]
98pub fn pre_merge_validations(
99    subgraphs: &[Subgraph<Validated>],
100) -> Result<(), Vec<CompositionError>> {
101    validate_consistent_root_fields(subgraphs)?;
102    // TODO: (FED-713) Implement any pre-merge validations that require knowledge of all subgraphs.
103    Ok(())
104}
105
106#[instrument(skip(subgraphs))]
107pub fn merge_subgraphs(
108    subgraphs: Vec<Subgraph<Validated>>,
109) -> Result<Supergraph<Merged>, Vec<CompositionError>> {
110    let merger = Merger::new(subgraphs, Default::default()).map_err(|e| {
111        vec![CompositionError::InternalError {
112            message: e.to_string(),
113        }]
114    })?;
115    let result = merger.merge().map_err(|e| {
116        vec![CompositionError::InternalError {
117            message: e.to_string(),
118        }]
119    })?;
120    tracing::trace!(
121        "Merge has {} errors and {} hints",
122        result.errors.len(),
123        result.hints.len()
124    );
125    if result.errors.is_empty() {
126        let Some(supergraph_schema) = result.supergraph else {
127            return Err(vec![CompositionError::InternalError {
128                message: "Merge completed with no supergraph schema".to_string(),
129            }]);
130        };
131        let supergraph = Supergraph::with_hints(supergraph_schema, result.hints);
132        Ok(supergraph)
133    } else {
134        Err(result.errors)
135    }
136}
137
138#[instrument(skip(_supergraph))]
139pub fn post_merge_validations(
140    _supergraph: &Supergraph<Merged>,
141) -> Result<(), Vec<CompositionError>> {
142    // TODO: (FED-714) Implement any post-merge validations other than satisfiability, which is
143    // checked separately.
144    Ok(())
145}
146
147/// Mirroring HybridComposition in the apollo-composition crate.
148/// - Expand connectors as needed.
149pub fn validate_satisfiability_with_connectors(
150    supergraph: Supergraph<Merged>,
151) -> Result<Supergraph<Satisfiable>, Vec<CompositionError>> {
152    // Expand connectors for satisfiability validation.
153    let supergraph_str = supergraph.schema().schema().to_string();
154    let expansion_result = expand_connectors(&supergraph_str, &Default::default())
155        .map_err(|e| vec![CompositionError::InternalError {
156            message: format!("Composition failed due to an internal error when expanding connectors, please report this: {e}"),
157        }])?;
158
159    // Verify satisfiability
160    match expansion_result {
161        ExpansionResult::Expanded {
162            raw_sdl,
163            connectors: Connectors {
164                by_service_name, ..
165            },
166            ..
167        } => {
168            let supergraph = Supergraph::parse(&raw_sdl).map_err(|e| {
169                vec![CompositionError::InternalError {
170                    message: e.to_string(),
171                }]
172            })?;
173            let mut result = validate_satisfiability(supergraph);
174
175            // Sanitize connectors subgraph names in errors and hints.
176            match &mut result {
177                Ok(supergraph) => {
178                    for hint in supergraph.hints_mut() {
179                        sanitize_connectors_message(&mut hint.message, by_service_name.iter());
180                    }
181                }
182                Err(issues) => {
183                    for issue in issues.iter_mut() {
184                        sanitize_connectors_error(issue, by_service_name.iter());
185                    }
186                }
187            }
188            result
189        }
190        ExpansionResult::Unchanged => validate_satisfiability(supergraph),
191    }
192}
193
194fn sanitize_connectors_error<'a>(
195    issue: &mut CompositionError,
196    connector_subgraphs: impl Iterator<Item = (&'a Arc<str>, &'a Connector)>,
197) {
198    match issue {
199        CompositionError::SatisfiabilityError { message } => {
200            sanitize_connectors_message(message, connector_subgraphs);
201        }
202        CompositionError::ShareableHasMismatchedRuntimeTypes { message } => {
203            sanitize_connectors_message(message, connector_subgraphs);
204        }
205        _ => {}
206    }
207}
208
209fn sanitize_connectors_message<'a>(
210    message: &mut String,
211    connector_subgraphs: impl Iterator<Item = (&'a Arc<str>, &'a Connector)>,
212) {
213    for (service_name, connector) in connector_subgraphs {
214        *message = message.replace(&**service_name, connector.id.subgraph_name.as_str());
215    }
216}