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