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