Skip to content

Commit 41c908c

Browse files
authored
feat(composition): Merge arguments (#8332)
1 parent 0243a7c commit 41c908c

File tree

5 files changed

+240
-123
lines changed

5 files changed

+240
-123
lines changed

apollo-federation/src/merger/merge_argument.rs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ use crate::merger::merge::Merger;
1616
use crate::merger::merge::Sources;
1717
use crate::merger::merge_field::PLACEHOLDER_TYPE_NAME;
1818
use crate::schema::FederationSchema;
19+
use crate::schema::position::HasDescription;
20+
use crate::schema::position::HasType;
1921
use crate::supergraph::CompositionHint;
2022
use crate::utils::human_readable::human_readable_subgraph_names;
2123

@@ -68,7 +70,7 @@ impl Merger {
6870
&mut self,
6971
sources: &Sources<T>,
7072
dest: &T,
71-
) -> Result<(), FederationError>
73+
) -> Result<IndexSet<Name>, FederationError>
7274
where
7375
T: HasArguments + Display,
7476
<T as HasArguments>::ArgumentPosition: Display,
@@ -84,7 +86,7 @@ impl Merger {
8486
}
8587
}
8688

87-
for arg_name in arg_names {
89+
for arg_name in &arg_names {
8890
// We add the argument unconditionally even if we're going to remove it later on.
8991
// This enables consistent mismatch/hint reporting.
9092
dest.insert_argument(
@@ -106,7 +108,7 @@ impl Merger {
106108
continue;
107109
};
108110
let subgraph = &self.subgraphs[*idx];
109-
let arg_opt = pos.get_argument(subgraph.schema(), &arg_name);
111+
let arg_opt = pos.get_argument(subgraph.schema(), arg_name);
110112

111113
if let Some(arg) = arg_opt
112114
&& let Ok(Some(from_context)) = subgraph.from_context_directive_name()
@@ -132,7 +134,7 @@ impl Merger {
132134
continue;
133135
};
134136
let subgraph = &self.subgraphs[*idx];
135-
if let Some(arg) = pos.get_argument(subgraph.schema(), &arg_name) {
137+
if let Some(arg) = pos.get_argument(subgraph.schema(), arg_name) {
136138
if arg.is_required() && arg.default_value.is_none() {
137139
self.error_reporter.add_error(CompositionError::ContextualArgumentNotContextualInAllSubgraphs {
138140
message: format!(
@@ -155,7 +157,7 @@ impl Merger {
155157
}
156158
// Note: we remove the element after the hint/error because we access it in
157159
// the hint message generation.
158-
dest.remove_argument(&mut self.merged, &arg_name)?;
160+
dest.remove_argument(&mut self.merged, arg_name)?;
159161
continue;
160162
}
161163

@@ -169,7 +171,7 @@ impl Merger {
169171
continue;
170172
};
171173
let subgraph = &self.subgraphs[*idx];
172-
if let Some(arg) = pos.get_argument(subgraph.schema(), &arg_name) {
174+
if let Some(arg) = pos.get_argument(subgraph.schema(), arg_name) {
173175
present_in.push(*idx);
174176
if arg.is_required() {
175177
required_in.push(*idx);
@@ -224,10 +226,25 @@ impl Merger {
224226

225227
// Note that we remove the element after the hint/error because we
226228
// access it in the hint message generation.
227-
dest.remove_argument(&mut self.merged, &arg_name)?;
229+
dest.remove_argument(&mut self.merged, arg_name)?;
228230
}
229231
}
230232

233+
Ok(arg_names)
234+
}
235+
236+
pub(in crate::merger) fn merge_argument<T>(
237+
&mut self,
238+
sources: &Sources<T>,
239+
dest: &T,
240+
) -> Result<(), FederationError>
241+
where
242+
T: Display + HasDefaultValue + HasDescription + HasType,
243+
{
244+
self.merge_description(sources, dest)?;
245+
self.record_applied_directives_to_merge(sources, dest);
246+
self.merge_type_reference(sources, dest, true)?;
247+
self.merge_default_value(sources, dest)?;
231248
Ok(())
232249
}
233250

apollo-federation/src/merger/merge_directive.rs

Lines changed: 56 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@ use apollo_compiler::ast::DirectiveLocation;
77
use apollo_compiler::collections::HashSet;
88
use itertools::Itertools;
99

10-
use crate::bail;
1110
use crate::error::FederationError;
1211
use crate::merger::hints::HintCode;
1312
use crate::merger::merge::Merger;
1413
use crate::merger::merge::Sources;
1514
use crate::merger::merge::map_sources;
16-
use crate::schema::position::DirectiveArgumentDefinitionPosition;
1715
use crate::schema::position::DirectiveDefinitionPosition;
1816
use crate::schema::referencer::DirectiveReferencers;
1917
use crate::subgraph::typestate::Subgraph;
@@ -180,17 +178,30 @@ impl Merger {
180178
{
181179
self.merge_custom_core_directive(name)?;
182180
} else {
183-
let sources = self.get_sources_for_directive(name)?;
181+
let sources = self
182+
.subgraphs
183+
.iter()
184+
.enumerate()
185+
.filter_map(|(idx, subgraph)| {
186+
subgraph
187+
.schema()
188+
.get_directive_definition(name)
189+
.map(|def| (idx, Some(def)))
190+
})
191+
.collect();
184192
if Self::some_sources(&sources, |source, idx| {
185193
let Some(source) = source else {
186194
return false;
187195
};
188-
self.is_merged_directive_definition(&self.names[idx], source)
196+
let Some(def) = source.try_get(self.subgraphs[idx].schema().schema()) else {
197+
return false;
198+
};
199+
self.is_merged_directive_definition(&self.names[idx], def)
189200
}) {
190201
self.merge_executable_directive_definition(
191202
name,
192203
&sources,
193-
DirectiveDefinitionPosition {
204+
&DirectiveDefinitionPosition {
194205
directive_name: name.clone(),
195206
},
196207
)?;
@@ -203,59 +214,63 @@ impl Merger {
203214
&mut self,
204215
name: &Name,
205216
) -> Result<(), FederationError> {
206-
let def = self
217+
let Some(def) = self
207218
.compose_directive_manager
208-
.get_latest_directive_definition(name)?;
209-
let Some(def) = def else {
219+
.get_latest_directive_definition(name)?
220+
else {
210221
return Ok(());
211222
};
212-
let Some(target) = self.merged.get_directive_definition(name) else {
213-
bail!("Directive definition not found in supergraph");
214-
};
215223

224+
let dest = DirectiveDefinitionPosition {
225+
directive_name: name.clone(),
226+
};
216227
// This replaces the calls to target.set_description, target.set_repeatable, and target.add_locations in the JS implementation
217-
target.insert(&mut self.merged, def.clone())?;
228+
dest.insert(&mut self.merged, def.clone())?;
218229

219-
let mut sources: Sources<Node<DirectiveDefinition>> = Default::default();
220-
sources.insert(0, Some(def.clone()));
221-
self.add_arguments_shallow_placeholder(&sources, &target);
230+
let sources = self
231+
.subgraphs
232+
.iter()
233+
.enumerate()
234+
.map(|(idx, subgraph)| (idx, subgraph.schema().get_directive_definition(name)))
235+
.collect();
236+
let arg_names = self.add_arguments_shallow(&sources, &dest)?;
222237

223-
for arg in &def.arguments {
224-
let dest_arg = target.argument(arg.name.clone());
225-
let sources = map_sources(&self.subgraph_sources(), |subgraph| {
226-
subgraph
227-
.as_ref()
228-
.and_then(|s| dest_arg.get(s.schema().schema()).ok().cloned())
238+
for arg_name in arg_names {
239+
let sources = map_sources(&sources, |source| {
240+
source.as_ref().map(|s| s.argument(arg_name.clone()))
229241
});
230-
self.merge_directive_argument(&sources, &dest_arg)?;
242+
let dest_arg = dest.argument(arg_name);
243+
self.merge_argument(&sources, &dest_arg)?;
231244
}
232245
Ok(())
233246
}
234247

235248
fn merge_executable_directive_definition(
236249
&mut self,
237250
name: &Name,
238-
sources: &Sources<Node<DirectiveDefinition>>,
239-
dest: DirectiveDefinitionPosition,
251+
sources: &Sources<DirectiveDefinitionPosition>,
252+
dest: &DirectiveDefinitionPosition,
240253
) -> Result<(), FederationError> {
241254
let mut repeatable: Option<bool> = None;
242255
let mut inconsistent_repeatable = false;
243256
let mut locations: Vec<DirectiveLocation> = Vec::new();
244257
let mut inconsistent_locations = false;
245258

246259
let supergraph_dest = dest.get(self.merged.schema())?.clone();
247-
let position_sources = map_sources(sources, |_| Some(dest.clone()));
248260

249-
for (_, source) in sources {
250-
let Some(source) = source else {
261+
for (idx, source) in sources {
262+
let Some(source) = source
263+
.as_ref()
264+
.and_then(|s| s.try_get(self.subgraphs[*idx].schema().schema()))
265+
else {
251266
// An executable directive could appear in any place of a query and thus get to any subgraph, so we cannot keep an
252267
// executable directive unless it is in all subgraphs. We use an 'intersection' strategy.
253268
dest.remove(&mut self.merged)?;
254269
self.error_reporter.report_mismatch_hint::<DirectiveDefinitionPosition, DirectiveDefinitionPosition,()>(
255270
HintCode::InconsistentExecutableDirectivePresence,
256271
format!("Executable directive \"{name}\" will not be part of the supergraph as it does not appear in all subgraphs: "),
257-
&dest,
258-
&position_sources,
272+
dest,
273+
sources,
259274
|_elt| Some("yes".to_string()),
260275
|_elt, _idx| Some("yes".to_string()),
261276
|_, subgraphs| format!("it is defined in {}", subgraphs.unwrap_or_default()),
@@ -285,13 +300,14 @@ impl Merger {
285300
locations.retain(|loc| source_locations.contains(loc));
286301

287302
if locations.is_empty() {
288-
self.error_reporter.report_mismatch_hint::<Node<DirectiveDefinition>, Node<DirectiveDefinition>, ()>(
303+
self.error_reporter.report_mismatch_hint::<Node<DirectiveDefinition>, DirectiveDefinitionPosition, ()>(
289304
HintCode::NoExecutableDirectiveLocationsIntersection,
290305
format!("Executable directive \"{name}\" has no location that is common to all subgraphs: "),
291306
&supergraph_dest,
292307
sources,
293308
|elt| Some(location_string(&extract_executable_locations(elt))),
294-
|elt, _idx| Some(location_string(&extract_executable_locations(elt))),
309+
|pos, idx| pos.try_get(self.subgraphs[idx].schema().schema())
310+
.map(|elt| location_string(&extract_executable_locations(elt))),
295311
|_, _subgraphs| "it will not appear in the supergraph as there no intersection between ".to_string(),
296312
|locs, subgraphs| format!("{locs} in {subgraphs}"),
297313
false,
@@ -303,32 +319,33 @@ impl Merger {
303319
dest.set_repeatable(&mut self.merged, repeatable.unwrap_or_default())?; // repeatable will always be Some() here
304320
dest.add_locations(&mut self.merged, &locations)?;
305321

306-
self.merge_description(&position_sources, &dest)?;
322+
self.merge_description(sources, dest)?;
307323

308324
if inconsistent_repeatable {
309-
self.error_reporter.report_mismatch_hint::<Node<DirectiveDefinition>, Node<DirectiveDefinition>, ()>(
325+
self.error_reporter.report_mismatch_hint::<Node<DirectiveDefinition>, DirectiveDefinitionPosition, ()>(
310326
HintCode::InconsistentExecutableDirectiveRepeatable,
311327
format!("Executable directive \"{name}\" will not be marked repeatable in the supergraph as it is inconsistently marked repeatable in subgraphs: "),
312328
&supergraph_dest,
313329
sources,
314330
|elt| if elt.repeatable { Some("yes".to_string()) } else { Some("no".to_string()) },
315-
|elt, _idx| if elt.repeatable { Some("yes".to_string()) } else { Some("no".to_string()) },
331+
|pos, idx| pos.try_get(self.subgraphs[idx].schema().schema())
332+
.map(|elt| if elt.repeatable { "yes".to_string() } else { "no".to_string() }),
316333
|_, subgraphs| format!("it is not repeatable in {}", subgraphs.unwrap_or_default()),
317334
|_, subgraphs| format!(" but is repeatable in {}", subgraphs),
318335
false,
319336
false,
320337
);
321338
}
322339
if inconsistent_locations {
323-
self.error_reporter.report_mismatch_hint::<Node<DirectiveDefinition>, Node<DirectiveDefinition>, ()>(
340+
self.error_reporter.report_mismatch_hint::<Node<DirectiveDefinition>, DirectiveDefinitionPosition, ()>(
324341
HintCode::InconsistentExecutableDirectiveLocations,
325342
format!(
326343
"Executable directive \"{name}\" has inconsistent locations across subgraphs: "
327344
),
328345
&supergraph_dest,
329346
sources,
330347
|elt| Some(location_string(&extract_executable_locations(elt))),
331-
|elt, _idx| Some(location_string(&extract_executable_locations(elt))),
348+
|pos, idx| pos.try_get(self.subgraphs[idx].schema().schema()).map(|elt| location_string(&extract_executable_locations(elt))),
332349
|_, _subgraphs| {
333350
"it will not appear in the supergraph as there no intersection between "
334351
.to_string()
@@ -340,19 +357,12 @@ impl Merger {
340357
}
341358

342359
// Doing args last, mostly so we don't bother adding if the directive doesn't make it in.
343-
self.add_arguments_shallow(&position_sources, &dest)?;
360+
self.add_arguments_shallow(sources, dest)?;
344361
for arg in &supergraph_dest.arguments {
345362
let subgraph_args = map_sources(sources, |src| {
346-
src.as_ref()
347-
.and_then(|src| src.arguments.iter().find(|a| a.name == arg.name).cloned())
363+
src.as_ref().map(|src| src.argument(arg.name.clone()))
348364
});
349-
self.merge_directive_argument(
350-
&subgraph_args,
351-
&DirectiveArgumentDefinitionPosition {
352-
directive_name: name.clone(),
353-
argument_name: arg.name.clone(),
354-
},
355-
)?;
365+
self.merge_argument(&subgraph_args, &dest.argument(arg.name.clone()))?;
356366
}
357367
Ok(())
358368
}
@@ -373,34 +383,6 @@ impl Merger {
373383
self.applied_directives_to_merge.clear();
374384
Ok(())
375385
}
376-
377-
fn get_sources_for_directive(
378-
&self,
379-
name: &Name,
380-
) -> Result<Sources<Node<DirectiveDefinition>>, FederationError> {
381-
let sources = self
382-
.subgraphs
383-
.iter()
384-
.enumerate()
385-
.filter_map(|(index, subgraph)| {
386-
subgraph
387-
.schema()
388-
.schema()
389-
.directive_definitions
390-
.get(name)
391-
.map(|directive_def| (index, Some(directive_def.clone())))
392-
})
393-
.collect();
394-
Ok(sources)
395-
}
396-
397-
fn add_arguments_shallow_placeholder(
398-
&self,
399-
_sources: &Sources<Node<DirectiveDefinition>>,
400-
_dest: &DirectiveDefinitionPosition,
401-
) {
402-
todo!("Implement add_arguments_shallow_placeholder")
403-
}
404386
}
405387

406388
fn extract_executable_locations(source: &Node<DirectiveDefinition>) -> Vec<DirectiveLocation> {

apollo-federation/src/merger/merge_field.rs

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use crate::link::federation_spec_definition::FEDERATION_USED_OVERRIDEN_ARGUMENT_
3535
use crate::merger::merge::Merger;
3636
use crate::merger::merge::Sources;
3737
use crate::merger::merge::map_sources;
38+
use crate::merger::merge_argument::HasArguments;
3839
use crate::schema::blueprint::FEDERATION_OPERATION_FIELDS;
3940
use crate::schema::position::DirectiveTargetPosition;
4041
use crate::schema::position::FieldDefinitionPosition;
@@ -259,24 +260,16 @@ impl Merger {
259260

260261
self.merge_description(&without_external, dest)?;
261262
self.record_applied_directives_to_merge(&without_external, dest);
262-
self.add_arguments_shallow(&without_external, dest)?;
263-
let dest_field = dest.get(self.merged.schema())?;
264-
let dest_arguments = dest_field.arguments.clone();
265-
for dest_arg in dest_arguments.iter() {
263+
let arg_names = self.add_arguments_shallow(&without_external, dest)?;
264+
265+
for arg_name in arg_names {
266266
let subgraph_args = map_sources(&without_external, |field| {
267-
field.as_ref().and_then(|f| {
268-
let field_def = match f.get(self.merged.schema()) {
269-
Ok(def) => def,
270-
Err(_) => return None,
271-
};
272-
field_def
273-
.arguments
274-
.iter()
275-
.find(|arg| arg.name == dest_arg.name)
276-
.cloned()
277-
})
267+
field
268+
.as_ref()
269+
.map(|f| f.argument_position(arg_name.clone()))
278270
});
279-
self.merge_argument(&subgraph_args, dest_arg)?;
271+
let dest_arg = dest.argument_position(arg_name);
272+
self.merge_argument(&subgraph_args, &dest_arg)?;
280273
}
281274

282275
// Note that due to @interfaceObject, it's possible that `withoutExternal` is "empty" (has no

0 commit comments

Comments
 (0)