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