Skip to main content

apollo_federation/supergraph/
mod.rs

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