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