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