apollo_federation/
lib.rs

1//! ## Usage
2//!
3//! This crate is internal to [Apollo Router](https://www.apollographql.com/docs/router/)
4//! and not intended to be used directly.
5//!
6//! ## Crate versioning
7//!
8//! The  `apollo-federation` crate does **not** adhere to [Semantic Versioning](https://semver.org/).
9//! Any version may have breaking API changes, as this APIĀ is expected to only be used by `apollo-router`.
10//! Instead, the version number matches exactly that of the `apollo-router` crate version using it.
11//!
12//! This version number is **not** that of the Apollo Federation specification being implemented.
13//! See [Router documentation](https://www.apollographql.com/docs/router/federation-version-support/)
14//! for which Federation versions are supported by which Router versions.
15
16#![warn(
17    rustdoc::broken_intra_doc_links,
18    unreachable_pub,
19    unreachable_patterns,
20    unused,
21    unused_qualifications,
22    dead_code,
23    while_true,
24    unconditional_panic,
25    clippy::all
26)]
27
28mod api_schema;
29mod compat;
30#[cfg(feature = "correctness")]
31pub mod correctness;
32mod display_helpers;
33pub mod error;
34pub mod link;
35pub mod merge;
36pub(crate) mod operation;
37pub mod query_graph;
38pub mod query_plan;
39pub mod schema;
40pub mod subgraph;
41pub(crate) mod supergraph;
42pub(crate) mod utils;
43
44use apollo_compiler::Schema;
45use apollo_compiler::ast::NamedType;
46use apollo_compiler::validation::Valid;
47use itertools::Itertools;
48use link::join_spec_definition::JOIN_VERSIONS;
49use schema::FederationSchema;
50
51pub use crate::api_schema::ApiSchemaOptions;
52use crate::error::FederationError;
53use crate::error::SingleFederationError;
54use crate::link::context_spec_definition::CONTEXT_VERSIONS;
55use crate::link::context_spec_definition::ContextSpecDefinition;
56use crate::link::join_spec_definition::JoinSpecDefinition;
57use crate::link::link_spec_definition::LinkSpecDefinition;
58use crate::link::spec::Identity;
59use crate::link::spec_definition::SpecDefinitions;
60use crate::merge::MergeFailure;
61use crate::merge::merge_subgraphs;
62use crate::schema::ValidFederationSchema;
63use crate::subgraph::ValidSubgraph;
64pub use crate::supergraph::ValidFederationSubgraph;
65pub use crate::supergraph::ValidFederationSubgraphs;
66
67pub(crate) type SupergraphSpecs = (
68    &'static LinkSpecDefinition,
69    &'static JoinSpecDefinition,
70    Option<&'static ContextSpecDefinition>,
71);
72
73pub(crate) fn validate_supergraph_for_query_planning(
74    supergraph_schema: &FederationSchema,
75) -> Result<SupergraphSpecs, FederationError> {
76    validate_supergraph(supergraph_schema, &JOIN_VERSIONS, &CONTEXT_VERSIONS)
77}
78
79/// Checks that required supergraph directives are in the schema, and returns which ones were used.
80pub(crate) fn validate_supergraph(
81    supergraph_schema: &FederationSchema,
82    join_versions: &'static SpecDefinitions<JoinSpecDefinition>,
83    context_versions: &'static SpecDefinitions<ContextSpecDefinition>,
84) -> Result<SupergraphSpecs, FederationError> {
85    let Some(metadata) = supergraph_schema.metadata() else {
86        return Err(SingleFederationError::InvalidFederationSupergraph {
87            message: "Invalid supergraph: must be a core schema".to_owned(),
88        }
89        .into());
90    };
91    let link_spec_definition = metadata.link_spec_definition()?;
92    let Some(join_link) = metadata.for_identity(&Identity::join_identity()) else {
93        return Err(SingleFederationError::InvalidFederationSupergraph {
94            message: "Invalid supergraph: must use the join spec".to_owned(),
95        }
96        .into());
97    };
98    let Some(join_spec_definition) = join_versions.find(&join_link.url.version) else {
99        return Err(SingleFederationError::InvalidFederationSupergraph {
100            message: format!(
101                "Invalid supergraph: uses unsupported join spec version {} (supported versions: {})",
102                join_link.url.version,
103                join_versions.versions().map(|v| v.to_string()).collect::<Vec<_>>().join(", "),
104            ),
105        }.into());
106    };
107    let context_spec_definition = metadata.for_identity(&Identity::context_identity()).map(|context_link| {
108        context_versions.find(&context_link.url.version).ok_or_else(|| {
109            SingleFederationError::InvalidFederationSupergraph {
110                message: format!(
111                    "Invalid supergraph: uses unsupported context spec version {} (supported versions: {})",
112                    context_link.url.version,
113                    context_versions.versions().join(", "),
114                ),
115            }
116        })
117    }).transpose()?;
118    Ok((
119        link_spec_definition,
120        join_spec_definition,
121        context_spec_definition,
122    ))
123}
124
125pub struct Supergraph {
126    pub schema: ValidFederationSchema,
127}
128
129impl Supergraph {
130    pub fn new(schema_str: &str) -> Result<Self, FederationError> {
131        let schema = Schema::parse_and_validate(schema_str, "schema.graphql")?;
132        Self::from_schema(schema)
133    }
134
135    pub fn from_schema(schema: Valid<Schema>) -> Result<Self, FederationError> {
136        let schema = schema.into_inner();
137        let schema = FederationSchema::new(schema)?;
138
139        let _ = validate_supergraph_for_query_planning(&schema)?;
140
141        Ok(Self {
142            // We know it's valid because the input was.
143            schema: schema.assume_valid()?,
144        })
145    }
146
147    pub fn compose(subgraphs: Vec<&ValidSubgraph>) -> Result<Self, MergeFailure> {
148        let schema = merge_subgraphs(subgraphs)?.schema;
149        Ok(Self {
150            schema: ValidFederationSchema::new(schema).map_err(Into::<MergeFailure>::into)?,
151        })
152    }
153
154    /// Generates an API Schema from this supergraph schema. The API Schema represents the combined
155    /// API of the supergraph that's visible to end users.
156    pub fn to_api_schema(
157        &self,
158        options: ApiSchemaOptions,
159    ) -> Result<ValidFederationSchema, FederationError> {
160        api_schema::to_api_schema(self.schema.clone(), options)
161    }
162
163    pub fn extract_subgraphs(&self) -> Result<ValidFederationSubgraphs, FederationError> {
164        supergraph::extract_subgraphs_from_supergraph(&self.schema, None)
165    }
166}
167
168const _: () = {
169    const fn assert_thread_safe<T: Sync + Send>() {}
170
171    assert_thread_safe::<Supergraph>();
172    assert_thread_safe::<query_plan::query_planner::QueryPlanner>();
173};
174
175/// Returns if the type of the node is a scalar or enum.
176pub(crate) fn is_leaf_type(schema: &Schema, ty: &NamedType) -> bool {
177    schema.get_scalar(ty).is_some() || schema.get_enum(ty).is_some()
178}