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