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::CompositionHint;
22pub use crate::supergraph::Merged;
23pub use crate::supergraph::Satisfiable;
24pub use crate::supergraph::Supergraph;
25
26#[derive(Debug)]
27pub struct CompositionFailure {
28 pub errors: Vec<CompositionError>,
29 pub hints: Vec<CompositionHint>,
30}
31
32impl CompositionFailure {
33 pub fn from_errors(errors: Vec<CompositionError>) -> Self {
34 Self {
35 errors,
36 hints: Vec::new(),
37 }
38 }
39}
40
41impl From<Vec<CompositionError>> for CompositionFailure {
42 fn from(errors: Vec<CompositionError>) -> Self {
43 Self::from_errors(errors)
44 }
45}
46
47#[derive(Debug, Default, Clone)]
51pub struct CompositionOptions {
52 pub max_validation_subgraph_paths: Option<usize>,
54}
55
56#[instrument(skip(subgraphs, options))]
58pub fn compose(
59 subgraphs: Vec<Subgraph<Initial>>,
60 options: CompositionOptions,
61) -> Result<Supergraph<Satisfiable>, CompositionFailure> {
62 let mut subgraphs = subgraphs;
65 subgraphs.sort_by(|s1, s2| s1.name.cmp(&s2.name));
66
67 tracing::debug!("Expanding subgraphs...");
68 let expanded_subgraphs = expand_subgraphs(subgraphs)?;
69 tracing::debug!("Upgrading subgraphs...");
70 let validated_subgraphs = upgrade_subgraphs_if_necessary(expanded_subgraphs)
71 .map_err(CompositionFailure::from_errors)?;
72
73 tracing::debug!("Pre-merge validations...");
74 pre_merge_validations(&validated_subgraphs)?;
75 tracing::debug!("Merging subgraphs...");
76 let supergraph = merge_subgraphs(validated_subgraphs, &options)?;
77 tracing::debug!("Post-merge validations...");
78 post_merge_validations(&supergraph)?;
79 tracing::debug!("Validating satisfiability...");
80 validate_satisfiability(supergraph, &options)
81}
82
83pub fn compose_with_connectors(
85 subgraphs: Vec<Subgraph<Initial>>,
86 options: CompositionOptions,
87) -> Result<Supergraph<Satisfiable>, CompositionFailure> {
88 tracing::debug!("Expanding subgraphs...");
95 let expanded_subgraphs = expand_subgraphs(subgraphs)?;
96
97 tracing::debug!("Upgrading subgraphs...");
98 let validated_subgraphs = upgrade_subgraphs_if_necessary(expanded_subgraphs)
99 .map_err(CompositionFailure::from_errors)?;
100
101 tracing::debug!("Pre-merge validations...");
102 pre_merge_validations(&validated_subgraphs)?;
103
104 tracing::debug!("Merging subgraphs...");
105 let supergraph = merge_subgraphs(validated_subgraphs, &options)?;
106
107 tracing::debug!("Post-merge validations...");
108 post_merge_validations(&supergraph)?;
109 tracing::debug!("Validating satisfiability...");
113 validate_satisfiability_with_connectors(supergraph, &options)
114}
115
116#[instrument(skip(subgraphs))]
119pub fn expand_subgraphs(
120 subgraphs: Vec<Subgraph<Initial>>,
121) -> Result<Vec<Subgraph<Expanded>>, CompositionFailure> {
122 let mut errors: Vec<CompositionError> = vec![];
123 let expanded: Vec<Subgraph<Expanded>> = subgraphs
124 .into_iter()
125 .map(|s| s.expand_links())
126 .filter_map(|r| r.map_err(|e| errors.extend(e.to_composition_errors())).ok())
127 .collect();
128 if errors.is_empty() {
129 Ok(expanded)
130 } else {
131 Err(CompositionFailure::from_errors(errors))
132 }
133}
134
135#[instrument(skip(subgraphs))]
137pub fn pre_merge_validations(subgraphs: &[Subgraph<Validated>]) -> Result<(), CompositionFailure> {
138 validate_consistent_root_fields(subgraphs).map_err(CompositionFailure::from_errors)?;
139 Ok(())
141}
142
143#[instrument(skip(subgraphs, options))]
144pub fn merge_subgraphs(
145 subgraphs: Vec<Subgraph<Validated>>,
146 options: &CompositionOptions,
147) -> Result<Supergraph<Merged>, CompositionFailure> {
148 let merger = Merger::new(subgraphs, options.clone()).map_err(|e| {
149 CompositionFailure::from_errors(vec![CompositionError::InternalError {
150 message: e.to_string(),
151 }])
152 })?;
153 let result = merger.merge().map_err(|e| {
154 CompositionFailure::from_errors(vec![CompositionError::InternalError {
155 message: e.to_string(),
156 }])
157 })?;
158 tracing::trace!(
159 "Merge has {} errors and {} hints",
160 result.errors.len(),
161 result.hints.len()
162 );
163 if result.errors.is_empty() {
164 let Some(supergraph_schema) = result.supergraph else {
165 return Err(CompositionFailure::from_errors(vec![
166 CompositionError::InternalError {
167 message: "Merge completed with no supergraph schema".to_string(),
168 },
169 ]));
170 };
171 let supergraph = Supergraph::with_hints(supergraph_schema, result.hints);
172 Ok(supergraph)
173 } else {
174 Err(CompositionFailure {
175 errors: result.errors,
176 hints: result.hints,
177 })
178 }
179}
180
181#[instrument(skip(_supergraph))]
182pub fn post_merge_validations(_supergraph: &Supergraph<Merged>) -> Result<(), CompositionFailure> {
183 Ok(())
186}
187
188pub fn validate_satisfiability_with_connectors(
191 supergraph: Supergraph<Merged>,
192 options: &CompositionOptions,
193) -> Result<Supergraph<Satisfiable>, CompositionFailure> {
194 let merge_hints = supergraph.hints().to_vec();
195 let supergraph_str = supergraph.schema().schema().to_string();
196 let expansion_result = match expand_connectors(&supergraph_str, &Default::default()) {
197 Ok(result) => result,
198 Err(e) => {
199 return Err(CompositionFailure {
200 errors: vec![CompositionError::InternalError {
201 message: format!(
202 "Composition failed due to an internal error when expanding connectors, please report this: {e}"
203 ),
204 }],
205 hints: merge_hints,
206 });
207 }
208 };
209
210 match expansion_result {
211 ExpansionResult::Expanded {
212 raw_sdl,
213 connectors: Connectors {
214 by_service_name, ..
215 },
216 ..
217 } => {
218 let expanded_supergraph = match Supergraph::parse(&raw_sdl) {
219 Ok(s) => s,
220 Err(e) => {
221 return Err(CompositionFailure {
222 errors: vec![CompositionError::InternalError {
223 message: e.to_string(),
224 }],
225 hints: merge_hints,
226 });
227 }
228 };
229 match validate_satisfiability(expanded_supergraph, options) {
230 Ok(mut supergraph) => {
231 for hint in supergraph.hints_mut() {
232 sanitize_connectors_message(&mut hint.message, by_service_name.iter());
233 }
234 Ok(supergraph)
235 }
236 Err(mut failure) => {
237 failure.hints.extend(merge_hints);
238 for error in failure.errors.iter_mut() {
239 sanitize_connectors_error(error, by_service_name.iter());
240 }
241 for hint in failure.hints.iter_mut() {
242 sanitize_connectors_message(&mut hint.message, by_service_name.iter());
243 }
244 Err(failure)
245 }
246 }
247 }
248 ExpansionResult::Unchanged => validate_satisfiability(supergraph, options),
249 }
250}
251
252fn sanitize_connectors_error<'a>(
253 issue: &mut CompositionError,
254 connector_subgraphs: impl Iterator<Item = (&'a Arc<str>, &'a Connector)>,
255) {
256 match issue {
257 CompositionError::SatisfiabilityError { message } => {
258 sanitize_connectors_message(message, connector_subgraphs);
259 }
260 CompositionError::ShareableHasMismatchedRuntimeTypes { message } => {
261 sanitize_connectors_message(message, connector_subgraphs);
262 }
263 _ => {}
264 }
265}
266
267fn sanitize_connectors_message<'a>(
268 message: &mut String,
269 connector_subgraphs: impl Iterator<Item = (&'a Arc<str>, &'a Connector)>,
270) {
271 for (service_name, connector) in connector_subgraphs {
272 *message = message.replace(&**service_name, connector.id.subgraph_name.as_str());
273 }
274}