Skip to main content

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