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