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