1mod join_directive;
2mod schema;
3mod subgraph;
4
5use std::fmt::Write;
6use std::ops::Deref;
7use std::ops::Not;
8use std::sync::Arc;
9use std::sync::LazyLock;
10
11use apollo_compiler::Name;
12use apollo_compiler::Node;
13use apollo_compiler::Schema;
14use apollo_compiler::ast::FieldDefinition;
15use apollo_compiler::collections::IndexMap;
16use apollo_compiler::collections::IndexSet;
17use apollo_compiler::executable;
18use apollo_compiler::executable::FieldSet;
19use apollo_compiler::name;
20use apollo_compiler::schema::Component;
21use apollo_compiler::schema::ComponentName;
22use apollo_compiler::schema::ComponentOrigin;
23use apollo_compiler::schema::DirectiveDefinition;
24use apollo_compiler::schema::DirectiveList;
25use apollo_compiler::schema::DirectiveLocation;
26use apollo_compiler::schema::EnumType;
27use apollo_compiler::schema::EnumValueDefinition;
28use apollo_compiler::schema::ExtendedType;
29use apollo_compiler::schema::ExtensionId;
30use apollo_compiler::schema::InputObjectType;
31use apollo_compiler::schema::InputValueDefinition;
32use apollo_compiler::schema::InterfaceType;
33use apollo_compiler::schema::NamedType;
34use apollo_compiler::schema::ObjectType;
35use apollo_compiler::schema::ScalarType;
36use apollo_compiler::schema::Type;
37use apollo_compiler::schema::UnionType;
38use apollo_compiler::validation::Valid;
39use itertools::Itertools;
40use time::OffsetDateTime;
41
42pub(crate) use self::schema::new_empty_fed_2_subgraph_schema;
43use self::subgraph::FederationSubgraph;
44use self::subgraph::FederationSubgraphs;
45pub use self::subgraph::ValidFederationSubgraph;
46pub use self::subgraph::ValidFederationSubgraphs;
47use crate::ApiSchemaOptions;
48use crate::api_schema;
49use crate::error::FederationError;
50use crate::error::Locations;
51use crate::error::MultipleFederationErrors;
52use crate::error::SingleFederationError;
53use crate::link::context_spec_definition::ContextSpecDefinition;
54use crate::link::cost_spec_definition::CostSpecDefinition;
55use crate::link::federation_spec_definition::FEDERATION_VERSIONS;
56use crate::link::federation_spec_definition::FederationSpecDefinition;
57use crate::link::federation_spec_definition::get_federation_spec_definition_from_subgraph;
58use crate::link::join_spec_definition::ContextArgument;
59use crate::link::join_spec_definition::FieldDirectiveArguments;
60use crate::link::join_spec_definition::JoinSpecDefinition;
61use crate::link::join_spec_definition::TypeDirectiveArguments;
62use crate::link::spec::Identity;
63use crate::link::spec::Version;
64use crate::link::spec_definition::SpecDefinition;
65use crate::schema::FederationSchema;
66use crate::schema::ValidFederationSchema;
67use crate::schema::field_set::parse_field_set_without_normalization;
68use crate::schema::position::CompositeTypeDefinitionPosition;
69use crate::schema::position::DirectiveDefinitionPosition;
70use crate::schema::position::EnumTypeDefinitionPosition;
71use crate::schema::position::FieldDefinitionPosition;
72use crate::schema::position::InputObjectFieldDefinitionPosition;
73use crate::schema::position::InputObjectTypeDefinitionPosition;
74use crate::schema::position::InterfaceTypeDefinitionPosition;
75use crate::schema::position::ObjectFieldDefinitionPosition;
76use crate::schema::position::ObjectOrInterfaceFieldDefinitionPosition;
77use crate::schema::position::ObjectOrInterfaceTypeDefinitionPosition;
78use crate::schema::position::ObjectTypeDefinitionPosition;
79use crate::schema::position::SchemaRootDefinitionKind;
80use crate::schema::position::SchemaRootDefinitionPosition;
81use crate::schema::position::TypeDefinitionPosition;
82use crate::schema::position::UnionTypeDefinitionPosition;
83use crate::schema::position::is_graphql_reserved_name;
84use crate::schema::type_and_directive_specification::FieldSpecification;
85use crate::schema::type_and_directive_specification::ObjectTypeSpecification;
86use crate::schema::type_and_directive_specification::ScalarTypeSpecification;
87use crate::schema::type_and_directive_specification::TypeAndDirectiveSpecification;
88use crate::schema::type_and_directive_specification::UnionTypeSpecification;
89use crate::utils::FallibleIterator;
90
91#[derive(Debug)]
92pub struct Supergraph<S> {
93 state: S,
94}
95
96impl Supergraph<Merged> {
97 pub fn with_hints(schema: ValidFederationSchema, hints: Vec<CompositionHint>) -> Self {
98 Self {
99 state: Merged { schema, hints },
100 }
101 }
102
103 pub fn parse(schema_str: &str) -> Result<Self, FederationError> {
104 let schema = Schema::parse_and_validate(schema_str, "schema.graphql")?;
105 Ok(Self {
106 state: Merged {
107 schema: ValidFederationSchema::new(schema)?,
108 hints: vec![],
109 },
110 })
111 }
112
113 pub fn assume_satisfiable(self) -> Supergraph<Satisfiable> {
114 todo!("unimplemented")
115 }
116
117 pub fn schema(&self) -> &ValidFederationSchema {
119 &self.state.schema
120 }
121
122 pub fn hints(&self) -> &Vec<CompositionHint> {
123 &self.state.hints
124 }
125
126 pub fn hints_mut(&mut self) -> &mut Vec<CompositionHint> {
127 &mut self.state.hints
128 }
129
130 #[allow(unused)]
131 pub(crate) fn subgraph_name_to_graph_enum_value(
132 &self,
133 ) -> Result<IndexMap<String, Name>, FederationError> {
134 let supergraph_schema = self.schema();
135 let (_link_spec_definition, join_spec_definition, _context_spec_definition) =
142 crate::validate_supergraph_for_query_planning(supergraph_schema)?;
143 let (_subgraphs, _federation_spec_definitions, graph_enum_value_name_to_subgraph_name) =
144 collect_empty_subgraphs(supergraph_schema, join_spec_definition)?;
145 Ok(graph_enum_value_name_to_subgraph_name
146 .into_iter()
147 .map(|(enum_value_name, subgraph_name)| {
148 (subgraph_name.to_string(), enum_value_name.clone())
149 })
150 .collect())
151 }
152}
153
154impl Supergraph<Satisfiable> {
155 pub fn new(schema: ValidFederationSchema, hints: Vec<CompositionHint>) -> Self {
156 Supergraph {
157 state: Satisfiable {
158 schema,
159 metadata: SupergraphMetadata {
163 interface_types_with_interface_objects: Default::default(),
164 abstract_types_with_inconsistent_runtime_types: Default::default(),
165 },
166 hints,
167 },
168 }
169 }
170
171 pub fn to_api_schema(
174 &self,
175 options: ApiSchemaOptions,
176 ) -> Result<ValidFederationSchema, FederationError> {
177 api_schema::to_api_schema(self.state.schema.clone(), options)
178 }
179
180 pub fn schema(&self) -> &ValidFederationSchema {
182 &self.state.schema
183 }
184
185 pub fn metadata(&self) -> &SupergraphMetadata {
186 &self.state.metadata
187 }
188
189 pub fn hints(&self) -> &Vec<CompositionHint> {
190 &self.state.hints
191 }
192}
193
194#[derive(Clone, Debug)]
195pub struct Merged {
196 schema: ValidFederationSchema,
197 hints: Vec<CompositionHint>,
198}
199
200#[derive(Clone, Debug)]
201pub struct Satisfiable {
202 schema: ValidFederationSchema,
203 metadata: SupergraphMetadata,
204 hints: Vec<CompositionHint>,
205}
206
207#[derive(Clone, Debug)]
208#[allow(unused)]
209#[allow(unreachable_pub)]
210pub struct SupergraphMetadata {
211 interface_types_with_interface_objects: IndexSet<InterfaceTypeDefinitionPosition>,
214 abstract_types_with_inconsistent_runtime_types: IndexSet<Name>,
217}
218
219#[derive(Clone, Debug)]
222pub struct CompositionHint {
223 pub message: String,
224 pub code: String,
225 pub locations: Locations,
226}
227
228impl CompositionHint {
229 pub fn code(&self) -> &str {
230 &self.code
231 }
232
233 pub fn message(&self) -> &str {
234 &self.message
235 }
236}
237
238pub(crate) fn extract_subgraphs_from_supergraph(
243 supergraph_schema: &FederationSchema,
244 validate_extracted_subgraphs: Option<bool>,
245) -> Result<ValidFederationSubgraphs, FederationError> {
246 let validate_extracted_subgraphs = validate_extracted_subgraphs.unwrap_or(true);
247 let (link_spec_definition, join_spec_definition, context_spec_definition) =
248 crate::validate_supergraph_for_query_planning(supergraph_schema)?;
249 let is_fed_1 = *join_spec_definition.version() == Version { major: 0, minor: 1 };
250 let (mut subgraphs, federation_spec_definitions, graph_enum_value_name_to_subgraph_name) =
251 collect_empty_subgraphs(supergraph_schema, join_spec_definition)?;
252
253 let filtered_types: Vec<_> = supergraph_schema
254 .get_types()
255 .fallible_filter(|type_definition_position| {
256 join_spec_definition
257 .is_spec_type_name(supergraph_schema, type_definition_position.type_name())
258 .map(Not::not)
259 })
260 .and_then_filter(|type_definition_position| {
261 link_spec_definition
262 .is_spec_type_name(supergraph_schema, type_definition_position.type_name())
263 .map(Not::not)
264 })
265 .try_collect()?;
266 if is_fed_1 {
267 let unsupported = SingleFederationError::UnsupportedFederationVersion {
268 message: String::from(
269 "Supergraphs composed with federation version 1 are not supported. Please recompose your supergraph with federation version 2 or greater",
270 ),
271 };
272 return Err(unsupported.into());
273 } else {
274 extract_subgraphs_from_fed_2_supergraph(
275 supergraph_schema,
276 &mut subgraphs,
277 &graph_enum_value_name_to_subgraph_name,
278 &federation_spec_definitions,
279 join_spec_definition,
280 context_spec_definition,
281 &filtered_types,
282 )?;
283 }
284
285 for graph_enum_value in graph_enum_value_name_to_subgraph_name.keys() {
286 let subgraph = get_subgraph(
287 &mut subgraphs,
288 &graph_enum_value_name_to_subgraph_name,
289 graph_enum_value,
290 )?;
291 let federation_spec_definition = federation_spec_definitions
292 .get(graph_enum_value)
293 .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph {
294 message: "Subgraph unexpectedly does not use federation spec".to_owned(),
295 })?;
296 add_federation_operations(subgraph, federation_spec_definition)?;
297 }
298
299 let mut valid_subgraphs = ValidFederationSubgraphs::new();
300 for (_, mut subgraph) in subgraphs {
301 let valid_subgraph_schema = if validate_extracted_subgraphs {
302 match subgraph.schema.validate_or_return_self() {
303 Ok(schema) => schema,
304 Err((schema, error)) => {
305 subgraph.schema = schema;
306 if is_fed_1 {
307 let message = String::from(
308 "Supergraphs composed with federation version 1 are not supported. Please recompose your supergraph with federation version 2 or greater",
309 );
310 return Err(SingleFederationError::UnsupportedFederationVersion {
311 message,
312 }
313 .into());
314 } else {
315 let mut message = format!(
316 "Unexpected error extracting {} from the supergraph: this is either a bug, or the supergraph has been corrupted.\n\nDetails:\n{error}",
317 subgraph.name,
318 );
319 maybe_dump_subgraph_schema(subgraph, &mut message);
320 return Err(
321 SingleFederationError::InvalidFederationSupergraph { message }.into(),
322 );
323 }
324 }
325 }
326 } else {
327 subgraph.schema.assume_valid()?
328 };
329 valid_subgraphs.add(ValidFederationSubgraph {
330 name: subgraph.name,
331 url: subgraph.url,
332 schema: valid_subgraph_schema,
333 })?;
334 }
335
336 Ok(valid_subgraphs)
337}
338
339type CollectEmptySubgraphsOk = (
340 FederationSubgraphs,
341 IndexMap<Name, &'static FederationSpecDefinition>,
342 IndexMap<Name, Arc<str>>,
343);
344fn collect_empty_subgraphs(
345 supergraph_schema: &FederationSchema,
346 join_spec_definition: &JoinSpecDefinition,
347) -> Result<CollectEmptySubgraphsOk, FederationError> {
348 let mut subgraphs = FederationSubgraphs::new();
349 let graph_directive_definition =
350 join_spec_definition.graph_directive_definition(supergraph_schema)?;
351 let graph_enum = join_spec_definition.graph_enum_definition(supergraph_schema)?;
352 let mut federation_spec_definitions = IndexMap::default();
353 let mut graph_enum_value_name_to_subgraph_name = IndexMap::default();
354 for (enum_value_name, enum_value_definition) in graph_enum.values.iter() {
355 let graph_application = enum_value_definition
356 .directives
357 .get(&graph_directive_definition.name)
358 .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph {
359 message: format!(
360 "Value \"{enum_value_name}\" of join__Graph enum has no @join__graph directive"
361 ),
362 })?;
363 let graph_arguments = join_spec_definition.graph_directive_arguments(graph_application)?;
364 let subgraph = FederationSubgraph {
365 name: graph_arguments.name.to_owned(),
366 url: graph_arguments.url.to_owned(),
367 schema: new_empty_fed_2_subgraph_schema()?,
368 graph_enum_value: enum_value_name.clone(),
369 };
370 let federation_link = &subgraph
371 .schema
372 .metadata()
373 .as_ref()
374 .and_then(|metadata| metadata.for_identity(&Identity::federation_identity()))
375 .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph {
376 message: "Subgraph unexpectedly does not use federation spec".to_owned(),
377 })?;
378 let federation_spec_definition = FEDERATION_VERSIONS
379 .find(&federation_link.url.version)
380 .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph {
381 message: "Subgraph unexpectedly does not use a supported federation spec version"
382 .to_owned(),
383 })?;
384 subgraphs.add(subgraph)?;
385 graph_enum_value_name_to_subgraph_name
386 .insert(enum_value_name.clone(), graph_arguments.name.into());
387 federation_spec_definitions.insert(enum_value_name.clone(), federation_spec_definition);
388 }
389 Ok((
390 subgraphs,
391 federation_spec_definitions,
392 graph_enum_value_name_to_subgraph_name,
393 ))
394}
395
396struct TypeInfo {
397 name: NamedType,
398 subgraph_info: IndexMap<Name, bool>,
400}
401
402struct TypeInfos {
403 object_types: Vec<TypeInfo>,
404 interface_types: Vec<TypeInfo>,
405 union_types: Vec<TypeInfo>,
406 enum_types: Vec<TypeInfo>,
407 input_object_types: Vec<TypeInfo>,
408}
409
410fn extract_subgraphs_from_fed_2_supergraph(
411 supergraph_schema: &FederationSchema,
412 subgraphs: &mut FederationSubgraphs,
413 graph_enum_value_name_to_subgraph_name: &IndexMap<Name, Arc<str>>,
414 federation_spec_definitions: &IndexMap<Name, &'static FederationSpecDefinition>,
415 join_spec_definition: &'static JoinSpecDefinition,
416 context_spec_definition: Option<&'static ContextSpecDefinition>,
417 filtered_types: &Vec<TypeDefinitionPosition>,
418) -> Result<(), FederationError> {
419 let TypeInfos {
420 object_types,
421 interface_types,
422 union_types,
423 enum_types,
424 input_object_types,
425 } = add_all_empty_subgraph_types(
426 supergraph_schema,
427 subgraphs,
428 graph_enum_value_name_to_subgraph_name,
429 federation_spec_definitions,
430 join_spec_definition,
431 context_spec_definition,
432 filtered_types,
433 )?;
434
435 extract_object_type_content(
436 supergraph_schema,
437 subgraphs,
438 graph_enum_value_name_to_subgraph_name,
439 federation_spec_definitions,
440 join_spec_definition,
441 &object_types,
442 )?;
443 extract_interface_type_content(
444 supergraph_schema,
445 subgraphs,
446 graph_enum_value_name_to_subgraph_name,
447 federation_spec_definitions,
448 join_spec_definition,
449 &interface_types,
450 )?;
451 extract_union_type_content(
452 supergraph_schema,
453 subgraphs,
454 graph_enum_value_name_to_subgraph_name,
455 join_spec_definition,
456 &union_types,
457 )?;
458 extract_enum_type_content(
459 supergraph_schema,
460 subgraphs,
461 graph_enum_value_name_to_subgraph_name,
462 join_spec_definition,
463 &enum_types,
464 )?;
465 extract_input_object_type_content(
466 supergraph_schema,
467 subgraphs,
468 graph_enum_value_name_to_subgraph_name,
469 join_spec_definition,
470 &input_object_types,
471 )?;
472
473 join_directive::extract(
474 supergraph_schema,
475 subgraphs,
476 graph_enum_value_name_to_subgraph_name,
477 )?;
478
479 let all_executable_directive_definitions = supergraph_schema
487 .schema()
488 .directive_definitions
489 .values()
490 .filter(|directive| !directive.is_built_in())
491 .filter_map(|directive_definition| {
492 let executable_locations = directive_definition
493 .locations
494 .iter()
495 .filter(|location| EXECUTABLE_DIRECTIVE_LOCATIONS.contains(*location))
496 .copied()
497 .collect::<Vec<_>>();
498 if executable_locations.is_empty() {
499 return None;
500 }
501 Some(Node::new(DirectiveDefinition {
502 description: None,
503 name: directive_definition.name.clone(),
504 arguments: directive_definition
505 .arguments
506 .iter()
507 .map(|argument| {
508 Node::new(InputValueDefinition {
509 description: None,
510 name: argument.name.clone(),
511 ty: argument.ty.clone(),
512 default_value: argument.default_value.clone(),
513 directives: Default::default(),
514 })
515 })
516 .collect::<Vec<_>>(),
517 repeatable: directive_definition.repeatable,
518 locations: executable_locations,
519 }))
520 })
521 .collect::<Vec<_>>();
522 for subgraph in subgraphs.subgraphs.values_mut() {
523 remove_inactive_requires_and_provides_from_subgraph(
524 supergraph_schema,
525 &mut subgraph.schema,
526 )?;
527 remove_unused_types_from_subgraph(&mut subgraph.schema)?;
528 for definition in all_executable_directive_definitions.iter() {
529 let pos = DirectiveDefinitionPosition {
530 directive_name: definition.name.clone(),
531 };
532 pos.pre_insert(&mut subgraph.schema)?;
533 pos.insert(&mut subgraph.schema, definition.clone())?;
534 }
535 }
536
537 Ok(())
538}
539
540#[allow(clippy::too_many_arguments)]
541fn add_all_empty_subgraph_types(
542 supergraph_schema: &FederationSchema,
543 subgraphs: &mut FederationSubgraphs,
544 graph_enum_value_name_to_subgraph_name: &IndexMap<Name, Arc<str>>,
545 federation_spec_definitions: &IndexMap<Name, &'static FederationSpecDefinition>,
546 join_spec_definition: &'static JoinSpecDefinition,
547 context_spec_definition: Option<&'static ContextSpecDefinition>,
548 filtered_types: &Vec<TypeDefinitionPosition>,
549) -> Result<TypeInfos, FederationError> {
550 let type_directive_definition =
551 join_spec_definition.type_directive_definition(supergraph_schema)?;
552
553 let mut object_types: Vec<TypeInfo> = Vec::new();
554 let mut interface_types: Vec<TypeInfo> = Vec::new();
555 let mut union_types: Vec<TypeInfo> = Vec::new();
556 let mut enum_types: Vec<TypeInfo> = Vec::new();
557 let mut input_object_types: Vec<TypeInfo> = Vec::new();
558
559 for type_definition_position in filtered_types {
560 let type_ = type_definition_position.get(supergraph_schema.schema())?;
561 let type_directive_applications: Vec<_> = type_
562 .directives()
563 .get_all(&type_directive_definition.name)
564 .map(|directive| join_spec_definition.type_directive_arguments(directive))
565 .try_collect()?;
566 let types_mut = match &type_definition_position {
567 TypeDefinitionPosition::Scalar(pos) => {
568 for type_directive_application in &type_directive_applications {
573 let subgraph = get_subgraph(
574 subgraphs,
575 graph_enum_value_name_to_subgraph_name,
576 &type_directive_application.graph,
577 )?;
578
579 pos.pre_insert(&mut subgraph.schema)?;
580 pos.insert(
581 &mut subgraph.schema,
582 Node::new(ScalarType {
583 description: None,
584 name: pos.type_name.clone(),
585 directives: Default::default(),
586 }),
587 )?;
588
589 CostSpecDefinition::propagate_demand_control_directives_for_scalar(
590 supergraph_schema,
591 &mut subgraph.schema,
592 pos,
593 )?;
594 }
595 None
596 }
597 TypeDefinitionPosition::Object(_) => Some(&mut object_types),
598 TypeDefinitionPosition::Interface(_) => Some(&mut interface_types),
599 TypeDefinitionPosition::Union(_) => Some(&mut union_types),
600 TypeDefinitionPosition::Enum(_) => Some(&mut enum_types),
601 TypeDefinitionPosition::InputObject(_) => Some(&mut input_object_types),
602 };
603 if let Some(types_mut) = types_mut {
604 types_mut.push(add_empty_type(
605 type_definition_position.clone(),
606 &type_directive_applications,
607 type_.directives(),
608 supergraph_schema,
609 subgraphs,
610 graph_enum_value_name_to_subgraph_name,
611 federation_spec_definitions,
612 context_spec_definition,
613 )?);
614 }
615 }
616
617 Ok(TypeInfos {
618 object_types,
619 interface_types,
620 union_types,
621 enum_types,
622 input_object_types,
623 })
624}
625
626#[allow(clippy::too_many_arguments)]
627fn add_empty_type(
628 type_definition_position: TypeDefinitionPosition,
629 type_directive_applications: &Vec<TypeDirectiveArguments>,
630 directives: &DirectiveList,
631 supergraph_schema: &FederationSchema,
632 subgraphs: &mut FederationSubgraphs,
633 graph_enum_value_name_to_subgraph_name: &IndexMap<Name, Arc<str>>,
634 federation_spec_definitions: &IndexMap<Name, &'static FederationSpecDefinition>,
635 context_spec_definition: Option<&'static ContextSpecDefinition>,
636) -> Result<TypeInfo, FederationError> {
637 if type_directive_applications.is_empty() {
639 return Err(SingleFederationError::InvalidFederationSupergraph {
640 message: format!("Missing @join__type on \"{type_definition_position}\""),
641 }
642 .into());
643 }
644 let mut type_info = TypeInfo {
645 name: type_definition_position.type_name().clone(),
646 subgraph_info: IndexMap::default(),
647 };
648 for type_directive_application in type_directive_applications {
649 let subgraph = get_subgraph(
650 subgraphs,
651 graph_enum_value_name_to_subgraph_name,
652 &type_directive_application.graph,
653 )?;
654 let federation_spec_definition = federation_spec_definitions
655 .get(&type_directive_application.graph)
656 .ok_or_else(|| SingleFederationError::Internal {
657 message: format!(
658 "Missing federation spec info for subgraph enum value \"{}\"",
659 type_directive_application.graph
660 ),
661 })?;
662
663 if !type_info
664 .subgraph_info
665 .contains_key(&type_directive_application.graph)
666 {
667 let mut is_interface_object = false;
668 match &type_definition_position {
669 TypeDefinitionPosition::Scalar(_) => {
670 return Err(SingleFederationError::Internal {
671 message: "\"add_empty_type()\" shouldn't be called for scalars".to_owned(),
672 }
673 .into());
674 }
675 TypeDefinitionPosition::Object(pos) => {
676 pos.pre_insert(&mut subgraph.schema)?;
677 pos.insert(
678 &mut subgraph.schema,
679 Node::new(ObjectType {
680 description: None,
681 name: pos.type_name.clone(),
682 implements_interfaces: Default::default(),
683 directives: Default::default(),
684 fields: Default::default(),
685 }),
686 )?;
687 if pos.type_name == "Query" {
688 let root_pos = SchemaRootDefinitionPosition {
689 root_kind: SchemaRootDefinitionKind::Query,
690 };
691 if root_pos.try_get(subgraph.schema.schema()).is_none() {
692 root_pos.insert(
693 &mut subgraph.schema,
694 ComponentName::from(&pos.type_name),
695 )?;
696 }
697 } else if pos.type_name == "Mutation" {
698 let root_pos = SchemaRootDefinitionPosition {
699 root_kind: SchemaRootDefinitionKind::Mutation,
700 };
701 if root_pos.try_get(subgraph.schema.schema()).is_none() {
702 root_pos.insert(
703 &mut subgraph.schema,
704 ComponentName::from(&pos.type_name),
705 )?;
706 }
707 } else if pos.type_name == "Subscription" {
708 let root_pos = SchemaRootDefinitionPosition {
709 root_kind: SchemaRootDefinitionKind::Subscription,
710 };
711 if root_pos.try_get(subgraph.schema.schema()).is_none() {
712 root_pos.insert(
713 &mut subgraph.schema,
714 ComponentName::from(&pos.type_name),
715 )?;
716 }
717 }
718 }
719 TypeDefinitionPosition::Interface(pos) => {
720 if type_directive_application.is_interface_object {
721 is_interface_object = true;
722 let interface_object_directive = federation_spec_definition
723 .interface_object_directive(&subgraph.schema)?;
724 let pos = ObjectTypeDefinitionPosition {
725 type_name: pos.type_name.clone(),
726 };
727 pos.pre_insert(&mut subgraph.schema)?;
728 pos.insert(
729 &mut subgraph.schema,
730 Node::new(ObjectType {
731 description: None,
732 name: pos.type_name.clone(),
733 implements_interfaces: Default::default(),
734 directives: DirectiveList(vec![Component::new(
735 interface_object_directive,
736 )]),
737 fields: Default::default(),
738 }),
739 )?;
740 } else {
741 pos.pre_insert(&mut subgraph.schema)?;
742 pos.insert(
743 &mut subgraph.schema,
744 Node::new(InterfaceType {
745 description: None,
746 name: pos.type_name.clone(),
747 implements_interfaces: Default::default(),
748 directives: Default::default(),
749 fields: Default::default(),
750 }),
751 )?;
752 }
753 }
754 TypeDefinitionPosition::Union(pos) => {
755 pos.pre_insert(&mut subgraph.schema)?;
756 pos.insert(
757 &mut subgraph.schema,
758 Node::new(UnionType {
759 description: None,
760 name: pos.type_name.clone(),
761 directives: Default::default(),
762 members: Default::default(),
763 }),
764 )?;
765 }
766 TypeDefinitionPosition::Enum(pos) => {
767 pos.pre_insert(&mut subgraph.schema)?;
768 pos.insert(
769 &mut subgraph.schema,
770 Node::new(EnumType {
771 description: None,
772 name: pos.type_name.clone(),
773 directives: Default::default(),
774 values: Default::default(),
775 }),
776 )?;
777 }
778 TypeDefinitionPosition::InputObject(pos) => {
779 pos.pre_insert(&mut subgraph.schema)?;
780 pos.insert(
781 &mut subgraph.schema,
782 Node::new(InputObjectType {
783 description: None,
784 name: pos.type_name.clone(),
785 directives: Default::default(),
786 fields: Default::default(),
787 }),
788 )?;
789 }
790 };
791 type_info.subgraph_info.insert(
792 type_directive_application.graph.clone(),
793 is_interface_object,
794 );
795 }
796
797 if let Some(key) = &type_directive_application.key {
798 let mut key_directive = Component::new(federation_spec_definition.key_directive(
799 &subgraph.schema,
800 key,
801 type_directive_application.resolvable,
802 )?);
803 if type_directive_application.extension {
804 key_directive.origin =
805 ComponentOrigin::Extension(ExtensionId::new(&key_directive.node))
806 }
807 let subgraph_type_definition_position = subgraph
808 .schema
809 .get_type(type_definition_position.type_name().clone())?;
810 match &subgraph_type_definition_position {
811 TypeDefinitionPosition::Scalar(_) => {
812 return Err(SingleFederationError::Internal {
813 message: "\"add_empty_type()\" shouldn't be called for scalars".to_owned(),
814 }
815 .into());
816 }
817 _ => {
818 subgraph_type_definition_position
819 .insert_directive(&mut subgraph.schema, key_directive)?;
820 }
821 };
822 }
823 }
824
825 if let Some(context_spec_definition) = context_spec_definition {
826 let context_directive_definition =
827 context_spec_definition.context_directive_definition(supergraph_schema)?;
828 for directive in directives.get_all(&context_directive_definition.name) {
829 let context_directive_application =
830 context_spec_definition.context_directive_arguments(directive)?;
831 let (subgraph_name, context_name) = context_directive_application
832 .name
833 .rsplit_once("__")
834 .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph {
835 message: format!(
836 "Invalid context \"{}\" in supergraph schema",
837 context_directive_application.name
838 ),
839 })?;
840 let subgraph = subgraphs.get_mut(subgraph_name).ok_or_else(|| {
841 SingleFederationError::Internal {
842 message:
843 "All subgraphs should have been created by \"collect_empty_subgraphs()\""
844 .to_owned(),
845 }
846 })?;
847 let federation_spec_definition = federation_spec_definitions
848 .get(&subgraph.graph_enum_value)
849 .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph {
850 message: "Subgraph unexpectedly does not use federation spec".to_owned(),
851 })?;
852 let context_directive = federation_spec_definition
853 .context_directive(&subgraph.schema, context_name.to_owned())?;
854 let subgraph_type_definition_position: CompositeTypeDefinitionPosition = subgraph
855 .schema
856 .get_type(type_definition_position.type_name().clone())?
857 .try_into()?;
858 subgraph_type_definition_position
859 .insert_directive(&mut subgraph.schema, Component::new(context_directive))?;
860 }
861 }
862
863 Ok(type_info)
864}
865
866fn extract_object_type_content(
867 supergraph_schema: &FederationSchema,
868 subgraphs: &mut FederationSubgraphs,
869 graph_enum_value_name_to_subgraph_name: &IndexMap<Name, Arc<str>>,
870 federation_spec_definitions: &IndexMap<Name, &'static FederationSpecDefinition>,
871 join_spec_definition: &JoinSpecDefinition,
872 info: &[TypeInfo],
873) -> Result<(), FederationError> {
874 let field_directive_definition =
875 join_spec_definition.field_directive_definition(supergraph_schema)?;
876 let implements_directive_definition = join_spec_definition
879 .implements_directive_definition(supergraph_schema)?
880 .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph {
881 message: "@join__implements should exist for a fed2 supergraph".to_owned(),
882 })?;
883
884 for TypeInfo {
885 name: type_name,
886 subgraph_info,
887 } in info.iter()
888 {
889 let pos = ObjectTypeDefinitionPosition {
890 type_name: (*type_name).clone(),
891 };
892 let type_ = pos.get(supergraph_schema.schema())?;
893
894 for directive in type_
895 .directives
896 .get_all(&implements_directive_definition.name)
897 {
898 let implements_directive_application =
899 join_spec_definition.implements_directive_arguments(directive)?;
900 if !subgraph_info.contains_key(&implements_directive_application.graph) {
901 return Err(
902 SingleFederationError::InvalidFederationSupergraph {
903 message: format!(
904 "@join__implements cannot exist on \"{}\" for subgraph \"{}\" without type-level @join__type",
905 type_name,
906 implements_directive_application.graph,
907 ),
908 }.into()
909 );
910 }
911 let subgraph = get_subgraph(
912 subgraphs,
913 graph_enum_value_name_to_subgraph_name,
914 &implements_directive_application.graph,
915 )?;
916 pos.insert_implements_interface(
917 &mut subgraph.schema,
918 ComponentName::from(Name::new(implements_directive_application.interface)?),
919 )?;
920 }
921
922 for graph_enum_value in subgraph_info.keys() {
923 let subgraph = get_subgraph(
924 subgraphs,
925 graph_enum_value_name_to_subgraph_name,
926 graph_enum_value,
927 )?;
928
929 CostSpecDefinition::propagate_demand_control_directives_for_object(
930 supergraph_schema,
931 &mut subgraph.schema,
932 &pos,
933 )?;
934 }
935
936 for (field_name, field) in type_.fields.iter() {
937 let field_pos = pos.field(field_name.clone());
938 let mut field_directive_applications = Vec::new();
939 for directive in field.directives.get_all(&field_directive_definition.name) {
940 field_directive_applications
941 .push(join_spec_definition.field_directive_arguments(directive)?);
942 }
943 if field_directive_applications.is_empty() {
944 let is_shareable = subgraph_info.len() > 1;
947 for graph_enum_value in subgraph_info.keys() {
948 let subgraph = get_subgraph(
949 subgraphs,
950 graph_enum_value_name_to_subgraph_name,
951 graph_enum_value,
952 )?;
953 let federation_spec_definition = federation_spec_definitions
954 .get(graph_enum_value)
955 .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph {
956 message: "Subgraph unexpectedly does not use federation spec"
957 .to_owned(),
958 })?;
959 add_subgraph_field(
960 field_pos.clone().into(),
961 field,
962 supergraph_schema,
963 subgraph,
964 federation_spec_definition,
965 is_shareable,
966 None,
967 )?;
968 }
969 } else {
970 let is_shareable = field_directive_applications
971 .iter()
972 .filter(|field_directive_application| {
973 !field_directive_application.external.unwrap_or(false)
974 && !field_directive_application.user_overridden.unwrap_or(false)
975 })
976 .count()
977 > 1;
978
979 for field_directive_application in &field_directive_applications {
980 let Some(graph_enum_value) = &field_directive_application.graph else {
981 continue;
985 };
986 let subgraph = get_subgraph(
987 subgraphs,
988 graph_enum_value_name_to_subgraph_name,
989 graph_enum_value,
990 )?;
991 let federation_spec_definition = federation_spec_definitions
992 .get(graph_enum_value)
993 .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph {
994 message: "Subgraph unexpectedly does not use federation spec"
995 .to_owned(),
996 })?;
997 if !subgraph_info.contains_key(graph_enum_value) {
998 return Err(
999 SingleFederationError::InvalidFederationSupergraph {
1000 message: format!(
1001 "@join__field cannot exist on {type_name}.{field_name} for subgraph {graph_enum_value} without type-level @join__type",
1002 ),
1003 }.into()
1004 );
1005 }
1006 add_subgraph_field(
1007 field_pos.clone().into(),
1008 field,
1009 supergraph_schema,
1010 subgraph,
1011 federation_spec_definition,
1012 is_shareable,
1013 Some(field_directive_application),
1014 )?;
1015 }
1016 }
1017 }
1018 }
1019
1020 Ok(())
1021}
1022
1023fn extract_interface_type_content(
1024 supergraph_schema: &FederationSchema,
1025 subgraphs: &mut FederationSubgraphs,
1026 graph_enum_value_name_to_subgraph_name: &IndexMap<Name, Arc<str>>,
1027 federation_spec_definitions: &IndexMap<Name, &'static FederationSpecDefinition>,
1028 join_spec_definition: &JoinSpecDefinition,
1029 info: &[TypeInfo],
1030) -> Result<(), FederationError> {
1031 let field_directive_definition =
1032 join_spec_definition.field_directive_definition(supergraph_schema)?;
1033 let implements_directive_definition = join_spec_definition
1036 .implements_directive_definition(supergraph_schema)?
1037 .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph {
1038 message: "@join__implements should exist for a fed2 supergraph".to_owned(),
1039 })?;
1040
1041 for TypeInfo {
1042 name: type_name,
1043 subgraph_info,
1044 } in info.iter()
1045 {
1046 let pos = InterfaceTypeDefinitionPosition {
1047 type_name: (*type_name).clone(),
1048 };
1049 let type_ = pos.get(supergraph_schema.schema())?;
1050 fn get_pos(
1051 subgraph: &FederationSubgraph,
1052 subgraph_info: &IndexMap<Name, bool>,
1053 graph_enum_value: &Name,
1054 type_name: NamedType,
1055 ) -> Result<ObjectOrInterfaceTypeDefinitionPosition, FederationError> {
1056 let is_interface_object = *subgraph_info.get(graph_enum_value).ok_or_else(|| {
1057 SingleFederationError::InvalidFederationSupergraph {
1058 message: format!(
1059 "@join__implements cannot exist on {type_name} for subgraph {graph_enum_value} without type-level @join__type",
1060 ),
1061 }
1062 })?;
1063 Ok(match subgraph.schema.get_type(type_name)? {
1064 TypeDefinitionPosition::Object(pos) => {
1065 if !is_interface_object {
1066 return Err(
1067 SingleFederationError::Internal {
1068 message: "\"extract_interface_type_content()\" encountered an unexpected interface object type in subgraph".to_owned(),
1069 }.into()
1070 );
1071 }
1072 pos.into()
1073 }
1074 TypeDefinitionPosition::Interface(pos) => {
1075 if is_interface_object {
1076 return Err(
1077 SingleFederationError::Internal {
1078 message: "\"extract_interface_type_content()\" encountered an interface type in subgraph that should have been an interface object".to_owned(),
1079 }.into()
1080 );
1081 }
1082 pos.into()
1083 }
1084 _ => {
1085 return Err(
1086 SingleFederationError::Internal {
1087 message: "\"extract_interface_type_content()\" encountered non-object/interface type in subgraph".to_owned(),
1088 }.into()
1089 );
1090 }
1091 })
1092 }
1093
1094 for directive in type_
1095 .directives
1096 .get_all(&implements_directive_definition.name)
1097 {
1098 let implements_directive_application =
1099 join_spec_definition.implements_directive_arguments(directive)?;
1100 let subgraph = get_subgraph(
1101 subgraphs,
1102 graph_enum_value_name_to_subgraph_name,
1103 &implements_directive_application.graph,
1104 )?;
1105 let pos = get_pos(
1106 subgraph,
1107 subgraph_info,
1108 &implements_directive_application.graph,
1109 type_name.clone(),
1110 )?;
1111 match pos {
1112 ObjectOrInterfaceTypeDefinitionPosition::Object(pos) => {
1113 pos.insert_implements_interface(
1114 &mut subgraph.schema,
1115 ComponentName::from(Name::new(implements_directive_application.interface)?),
1116 )?;
1117 }
1118 ObjectOrInterfaceTypeDefinitionPosition::Interface(pos) => {
1119 pos.insert_implements_interface(
1120 &mut subgraph.schema,
1121 ComponentName::from(Name::new(implements_directive_application.interface)?),
1122 )?;
1123 }
1124 }
1125 }
1126
1127 for (field_name, field) in type_.fields.iter() {
1128 let mut field_directive_applications = Vec::new();
1129 for directive in field.directives.get_all(&field_directive_definition.name) {
1130 field_directive_applications
1131 .push(join_spec_definition.field_directive_arguments(directive)?);
1132 }
1133 if field_directive_applications.is_empty() {
1134 for graph_enum_value in subgraph_info.keys() {
1137 let subgraph = get_subgraph(
1138 subgraphs,
1139 graph_enum_value_name_to_subgraph_name,
1140 graph_enum_value,
1141 )?;
1142 let pos =
1143 get_pos(subgraph, subgraph_info, graph_enum_value, type_name.clone())?;
1144 let federation_spec_definition = federation_spec_definitions
1145 .get(graph_enum_value)
1146 .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph {
1147 message: "Subgraph unexpectedly does not use federation spec"
1148 .to_owned(),
1149 })?;
1150 add_subgraph_field(
1151 pos.field(field_name.clone()),
1152 field,
1153 supergraph_schema,
1154 subgraph,
1155 federation_spec_definition,
1156 false,
1157 None,
1158 )?;
1159 }
1160 } else {
1161 for field_directive_application in &field_directive_applications {
1162 let Some(graph_enum_value) = &field_directive_application.graph else {
1163 continue;
1167 };
1168 let subgraph = get_subgraph(
1169 subgraphs,
1170 graph_enum_value_name_to_subgraph_name,
1171 graph_enum_value,
1172 )?;
1173 let pos =
1174 get_pos(subgraph, subgraph_info, graph_enum_value, type_name.clone())?;
1175 let federation_spec_definition = federation_spec_definitions
1176 .get(graph_enum_value)
1177 .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph {
1178 message: "Subgraph unexpectedly does not use federation spec"
1179 .to_owned(),
1180 })?;
1181 if !subgraph_info.contains_key(graph_enum_value) {
1182 return Err(
1183 SingleFederationError::InvalidFederationSupergraph {
1184 message: format!(
1185 "@join__field cannot exist on {type_name}.{field_name} for subgraph {graph_enum_value} without type-level @join__type",
1186 ),
1187 }.into()
1188 );
1189 }
1190 add_subgraph_field(
1191 pos.field(field_name.clone()),
1192 field,
1193 supergraph_schema,
1194 subgraph,
1195 federation_spec_definition,
1196 false,
1197 Some(field_directive_application),
1198 )?;
1199 }
1200 }
1201 }
1202 }
1203
1204 Ok(())
1205}
1206
1207fn extract_union_type_content(
1208 supergraph_schema: &FederationSchema,
1209 subgraphs: &mut FederationSubgraphs,
1210 graph_enum_value_name_to_subgraph_name: &IndexMap<Name, Arc<str>>,
1211 join_spec_definition: &JoinSpecDefinition,
1212 info: &[TypeInfo],
1213) -> Result<(), FederationError> {
1214 let union_member_directive_definition =
1216 join_spec_definition.union_member_directive_definition(supergraph_schema)?;
1217
1218 for TypeInfo {
1222 name: type_name,
1223 subgraph_info,
1224 } in info.iter()
1225 {
1226 let pos = UnionTypeDefinitionPosition {
1227 type_name: (*type_name).clone(),
1228 };
1229 let type_ = pos.get(supergraph_schema.schema())?;
1230
1231 let mut union_member_directive_applications = Vec::new();
1232 if let Some(union_member_directive_definition) = union_member_directive_definition {
1233 for directive in type_
1234 .directives
1235 .get_all(&union_member_directive_definition.name)
1236 {
1237 union_member_directive_applications
1238 .push(join_spec_definition.union_member_directive_arguments(directive)?);
1239 }
1240 }
1241 if union_member_directive_applications.is_empty() {
1242 for graph_enum_value in subgraph_info.keys() {
1245 let subgraph = get_subgraph(
1246 subgraphs,
1247 graph_enum_value_name_to_subgraph_name,
1248 graph_enum_value,
1249 )?;
1250 let subgraph_members = type_
1253 .members
1254 .iter()
1255 .filter(|member| {
1256 subgraph
1257 .schema
1258 .schema()
1259 .types
1260 .contains_key((*member).deref())
1261 })
1262 .collect::<Vec<_>>();
1263 for member in subgraph_members {
1264 pos.insert_member(&mut subgraph.schema, ComponentName::from(&member.name))?;
1265 }
1266 }
1267 } else {
1268 for union_member_directive_application in &union_member_directive_applications {
1269 let subgraph = get_subgraph(
1270 subgraphs,
1271 graph_enum_value_name_to_subgraph_name,
1272 &union_member_directive_application.graph,
1273 )?;
1274 if !subgraph_info.contains_key(&union_member_directive_application.graph) {
1275 return Err(
1276 SingleFederationError::InvalidFederationSupergraph {
1277 message: format!(
1278 "@join__unionMember cannot exist on {} for subgraph {} without type-level @join__type",
1279 type_name,
1280 union_member_directive_application.graph,
1281 ),
1282 }.into()
1283 );
1284 }
1285 pos.insert_member(
1289 &mut subgraph.schema,
1290 ComponentName::from(Name::new(union_member_directive_application.member)?),
1291 )?;
1292 }
1293 }
1294 }
1295
1296 Ok(())
1297}
1298
1299fn extract_enum_type_content(
1300 supergraph_schema: &FederationSchema,
1301 subgraphs: &mut FederationSubgraphs,
1302 graph_enum_value_name_to_subgraph_name: &IndexMap<Name, Arc<str>>,
1303 join_spec_definition: &JoinSpecDefinition,
1304 info: &[TypeInfo],
1305) -> Result<(), FederationError> {
1306 let enum_value_directive_definition =
1308 join_spec_definition.enum_value_directive_definition(supergraph_schema)?;
1309
1310 for TypeInfo {
1311 name: type_name,
1312 subgraph_info,
1313 } in info.iter()
1314 {
1315 let pos = EnumTypeDefinitionPosition {
1316 type_name: (*type_name).clone(),
1317 };
1318 let type_ = pos.get(supergraph_schema.schema())?;
1319
1320 for graph_enum_value in subgraph_info.keys() {
1321 let subgraph = get_subgraph(
1322 subgraphs,
1323 graph_enum_value_name_to_subgraph_name,
1324 graph_enum_value,
1325 )?;
1326
1327 CostSpecDefinition::propagate_demand_control_directives_for_enum(
1328 supergraph_schema,
1329 &mut subgraph.schema,
1330 &pos,
1331 )?;
1332 }
1333
1334 for (value_name, value) in type_.values.iter() {
1335 let value_pos = pos.value(value_name.clone());
1336 let mut enum_value_directive_applications = Vec::new();
1337 if let Some(enum_value_directive_definition) = enum_value_directive_definition {
1338 for directive in value
1339 .directives
1340 .get_all(&enum_value_directive_definition.name)
1341 {
1342 enum_value_directive_applications
1343 .push(join_spec_definition.enum_value_directive_arguments(directive)?);
1344 }
1345 }
1346 if enum_value_directive_applications.is_empty() {
1347 for graph_enum_value in subgraph_info.keys() {
1348 let subgraph = get_subgraph(
1349 subgraphs,
1350 graph_enum_value_name_to_subgraph_name,
1351 graph_enum_value,
1352 )?;
1353 value_pos.insert(
1354 &mut subgraph.schema,
1355 Component::new(EnumValueDefinition {
1356 description: None,
1357 value: value_name.clone(),
1358 directives: Default::default(),
1359 }),
1360 )?;
1361 }
1362 } else {
1363 for enum_value_directive_application in &enum_value_directive_applications {
1364 let subgraph = get_subgraph(
1365 subgraphs,
1366 graph_enum_value_name_to_subgraph_name,
1367 &enum_value_directive_application.graph,
1368 )?;
1369 if !subgraph_info.contains_key(&enum_value_directive_application.graph) {
1370 return Err(
1371 SingleFederationError::InvalidFederationSupergraph {
1372 message: format!(
1373 "@join__enumValue cannot exist on {}.{} for subgraph {} without type-level @join__type",
1374 type_name,
1375 value_name,
1376 enum_value_directive_application.graph,
1377 ),
1378 }.into()
1379 );
1380 }
1381 value_pos.insert(
1382 &mut subgraph.schema,
1383 Component::new(EnumValueDefinition {
1384 description: None,
1385 value: value_name.clone(),
1386 directives: Default::default(),
1387 }),
1388 )?;
1389 }
1390 }
1391 }
1392 }
1393
1394 Ok(())
1395}
1396
1397fn extract_input_object_type_content(
1398 supergraph_schema: &FederationSchema,
1399 subgraphs: &mut FederationSubgraphs,
1400 graph_enum_value_name_to_subgraph_name: &IndexMap<Name, Arc<str>>,
1401 join_spec_definition: &JoinSpecDefinition,
1402 info: &[TypeInfo],
1403) -> Result<(), FederationError> {
1404 let field_directive_definition =
1405 join_spec_definition.field_directive_definition(supergraph_schema)?;
1406
1407 for TypeInfo {
1408 name: type_name,
1409 subgraph_info,
1410 } in info.iter()
1411 {
1412 let pos = InputObjectTypeDefinitionPosition {
1413 type_name: (*type_name).clone(),
1414 };
1415 let type_ = pos.get(supergraph_schema.schema())?;
1416
1417 for (input_field_name, input_field) in type_.fields.iter() {
1418 let input_field_pos = pos.field(input_field_name.clone());
1419 let mut field_directive_applications = Vec::new();
1420 for directive in input_field
1421 .directives
1422 .get_all(&field_directive_definition.name)
1423 {
1424 field_directive_applications
1425 .push(join_spec_definition.field_directive_arguments(directive)?);
1426 }
1427 if field_directive_applications.is_empty() {
1428 for graph_enum_value in subgraph_info.keys() {
1429 let subgraph = get_subgraph(
1430 subgraphs,
1431 graph_enum_value_name_to_subgraph_name,
1432 graph_enum_value,
1433 )?;
1434 add_subgraph_input_field(
1435 input_field_pos.clone(),
1436 input_field,
1437 supergraph_schema,
1438 subgraph,
1439 None,
1440 )?;
1441 }
1442 } else {
1443 for field_directive_application in &field_directive_applications {
1444 let Some(graph_enum_value) = &field_directive_application.graph else {
1445 continue;
1449 };
1450 let subgraph = get_subgraph(
1451 subgraphs,
1452 graph_enum_value_name_to_subgraph_name,
1453 graph_enum_value,
1454 )?;
1455 if !subgraph_info.contains_key(graph_enum_value) {
1456 return Err(
1457 SingleFederationError::InvalidFederationSupergraph {
1458 message: format!(
1459 "@join__field cannot exist on {type_name}.{input_field_name} for subgraph {graph_enum_value} without type-level @join__type",
1460 ),
1461 }.into()
1462 );
1463 }
1464 add_subgraph_input_field(
1465 input_field_pos.clone(),
1466 input_field,
1467 supergraph_schema,
1468 subgraph,
1469 Some(field_directive_application),
1470 )?;
1471 }
1472 }
1473 }
1474 }
1475
1476 Ok(())
1477}
1478
1479#[allow(clippy::too_many_arguments)]
1480fn add_subgraph_field(
1481 object_or_interface_field_definition_position: ObjectOrInterfaceFieldDefinitionPosition,
1482 field: &FieldDefinition,
1483 supergraph_schema: &FederationSchema,
1484 subgraph: &mut FederationSubgraph,
1485 federation_spec_definition: &'static FederationSpecDefinition,
1486 is_shareable: bool,
1487 field_directive_application: Option<&FieldDirectiveArguments>,
1488) -> Result<(), FederationError> {
1489 let field_directive_application =
1490 field_directive_application.unwrap_or_else(|| &FieldDirectiveArguments {
1491 graph: None,
1492 requires: None,
1493 provides: None,
1494 type_: None,
1495 external: None,
1496 override_: None,
1497 override_label: None,
1498 user_overridden: None,
1499 context_arguments: None,
1500 });
1501 let subgraph_field_type = match &field_directive_application.type_ {
1502 Some(t) => decode_type(t)?,
1503 None => field.ty.clone(),
1504 };
1505 let mut subgraph_field = FieldDefinition {
1506 description: None,
1507 name: object_or_interface_field_definition_position
1508 .field_name()
1509 .clone(),
1510 arguments: vec![],
1511 ty: subgraph_field_type,
1512 directives: Default::default(),
1513 };
1514
1515 for argument in &field.arguments {
1516 let mut destination_argument = InputValueDefinition {
1517 description: None,
1518 name: argument.name.clone(),
1519 ty: argument.ty.clone(),
1520 default_value: argument.default_value.clone(),
1521 directives: Default::default(),
1522 };
1523
1524 CostSpecDefinition::propagate_demand_control_directives(
1525 supergraph_schema,
1526 &argument.directives,
1527 &subgraph.schema,
1528 &mut destination_argument.directives,
1529 )?;
1530
1531 subgraph_field
1532 .arguments
1533 .push(Node::new(destination_argument))
1534 }
1535 if let Some(requires) = &field_directive_application.requires {
1536 subgraph_field.directives.push(Node::new(
1537 federation_spec_definition
1538 .requires_directive(&subgraph.schema, requires.to_string())?,
1539 ));
1540 }
1541 if let Some(provides) = &field_directive_application.provides {
1542 subgraph_field.directives.push(Node::new(
1543 federation_spec_definition
1544 .provides_directive(&subgraph.schema, provides.to_string())?,
1545 ));
1546 }
1547 let external = field_directive_application.external.unwrap_or(false);
1548 if external {
1549 subgraph_field.directives.push(Node::new(
1550 federation_spec_definition.external_directive(&subgraph.schema, None)?,
1551 ));
1552 }
1553 let user_overridden = field_directive_application.user_overridden.unwrap_or(false);
1554 if user_overridden && field_directive_application.override_label.is_none() {
1555 subgraph_field.directives.push(Node::new(
1556 federation_spec_definition
1557 .external_directive(&subgraph.schema, Some("[overridden]".to_string()))?,
1558 ));
1559 }
1560 if let Some(override_) = &field_directive_application.override_ {
1561 subgraph_field
1562 .directives
1563 .push(Node::new(federation_spec_definition.override_directive(
1564 &subgraph.schema,
1565 override_.to_string(),
1566 &field_directive_application.override_label,
1567 )?));
1568 }
1569 if is_shareable && !external && !user_overridden {
1570 subgraph_field.directives.push(Node::new(
1571 federation_spec_definition.shareable_directive(&subgraph.schema)?,
1572 ));
1573 }
1574
1575 CostSpecDefinition::propagate_demand_control_directives(
1576 supergraph_schema,
1577 &field.directives,
1578 &subgraph.schema,
1579 &mut subgraph_field.directives,
1580 )?;
1581
1582 if let Some(context_arguments) = &field_directive_application.context_arguments {
1583 for args in context_arguments {
1584 let ContextArgument {
1585 name,
1586 type_,
1587 context,
1588 selection,
1589 } = args;
1590 let (_, context_name_in_subgraph) = context.rsplit_once("__").ok_or_else(|| {
1591 SingleFederationError::InvalidFederationSupergraph {
1592 message: format!(r#"Invalid context "{context}" in supergraph schema"#),
1593 }
1594 })?;
1595
1596 let arg = format!("${context_name_in_subgraph} {selection}");
1597 let from_context_directive =
1598 federation_spec_definition.from_context_directive(&subgraph.schema, arg)?;
1599 let directives = std::iter::once(from_context_directive).collect();
1600 let ty = decode_type(type_)?;
1601 let node = Node::new(InputValueDefinition {
1602 name: Name::new(name)?,
1603 ty: ty.into(),
1604 directives,
1605 default_value: None,
1606 description: None,
1607 });
1608 subgraph_field.arguments.push(node);
1609 }
1610 }
1611
1612 match object_or_interface_field_definition_position {
1613 ObjectOrInterfaceFieldDefinitionPosition::Object(pos) => {
1614 pos.insert(&mut subgraph.schema, Component::from(subgraph_field))?;
1615 }
1616 ObjectOrInterfaceFieldDefinitionPosition::Interface(pos) => {
1617 pos.insert(&mut subgraph.schema, Component::from(subgraph_field))?;
1618 }
1619 };
1620
1621 Ok(())
1622}
1623
1624fn add_subgraph_input_field(
1625 input_object_field_definition_position: InputObjectFieldDefinitionPosition,
1626 input_field: &InputValueDefinition,
1627 supergraph_schema: &FederationSchema,
1628 subgraph: &mut FederationSubgraph,
1629 field_directive_application: Option<&FieldDirectiveArguments>,
1630) -> Result<(), FederationError> {
1631 let field_directive_application =
1632 field_directive_application.unwrap_or_else(|| &FieldDirectiveArguments {
1633 graph: None,
1634 requires: None,
1635 provides: None,
1636 type_: None,
1637 external: None,
1638 override_: None,
1639 override_label: None,
1640 user_overridden: None,
1641 context_arguments: None,
1642 });
1643 let subgraph_input_field_type = match &field_directive_application.type_ {
1644 Some(t) => Node::new(decode_type(t)?),
1645 None => input_field.ty.clone(),
1646 };
1647 let mut subgraph_input_field = InputValueDefinition {
1648 description: None,
1649 name: input_object_field_definition_position.field_name.clone(),
1650 ty: subgraph_input_field_type,
1651 default_value: input_field.default_value.clone(),
1652 directives: Default::default(),
1653 };
1654
1655 CostSpecDefinition::propagate_demand_control_directives(
1656 supergraph_schema,
1657 &input_field.directives,
1658 &subgraph.schema,
1659 &mut subgraph_input_field.directives,
1660 )?;
1661
1662 input_object_field_definition_position
1663 .insert(&mut subgraph.schema, Component::from(subgraph_input_field))?;
1664
1665 Ok(())
1666}
1667
1668fn decode_type(type_: &str) -> Result<Type, FederationError> {
1670 Ok(Type::parse(type_, "")?)
1671}
1672
1673fn get_subgraph<'subgraph>(
1674 subgraphs: &'subgraph mut FederationSubgraphs,
1675 graph_enum_value_name_to_subgraph_name: &IndexMap<Name, Arc<str>>,
1676 graph_enum_value: &Name,
1677) -> Result<&'subgraph mut FederationSubgraph, FederationError> {
1678 let subgraph_name = graph_enum_value_name_to_subgraph_name
1679 .get(graph_enum_value)
1680 .ok_or_else(|| {
1681 SingleFederationError::Internal {
1682 message: format!(
1683 "Invalid graph enum_value \"{graph_enum_value}\": does not match an enum value defined in the @join__Graph enum",
1684 ),
1685 }
1686 })?;
1687 subgraphs.get_mut(subgraph_name).ok_or_else(|| {
1688 SingleFederationError::Internal {
1689 message: "All subgraphs should have been created by \"collect_empty_subgraphs()\""
1690 .to_owned(),
1691 }
1692 .into()
1693 })
1694}
1695
1696pub(crate) static EXECUTABLE_DIRECTIVE_LOCATIONS: LazyLock<IndexSet<DirectiveLocation>> =
1697 LazyLock::new(|| {
1698 [
1699 DirectiveLocation::Query,
1700 DirectiveLocation::Mutation,
1701 DirectiveLocation::Subscription,
1702 DirectiveLocation::Field,
1703 DirectiveLocation::FragmentDefinition,
1704 DirectiveLocation::FragmentSpread,
1705 DirectiveLocation::InlineFragment,
1706 DirectiveLocation::VariableDefinition,
1707 ]
1708 .into_iter()
1709 .collect()
1710 });
1711
1712fn remove_unused_types_from_subgraph(schema: &mut FederationSchema) -> Result<(), FederationError> {
1713 let mut type_definition_positions: Vec<TypeDefinitionPosition> = Vec::new();
1720 for (type_name, type_) in schema.schema().types.iter() {
1721 match type_ {
1722 ExtendedType::Object(type_) => {
1723 if type_.fields.is_empty() {
1724 type_definition_positions.push(
1725 ObjectTypeDefinitionPosition {
1726 type_name: type_name.clone(),
1727 }
1728 .into(),
1729 );
1730 }
1731 }
1732 ExtendedType::Interface(type_) => {
1733 if type_.fields.is_empty() {
1734 type_definition_positions.push(
1735 InterfaceTypeDefinitionPosition {
1736 type_name: type_name.clone(),
1737 }
1738 .into(),
1739 );
1740 }
1741 }
1742 ExtendedType::Union(type_) => {
1743 if type_.members.is_empty() {
1744 type_definition_positions.push(
1745 UnionTypeDefinitionPosition {
1746 type_name: type_name.clone(),
1747 }
1748 .into(),
1749 );
1750 }
1751 }
1752 ExtendedType::InputObject(type_) => {
1753 if type_.fields.is_empty() {
1754 type_definition_positions.push(
1755 InputObjectTypeDefinitionPosition {
1756 type_name: type_name.clone(),
1757 }
1758 .into(),
1759 );
1760 }
1761 }
1762 _ => {}
1763 }
1764 }
1765
1766 for position in type_definition_positions {
1769 match position {
1770 TypeDefinitionPosition::Object(position) => {
1771 position.remove_recursive(schema)?;
1772 }
1773 TypeDefinitionPosition::Interface(position) => {
1774 position.remove_recursive(schema)?;
1775 }
1776 TypeDefinitionPosition::Union(position) => {
1777 position.remove_recursive(schema)?;
1778 }
1779 TypeDefinitionPosition::InputObject(position) => {
1780 position.remove_recursive(schema)?;
1781 }
1782 _ => {
1783 return Err(SingleFederationError::Internal {
1784 message: "Encountered type kind that shouldn't have been removed".to_owned(),
1785 }
1786 .into());
1787 }
1788 }
1789 }
1790
1791 Ok(())
1792}
1793
1794pub(crate) const FEDERATION_ANY_TYPE_NAME: Name = name!("_Any");
1795const FEDERATION_SERVICE_TYPE_NAME: Name = name!("_Service");
1796const FEDERATION_SDL_FIELD_NAME: Name = name!("sdl");
1797pub(crate) const FEDERATION_ENTITY_TYPE_NAME: Name = name!("_Entity");
1798pub(crate) const FEDERATION_SERVICE_FIELD_NAME: Name = name!("_service");
1799pub(crate) const FEDERATION_ENTITIES_FIELD_NAME: Name = name!("_entities");
1800pub(crate) const FEDERATION_REPRESENTATIONS_ARGUMENTS_NAME: Name = name!("representations");
1801pub(crate) const FEDERATION_REPRESENTATIONS_VAR_NAME: Name = name!("representations");
1802
1803pub(crate) const GRAPHQL_STRING_TYPE_NAME: Name = name!("String");
1804pub(crate) const GRAPHQL_QUERY_TYPE_NAME: Name = name!("Query");
1805pub(crate) const GRAPHQL_MUTATION_TYPE_NAME: Name = name!("Mutation");
1806pub(crate) const GRAPHQL_SUBSCRIPTION_TYPE_NAME: Name = name!("Subscription");
1807
1808pub(crate) const ANY_TYPE_SPEC: ScalarTypeSpecification = ScalarTypeSpecification {
1809 name: FEDERATION_ANY_TYPE_NAME,
1810};
1811
1812pub(crate) const SERVICE_TYPE_SPEC: ObjectTypeSpecification = ObjectTypeSpecification {
1813 name: FEDERATION_SERVICE_TYPE_NAME,
1814 fields: |_schema| {
1815 [FieldSpecification {
1816 name: FEDERATION_SDL_FIELD_NAME,
1817 ty: Type::Named(GRAPHQL_STRING_TYPE_NAME),
1818 arguments: Default::default(),
1819 }]
1820 .into()
1821 },
1822};
1823
1824pub(crate) const EMPTY_QUERY_TYPE_SPEC: ObjectTypeSpecification = ObjectTypeSpecification {
1825 name: GRAPHQL_QUERY_TYPE_NAME,
1826 fields: |_schema| Default::default(), };
1828
1829fn collect_entity_members(
1832 schema: &FederationSchema,
1833 key_directive_definition: &Node<DirectiveDefinition>,
1834) -> IndexSet<ComponentName> {
1835 schema
1836 .schema()
1837 .types
1838 .iter()
1839 .filter_map(|(type_name, type_)| {
1840 let ExtendedType::Object(type_) = type_ else {
1841 return None;
1842 };
1843 if !type_.directives.has(&key_directive_definition.name) {
1844 return None;
1845 }
1846 Some(ComponentName::from(type_name))
1847 })
1848 .collect::<IndexSet<_>>()
1849}
1850
1851fn add_federation_operations(
1852 subgraph: &mut FederationSubgraph,
1853 federation_spec_definition: &'static FederationSpecDefinition,
1854) -> Result<(), FederationError> {
1855 ANY_TYPE_SPEC.check_or_add(&mut subgraph.schema, None)?;
1857 SERVICE_TYPE_SPEC.check_or_add(&mut subgraph.schema, None)?;
1858
1859 let key_directive_definition =
1861 federation_spec_definition.key_directive_definition(&subgraph.schema)?;
1862 let entity_members = collect_entity_members(&subgraph.schema, key_directive_definition);
1863 let has_entity_type = !entity_members.is_empty();
1864 if has_entity_type {
1865 UnionTypeSpecification {
1866 name: FEDERATION_ENTITY_TYPE_NAME,
1867 members: Box::new(move |_| entity_members.clone()),
1868 }
1869 .check_or_add(&mut subgraph.schema, None)?;
1870 }
1871
1872 let query_root_pos = SchemaRootDefinitionPosition {
1874 root_kind: SchemaRootDefinitionKind::Query,
1875 };
1876 if query_root_pos.try_get(subgraph.schema.schema()).is_none() {
1877 EMPTY_QUERY_TYPE_SPEC.check_or_add(&mut subgraph.schema, None)?;
1878 query_root_pos.insert(
1879 &mut subgraph.schema,
1880 ComponentName::from(EMPTY_QUERY_TYPE_SPEC.name),
1881 )?;
1882 }
1883
1884 let query_root_type_name = query_root_pos.get(subgraph.schema.schema())?.name.clone();
1886 let entity_field_pos = ObjectFieldDefinitionPosition {
1887 type_name: query_root_type_name.clone(),
1888 field_name: FEDERATION_ENTITIES_FIELD_NAME,
1889 };
1890 if has_entity_type {
1891 entity_field_pos.insert(
1892 &mut subgraph.schema,
1893 Component::new(FieldDefinition {
1894 description: None,
1895 name: FEDERATION_ENTITIES_FIELD_NAME,
1896 arguments: vec![Node::new(InputValueDefinition {
1897 description: None,
1898 name: FEDERATION_REPRESENTATIONS_ARGUMENTS_NAME,
1899 ty: Node::new(Type::NonNullList(Box::new(Type::NonNullNamed(
1900 FEDERATION_ANY_TYPE_NAME,
1901 )))),
1902 default_value: None,
1903 directives: Default::default(),
1904 })],
1905 ty: Type::NonNullList(Box::new(Type::Named(FEDERATION_ENTITY_TYPE_NAME))),
1906 directives: Default::default(),
1907 }),
1908 )?;
1909 } else {
1910 entity_field_pos.remove(&mut subgraph.schema)?;
1911 }
1912
1913 ObjectFieldDefinitionPosition {
1915 type_name: query_root_type_name,
1916 field_name: FEDERATION_SERVICE_FIELD_NAME,
1917 }
1918 .insert(
1919 &mut subgraph.schema,
1920 Component::new(FieldDefinition {
1921 description: None,
1922 name: FEDERATION_SERVICE_FIELD_NAME,
1923 arguments: Vec::new(),
1924 ty: Type::NonNullNamed(FEDERATION_SERVICE_TYPE_NAME),
1925 directives: Default::default(),
1926 }),
1927 )?;
1928
1929 Ok(())
1930}
1931
1932pub(crate) fn remove_inactive_requires_and_provides_from_subgraph(
1942 supergraph_schema: &FederationSchema,
1943 schema: &mut FederationSchema,
1944) -> Result<(), FederationError> {
1945 let federation_spec_definition = get_federation_spec_definition_from_subgraph(schema)?;
1946 let requires_directive_definition_name = federation_spec_definition
1947 .requires_directive_definition(schema)?
1948 .name
1949 .clone();
1950 let provides_directive_definition_name = federation_spec_definition
1951 .provides_directive_definition(schema)?
1952 .name
1953 .clone();
1954
1955 let mut object_or_interface_field_definition_positions: Vec<
1956 ObjectOrInterfaceFieldDefinitionPosition,
1957 > = vec![];
1958 for type_pos in schema.get_types() {
1959 if is_graphql_reserved_name(type_pos.type_name()) {
1961 continue;
1962 }
1963
1964 let Ok(type_pos) = ObjectOrInterfaceTypeDefinitionPosition::try_from(type_pos) else {
1966 continue;
1967 };
1968
1969 match type_pos {
1970 ObjectOrInterfaceTypeDefinitionPosition::Object(type_pos) => {
1971 object_or_interface_field_definition_positions.extend(
1972 type_pos
1973 .get(schema.schema())?
1974 .fields
1975 .keys()
1976 .map(|field_name| type_pos.field(field_name.clone()).into()),
1977 )
1978 }
1979 ObjectOrInterfaceTypeDefinitionPosition::Interface(type_pos) => {
1980 object_or_interface_field_definition_positions.extend(
1981 type_pos
1982 .get(schema.schema())?
1983 .fields
1984 .keys()
1985 .map(|field_name| type_pos.field(field_name.clone()).into()),
1986 )
1987 }
1988 };
1989 }
1990
1991 for pos in object_or_interface_field_definition_positions {
1992 remove_inactive_applications(
1993 supergraph_schema,
1994 schema,
1995 federation_spec_definition,
1996 FieldSetDirectiveKind::Requires,
1997 &requires_directive_definition_name,
1998 pos.clone(),
1999 )?;
2000 remove_inactive_applications(
2001 supergraph_schema,
2002 schema,
2003 federation_spec_definition,
2004 FieldSetDirectiveKind::Provides,
2005 &provides_directive_definition_name,
2006 pos,
2007 )?;
2008 }
2009
2010 Ok(())
2011}
2012
2013enum FieldSetDirectiveKind {
2014 Provides,
2015 Requires,
2016}
2017
2018fn remove_inactive_applications(
2019 supergraph_schema: &FederationSchema,
2020 schema: &mut FederationSchema,
2021 federation_spec_definition: &'static FederationSpecDefinition,
2022 directive_kind: FieldSetDirectiveKind,
2023 name_in_schema: &Name,
2024 object_or_interface_field_definition_position: ObjectOrInterfaceFieldDefinitionPosition,
2025) -> Result<(), FederationError> {
2026 let mut replacement_directives = Vec::new();
2027 let field = object_or_interface_field_definition_position.get(schema.schema())?;
2028 for directive in field.directives.get_all(name_in_schema) {
2029 let (fields, parent_type_pos, target_schema) = match directive_kind {
2030 FieldSetDirectiveKind::Provides => {
2031 let fields = federation_spec_definition
2032 .provides_directive_arguments(directive)?
2033 .fields;
2034 let Ok(parent_type_pos) = CompositeTypeDefinitionPosition::try_from(
2035 schema.get_type(field.ty.inner_named_type().clone())?,
2036 ) else {
2037 continue;
2040 };
2041 (fields, parent_type_pos, schema.schema())
2042 }
2043 FieldSetDirectiveKind::Requires => {
2044 let fields = federation_spec_definition
2045 .requires_directive_arguments(directive)?
2046 .fields;
2047 let parent_type_pos: CompositeTypeDefinitionPosition =
2048 object_or_interface_field_definition_position
2049 .parent()
2050 .clone()
2051 .into();
2052 (fields, parent_type_pos, supergraph_schema.schema())
2054 }
2055 };
2056 let valid_schema = Valid::assume_valid_ref(target_schema);
2064 let (mut fields, mut is_modified) = parse_field_set_without_normalization(
2070 valid_schema,
2071 parent_type_pos.type_name().clone(),
2072 fields,
2073 true,
2074 )?;
2075
2076 if remove_non_external_leaf_fields(schema, &mut fields)? {
2077 is_modified = true;
2078 }
2079 if is_modified {
2080 let replacement_directive = if fields.selections.is_empty() {
2081 None
2082 } else {
2083 let fields = FieldSet {
2084 sources: Default::default(),
2085 selection_set: fields,
2086 }
2087 .serialize()
2088 .no_indent()
2089 .to_string();
2090
2091 Some(Node::new(match directive_kind {
2092 FieldSetDirectiveKind::Provides => {
2093 federation_spec_definition.provides_directive(schema, fields)?
2094 }
2095 FieldSetDirectiveKind::Requires => {
2096 federation_spec_definition.requires_directive(schema, fields)?
2097 }
2098 }))
2099 };
2100 replacement_directives.push((directive.clone(), replacement_directive))
2101 }
2102 }
2103
2104 for (old_directive, new_directive) in replacement_directives {
2105 object_or_interface_field_definition_position.remove_directive(schema, &old_directive);
2106 if let Some(new_directive) = new_directive {
2107 object_or_interface_field_definition_position
2108 .insert_directive(schema, new_directive)?;
2109 }
2110 }
2111 Ok(())
2112}
2113
2114fn remove_non_external_leaf_fields(
2117 schema: &FederationSchema,
2118 selection_set: &mut executable::SelectionSet,
2119) -> Result<bool, FederationError> {
2120 let federation_spec_definition = get_federation_spec_definition_from_subgraph(schema)?;
2121 let external_directive_definition_name = federation_spec_definition
2122 .external_directive_definition(schema)?
2123 .name
2124 .clone();
2125 remove_non_external_leaf_fields_internal(
2126 schema,
2127 &external_directive_definition_name,
2128 selection_set,
2129 )
2130}
2131
2132fn remove_non_external_leaf_fields_internal(
2133 schema: &FederationSchema,
2134 external_directive_definition_name: &Name,
2135 selection_set: &mut executable::SelectionSet,
2136) -> Result<bool, FederationError> {
2137 let mut is_modified = false;
2138 let mut errors = MultipleFederationErrors { errors: Vec::new() };
2139 selection_set.selections.retain_mut(|selection| {
2140 let child_selection_set = match selection {
2141 executable::Selection::Field(field) => {
2142 match is_external_or_has_external_implementations(
2143 schema,
2144 external_directive_definition_name,
2145 &selection_set.ty,
2146 field,
2147 ) {
2148 Ok(is_external) => {
2149 if is_external {
2150 return true;
2153 }
2154 }
2155 Err(error) => {
2156 errors.push(error);
2157 return false;
2158 }
2159 };
2160 if field.selection_set.selections.is_empty() {
2161 is_modified = true;
2164 return false;
2165 }
2166 &mut field.make_mut().selection_set
2167 }
2168 executable::Selection::InlineFragment(inline_fragment) => {
2169 &mut inline_fragment.make_mut().selection_set
2170 }
2171 executable::Selection::FragmentSpread(_) => {
2172 errors.push(
2173 SingleFederationError::Internal {
2174 message: "Unexpectedly found named fragment in FieldSet scalar".to_owned(),
2175 }
2176 .into(),
2177 );
2178 return false;
2179 }
2180 };
2181 match remove_non_external_leaf_fields_internal(
2184 schema,
2185 external_directive_definition_name,
2186 child_selection_set,
2187 ) {
2188 Ok(is_child_modified) => {
2189 if is_child_modified {
2190 is_modified = true;
2191 }
2192 }
2193 Err(error) => {
2194 errors.push(error);
2195 return false;
2196 }
2197 }
2198 !child_selection_set.selections.is_empty()
2202 });
2203 if errors.errors.is_empty() {
2204 Ok(is_modified)
2205 } else {
2206 Err(errors.into())
2207 }
2208}
2209
2210fn is_external_or_has_external_implementations(
2211 schema: &FederationSchema,
2212 external_directive_definition_name: &Name,
2213 parent_type_name: &NamedType,
2214 selection: &Node<executable::Field>,
2215) -> Result<bool, FederationError> {
2216 let type_pos: CompositeTypeDefinitionPosition =
2217 schema.get_type(parent_type_name.clone())?.try_into()?;
2218 let field_pos = type_pos.field(selection.name.clone())?;
2219 let field = field_pos.get(schema.schema())?;
2220 if field.directives.has(external_directive_definition_name) {
2221 return Ok(true);
2222 }
2223 if let FieldDefinitionPosition::Interface(field_pos) = field_pos {
2224 for runtime_object_pos in schema.possible_runtime_types(field_pos.parent().into())? {
2225 let runtime_field_pos = runtime_object_pos.field(field_pos.field_name.clone());
2226 let runtime_field = runtime_field_pos.get(schema.schema())?;
2227 if runtime_field
2228 .directives
2229 .has(external_directive_definition_name)
2230 {
2231 return Ok(true);
2232 }
2233 }
2234 }
2235 Ok(false)
2236}
2237
2238static DEBUG_SUBGRAPHS_ENV_VARIABLE_NAME: &str = "APOLLO_FEDERATION_DEBUG_SUBGRAPHS";
2239
2240fn maybe_dump_subgraph_schema(subgraph: FederationSubgraph, message: &mut String) {
2241 _ = match std::env::var(DEBUG_SUBGRAPHS_ENV_VARIABLE_NAME).map(|v| v.parse::<bool>()) {
2244 Ok(Ok(true)) => {
2245 let time = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc());
2246 let filename = format!("extracted-subgraph-{}-{time}.graphql", subgraph.name,);
2247 let contents = subgraph.schema.schema().to_string();
2248 match std::fs::write(&filename, contents) {
2249 Ok(_) => write!(
2250 message,
2251 "The (invalid) extracted subgraph has been written in: {filename}."
2252 ),
2253 Err(e) => write!(
2254 message,
2255 r#"Was not able to print generated subgraph for "{}" because: {e}"#,
2256 subgraph.name
2257 ),
2258 }
2259 }
2260 _ => write!(
2261 message,
2262 "Re-run with environment variable '{DEBUG_SUBGRAPHS_ENV_VARIABLE_NAME}' set to 'true' to extract the invalid subgraph"
2263 ),
2264 };
2265}
2266
2267#[cfg(test)]
2268mod tests {
2269 use apollo_compiler::Schema;
2270 use apollo_compiler::name;
2271 use insta::assert_snapshot;
2272
2273 use crate::ValidFederationSubgraphs;
2274 use crate::schema::FederationSchema;
2275
2276 #[test]
2280 fn handles_types_having_no_fields_referenced_by_other_interfaces_in_a_subgraph_correctly() {
2281 let supergraph = r#"
2327 schema
2328 @link(url: "https://specs.apollo.dev/link/v1.0")
2329 @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION)
2330 {
2331 query: Query
2332 }
2333
2334 directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE
2335
2336 directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
2337
2338 directive @join__graph(name: String!, url: String!) on ENUM_VALUE
2339
2340 directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE
2341
2342 directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR
2343
2344 directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION
2345
2346 directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
2347
2348 interface A
2349 @join__type(graph: A)
2350 {
2351 a: B
2352 }
2353
2354 type B
2355 @join__type(graph: A)
2356 {
2357 b: C
2358 }
2359
2360 type C
2361 @join__type(graph: A)
2362 @join__type(graph: B)
2363 {
2364 c: String
2365 }
2366
2367 type D
2368 @join__type(graph: C)
2369 {
2370 d: String
2371 }
2372
2373 scalar join__FieldSet
2374
2375 enum join__Graph {
2376 A @join__graph(name: "a", url: "http://a")
2377 B @join__graph(name: "b", url: "http://b")
2378 C @join__graph(name: "c", url: "http://c")
2379 }
2380
2381 scalar link__Import
2382
2383 enum link__Purpose {
2384 """
2385 `SECURITY` features provide metadata necessary to securely resolve fields.
2386 """
2387 SECURITY
2388
2389 """
2390 `EXECUTION` features provide metadata necessary for operation execution.
2391 """
2392 EXECUTION
2393 }
2394
2395 type Query
2396 @join__type(graph: A)
2397 @join__type(graph: B)
2398 @join__type(graph: C)
2399 {
2400 q: A @join__field(graph: A)
2401 }
2402 "#;
2403
2404 let schema = Schema::parse(supergraph, "supergraph.graphql").unwrap();
2405 let ValidFederationSubgraphs { subgraphs } = super::extract_subgraphs_from_supergraph(
2406 &FederationSchema::new(schema).unwrap(),
2407 Some(true),
2408 )
2409 .unwrap();
2410
2411 assert_eq!(subgraphs.len(), 3);
2412
2413 let a = subgraphs.get("a").unwrap();
2414 assert!(a.schema.schema().get_interface("A").is_some());
2417 assert!(a.schema.schema().get_object("B").is_some());
2418
2419 let b = subgraphs.get("b").unwrap();
2420 assert!(b.schema.schema().get_interface("A").is_none());
2421 assert!(b.schema.schema().get_object("B").is_none());
2422
2423 let c = subgraphs.get("c").unwrap();
2424 assert!(c.schema.schema().get_interface("A").is_none());
2425 assert!(c.schema.schema().get_object("B").is_none());
2426 }
2427
2428 #[test]
2429 fn handles_types_having_no_fields_referenced_by_other_unions_in_a_subgraph_correctly() {
2430 let supergraph = r#"
2470 schema
2471 @link(url: "https://specs.apollo.dev/link/v1.0")
2472 @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION)
2473 {
2474 query: Query
2475 }
2476
2477 directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE
2478
2479 directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
2480
2481 directive @join__graph(name: String!, url: String!) on ENUM_VALUE
2482
2483 directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE
2484
2485 directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR
2486
2487 directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION
2488
2489 directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
2490
2491 union A
2492 @join__type(graph: A)
2493 @join__unionMember(graph: A, member: "B")
2494 @join__unionMember(graph: A, member: "C")
2495 = B | C
2496
2497 type B
2498 @join__type(graph: A)
2499 {
2500 b: D
2501 }
2502
2503 type C
2504 @join__type(graph: A)
2505 {
2506 c: D
2507 }
2508
2509 type D
2510 @join__type(graph: A)
2511 @join__type(graph: B)
2512 {
2513 d: String
2514 }
2515
2516 scalar join__FieldSet
2517
2518 enum join__Graph {
2519 A @join__graph(name: "a", url: "http://a")
2520 B @join__graph(name: "b", url: "http://b")
2521 }
2522
2523 scalar link__Import
2524
2525 enum link__Purpose {
2526 """
2527 `SECURITY` features provide metadata necessary to securely resolve fields.
2528 """
2529 SECURITY
2530
2531 """
2532 `EXECUTION` features provide metadata necessary for operation execution.
2533 """
2534 EXECUTION
2535 }
2536
2537 type Query
2538 @join__type(graph: A)
2539 @join__type(graph: B)
2540 {
2541 q: A @join__field(graph: A)
2542 }
2543 "#;
2544
2545 let schema = Schema::parse(supergraph, "supergraph.graphql").unwrap();
2546 let ValidFederationSubgraphs { subgraphs } = super::extract_subgraphs_from_supergraph(
2547 &FederationSchema::new(schema).unwrap(),
2548 Some(true),
2549 )
2550 .unwrap();
2551
2552 assert_eq!(subgraphs.len(), 2);
2553
2554 let a = subgraphs.get("a").unwrap();
2555 assert!(a.schema.schema().get_union("A").is_some());
2558 assert!(a.schema.schema().get_object("B").is_some());
2559 assert!(a.schema.schema().get_object("C").is_some());
2560 assert!(a.schema.schema().get_object("D").is_some());
2561
2562 let b = subgraphs.get("b").unwrap();
2563 assert!(b.schema.schema().get_union("A").is_none());
2564 assert!(b.schema.schema().get_object("B").is_none());
2565 assert!(b.schema.schema().get_object("C").is_none());
2566 assert!(b.schema.schema().get_object("D").is_some());
2567 }
2568
2569 #[test]
2575 fn handles_unions_types_having_no_members_in_a_subgraph_correctly() {
2576 let supergraph = r#"
2618 schema
2619 @link(url: "https://specs.apollo.dev/link/v1.0")
2620 @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION)
2621 {
2622 query: Query
2623 }
2624
2625 directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE
2626
2627 directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
2628
2629 directive @join__graph(name: String!, url: String!) on ENUM_VALUE
2630
2631 directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE
2632
2633 directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR
2634
2635 directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION
2636
2637 directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
2638
2639 union A
2640 @join__type(graph: A)
2641 @join__unionMember(graph: A, member: "B")
2642 @join__unionMember(graph: A, member: "C")
2643 = B | C
2644
2645 type B
2646 @join__type(graph: A, key: "b { d }")
2647 {
2648 b: D
2649 }
2650
2651 type C
2652 @join__type(graph: A, key: "c { d }")
2653 {
2654 c: D
2655 }
2656
2657 type D
2658 @join__type(graph: A)
2659 @join__type(graph: B)
2660 {
2661 d: String
2662 }
2663
2664 scalar join__FieldSet
2665
2666 enum join__Graph {
2667 A @join__graph(name: "a", url: "http://a")
2668 B @join__graph(name: "b", url: "http://b")
2669 }
2670
2671 scalar link__Import
2672
2673 enum link__Purpose {
2674 """
2675 `SECURITY` features provide metadata necessary to securely resolve fields.
2676 """
2677 SECURITY
2678
2679 """
2680 `EXECUTION` features provide metadata necessary for operation execution.
2681 """
2682 EXECUTION
2683 }
2684
2685 type Query
2686 @join__type(graph: A)
2687 @join__type(graph: B)
2688 {
2689 q: A @join__field(graph: A)
2690 }
2691 "#;
2692
2693 let schema = Schema::parse(supergraph, "supergraph.graphql").unwrap();
2694 let ValidFederationSubgraphs { subgraphs } = super::extract_subgraphs_from_supergraph(
2695 &FederationSchema::new(schema).unwrap(),
2696 Some(true),
2697 )
2698 .unwrap();
2699
2700 assert_eq!(subgraphs.len(), 2);
2701
2702 let a = subgraphs.get("a").unwrap();
2703 assert!(a.schema.schema().get_union("A").is_some());
2706 assert!(a.schema.schema().get_object("B").is_some());
2707 assert!(a.schema.schema().get_object("C").is_some());
2708 assert!(a.schema.schema().get_object("D").is_some());
2709
2710 let b = subgraphs.get("b").unwrap();
2711 assert!(b.schema.schema().get_union("A").is_none());
2712 assert!(b.schema.schema().get_object("B").is_none());
2713 assert!(b.schema.schema().get_object("C").is_none());
2714 assert!(b.schema.schema().get_object("D").is_some());
2715 }
2716
2717 #[test]
2718 fn preserves_default_values_of_input_object_fields() {
2719 let supergraph = r#"
2720 schema
2721 @link(url: "https://specs.apollo.dev/link/v1.0")
2722 @link(url: "https://specs.apollo.dev/join/v0.2", for: EXECUTION)
2723 {
2724 query: Query
2725 }
2726
2727 directive @join__field(graph: join__Graph!, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
2728
2729 directive @join__graph(name: String!, url: String!) on ENUM_VALUE
2730
2731 directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE
2732
2733 directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR
2734
2735 directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
2736
2737 input Input
2738 @join__type(graph: SERVICE)
2739 {
2740 a: Int! = 1234
2741 }
2742
2743 scalar join__FieldSet
2744
2745 enum join__Graph {
2746 SERVICE @join__graph(name: "service", url: "")
2747 }
2748
2749 scalar link__Import
2750
2751 enum link__Purpose {
2752 """
2753 `SECURITY` features provide metadata necessary to securely resolve fields.
2754 """
2755 SECURITY
2756
2757 """
2758 `EXECUTION` features provide metadata necessary for operation execution.
2759 """
2760 EXECUTION
2761 }
2762
2763 type Query
2764 @join__type(graph: SERVICE)
2765 {
2766 field(input: Input!): String
2767 }
2768 "#;
2769
2770 let schema = Schema::parse(supergraph, "supergraph.graphql").unwrap();
2771 let ValidFederationSubgraphs { subgraphs } = super::extract_subgraphs_from_supergraph(
2772 &FederationSchema::new(schema).unwrap(),
2773 Some(true),
2774 )
2775 .unwrap();
2776
2777 assert_eq!(subgraphs.len(), 1);
2778 let subgraph = subgraphs.get("service").unwrap();
2779 let input_type = subgraph.schema.schema().get_input_object("Input").unwrap();
2780 let input_field_a = input_type
2781 .fields
2782 .iter()
2783 .find(|(name, _)| name == &&name!("a"))
2784 .unwrap();
2785 assert_eq!(
2786 input_field_a.1.default_value.as_ref().unwrap().to_i32(),
2787 Some(1234)
2788 );
2789 }
2790
2791 #[test]
2798 fn types_that_are_empty_because_of_overridden_fields_are_erased() {
2799 let supergraph = r#"
2800 schema
2801 @link(url: "https://specs.apollo.dev/link/v1.0")
2802 @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION)
2803 @link(url: "https://specs.apollo.dev/tag/v0.3")
2804 {
2805 query: Query
2806 }
2807
2808 directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE
2809
2810 directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
2811
2812 directive @join__graph(name: String!, url: String!) on ENUM_VALUE
2813
2814 directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE
2815
2816 directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR
2817
2818 directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION
2819
2820 directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
2821
2822 directive @tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA
2823 input Input
2824 @join__type(graph: B)
2825 {
2826 a: Int! = 1234
2827 }
2828
2829 scalar join__FieldSet
2830
2831 enum join__Graph {
2832 A @join__graph(name: "a", url: "")
2833 B @join__graph(name: "b", url: "")
2834 }
2835
2836 scalar link__Import
2837
2838 enum link__Purpose {
2839 """
2840 `SECURITY` features provide metadata necessary to securely resolve fields.
2841 """
2842 SECURITY
2843
2844 """
2845 `EXECUTION` features provide metadata necessary for operation execution.
2846 """
2847 EXECUTION
2848 }
2849
2850 type Query
2851 @join__type(graph: A)
2852 {
2853 field: String
2854 }
2855
2856 type User
2857 @join__type(graph: A)
2858 @join__type(graph: B)
2859 {
2860 foo: String @join__field(graph: A, override: "b")
2861
2862 bar: String @join__field(graph: A)
2863
2864 baz: String @join__field(graph: A)
2865 }
2866 "#;
2867
2868 let schema = Schema::parse(supergraph, "supergraph.graphql").unwrap();
2869 let ValidFederationSubgraphs { subgraphs } = super::extract_subgraphs_from_supergraph(
2870 &FederationSchema::new(schema).unwrap(),
2871 Some(true),
2872 )
2873 .unwrap();
2874
2875 let subgraph = subgraphs.get("a").unwrap();
2876 let user_type = subgraph.schema.schema().get_object("User");
2877 assert!(user_type.is_some());
2878
2879 let subgraph = subgraphs.get("b").unwrap();
2880 let user_type = subgraph.schema.schema().get_object("User");
2881 assert!(user_type.is_none());
2882 }
2883
2884 #[test]
2885 fn test_join_directives() {
2886 let supergraph = r###"schema
2887 @link(url: "https://specs.apollo.dev/link/v1.0")
2888 @link(url: "https://specs.apollo.dev/join/v0.5", for: EXECUTION)
2889 @join__directive(graphs: [SUBGRAPH], name: "link", args: {url: "https://specs.apollo.dev/connect/v0.2", import: ["@connect"]})
2890 {
2891 query: Query
2892 }
2893
2894 directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION
2895
2896 directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE
2897
2898 directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean, overrideLabel: String, contextArguments: [join__ContextArgument!]) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
2899
2900 directive @join__graph(name: String!, url: String!) on ENUM_VALUE
2901
2902 directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE
2903
2904 directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR
2905
2906 directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION
2907
2908 directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
2909
2910 input join__ContextArgument {
2911 name: String!
2912 type: String!
2913 context: String!
2914 selection: join__FieldValue!
2915 }
2916
2917 scalar join__DirectiveArguments
2918
2919 scalar join__FieldSet
2920
2921 scalar join__FieldValue
2922
2923 enum join__Graph {
2924 SUBGRAPH @join__graph(name: "subgraph", url: "none")
2925 SUBGRAPH2 @join__graph(name: "subgraph2", url: "none")
2926 }
2927
2928 scalar link__Import
2929
2930 enum link__Purpose {
2931 """
2932 `SECURITY` features provide metadata necessary to securely resolve fields.
2933 """
2934 SECURITY
2935
2936 """
2937 `EXECUTION` features provide metadata necessary for operation execution.
2938 """
2939 EXECUTION
2940 }
2941
2942 type Query
2943 @join__type(graph: SUBGRAPH)
2944 @join__type(graph: SUBGRAPH2)
2945 {
2946 f: String
2947 @join__field(graph: SUBGRAPH)
2948 @join__directive(graphs: [SUBGRAPH], name: "connect", args: {http: {GET: "http://localhost/"}, selection: "$"})
2949 i: I
2950 @join__field(graph: SUBGRAPH2)
2951 }
2952
2953 type T
2954 @join__type(graph: SUBGRAPH)
2955 @join__directive(graphs: [SUBGRAPH], name: "connect", args: {http: {GET: "http://localhost/{$batch.id}"}, selection: "$"})
2956 {
2957 id: ID!
2958 f: String
2959 }
2960
2961 interface I
2962 @join__type(graph: SUBGRAPH2, key: "f")
2963 @join__type(graph: SUBGRAPH, isInterfaceObject: true)
2964 @join__directive(graphs: [SUBGRAPH], name: "connect", args: {http: {GET: "http://localhost/{$this.id}"}, selection: "f"})
2965 {
2966 f: String
2967 }
2968
2969 type A implements I
2970 @join__type(graph: SUBGRAPH2)
2971 {
2972 f: String
2973 }
2974
2975 type B implements I
2976 @join__type(graph: SUBGRAPH2)
2977 {
2978 f: String
2979 }
2980 "###;
2981
2982 let schema = Schema::parse(supergraph, "supergraph.graphql").unwrap();
2983 let ValidFederationSubgraphs { subgraphs } = super::extract_subgraphs_from_supergraph(
2984 &FederationSchema::new(schema).unwrap(),
2985 Some(true),
2986 )
2987 .unwrap();
2988
2989 let subgraph = subgraphs.get("subgraph").unwrap();
2990 assert_snapshot!(subgraph.schema.schema().schema_definition.directives, @r#" @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.12") @link(url: "https://specs.apollo.dev/connect/v0.2", import: ["@connect"])"#);
2991 assert_snapshot!(subgraph.schema.schema().type_field("Query", "f").unwrap().directives, @r#" @connect(http: {GET: "http://localhost/"}, selection: "$")"#);
2992 assert_snapshot!(subgraph.schema.schema().get_object("T").unwrap().directives, @r#" @connect(http: {GET: "http://localhost/{$batch.id}"}, selection: "$")"#);
2993 assert_snapshot!(subgraph.schema.schema().get_object("I").unwrap().directives, @r###" @federation__interfaceObject @connect(http: {GET: "http://localhost/{$this.id}"}, selection: "f")"###);
2994 }
2995}