apollo_federation/schema/
mod.rs

1use std::hash::Hash;
2use std::hash::Hasher;
3use std::ops::Deref;
4use std::ops::Range;
5use std::sync::Arc;
6
7use apollo_compiler::Name;
8use apollo_compiler::Node;
9use apollo_compiler::Schema;
10use apollo_compiler::ast::Directive;
11use apollo_compiler::ast::FieldDefinition;
12use apollo_compiler::ast::Value;
13use apollo_compiler::collections::IndexSet;
14use apollo_compiler::executable::FieldSet;
15use apollo_compiler::parser::LineColumn;
16use apollo_compiler::schema::ComponentOrigin;
17use apollo_compiler::schema::ExtendedType;
18use apollo_compiler::schema::ExtensionId;
19use apollo_compiler::schema::SchemaDefinition;
20use apollo_compiler::validation::Valid;
21use apollo_compiler::validation::WithErrors;
22use itertools::Itertools;
23use position::DirectiveTargetPosition;
24use position::FieldArgumentDefinitionPosition;
25use position::ObjectOrInterfaceTypeDefinitionPosition;
26use position::TagDirectiveTargetPosition;
27use referencer::Referencers;
28
29use crate::bail;
30use crate::error::FederationError;
31use crate::error::SingleFederationError;
32use crate::internal_error;
33use crate::link::Link;
34use crate::link::LinksMetadata;
35use crate::link::context_spec_definition::ContextSpecDefinition;
36use crate::link::cost_spec_definition;
37use crate::link::cost_spec_definition::CostSpecDefinition;
38use crate::link::federation_spec_definition::CacheTagDirectiveArguments;
39use crate::link::federation_spec_definition::ComposeDirectiveArguments;
40use crate::link::federation_spec_definition::ContextDirectiveArguments;
41use crate::link::federation_spec_definition::FEDERATION_ENTITY_TYPE_NAME_IN_SPEC;
42use crate::link::federation_spec_definition::FEDERATION_FIELDS_ARGUMENT_NAME;
43use crate::link::federation_spec_definition::FEDERATION_FIELDSET_TYPE_NAME_IN_SPEC;
44use crate::link::federation_spec_definition::FEDERATION_SERVICE_TYPE_NAME_IN_SPEC;
45use crate::link::federation_spec_definition::FederationSpecDefinition;
46use crate::link::federation_spec_definition::FromContextDirectiveArguments;
47use crate::link::federation_spec_definition::KeyDirectiveArguments;
48use crate::link::federation_spec_definition::ProvidesDirectiveArguments;
49use crate::link::federation_spec_definition::RequiresDirectiveArguments;
50use crate::link::federation_spec_definition::TagDirectiveArguments;
51use crate::link::federation_spec_definition::get_federation_spec_definition_from_subgraph;
52use crate::link::spec::Version;
53use crate::link::spec_definition::SPEC_REGISTRY;
54use crate::link::spec_definition::SpecDefinition;
55use crate::schema::position::CompositeTypeDefinitionPosition;
56use crate::schema::position::DirectiveDefinitionPosition;
57use crate::schema::position::EnumTypeDefinitionPosition;
58use crate::schema::position::InputObjectTypeDefinitionPosition;
59use crate::schema::position::InterfaceTypeDefinitionPosition;
60use crate::schema::position::ObjectOrInterfaceFieldDefinitionPosition;
61use crate::schema::position::ObjectTypeDefinitionPosition;
62use crate::schema::position::ScalarTypeDefinitionPosition;
63use crate::schema::position::TypeDefinitionPosition;
64use crate::schema::position::UnionTypeDefinitionPosition;
65use crate::schema::subgraph_metadata::SubgraphMetadata;
66
67pub(crate) mod argument_composition_strategies;
68pub(crate) mod blueprint;
69pub(crate) mod definitions;
70pub(crate) mod directive_location;
71pub(crate) mod field_set;
72pub(crate) mod locations;
73pub(crate) mod position;
74pub(crate) mod referencer;
75pub(crate) mod schema_upgrader;
76pub(crate) mod subgraph_metadata;
77pub(crate) mod validators;
78
79pub(crate) fn compute_subgraph_metadata(
80    schema: &FederationSchema,
81) -> Result<Option<SubgraphMetadata>, FederationError> {
82    Ok(
83        if let Ok(federation_spec_definition) = get_federation_spec_definition_from_subgraph(schema)
84        {
85            Some(SubgraphMetadata::new(schema, federation_spec_definition)?)
86        } else {
87            None
88        },
89    )
90}
91pub(crate) mod type_and_directive_specification;
92
93/// A GraphQL schema with federation data.
94#[derive(Clone, Debug)]
95pub struct FederationSchema {
96    schema: Schema,
97    referencers: Referencers,
98    links_metadata: Option<Box<LinksMetadata>>,
99    /// This is only populated for valid subgraphs, and can only be accessed if you have a
100    /// `ValidFederationSchema`.
101    subgraph_metadata: Option<Box<SubgraphMetadata>>,
102}
103
104impl FederationSchema {
105    pub(crate) fn schema(&self) -> &Schema {
106        &self.schema
107    }
108
109    /// Discard the Federation metadata and return the apollo-compiler schema.
110    pub fn into_inner(self) -> Schema {
111        self.schema
112    }
113
114    pub(crate) fn metadata(&self) -> Option<&LinksMetadata> {
115        self.links_metadata.as_deref()
116    }
117
118    pub(crate) fn referencers(&self) -> &Referencers {
119        &self.referencers
120    }
121
122    /// Returns all the types in the schema, minus builtins.
123    pub(crate) fn get_types(&self) -> impl Iterator<Item = TypeDefinitionPosition> {
124        self.schema
125            .types
126            .iter()
127            .filter(|(_, ty)| !ty.is_built_in())
128            .map(|(type_name, type_)| {
129                let type_name = type_name.clone();
130                match type_ {
131                    ExtendedType::Scalar(_) => ScalarTypeDefinitionPosition { type_name }.into(),
132                    ExtendedType::Object(_) => ObjectTypeDefinitionPosition { type_name }.into(),
133                    ExtendedType::Interface(_) => {
134                        InterfaceTypeDefinitionPosition { type_name }.into()
135                    }
136                    ExtendedType::Union(_) => UnionTypeDefinitionPosition { type_name }.into(),
137                    ExtendedType::Enum(_) => EnumTypeDefinitionPosition { type_name }.into(),
138                    ExtendedType::InputObject(_) => {
139                        InputObjectTypeDefinitionPosition { type_name }.into()
140                    }
141                }
142            })
143    }
144
145    pub(crate) fn get_directive_definitions(
146        &self,
147    ) -> impl Iterator<Item = DirectiveDefinitionPosition> {
148        self.schema
149            .directive_definitions
150            .keys()
151            .map(|name| DirectiveDefinitionPosition {
152                directive_name: name.clone(),
153            })
154    }
155
156    pub(crate) fn get_type(
157        &self,
158        type_name: Name,
159    ) -> Result<TypeDefinitionPosition, FederationError> {
160        let type_ =
161            self.schema
162                .types
163                .get(&type_name)
164                .ok_or_else(|| SingleFederationError::Internal {
165                    message: format!("Schema has no type \"{type_name}\""),
166                })?;
167        Ok(match type_ {
168            ExtendedType::Scalar(_) => ScalarTypeDefinitionPosition { type_name }.into(),
169            ExtendedType::Object(_) => ObjectTypeDefinitionPosition { type_name }.into(),
170            ExtendedType::Interface(_) => InterfaceTypeDefinitionPosition { type_name }.into(),
171            ExtendedType::Union(_) => UnionTypeDefinitionPosition { type_name }.into(),
172            ExtendedType::Enum(_) => EnumTypeDefinitionPosition { type_name }.into(),
173            ExtendedType::InputObject(_) => InputObjectTypeDefinitionPosition { type_name }.into(),
174        })
175    }
176
177    pub(crate) fn try_get_type(&self, type_name: Name) -> Option<TypeDefinitionPosition> {
178        self.get_type(type_name).ok()
179    }
180
181    pub(crate) fn is_root_type(&self, type_name: &Name) -> bool {
182        self.schema()
183            .schema_definition
184            .iter_root_operations()
185            .any(|op| *op.1 == *type_name)
186    }
187
188    pub(crate) fn is_subscription_root_type(&self, type_name: &Name) -> bool {
189        let subscription = &self.schema().schema_definition.subscription;
190        subscription.as_ref().is_some_and(|name| name == type_name)
191    }
192
193    /// Return the possible runtime types for a definition.
194    ///
195    /// For a union, the possible runtime types are its members.
196    /// For an interface, the possible runtime types are its implementers.
197    ///
198    /// Note this always allocates a set for the result. Avoid calling it frequently.
199    pub(crate) fn possible_runtime_types(
200        &self,
201        composite_type_definition_position: CompositeTypeDefinitionPosition,
202    ) -> Result<IndexSet<ObjectTypeDefinitionPosition>, FederationError> {
203        Ok(match composite_type_definition_position {
204            CompositeTypeDefinitionPosition::Object(pos) => IndexSet::from_iter([pos]),
205            CompositeTypeDefinitionPosition::Interface(pos) => self
206                .referencers()
207                .get_interface_type(&pos.type_name)?
208                .object_types
209                .clone(),
210            CompositeTypeDefinitionPosition::Union(pos) => pos
211                .get(self.schema())?
212                .members
213                .iter()
214                .map(|t| ObjectTypeDefinitionPosition {
215                    type_name: t.name.clone(),
216                })
217                .collect::<IndexSet<_>>(),
218        })
219    }
220
221    /// Return all implementing types (i.e. both object and interface) for an interface definition.
222    ///
223    /// Note this always allocates a set for the result. Avoid calling it frequently.
224    pub(crate) fn all_implementation_types(
225        &self,
226        interface_type_definition_position: &InterfaceTypeDefinitionPosition,
227    ) -> Result<IndexSet<ObjectOrInterfaceTypeDefinitionPosition>, FederationError> {
228        let referencers = self
229            .referencers()
230            .get_interface_type(&interface_type_definition_position.type_name)?;
231        Ok(referencers
232            .object_types
233            .iter()
234            .cloned()
235            .map(ObjectOrInterfaceTypeDefinitionPosition::from)
236            .chain(
237                referencers
238                    .interface_types
239                    .iter()
240                    .cloned()
241                    .map(ObjectOrInterfaceTypeDefinitionPosition::from),
242            )
243            .collect())
244    }
245
246    /// Similar to `Self::validate` but returns `self` as part of the error should it be needed by
247    /// the caller
248    #[allow(clippy::result_large_err)] // lint is accurate but this is not in a hot path
249    pub(crate) fn validate_or_return_self(
250        mut self,
251    ) -> Result<ValidFederationSchema, (Self, FederationError)> {
252        let schema = match self.schema.validate() {
253            Ok(schema) => schema.into_inner(),
254            Err(e) => {
255                self.schema = e.partial;
256                return Err((self, e.errors.into()));
257            }
258        };
259        ValidFederationSchema::new_assume_valid(FederationSchema { schema, ..self })
260    }
261
262    pub(crate) fn assume_valid(self) -> Result<ValidFederationSchema, FederationError> {
263        ValidFederationSchema::new_assume_valid(self).map_err(|(_schema, error)| error)
264    }
265
266    pub(crate) fn get_directive_definition(
267        &self,
268        name: &Name,
269    ) -> Option<DirectiveDefinitionPosition> {
270        self.schema
271            .directive_definitions
272            .contains_key(name)
273            .then(|| DirectiveDefinitionPosition {
274                directive_name: name.clone(),
275            })
276    }
277
278    /// Note that a subgraph may have no "entities" and so no `_Entity` type.
279    // PORT_NOTE: Corresponds to `FederationMetadata.entityType` in JS
280    pub(crate) fn entity_type(
281        &self,
282    ) -> Result<Option<UnionTypeDefinitionPosition>, FederationError> {
283        // Note that the _Entity type is special in that:
284        // 1. Spec renaming doesn't take place for it (there's no prefixing or importing needed),
285        //    in order to maintain backwards compatibility with Fed 1.
286        // 2. Its presence is optional; if absent, it means the subgraph has no resolvable keys.
287        match self.schema.types.get(&FEDERATION_ENTITY_TYPE_NAME_IN_SPEC) {
288            Some(ExtendedType::Union(_)) => Ok(Some(UnionTypeDefinitionPosition {
289                type_name: FEDERATION_ENTITY_TYPE_NAME_IN_SPEC,
290            })),
291            Some(_) => Err(FederationError::internal(format!(
292                "Unexpectedly found non-union for federation spec's `{FEDERATION_ENTITY_TYPE_NAME_IN_SPEC}` type definition"
293            ))),
294            None => Ok(None),
295        }
296    }
297
298    // PORT_NOTE: Corresponds to `FederationMetadata.serviceType` in JS
299    pub(crate) fn service_type(&self) -> Result<ObjectTypeDefinitionPosition, FederationError> {
300        // Note: `_Service` type name can't be renamed.
301        match self.schema.types.get(&FEDERATION_SERVICE_TYPE_NAME_IN_SPEC) {
302            Some(ExtendedType::Object(_)) => Ok(ObjectTypeDefinitionPosition {
303                type_name: FEDERATION_SERVICE_TYPE_NAME_IN_SPEC,
304            }),
305            Some(_) => bail!(
306                "Unexpected type found for federation spec's `{spec_name}` type definition",
307                spec_name = FEDERATION_SERVICE_TYPE_NAME_IN_SPEC,
308            ),
309            None => bail!(
310                "Unexpected: type not found for federation spec's `{spec_name}`",
311                spec_name = FEDERATION_SERVICE_TYPE_NAME_IN_SPEC,
312            ),
313        }
314    }
315
316    // PORT_NOTE: Corresponds to `FederationMetadata.isFed2Schema` in JS
317    // This works even if the schema bootstrapping was not completed.
318    pub(crate) fn is_fed_2(&self) -> bool {
319        self.federation_link()
320            .is_some_and(|link| link.url.version.satisfies(&Version { major: 2, minor: 0 }))
321    }
322
323    // PORT_NOTE: Corresponds to `FederationMetadata.federationFeature` in JS
324    fn federation_link(&self) -> Option<&Arc<Link>> {
325        self.metadata().and_then(|metadata| {
326            metadata
327                .by_identity
328                .get(FederationSpecDefinition::latest().identity())
329        })
330    }
331
332    // PORT_NOTE: Corresponds to `FederationMetadata.fieldSetType` in JS.
333    pub(crate) fn field_set_type(&self) -> Result<ScalarTypeDefinitionPosition, FederationError> {
334        let name_in_schema =
335            self.federation_type_name_in_schema(FEDERATION_FIELDSET_TYPE_NAME_IN_SPEC)?;
336        match self.schema.types.get(&name_in_schema) {
337            Some(ExtendedType::Scalar(_)) => Ok(ScalarTypeDefinitionPosition {
338                type_name: name_in_schema,
339            }),
340            Some(_) => bail!(
341                "Unexpected type found for federation spec's `{name_in_schema}` type definition"
342            ),
343            None => {
344                bail!("Unexpected: type not found for federation spec's `{name_in_schema}`")
345            }
346        }
347    }
348
349    // PORT_NOTE: Corresponds to `FederationMetadata.federationTypeNameInSchema` in JS.
350    // Note: Unfortunately, this overlaps with `ValidFederationSchema`'s
351    //       `federation_type_name_in_schema` method. This method was added because it's used
352    //       during composition before `ValidFederationSchema` is created.
353    pub(crate) fn federation_type_name_in_schema(
354        &self,
355        name: Name,
356    ) -> Result<Name, FederationError> {
357        // Currently, the types used to define the federation operations, that is _Any, _Entity and
358        // _Service, are not considered part of the federation spec, and are instead hardcoded to
359        // the names above. The reason being that there is no way to maintain backward
360        // compatibility with fed2 if we were to add those to the federation spec without requiring
361        // users to add those types to their @link `import`, and that wouldn't be a good user
362        // experience (because most users don't really know what those types are/do). And so we
363        // special case it.
364        if name.starts_with('_') {
365            return Ok(name);
366        }
367
368        if self.is_fed_2() {
369            let Some(links) = self.metadata() else {
370                bail!("Schema should be a core schema")
371            };
372            let Some(federation_link) = links
373                .by_identity
374                .get(FederationSpecDefinition::latest().identity())
375            else {
376                bail!("Schema should have the latest federation link")
377            };
378            Ok(federation_link.type_name_in_schema(&name))
379        } else {
380            // The only type here so far is the the `FieldSet` one. And in fed1, it's called `_FieldSet`, so ...
381            Name::new(&format!("_{name}"))
382                .map_err(|e| internal_error!("Invalid name `_{name}`: {e}"))
383        }
384    }
385
386    pub(crate) fn compose_directive_applications(
387        &self,
388    ) -> FallibleDirectiveIterator<ComposeDirectiveDirective<'_>> {
389        let federation_spec = get_federation_spec_definition_from_subgraph(self)?;
390        let compose_directive_definition = federation_spec.compose_directive_definition(self)?;
391        let directives = self
392            .schema()
393            .schema_definition
394            .directives
395            .get_all(&compose_directive_definition.name)
396            .map(|d| {
397                let arguments = federation_spec.compose_directive_arguments(d);
398                arguments.map(|args| ComposeDirectiveDirective { arguments: args })
399            })
400            .collect();
401        Ok(directives)
402    }
403
404    /// For subgraph schemas where the `@context` directive is a federation spec directive.
405    pub(crate) fn context_directive_applications(
406        &self,
407    ) -> FallibleDirectiveIterator<ContextDirective<'_>> {
408        let federation_spec = get_federation_spec_definition_from_subgraph(self)?;
409        let context_directive_definition = federation_spec.context_directive_definition(self)?;
410        let context_directive_referencers = self
411            .referencers()
412            .get_directive(&context_directive_definition.name)?;
413
414        let mut applications = Vec::new();
415        for interface_type_position in &context_directive_referencers.interface_types {
416            match interface_type_position.get(self.schema()) {
417                Ok(interface_type) => {
418                    let directives = &interface_type.directives;
419                    for directive in directives.get_all(&context_directive_definition.name) {
420                        let arguments = federation_spec.context_directive_arguments(directive);
421                        applications.push(arguments.map(|args| ContextDirective {
422                            arguments: args,
423                            target: interface_type_position.clone().into(),
424                        }));
425                    }
426                }
427                Err(error) => applications.push(Err(error.into())),
428            }
429        }
430        for object_type_position in &context_directive_referencers.object_types {
431            match object_type_position.get(self.schema()) {
432                Ok(object_type) => {
433                    let directives = &object_type.directives;
434                    for directive in directives.get_all(&context_directive_definition.name) {
435                        let arguments = federation_spec.context_directive_arguments(directive);
436                        applications.push(arguments.map(|args| ContextDirective {
437                            arguments: args,
438                            target: object_type_position.clone().into(),
439                        }));
440                    }
441                }
442                Err(error) => applications.push(Err(error.into())),
443            }
444        }
445        for union_type_position in &context_directive_referencers.union_types {
446            match union_type_position.get(self.schema()) {
447                Ok(union_type) => {
448                    let directives = &union_type.directives;
449                    for directive in directives.get_all(&context_directive_definition.name) {
450                        let arguments = federation_spec.context_directive_arguments(directive);
451                        applications.push(arguments.map(|args| ContextDirective {
452                            arguments: args,
453                            target: union_type_position.clone().into(),
454                        }));
455                    }
456                }
457                Err(error) => applications.push(Err(error.into())),
458            }
459        }
460        Ok(applications)
461    }
462
463    /// For supergraph schemas where the `@context` directive is a "context" spec directive.
464    pub(crate) fn context_directive_applications_in_supergraph(
465        &self,
466        context_spec: &ContextSpecDefinition,
467    ) -> FallibleDirectiveIterator<ContextDirective<'_>> {
468        let context_directive_definition = context_spec.context_directive_definition(self)?;
469        let context_directive_referencers = self
470            .referencers()
471            .get_directive(&context_directive_definition.name)?;
472        let mut applications = Vec::new();
473        for type_pos in context_directive_referencers.composite_type_positions() {
474            let directive_apps =
475                type_pos.get_applied_directives(self, &context_directive_definition.name);
476            for app in directive_apps {
477                let arguments = context_spec.context_directive_arguments(app);
478                applications.push(arguments.map(|args| ContextDirective {
479                    // Note: `ContextDirectiveArguments` is also defined in `context_spec_definition` module.
480                    //       So, it is converted to the one defined in this module.
481                    arguments: ContextDirectiveArguments { name: args.name },
482                    target: type_pos.clone(),
483                }));
484            }
485        }
486        Ok(applications)
487    }
488
489    #[allow(clippy::wrong_self_convention)]
490    pub(crate) fn from_context_directive_applications(
491        &self,
492    ) -> FallibleDirectiveIterator<FromContextDirective<'_>> {
493        let federation_spec = get_federation_spec_definition_from_subgraph(self)?;
494        let from_context_directive_definition =
495            federation_spec.from_context_directive_definition(self)?;
496        let from_context_directive_referencers = self
497            .referencers()
498            .get_directive(&from_context_directive_definition.name)?;
499
500        let mut applications = Vec::new();
501
502        // Check for @fromContext on directive definition arguments (not allowed)
503        for directive_argument_position in &from_context_directive_referencers.directive_arguments {
504            applications.push(Err(SingleFederationError::ContextNotSet {
505                message: format!(
506                    "@fromContext argument cannot be used on a directive definition argument \"{}\".",
507                    directive_argument_position
508                ),
509            }
510            .into()));
511        }
512        for interface_field_argument_position in
513            &from_context_directive_referencers.interface_field_arguments
514        {
515            match interface_field_argument_position.get(self.schema()) {
516                Ok(interface_field_argument) => {
517                    let directives = &interface_field_argument.directives;
518                    for directive in directives.get_all(&from_context_directive_definition.name) {
519                        let arguments = federation_spec.from_context_directive_arguments(directive);
520                        applications.push(arguments.map(|args| FromContextDirective {
521                            arguments: args,
522                            target: interface_field_argument_position.clone().into(),
523                        }));
524                    }
525                }
526                Err(error) => applications.push(Err(error.into())),
527            }
528        }
529        for object_field_argument_position in
530            &from_context_directive_referencers.object_field_arguments
531        {
532            match object_field_argument_position.get(self.schema()) {
533                Ok(object_field_argument) => {
534                    let directives = &object_field_argument.directives;
535                    for directive in directives.get_all(&from_context_directive_definition.name) {
536                        let arguments = federation_spec.from_context_directive_arguments(directive);
537                        applications.push(arguments.map(|args| FromContextDirective {
538                            arguments: args,
539                            target: object_field_argument_position.clone().into(),
540                        }));
541                    }
542                }
543                Err(error) => applications.push(Err(error.into())),
544            }
545        }
546        Ok(applications)
547    }
548
549    pub(crate) fn key_directive_applications(&self) -> FallibleDirectiveIterator<KeyDirective<'_>> {
550        let federation_spec = get_federation_spec_definition_from_subgraph(self)?;
551        let key_directive_definition = federation_spec.key_directive_definition(self)?;
552        let key_directive_referencers = self
553            .referencers()
554            .get_directive(&key_directive_definition.name)?;
555
556        let mut applications: Vec<Result<KeyDirective, FederationError>> = Vec::new();
557        for object_type_position in &key_directive_referencers.object_types {
558            match object_type_position.get(self.schema()) {
559                Ok(object_type) => {
560                    let directives = &object_type.directives;
561                    for directive in directives.get_all(&key_directive_definition.name) {
562                        if !matches!(
563                            directive
564                                .argument_by_name(&FEDERATION_FIELDS_ARGUMENT_NAME, self.schema())
565                                .map(|arg| arg.as_ref()),
566                            Ok(Value::String(_)),
567                        ) {
568                            // Not ideal, but the call to `federation_spec.key_directive_arguments` below will return an internal error
569                            // when this isn't the right type. We preempt that here to provide a better error to the user during validation.
570                            applications.push(Err(SingleFederationError::KeyInvalidFieldsType {
571                                target_type: object_type_position.type_name.clone(),
572                                application: directive.to_string(),
573                            }
574                            .into()))
575                        } else {
576                            let arguments = federation_spec.key_directive_arguments(directive);
577                            applications.push(arguments.map(|args| KeyDirective {
578                                arguments: args,
579                                schema_directive: directive,
580                                sibling_directives: directives,
581                                target: object_type_position.clone().into(),
582                            }));
583                        }
584                    }
585                }
586                Err(error) => applications.push(Err(error.into())),
587            }
588        }
589        for interface_type_position in &key_directive_referencers.interface_types {
590            match interface_type_position.get(self.schema()) {
591                Ok(interface_type) => {
592                    let directives = &interface_type.directives;
593                    for directive in directives.get_all(&key_directive_definition.name) {
594                        let arguments = federation_spec.key_directive_arguments(directive);
595                        applications.push(arguments.map(|args| KeyDirective {
596                            arguments: args,
597                            schema_directive: directive,
598                            sibling_directives: directives,
599                            target: interface_type_position.clone().into(),
600                        }));
601                    }
602                }
603                Err(error) => applications.push(Err(error.into())),
604            }
605        }
606        Ok(applications)
607    }
608
609    pub(crate) fn provides_directive_applications(
610        &self,
611    ) -> FallibleDirectiveIterator<ProvidesDirective<'_>> {
612        let federation_spec = get_federation_spec_definition_from_subgraph(self)?;
613        let provides_directive_definition = federation_spec.provides_directive_definition(self)?;
614        let provides_directive_referencers = self
615            .referencers()
616            .get_directive(&provides_directive_definition.name)?;
617
618        let mut applications: Vec<Result<ProvidesDirective, FederationError>> = Vec::new();
619        for field_definition_position in provides_directive_referencers.object_or_interface_fields()
620        {
621            match field_definition_position.get(self.schema()) {
622                Ok(field_definition) => {
623                    let directives = &field_definition.directives;
624                    for provides_directive_application in
625                        directives.get_all(&provides_directive_definition.name)
626                    {
627                        if !matches!(
628                            provides_directive_application
629                                .argument_by_name(&FEDERATION_FIELDS_ARGUMENT_NAME, self.schema())
630                                .map(|arg| arg.as_ref()),
631                            Ok(Value::String(_)),
632                        ) {
633                            // Not ideal, but the call to `federation_spec.provides_directive_arguments` below will return an internal error
634                            // when this isn't the right type. We preempt that here to provide a better error to the user during validation.
635                            applications.push(Err(
636                                SingleFederationError::ProvidesInvalidFieldsType {
637                                    coordinate: field_definition_position.coordinate(),
638                                    application: provides_directive_application.to_string(),
639                                }
640                                .into(),
641                            ))
642                        } else {
643                            let arguments = federation_spec
644                                .provides_directive_arguments(provides_directive_application);
645                            applications.push(arguments.map(|args| ProvidesDirective {
646                                arguments: args,
647                                schema_directive: provides_directive_application,
648                                target: field_definition_position.clone(),
649                                target_return_type: field_definition.ty.inner_named_type(),
650                            }));
651                        }
652                    }
653                }
654                Err(error) => applications.push(Err(error.into())),
655            }
656        }
657        Ok(applications)
658    }
659
660    pub(crate) fn requires_directive_applications(
661        &self,
662    ) -> FallibleDirectiveIterator<RequiresDirective<'_>> {
663        let federation_spec = get_federation_spec_definition_from_subgraph(self)?;
664        let requires_directive_definition = federation_spec.requires_directive_definition(self)?;
665        let requires_directive_referencers = self
666            .referencers()
667            .get_directive(&requires_directive_definition.name)?;
668
669        let mut applications = Vec::new();
670        for field_definition_position in requires_directive_referencers.object_or_interface_fields()
671        {
672            match field_definition_position.get(self.schema()) {
673                Ok(field_definition) => {
674                    let directives = &field_definition.directives;
675                    for requires_directive_application in
676                        directives.get_all(&requires_directive_definition.name)
677                    {
678                        if !matches!(
679                            requires_directive_application
680                                .argument_by_name(&FEDERATION_FIELDS_ARGUMENT_NAME, self.schema())
681                                .map(|arg| arg.as_ref()),
682                            Ok(Value::String(_)),
683                        ) {
684                            // Not ideal, but the call to `federation_spec.requires_directive_arguments` below will return an internal error
685                            // when this isn't the right type. We preempt that here to provide a better error to the user during validation.
686                            applications.push(Err(
687                                SingleFederationError::RequiresInvalidFieldsType {
688                                    coordinate: field_definition_position.coordinate(),
689                                    application: requires_directive_application.to_string(),
690                                }
691                                .into(),
692                            ))
693                        } else {
694                            let arguments = federation_spec
695                                .requires_directive_arguments(requires_directive_application);
696                            applications.push(arguments.map(|args| RequiresDirective {
697                                arguments: args,
698                                schema_directive: requires_directive_application,
699                                target: field_definition_position.clone(),
700                            }));
701                        }
702                    }
703                }
704                Err(error) => applications.push(Err(error.into())),
705            }
706        }
707        Ok(applications)
708    }
709
710    pub(crate) fn tag_directive_applications(&self) -> FallibleDirectiveIterator<TagDirective<'_>> {
711        let federation_spec = get_federation_spec_definition_from_subgraph(self)?;
712        let tag_directive_definition = federation_spec.tag_directive_definition(self)?;
713        let tag_directive_referencers = self
714            .referencers()
715            .get_directive(&tag_directive_definition.name)?;
716
717        let mut applications = Vec::new();
718        // Schema
719        if let Some(schema_position) = &tag_directive_referencers.schema {
720            let schema_def = schema_position.get(self.schema());
721            let directives = &schema_def.directives;
722            for tag_directive_application in directives.get_all(&tag_directive_definition.name) {
723                let arguments = federation_spec.tag_directive_arguments(tag_directive_application);
724                applications.push(arguments.map(|args| TagDirective {
725                    arguments: args,
726                    target: TagDirectiveTargetPosition::Schema(schema_position.clone()),
727                    directive: tag_directive_application,
728                }));
729            }
730        }
731        // Interface types
732        for interface_type_position in &tag_directive_referencers.interface_types {
733            match interface_type_position.get(self.schema()) {
734                Ok(interface_type) => {
735                    let directives = &interface_type.directives;
736                    for tag_directive_application in
737                        directives.get_all(&tag_directive_definition.name)
738                    {
739                        let arguments =
740                            federation_spec.tag_directive_arguments(tag_directive_application);
741                        applications.push(arguments.map(|args| TagDirective {
742                            arguments: args,
743                            target: TagDirectiveTargetPosition::Interface(
744                                interface_type_position.clone(),
745                            ),
746                            directive: tag_directive_application,
747                        }));
748                    }
749                }
750                Err(error) => applications.push(Err(error.into())),
751            }
752        }
753        // Interface fields
754        for field_definition_position in &tag_directive_referencers.interface_fields {
755            match field_definition_position.get(self.schema()) {
756                Ok(field_definition) => {
757                    let directives = &field_definition.directives;
758                    for tag_directive_application in
759                        directives.get_all(&tag_directive_definition.name)
760                    {
761                        let arguments =
762                            federation_spec.tag_directive_arguments(tag_directive_application);
763                        applications.push(arguments.map(|args| TagDirective {
764                            arguments: args,
765                            target: TagDirectiveTargetPosition::InterfaceField(
766                                field_definition_position.clone(),
767                            ),
768                            directive: tag_directive_application,
769                        }));
770                    }
771                }
772                Err(error) => applications.push(Err(error.into())),
773            }
774        }
775        // Interface field arguments
776        for argument_definition_position in &tag_directive_referencers.interface_field_arguments {
777            match argument_definition_position.get(self.schema()) {
778                Ok(argument_definition) => {
779                    let directives = &argument_definition.directives;
780                    for tag_directive_application in
781                        directives.get_all(&tag_directive_definition.name)
782                    {
783                        let arguments =
784                            federation_spec.tag_directive_arguments(tag_directive_application);
785                        applications.push(arguments.map(|args| TagDirective {
786                            arguments: args,
787                            target: TagDirectiveTargetPosition::ArgumentDefinition(
788                                argument_definition_position.clone().into(),
789                            ),
790                            directive: tag_directive_application,
791                        }));
792                    }
793                }
794                Err(error) => applications.push(Err(error.into())),
795            }
796        }
797        // Object types
798        for object_type_position in &tag_directive_referencers.object_types {
799            match object_type_position.get(self.schema()) {
800                Ok(object_type) => {
801                    let directives = &object_type.directives;
802                    for tag_directive_application in
803                        directives.get_all(&tag_directive_definition.name)
804                    {
805                        let arguments =
806                            federation_spec.tag_directive_arguments(tag_directive_application);
807                        applications.push(arguments.map(|args| TagDirective {
808                            arguments: args,
809                            target: TagDirectiveTargetPosition::Object(
810                                object_type_position.clone(),
811                            ),
812                            directive: tag_directive_application,
813                        }));
814                    }
815                }
816                Err(error) => applications.push(Err(error.into())),
817            }
818        }
819        // Object fields
820        for field_definition_position in &tag_directive_referencers.object_fields {
821            match field_definition_position.get(self.schema()) {
822                Ok(field_definition) => {
823                    let directives = &field_definition.directives;
824                    for tag_directive_application in
825                        directives.get_all(&tag_directive_definition.name)
826                    {
827                        let arguments =
828                            federation_spec.tag_directive_arguments(tag_directive_application);
829                        applications.push(arguments.map(|args| TagDirective {
830                            arguments: args,
831                            target: TagDirectiveTargetPosition::ObjectField(
832                                field_definition_position.clone(),
833                            ),
834                            directive: tag_directive_application,
835                        }));
836                    }
837                }
838                Err(error) => applications.push(Err(error.into())),
839            }
840        }
841        // Object field arguments
842        for argument_definition_position in &tag_directive_referencers.object_field_arguments {
843            match argument_definition_position.get(self.schema()) {
844                Ok(argument_definition) => {
845                    let directives = &argument_definition.directives;
846                    for tag_directive_application in
847                        directives.get_all(&tag_directive_definition.name)
848                    {
849                        let arguments =
850                            federation_spec.tag_directive_arguments(tag_directive_application);
851                        applications.push(arguments.map(|args| TagDirective {
852                            arguments: args,
853                            target: TagDirectiveTargetPosition::ArgumentDefinition(
854                                argument_definition_position.clone().into(),
855                            ),
856                            directive: tag_directive_application,
857                        }));
858                    }
859                }
860                Err(error) => applications.push(Err(error.into())),
861            }
862        }
863        // Union types
864        for union_type_position in &tag_directive_referencers.union_types {
865            match union_type_position.get(self.schema()) {
866                Ok(union_type) => {
867                    let directives = &union_type.directives;
868                    for tag_directive_application in
869                        directives.get_all(&tag_directive_definition.name)
870                    {
871                        let arguments =
872                            federation_spec.tag_directive_arguments(tag_directive_application);
873                        applications.push(arguments.map(|args| TagDirective {
874                            arguments: args,
875                            target: TagDirectiveTargetPosition::Union(union_type_position.clone()),
876                            directive: tag_directive_application,
877                        }));
878                    }
879                }
880                Err(error) => applications.push(Err(error.into())),
881            }
882        }
883
884        // Scalar types
885        for scalar_type_position in &tag_directive_referencers.scalar_types {
886            match scalar_type_position.get(self.schema()) {
887                Ok(scalar_type) => {
888                    let directives = &scalar_type.directives;
889                    for tag_directive_application in
890                        directives.get_all(&tag_directive_definition.name)
891                    {
892                        let arguments =
893                            federation_spec.tag_directive_arguments(tag_directive_application);
894                        applications.push(arguments.map(|args| TagDirective {
895                            arguments: args,
896                            target: TagDirectiveTargetPosition::Scalar(
897                                scalar_type_position.clone(),
898                            ),
899                            directive: tag_directive_application,
900                        }));
901                    }
902                }
903                Err(error) => applications.push(Err(error.into())),
904            }
905        }
906        // Enum types
907        for enum_type_position in &tag_directive_referencers.enum_types {
908            match enum_type_position.get(self.schema()) {
909                Ok(enum_type) => {
910                    let directives = &enum_type.directives;
911                    for tag_directive_application in
912                        directives.get_all(&tag_directive_definition.name)
913                    {
914                        let arguments =
915                            federation_spec.tag_directive_arguments(tag_directive_application);
916                        applications.push(arguments.map(|args| TagDirective {
917                            arguments: args,
918                            target: TagDirectiveTargetPosition::Enum(enum_type_position.clone()),
919                            directive: tag_directive_application,
920                        }));
921                    }
922                }
923                Err(error) => applications.push(Err(error.into())),
924            }
925        }
926        // Enum values
927        for enum_value_position in &tag_directive_referencers.enum_values {
928            match enum_value_position.get(self.schema()) {
929                Ok(enum_value) => {
930                    let directives = &enum_value.directives;
931                    for tag_directive_application in
932                        directives.get_all(&tag_directive_definition.name)
933                    {
934                        let arguments =
935                            federation_spec.tag_directive_arguments(tag_directive_application);
936                        applications.push(arguments.map(|args| TagDirective {
937                            arguments: args,
938                            target: TagDirectiveTargetPosition::EnumValue(
939                                enum_value_position.clone(),
940                            ),
941                            directive: tag_directive_application,
942                        }));
943                    }
944                }
945                Err(error) => applications.push(Err(error.into())),
946            }
947        }
948        // Input object types
949        for input_object_type_position in &tag_directive_referencers.input_object_types {
950            match input_object_type_position.get(self.schema()) {
951                Ok(input_object_type) => {
952                    let directives = &input_object_type.directives;
953                    for tag_directive_application in
954                        directives.get_all(&tag_directive_definition.name)
955                    {
956                        let arguments =
957                            federation_spec.tag_directive_arguments(tag_directive_application);
958                        applications.push(arguments.map(|args| TagDirective {
959                            arguments: args,
960                            target: TagDirectiveTargetPosition::InputObject(
961                                input_object_type_position.clone(),
962                            ),
963                            directive: tag_directive_application,
964                        }));
965                    }
966                }
967                Err(error) => applications.push(Err(error.into())),
968            }
969        }
970        // Input field definitions
971        for input_field_definition_position in &tag_directive_referencers.input_object_fields {
972            match input_field_definition_position.get(self.schema()) {
973                Ok(input_field_definition) => {
974                    let directives = &input_field_definition.directives;
975                    for tag_directive_application in
976                        directives.get_all(&tag_directive_definition.name)
977                    {
978                        let arguments =
979                            federation_spec.tag_directive_arguments(tag_directive_application);
980                        applications.push(arguments.map(|args| TagDirective {
981                            arguments: args,
982                            target: TagDirectiveTargetPosition::InputObjectFieldDefinition(
983                                input_field_definition_position.clone(),
984                            ),
985                            directive: tag_directive_application,
986                        }));
987                    }
988                }
989                Err(error) => applications.push(Err(error.into())),
990            }
991        }
992        // Directive definition arguments
993        for directive_definition_position in &tag_directive_referencers.directive_arguments {
994            match directive_definition_position.get(self.schema()) {
995                Ok(directive_definition) => {
996                    let directives = &directive_definition.directives;
997                    for tag_directive_application in
998                        directives.get_all(&tag_directive_definition.name)
999                    {
1000                        let arguments =
1001                            federation_spec.tag_directive_arguments(tag_directive_application);
1002                        applications.push(arguments.map(|args| TagDirective {
1003                            arguments: args,
1004                            target: TagDirectiveTargetPosition::DirectiveArgumentDefinition(
1005                                directive_definition_position.clone(),
1006                            ),
1007                            directive: tag_directive_application,
1008                        }));
1009                    }
1010                }
1011                Err(error) => applications.push(Err(error.into())),
1012            }
1013        }
1014
1015        Ok(applications)
1016    }
1017
1018    pub(crate) fn list_size_directive_applications(
1019        &self,
1020    ) -> FallibleDirectiveIterator<ListSizeDirective<'_>> {
1021        let Some(list_size_directive_name) = CostSpecDefinition::list_size_directive_name(self)?
1022        else {
1023            return Ok(Vec::new());
1024        };
1025        let Ok(list_size_directive_referencers) = self
1026            .referencers()
1027            .get_directive(list_size_directive_name.as_str())
1028        else {
1029            return Ok(Vec::new());
1030        };
1031
1032        let mut applications = Vec::new();
1033        for field_definition_position in
1034            list_size_directive_referencers.object_or_interface_fields()
1035        {
1036            let field_definition = field_definition_position.get(self.schema())?;
1037            match CostSpecDefinition::list_size_directive_from_field_definition(
1038                self,
1039                field_definition,
1040            ) {
1041                Ok(Some(list_size_directive)) => {
1042                    applications.push(Ok(ListSizeDirective {
1043                        directive: list_size_directive,
1044                        parent_type: field_definition_position.type_name().clone(),
1045                        target: field_definition,
1046                    }));
1047                }
1048                Ok(None) => {
1049                    // No listSize directive found, continue
1050                }
1051                Err(error) => {
1052                    applications.push(Err(error));
1053                }
1054            }
1055        }
1056
1057        Ok(applications)
1058    }
1059
1060    pub(crate) fn cache_tag_directive_applications(
1061        &self,
1062    ) -> FallibleDirectiveIterator<CacheTagDirective<'_>> {
1063        let federation_spec = get_federation_spec_definition_from_subgraph(self)?;
1064        let Ok(cache_tag_directive_definition) =
1065            federation_spec.cache_tag_directive_definition(self)
1066        else {
1067            return Ok(Vec::new());
1068        };
1069
1070        let result = self
1071            .referencers()
1072            .get_directive_applications(self, &cache_tag_directive_definition.name)?
1073            .map(|(pos, application)| {
1074                let arguments = federation_spec.cache_tag_directive_arguments(application);
1075                arguments.map(|args| CacheTagDirective {
1076                    arguments: args,
1077                    target: pos,
1078                })
1079            })
1080            .collect();
1081        Ok(result)
1082    }
1083
1084    pub(crate) fn is_interface(&self, type_name: &Name) -> bool {
1085        self.referencers().interface_types.contains_key(type_name)
1086    }
1087
1088    pub(crate) fn all_features(&self) -> Result<Vec<&'static dyn SpecDefinition>, FederationError> {
1089        let Some(links) = self.metadata() else {
1090            return Ok(Vec::new());
1091        };
1092
1093        let mut features: Vec<&'static dyn SpecDefinition> =
1094            Vec::with_capacity(links.all_links().len());
1095
1096        for link in links.all_links() {
1097            if let Some(spec) = SPEC_REGISTRY.get_definition(&link.url) {
1098                features.push(*spec);
1099            } else if let Some(supported_versions) = SPEC_REGISTRY.get_versions(&link.url.identity)
1100            {
1101                return Err(
1102        SingleFederationError::UnknownLinkVersion {
1103            message: format!(
1104                "Detected unsupported {} specification version {}. Please upgrade to a composition version which supports that version, or select one of the following supported versions: {}.",
1105                link.url.identity.name,
1106                link.url.version,
1107                supported_versions.iter().join(", ")
1108            ),
1109        }.into());
1110            }
1111        }
1112
1113        Ok(features)
1114    }
1115
1116    pub(crate) fn node_locations<T>(
1117        &self,
1118        node: &Node<T>,
1119    ) -> impl Iterator<Item = Range<LineColumn>> {
1120        node.line_column_range(&self.schema().sources).into_iter()
1121    }
1122}
1123
1124type FallibleDirectiveIterator<D> = Result<Vec<Result<D, FederationError>>, FederationError>;
1125
1126#[derive(Clone)]
1127pub(crate) struct ComposeDirectiveDirective<'schema> {
1128    /// The parsed arguments of this `@composeDirective` application
1129    pub(crate) arguments: ComposeDirectiveArguments<'schema>,
1130}
1131
1132pub(crate) struct ContextDirective<'schema> {
1133    /// The parsed arguments of this `@context` application
1134    arguments: ContextDirectiveArguments<'schema>,
1135    /// The schema position to which this directive is applied
1136    target: CompositeTypeDefinitionPosition,
1137}
1138
1139impl ContextDirective<'_> {
1140    pub(crate) fn arguments(&self) -> &ContextDirectiveArguments<'_> {
1141        &self.arguments
1142    }
1143
1144    pub(crate) fn target(&self) -> &CompositeTypeDefinitionPosition {
1145        &self.target
1146    }
1147}
1148
1149pub(crate) struct FromContextDirective<'schema> {
1150    /// The parsed arguments of this `@fromContext` application
1151    arguments: FromContextDirectiveArguments<'schema>,
1152    /// The schema position to which this directive is applied
1153    target: FieldArgumentDefinitionPosition,
1154}
1155
1156pub(crate) struct KeyDirective<'schema> {
1157    /// The parsed arguments of this `@key` application
1158    arguments: KeyDirectiveArguments<'schema>,
1159    /// The original `Directive` instance from the AST with unparsed arguments
1160    schema_directive: &'schema apollo_compiler::schema::Component<Directive>,
1161    /// The `DirectiveList` containing all directives applied to the target position, including this one
1162    sibling_directives: &'schema apollo_compiler::schema::DirectiveList,
1163    /// The schema position to which this directive is applied
1164    target: ObjectOrInterfaceTypeDefinitionPosition,
1165}
1166
1167impl HasFields for KeyDirective<'_> {
1168    fn fields(&self) -> &str {
1169        self.arguments.fields
1170    }
1171
1172    fn target_type(&self) -> &Name {
1173        self.target.type_name()
1174    }
1175}
1176
1177impl KeyDirective<'_> {
1178    pub(crate) fn target(&self) -> &ObjectOrInterfaceTypeDefinitionPosition {
1179        &self.target
1180    }
1181}
1182
1183pub(crate) struct ListSizeDirective<'schema> {
1184    /// The parsed directive
1185    directive: cost_spec_definition::ListSizeDirective,
1186    /// The parent type of `target`
1187    parent_type: Name,
1188    /// The schema position to which this directive is applied
1189    target: &'schema FieldDefinition,
1190}
1191
1192pub(crate) struct ProvidesDirective<'schema> {
1193    /// The parsed arguments of this `@provides` application
1194    arguments: ProvidesDirectiveArguments<'schema>,
1195    /// The original `Directive` instance from the AST with unparsed arguments
1196    schema_directive: &'schema Node<Directive>,
1197    /// The schema position to which this directive is applied
1198    /// - Although the directive is not allowed on interfaces, we still need to collect them
1199    ///   for validation purposes.
1200    target: ObjectOrInterfaceFieldDefinitionPosition,
1201    /// The return type of the target field
1202    target_return_type: &'schema Name,
1203}
1204
1205impl HasFields for ProvidesDirective<'_> {
1206    /// The string representation of the field set
1207    fn fields(&self) -> &str {
1208        self.arguments.fields
1209    }
1210
1211    /// The type from which the field set selects
1212    fn target_type(&self) -> &Name {
1213        self.target_return_type
1214    }
1215}
1216
1217pub(crate) struct RequiresDirective<'schema> {
1218    /// The parsed arguments of this `@requires` application
1219    arguments: RequiresDirectiveArguments<'schema>,
1220    /// The original `Directive` instance from the AST with unparsed arguments
1221    schema_directive: &'schema Node<Directive>,
1222    /// The schema position to which this directive is applied
1223    /// - Although the directive is not allowed on interfaces, we still need to collect them
1224    ///   for validation purposes.
1225    target: ObjectOrInterfaceFieldDefinitionPosition,
1226}
1227
1228impl HasFields for RequiresDirective<'_> {
1229    fn fields(&self) -> &str {
1230        self.arguments.fields
1231    }
1232
1233    fn target_type(&self) -> &Name {
1234        self.target.type_name()
1235    }
1236}
1237
1238pub(crate) struct TagDirective<'schema> {
1239    /// The parsed arguments of this `@tag` application
1240    arguments: TagDirectiveArguments<'schema>,
1241    /// The schema position to which this directive is applied
1242    target: TagDirectiveTargetPosition, // TODO: Make this a reference
1243    /// Reference to the directive in the schema
1244    directive: &'schema Node<Directive>,
1245}
1246
1247pub(crate) struct CacheTagDirective<'schema> {
1248    /// The parsed arguments of this `@cacheTag` application
1249    arguments: CacheTagDirectiveArguments<'schema>,
1250    /// The schema position to which this directive is applied
1251    target: DirectiveTargetPosition,
1252}
1253
1254pub(crate) trait HasFields {
1255    fn fields(&self) -> &str;
1256    fn target_type(&self) -> &Name;
1257
1258    fn parse_fields(&self, schema: &Schema) -> Result<FieldSet, WithErrors<FieldSet>> {
1259        FieldSet::parse(
1260            Valid::assume_valid_ref(schema),
1261            self.target_type().clone(),
1262            self.fields(),
1263            "field_set.graphql",
1264        )
1265    }
1266}
1267
1268/// A GraphQL schema with federation data that is known to be valid, and cheap to clone.
1269#[derive(Clone)]
1270pub struct ValidFederationSchema {
1271    schema: Arc<Valid<FederationSchema>>,
1272}
1273
1274impl ValidFederationSchema {
1275    pub fn new(schema: Valid<Schema>) -> Result<ValidFederationSchema, FederationError> {
1276        let schema = FederationSchema::new(schema.into_inner())?;
1277
1278        Self::new_assume_valid(schema).map_err(|(_schema, error)| error)
1279    }
1280
1281    /// Construct a ValidFederationSchema by assuming the given FederationSchema is valid.
1282    #[allow(clippy::result_large_err)] // lint is accurate but this is not in a hot path
1283    pub fn new_assume_valid(
1284        mut schema: FederationSchema,
1285    ) -> Result<ValidFederationSchema, (FederationSchema, FederationError)> {
1286        // Populating subgraph metadata requires a mutable FederationSchema, while computing the subgraph
1287        // metadata requires a valid FederationSchema. Since valid schemas are immutable, we have
1288        // to jump through some hoops here. We already assume that `schema` is valid GraphQL, so we
1289        // can temporarily create a `&Valid<FederationSchema>` to compute subgraph metadata, drop
1290        // that reference to populate the metadata, and finally move the finished FederationSchema into
1291        // the ValidFederationSchema instance.
1292        let valid_schema = Valid::assume_valid_ref(&schema);
1293        let subgraph_metadata = match compute_subgraph_metadata(valid_schema) {
1294            Ok(metadata) => metadata.map(Box::new),
1295            Err(err) => return Err((schema, err)),
1296        };
1297        schema.subgraph_metadata = subgraph_metadata;
1298
1299        let schema = Arc::new(Valid::assume_valid(schema));
1300        Ok(ValidFederationSchema { schema })
1301    }
1302
1303    /// Access the GraphQL schema.
1304    pub fn schema(&self) -> &Valid<Schema> {
1305        Valid::assume_valid_ref(&self.schema.schema)
1306    }
1307
1308    /// Returns subgraph-specific metadata.
1309    ///
1310    /// Returns `None` for supergraph schemas.
1311    pub(crate) fn subgraph_metadata(&self) -> Option<&SubgraphMetadata> {
1312        self.schema.subgraph_metadata.as_deref()
1313    }
1314
1315    pub(crate) fn federation_type_name_in_schema(
1316        &self,
1317        name: Name,
1318    ) -> Result<Name, FederationError> {
1319        // Currently, the types used to define the federation operations, that is _Any, _Entity and _Service,
1320        // are not considered part of the federation spec, and are instead hardcoded to the names above.
1321        // The reason being that there is no way to maintain backward compatibility with fed2 if we were to add
1322        // those to the federation spec without requiring users to add those types to their @link `import`,
1323        // and that wouldn't be a good user experience (because most users don't really know what those types
1324        // are/do). And so we special case it.
1325        if name.starts_with('_') {
1326            return Ok(name);
1327        }
1328
1329        // TODO for composition: this otherwise needs to check for a type name in schema based
1330        // on the latest federation version.
1331        // This code path is not hit during planning.
1332        Err(FederationError::internal(
1333            "typename should have been looked in a federation feature",
1334        ))
1335    }
1336
1337    pub(crate) fn is_interface_object_type(
1338        &self,
1339        type_definition_position: TypeDefinitionPosition,
1340    ) -> Result<bool, FederationError> {
1341        let Some(subgraph_metadata) = &self.subgraph_metadata else {
1342            return Ok(false);
1343        };
1344        let Some(interface_object_directive_definition) = subgraph_metadata
1345            .federation_spec_definition()
1346            .interface_object_directive_definition(self)?
1347        else {
1348            return Ok(false);
1349        };
1350        match type_definition_position {
1351            TypeDefinitionPosition::Object(type_) => Ok(type_
1352                .get(self.schema())?
1353                .directives
1354                .has(&interface_object_directive_definition.name)),
1355            _ => Ok(false),
1356        }
1357    }
1358}
1359
1360impl Deref for ValidFederationSchema {
1361    type Target = FederationSchema;
1362
1363    fn deref(&self) -> &Self::Target {
1364        &self.schema
1365    }
1366}
1367
1368impl Eq for ValidFederationSchema {}
1369
1370impl PartialEq for ValidFederationSchema {
1371    fn eq(&self, other: &ValidFederationSchema) -> bool {
1372        Arc::ptr_eq(&self.schema, &other.schema)
1373    }
1374}
1375
1376impl Hash for ValidFederationSchema {
1377    fn hash<H: Hasher>(&self, state: &mut H) {
1378        Arc::as_ptr(&self.schema).hash(state);
1379    }
1380}
1381
1382impl std::fmt::Debug for ValidFederationSchema {
1383    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1384        write!(f, "ValidFederationSchema @ {:?}", Arc::as_ptr(&self.schema))
1385    }
1386}
1387
1388impl From<ValidFederationSchema> for FederationSchema {
1389    fn from(value: ValidFederationSchema) -> Self {
1390        Arc::unwrap_or_clone(value.schema).into_inner()
1391    }
1392}
1393
1394pub(crate) trait SchemaElement {
1395    /// Iterates over the origins of the schema element.
1396    /// - Expected to use the apollo_compiler's `iter_origins` implementation.
1397    fn iter_origins(&self) -> impl Iterator<Item = &ComponentOrigin>;
1398
1399    /// Returns true in the first tuple element if `self` has a definition.
1400    /// Returns a set of extension IDs in the second tuple element, if any.
1401    fn definition_and_extensions(&self) -> (bool, IndexSet<&ExtensionId>) {
1402        let mut extensions = IndexSet::default();
1403        let mut has_definition = false;
1404        for origin in self.iter_origins() {
1405            if let Some(extension_id) = origin.extension_id() {
1406                extensions.insert(extension_id);
1407            } else {
1408                has_definition = true;
1409            }
1410        }
1411        (has_definition, extensions)
1412    }
1413
1414    fn extensions(&self) -> IndexSet<&ExtensionId> {
1415        self.definition_and_extensions().1
1416    }
1417
1418    fn has_extension_elements(&self) -> bool {
1419        !self.extensions().is_empty()
1420    }
1421
1422    fn origin_to_use(&self) -> ComponentOrigin {
1423        let extensions = self.extensions();
1424        // Find an arbitrary extension origin if the schema definition has any extension elements.
1425        // Note: No defined ordering between origins.
1426        let first_extension = extensions.first();
1427        if let Some(first_extension) = first_extension {
1428            // If there is an extension, use the first extension.
1429            ComponentOrigin::Extension((*first_extension).clone())
1430        } else {
1431            // Use the existing definition if exists, or maybe a new definition if no definition
1432            // nor extensions exist.
1433            ComponentOrigin::Definition
1434        }
1435    }
1436}
1437
1438impl SchemaElement for SchemaDefinition {
1439    fn iter_origins(&self) -> impl Iterator<Item = &ComponentOrigin> {
1440        self.iter_origins()
1441    }
1442}
1443
1444impl SchemaElement for ExtendedType {
1445    fn iter_origins(&self) -> impl Iterator<Item = &ComponentOrigin> {
1446        self.iter_origins()
1447    }
1448}