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