apollo_federation/composition/
mod.rs1mod 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#[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
45pub fn compose_with_connectors(
47 subgraphs: Vec<Subgraph<Initial>>,
48) -> Result<Supergraph<Satisfiable>, Vec<CompositionError>> {
49 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 tracing::debug!("Validating satisfiability...");
74 validate_satisfiability_with_connectors(supergraph)
75}
76
77#[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#[instrument(skip(subgraphs))]
98pub fn pre_merge_validations(
99 subgraphs: &[Subgraph<Validated>],
100) -> Result<(), Vec<CompositionError>> {
101 validate_consistent_root_fields(subgraphs)?;
102 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 Ok(())
145}
146
147pub fn validate_satisfiability_with_connectors(
150 supergraph: Supergraph<Merged>,
151) -> Result<Supergraph<Satisfiable>, Vec<CompositionError>> {
152 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 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 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}