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