Skip to main content

apollo_federation/supergraph/
mod.rs

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