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