Skip to content

feat(federation/composition): SingleCompositionError #7590

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions apollo-federation/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,10 @@ fn cmd_subgraph(file_path: &Path) -> Result<(), FederationError> {
.file_name()
.and_then(|name| name.to_str().map(|x| x.to_string()));
let name = name.unwrap_or("subgraph".to_string());
let subgraph = typestate::Subgraph::parse(&name, &format!("http://{name}"), &doc_str)?
.expand_links()?
let subgraph = typestate::Subgraph::parse(&name, &format!("http://{name}"), &doc_str)
.map_err(|e| e.into_inner())?
.expand_links()
.map_err(|e| e.into_inner())?
.assume_upgraded()
.validate()
.map_err(|e| e.into_inner())?;
Expand Down
18 changes: 9 additions & 9 deletions apollo-federation/src/composition/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ mod satisfiability;
use std::vec;

pub use crate::composition::satisfiability::validate_satisfiability;
use crate::error::FederationError;
use crate::error::CompositionError;
pub use crate::schema::schema_upgrader::upgrade_subgraphs_if_necessary;
use crate::subgraph::typestate::Expanded;
use crate::subgraph::typestate::Initial;
Expand All @@ -16,7 +16,7 @@ pub use crate::supergraph::Supergraph;

pub fn compose(
subgraphs: Vec<Subgraph<Initial>>,
) -> Result<Supergraph<Satisfiable>, Vec<FederationError>> {
) -> Result<Supergraph<Satisfiable>, Vec<CompositionError>> {
let expanded_subgraphs = expand_subgraphs(subgraphs)?;
let upgraded_subgraphs = upgrade_subgraphs_if_necessary(expanded_subgraphs)?;
let validated_subgraphs = validate_subgraphs(upgraded_subgraphs)?;
Expand All @@ -31,8 +31,8 @@ pub fn compose(
/// `@link`). This function will update subgraph schemas with all missing federation definitions.
pub fn expand_subgraphs(
subgraphs: Vec<Subgraph<Initial>>,
) -> Result<Vec<Subgraph<Expanded>>, Vec<FederationError>> {
let mut errors: Vec<FederationError> = vec![];
) -> Result<Vec<Subgraph<Expanded>>, Vec<CompositionError>> {
let mut errors: Vec<CompositionError> = vec![];
let expanded: Vec<Subgraph<Expanded>> = subgraphs
.into_iter()
.map(|s| s.expand_links())
Expand All @@ -49,8 +49,8 @@ pub fn expand_subgraphs(
/// `@key` specifies valid `FieldSet`s etc).
pub fn validate_subgraphs(
subgraphs: Vec<Subgraph<Upgraded>>,
) -> Result<Vec<Subgraph<Validated>>, Vec<FederationError>> {
let mut errors: Vec<FederationError> = vec![];
) -> Result<Vec<Subgraph<Validated>>, Vec<CompositionError>> {
let mut errors: Vec<CompositionError> = vec![];
let validated: Vec<Subgraph<Validated>> = subgraphs
.into_iter()
.map(|s| s.validate())
Expand All @@ -66,18 +66,18 @@ pub fn validate_subgraphs(
/// Perform validations that require information about all available subgraphs.
pub fn pre_merge_validations(
_subgraphs: &[Subgraph<Validated>],
) -> Result<(), Vec<FederationError>> {
) -> Result<(), Vec<CompositionError>> {
panic!("pre_merge_validations is not implemented yet")
}

pub fn merge_subgraphs(
_subgraphs: Vec<Subgraph<Validated>>,
) -> Result<Supergraph<Merged>, Vec<FederationError>> {
) -> Result<Supergraph<Merged>, Vec<CompositionError>> {
panic!("merge_subgraphs is not implemented yet")
}

pub fn post_merge_validations(
_supergraph: &Supergraph<Merged>,
) -> Result<(), Vec<FederationError>> {
) -> Result<(), Vec<CompositionError>> {
panic!("post_merge_validations is not implemented yet")
}
4 changes: 2 additions & 2 deletions apollo-federation/src/composition/satisfiability.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
mod satisfiability_error;

use crate::error::FederationError;
use crate::error::CompositionError;
use crate::supergraph::Merged;
use crate::supergraph::Satisfiable;
use crate::supergraph::Supergraph;

pub fn validate_satisfiability(
_supergraph: Supergraph<Merged>,
) -> Result<Supergraph<Satisfiable>, Vec<FederationError>> {
) -> Result<Supergraph<Satisfiable>, Vec<CompositionError>> {
panic!("validate_satisfiability is not implemented yet")
}
157 changes: 43 additions & 114 deletions apollo-federation/src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use apollo_compiler::ast::OperationType;
use apollo_compiler::validation::DiagnosticList;
use apollo_compiler::validation::WithErrors;

use crate::subgraph::SubgraphError;
use crate::subgraph::spec::FederationSpecError;

/// Create an internal error.
Expand Down Expand Up @@ -114,6 +115,48 @@ pub enum UnsupportedFeatureKind {
Alias,
}

#[derive(Debug, Clone, thiserror::Error)]
pub enum CompositionError {
#[error("[{subgraph}] {error}")]
SubgraphError {
subgraph: String,
error: FederationError,
},
#[error("{message}")]
InvalidGraphQL { message: String },
#[error(transparent)]
InvalidGraphQLName(InvalidNameError),
#[error(r#"{message} in @fromContext substring "{context}""#)]
FromContextParseError { context: String, message: String },
#[error(
"Unsupported custom directive @{name} on fragment spread. Due to query transformations during planning, the router requires directives on fragment spreads to support both the FRAGMENT_SPREAD and INLINE_FRAGMENT locations."
)]
UnsupportedSpreadDirective { name: Name },
#[error("{message}")]
DirectiveDefinitionInvalid { message: String },
#[error("{message}")]
TypeDefinitionInvalid { message: String },
#[error("{message}")]
InterfaceObjectUsageError { message: String },
}

impl From<SubgraphError> for CompositionError {
fn from(SubgraphError { subgraph, error }: SubgraphError) -> Self {
Self::SubgraphError { subgraph, error }
}
}

/* TODO(@tylerbloom): This is currently not needed. SingleFederation errors are aggregated using
* MultipleFederationErrors. This is then turned into a FederationError, then in a SubgraphError,
* and finally into a CompositionError. Not implementing this yet also ensures that any
* SingleFederationErrors that are intented on becoming SubgraphErrors still do.
impl<E: Into<FederationError>> From<E> for SingleCompositionError {
fn from(_value: E) -> Self {
todo!()
}
}
*/

#[derive(Debug, Clone, thiserror::Error)]
pub enum SingleFederationError {
#[error(
Expand All @@ -126,11 +169,6 @@ pub enum SingleFederationError {
// This is a known bug that will take time to fix, and does not require reporting.
#[error("{message}")]
InternalUnmergeableFields { message: String },
#[error("[{subgraph}] {error}")]
SubgraphError {
subgraph: String,
error: Box<SingleFederationError>,
},
// InvalidGraphQL: We need to be able to modify the message text from apollo-compiler. So, we
// format the DiagnosticData into String here. We can add additional data as
// necessary.
Expand Down Expand Up @@ -431,7 +469,6 @@ impl SingleFederationError {
SingleFederationError::Internal { .. } => ErrorCode::Internal,
SingleFederationError::InternalRebaseError { .. } => ErrorCode::Internal,
SingleFederationError::InternalUnmergeableFields { .. } => ErrorCode::Internal,
SingleFederationError::SubgraphError { error, .. } => error.code(),
SingleFederationError::InvalidGraphQL { .. }
| SingleFederationError::InvalidGraphQLName(_) => ErrorCode::InvalidGraphQL,
SingleFederationError::InvalidSubgraph { .. } => ErrorCode::InvalidGraphQL,
Expand All @@ -454,7 +491,6 @@ impl SingleFederationError {
SingleFederationError::UnsupportedFederationVersion { .. } => {
ErrorCode::UnsupportedFederationVersion
}

SingleFederationError::UnsupportedLinkedFeature { .. } => {
ErrorCode::UnsupportedLinkedFeature
}
Expand Down Expand Up @@ -674,43 +710,6 @@ impl SingleFederationError {
},
}
}

// TODO: This logic is here to avoid accidentally nesting subgraph name annotations, in the
// future we should change the composition error type to make nesting impossible.
pub(crate) fn unwrap_subgraph_error(self, expected_subgraph: &str) -> Self {
if let Self::SubgraphError { subgraph, error } = self {
debug_assert_eq!(
subgraph, expected_subgraph,
"Unexpectedly found subgraph {} instead of {} for the following error: {}",
subgraph, expected_subgraph, error,
);
*error
} else {
self
}
}

// TODO: This logic is here to avoid accidentally nesting subgraph name annotations, in the
// future we should change the composition error type to make nesting impossible.
pub(crate) fn add_subgraph(self, subgraph: String) -> Self {
if let Self::SubgraphError {
subgraph: existing_subgraph,
error,
} = &self
{
debug_assert_eq!(
existing_subgraph, &subgraph,
"Unexpectedly found subgraph {} instead of {} for the following error: {}",
existing_subgraph, subgraph, error,
);
self
} else {
Self::SubgraphError {
subgraph,
error: Box::new(self),
}
}
}
}

impl From<InvalidNameError> for FederationError {
Expand Down Expand Up @@ -769,26 +768,6 @@ impl MultipleFederationErrors {
}
}
}

pub(crate) fn unwrap_subgraph_errors(self, expected_subgraph: &str) -> Self {
Self {
errors: self
.errors
.into_iter()
.map(|e| e.unwrap_subgraph_error(expected_subgraph))
.collect(),
}
}

pub(crate) fn add_subgraph(self, subgraph: String) -> Self {
Self {
errors: self
.errors
.into_iter()
.map(|e| e.add_subgraph(subgraph.clone()))
.collect(),
}
}
}

impl Display for MultipleFederationErrors {
Expand Down Expand Up @@ -823,32 +802,6 @@ pub struct AggregateFederationError {
pub causes: Vec<SingleFederationError>,
}

impl AggregateFederationError {
pub(crate) fn unwrap_subgraph_errors(self, expected_subgraph: &str) -> Self {
Self {
code: self.code,
message: self.message,
causes: self
.causes
.into_iter()
.map(|e| e.unwrap_subgraph_error(expected_subgraph))
.collect(),
}
}

pub(crate) fn add_subgraph(self, subgraph: String) -> Self {
Self {
code: self.code,
message: self.message,
causes: self
.causes
.into_iter()
.map(|e| e.add_subgraph(subgraph.clone()))
.collect(),
}
}
}

impl Display for AggregateFederationError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}] {}\nCaused by:", self.code, self.message)?;
Expand Down Expand Up @@ -948,30 +901,6 @@ impl FederationError {
.into_iter()
.any(|e| matches!(e, SingleFederationError::InvalidGraphQL { .. }))
}

pub(crate) fn unwrap_subgraph_errors(self, expected_subgraph: &str) -> Self {
match self {
FederationError::SingleFederationError(e) => {
e.unwrap_subgraph_error(expected_subgraph).into()
}
FederationError::MultipleFederationErrors(e) => {
e.unwrap_subgraph_errors(expected_subgraph).into()
}
FederationError::AggregateFederationError(e) => {
e.unwrap_subgraph_errors(expected_subgraph).into()
}
}
}

// PORT_NOTE: Named `addSubgraphToError` in the JS codebase, but converted into a method here.
#[allow(dead_code)]
pub(crate) fn add_subgraph(self, subgraph: String) -> Self {
match self {
FederationError::SingleFederationError(e) => e.add_subgraph(subgraph).into(),
FederationError::MultipleFederationErrors(e) => e.add_subgraph(subgraph).into(),
FederationError::AggregateFederationError(e) => e.add_subgraph(subgraph).into(),
}
}
}

// Similar to `multi_try` crate, but with `FederationError` instead of `Vec<E>`.
Expand Down
4 changes: 2 additions & 2 deletions apollo-federation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ mod test_supergraph {
let res = Supergraph::new(
r#"
extend schema @link(url: "https://specs.apollo.dev/connect/v99.99")

# Required stuff for the supergraph to parse at all, not what we're testing
extend schema
@link(url: "https://specs.apollo.dev/link/v1.0")
Expand All @@ -338,7 +338,7 @@ mod test_supergraph {
`SECURITY` features provide metadata necessary to securely resolve fields.
"""
SECURITY

"""
`EXECUTION` features provide metadata necessary for operation execution.
"""
Expand Down
22 changes: 16 additions & 6 deletions apollo-federation/src/merger/error_reporter.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::error::SingleFederationError;
use crate::error::CompositionError;
use crate::error::FederationError;
use crate::subgraph::SubgraphError;
use crate::supergraph::CompositionHint;

pub(crate) struct ErrorReporter {
errors: Vec<SingleFederationError>,
errors: Vec<CompositionError>,
hints: Vec<CompositionHint>,
}

Expand All @@ -15,7 +17,17 @@ impl ErrorReporter {
}

#[allow(dead_code)]
pub(crate) fn add_error(&mut self, error: SingleFederationError) {
pub(crate) fn add_subgraph_error(&mut self, name: &str, error: impl Into<FederationError>) {
let error = error.into();
let error = SubgraphError {
subgraph: name.into(),
error,
};
self.errors.push(error.into());
}

#[allow(dead_code)]
pub(crate) fn add_error(&mut self, error: CompositionError) {
self.errors.push(error);
}

Expand All @@ -28,9 +40,7 @@ impl ErrorReporter {
!self.errors.is_empty()
}

pub(crate) fn into_errors_and_hints(
self,
) -> (Vec<SingleFederationError>, Vec<CompositionHint>) {
pub(crate) fn into_errors_and_hints(self) -> (Vec<CompositionError>, Vec<CompositionHint>) {
(self.errors, self.hints)
}
}
4 changes: 2 additions & 2 deletions apollo-federation/src/merger/merger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use apollo_compiler::Name;
use apollo_compiler::Schema;
use apollo_compiler::validation::Valid;

use crate::error::SingleFederationError;
use crate::error::CompositionError;
use crate::merger::error_reporter::ErrorReporter;
use crate::subgraph::typestate::Subgraph;
use crate::subgraph::typestate::Validated;
Expand All @@ -16,7 +16,7 @@ pub(crate) struct MergeResult {
#[allow(dead_code)]
pub(crate) supergraph: Option<Valid<Schema>>,
#[allow(dead_code)]
pub(crate) errors: Vec<SingleFederationError>,
pub(crate) errors: Vec<CompositionError>,
#[allow(dead_code)]
pub(crate) hints: Vec<CompositionHint>,
}
Expand Down
Loading