From 3875222f2f37d51e6571e2f9054d51f107f0990a Mon Sep 17 00:00:00 2001 From: Brian George Date: Thu, 5 Dec 2024 18:09:22 -0500 Subject: [PATCH 1/3] Extract federation version resolution --- src/command/supergraph/compose/do_compose.rs | 2 +- .../supergraph/config/federation.rs | 160 ++++++++++++++++++ .../supergraph/config/full/supergraph.rs | 41 +---- .../supergraph/config/lazy/supergraph.rs | 2 +- src/composition/supergraph/config/mod.rs | 1 + .../supergraph/config/resolver/mod.rs | 50 +++--- .../supergraph/config/resolver/state.rs | 12 +- .../config/unresolved/supergraph.rs | 35 ++-- 8 files changed, 222 insertions(+), 81 deletions(-) create mode 100644 src/composition/supergraph/config/federation.rs diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index 78ed80c54..5d19b8b2d 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -106,7 +106,7 @@ pub struct SupergraphComposeOpts { /// The version of Apollo Federation to use for composition. If no version is supplied, Rover /// will automatically determine the version from the supergraph config #[arg(long = "federation-version")] - federation_version: Option, + pub federation_version: Option, } impl Compose { diff --git a/src/composition/supergraph/config/federation.rs b/src/composition/supergraph/config/federation.rs new file mode 100644 index 000000000..03c9673db --- /dev/null +++ b/src/composition/supergraph/config/federation.rs @@ -0,0 +1,160 @@ +//! Provides objects and utilities to resolve the federation version from user input, +//! including CLI args, [`SupergraphConfig`] input, and subgraph SDLs + +use std::marker::PhantomData; + +use apollo_federation_types::config::{FederationVersion, SupergraphConfig}; +use derive_getters::Getters; + +use crate::command::supergraph::compose::do_compose::SupergraphComposeOpts; + +use super::full::FullyResolvedSubgraph; + +mod state { + #[derive(Clone, Debug)] + pub struct FromSupergraphConfig; + #[derive(Clone, Debug)] + pub struct FromSubgraphs; +} + +/// Error that occurs when the user-selected `FederationVersion` is within Federation 1 boundaries, +/// but the subgraphs use the `@link` directive, which requires Federation 2 +#[derive(thiserror::Error, Debug, Getters)] +#[error( + "The 'federation_version' specified ({}) is invalid. The following subgraphs contain '@link' directives, which are only valid in Federation 2: {}", + specified_federation_version, + subgraph_names.join(", ") +)] +pub struct FederationVersionMismatch { + /// The user specified federation version + specified_federation_version: FederationVersion, + /// The subgraph names that have requested Federation 2 features + subgraph_names: Vec, +} + +/// This is the harness for resolving a FederationVersion +#[derive(Clone, Debug)] +pub struct FederationVersionResolver { + state: PhantomData, + federation_version: Option, +} + +impl Default for FederationVersionResolver { + fn default() -> Self { + FederationVersionResolver { + state: PhantomData::, + federation_version: None, + } + } +} + +/// Represents a `FederationVersionResolver` that has been initiated from user input, if any +/// and is ready to take into account a supergraph config file, resolve immediately, or proceed +/// to a later stage +pub type FederationVersionResolverFromSupergraphConfig = + FederationVersionResolver; + +impl FederationVersionResolver { + /// Creates a new `FederationVersionResolver` from a [`FederationVersion`] + pub fn new( + federation_version: FederationVersion, + ) -> FederationVersionResolver { + FederationVersionResolver { + federation_version: Some(federation_version), + state: PhantomData::, + } + } + + /// Produces a new `FederationVersionResolver` that takes into account the [`FederationVersion`] + /// from a [`SupergraphConfig`] (if it has one) + pub fn from_supergraph_config( + self, + supergraph_config: &SupergraphConfig, + ) -> FederationVersionResolver { + let federation_version = self + .federation_version + .or(supergraph_config.get_federation_version()); + FederationVersionResolver { + state: PhantomData::, + federation_version, + } + } + + /// Skips [`SupergraphConfig`] resolution, presumably because there is none + pub fn skip_supergraph_resolution(self) -> FederationVersionResolver { + FederationVersionResolver { + state: PhantomData::, + federation_version: self.federation_version, + } + } + + /// Resolves the federation immediately without taking into account subgraph SDLs + pub fn resolve(self) -> FederationVersion { + self.federation_version + .unwrap_or(FederationVersion::LatestFedTwo) + } +} + +impl From<&SupergraphComposeOpts> for FederationVersionResolver { + fn from(value: &SupergraphComposeOpts) -> Self { + FederationVersionResolver { + federation_version: value.federation_version.clone(), + state: PhantomData::, + } + } +} + +/// Public alias for `FederationVersionResolver` +pub type FederationVersionResolverFromSubgraphs = FederationVersionResolver; + +impl FederationVersionResolver { + #[cfg(test)] + pub fn new( + target_federation_version: Option, + ) -> FederationVersionResolver { + FederationVersionResolver { + state: PhantomData::, + federation_version: target_federation_version, + } + } + + /// Returns the target [`FederationVersion`] that was defined by the user + pub fn target_federation_version(&self) -> Option { + self.federation_version.clone() + } + + /// Resolves the [`FederationVersion`] against user input and the subgraph SDLs provided + pub fn resolve<'a>( + &self, + subgraphs: &'a mut impl Iterator, + ) -> Result { + let fed_two_subgraphs = subgraphs + .filter_map(|(subgraph_name, subgraph)| { + if *subgraph.is_fed_two() { + Some(subgraph_name.to_string()) + } else { + None + } + }) + .collect::>(); + let contains_fed_two_subgraphs = !fed_two_subgraphs.is_empty(); + match &self.federation_version { + Some(specified_federation_version) => { + let specified_federation_version = specified_federation_version.clone(); + if specified_federation_version.is_fed_one() { + if contains_fed_two_subgraphs { + Err(FederationVersionMismatch { + specified_federation_version, + subgraph_names: fed_two_subgraphs, + }) + } else { + Ok(specified_federation_version) + } + } else { + Ok(specified_federation_version) + } + } + None => Ok(FederationVersion::LatestFedTwo), + } + } +} diff --git a/src/composition/supergraph/config/full/supergraph.rs b/src/composition/supergraph/config/full/supergraph.rs index b54d7e2f3..01bd90256 100644 --- a/src/composition/supergraph/config/full/supergraph.rs +++ b/src/composition/supergraph/config/full/supergraph.rs @@ -65,10 +65,9 @@ impl FullyResolvedSupergraphConfig { ) = subgraphs.into_iter().partition_result(); if errors.is_empty() { let subgraphs = BTreeMap::from_iter(subgraphs); - let federation_version = Self::resolve_federation_version( - unresolved_supergraph_config.federation_version().as_ref(), - &mut subgraphs.iter(), - )?; + let federation_version = unresolved_supergraph_config + .federation_version_resolver() + .resolve(&mut subgraphs.iter())?; Ok(FullyResolvedSupergraphConfig { origin_path: unresolved_supergraph_config.origin_path().clone(), subgraphs, @@ -78,38 +77,4 @@ impl FullyResolvedSupergraphConfig { Err(ResolveSupergraphConfigError::ResolveSubgraphs(errors)) } } - - fn resolve_federation_version<'a>( - specified_federation_version: Option<&FederationVersion>, - subgraphs: &'a mut impl Iterator, - ) -> Result { - let fed_two_subgraphs = subgraphs - .filter_map(|(subgraph_name, subgraph)| { - if *subgraph.is_fed_two() { - Some(subgraph_name.to_string()) - } else { - None - } - }) - .collect::>(); - let contains_fed_two_subgraphs = !fed_two_subgraphs.is_empty(); - match specified_federation_version { - Some(specified_federation_version) => { - let specified_federation_version = specified_federation_version.clone(); - if specified_federation_version.is_fed_one() { - if contains_fed_two_subgraphs { - Err(ResolveSupergraphConfigError::FederationVersionMismatch { - specified_federation_version, - subgraph_names: fed_two_subgraphs, - }) - } else { - Ok(specified_federation_version) - } - } else { - Ok(specified_federation_version) - } - } - None => Ok(FederationVersion::LatestFedTwo), - } - } } diff --git a/src/composition/supergraph/config/lazy/supergraph.rs b/src/composition/supergraph/config/lazy/supergraph.rs index 5f10f986d..722e86751 100644 --- a/src/composition/supergraph/config/lazy/supergraph.rs +++ b/src/composition/supergraph/config/lazy/supergraph.rs @@ -49,7 +49,7 @@ impl LazilyResolvedSupergraphConfig { Ok(LazilyResolvedSupergraphConfig { origin_path: unresolved_supergraph_config.origin_path().clone(), subgraphs: BTreeMap::from_iter(subgraphs), - federation_version: unresolved_supergraph_config.federation_version().clone(), + federation_version: unresolved_supergraph_config.target_federation_version(), }) } else { Err(errors) diff --git a/src/composition/supergraph/config/mod.rs b/src/composition/supergraph/config/mod.rs index 154e0cab0..f68a85bed 100644 --- a/src/composition/supergraph/config/mod.rs +++ b/src/composition/supergraph/config/mod.rs @@ -3,6 +3,7 @@ #![warn(missing_docs)] pub mod error; +pub mod federation; pub mod full; pub mod lazy; pub mod resolver; diff --git a/src/composition/supergraph/config/resolver/mod.rs b/src/composition/supergraph/config/resolver/mod.rs index c57588ebe..5c99ad99a 100644 --- a/src/composition/supergraph/config/resolver/mod.rs +++ b/src/composition/supergraph/config/resolver/mod.rs @@ -35,8 +35,14 @@ use crate::{ use self::state::ResolveSubgraphs; use super::{ - error::ResolveSubgraphError, full::FullyResolvedSupergraphConfig, - lazy::LazilyResolvedSupergraphConfig, unresolved::UnresolvedSupergraphConfig, + error::ResolveSubgraphError, + federation::{ + FederationVersionMismatch, FederationVersionResolver, + FederationVersionResolverFromSupergraphConfig, + }, + full::FullyResolvedSupergraphConfig, + lazy::LazilyResolvedSupergraphConfig, + unresolved::UnresolvedSupergraphConfig, }; mod state; @@ -53,7 +59,9 @@ impl SupergraphConfigResolver { ) -> SupergraphConfigResolver { SupergraphConfigResolver { state: state::LoadRemoteSubgraphs { - federation_version: Some(federation_version), + federation_version_resolver: FederationVersionResolverFromSupergraphConfig::new( + federation_version, + ), }, } } @@ -63,7 +71,7 @@ impl Default for SupergraphConfigResolver { fn default() -> Self { SupergraphConfigResolver { state: state::LoadRemoteSubgraphs { - federation_version: None, + federation_version_resolver: FederationVersionResolver::default(), }, } } @@ -95,14 +103,14 @@ impl SupergraphConfigResolver { })?; Ok(SupergraphConfigResolver { state: state::LoadSupergraphConfig { - federation_version: self.state.federation_version, + federation_version_resolver: self.state.federation_version_resolver, subgraphs: remote_subgraphs, }, }) } else { Ok(SupergraphConfigResolver { state: state::LoadSupergraphConfig { - federation_version: self.state.federation_version, + federation_version_resolver: self.state.federation_version_resolver, subgraphs: BTreeMap::default(), }, }) @@ -141,10 +149,10 @@ impl SupergraphConfigResolver { FileDescriptorType::File(file) => Some(file.clone()), FileDescriptorType::Stdin => None, }; - let federation_version = self + let federation_version_resolver = self .state - .federation_version - .or_else(|| supergraph_config.get_federation_version()); + .federation_version_resolver + .from_supergraph_config(&supergraph_config); let mut merged_subgraphs = self.state.subgraphs; for (name, subgraph_config) in supergraph_config.into_iter() { let subgraph_config = SubgraphConfig { @@ -160,7 +168,7 @@ impl SupergraphConfigResolver { Ok(SupergraphConfigResolver { state: ResolveSubgraphs { origin_path, - federation_version, + federation_version_resolver, subgraphs: merged_subgraphs, }, }) @@ -168,7 +176,10 @@ impl SupergraphConfigResolver { Ok(SupergraphConfigResolver { state: ResolveSubgraphs { origin_path: None, - federation_version: self.state.federation_version, + federation_version_resolver: self + .state + .federation_version_resolver + .skip_supergraph_resolution(), subgraphs: self.state.subgraphs, }, }) @@ -188,17 +199,8 @@ pub enum ResolveSupergraphConfigError { ResolveSubgraphs(Vec), /// Occurs when the user-selected `FederationVersion` is within Federation 1 boundaries, but the /// subgraphs use the `@link` directive, which requires Federation 2 - #[error( - "The 'federation_version' specified ({}) is invalid. The following subgraphs contain '@link' directives, which are only valid in Federation 2: {}", - specified_federation_version, - subgraph_names.join(", ") - )] - FederationVersionMismatch { - /// The user specified federation version - specified_federation_version: FederationVersion, - /// The subgraph names that have requested Federation 2 features - subgraph_names: Vec, - }, + #[error(transparent)] + FederationVersionMismatch(#[from] FederationVersionMismatch), } impl SupergraphConfigResolver { @@ -212,7 +214,7 @@ impl SupergraphConfigResolver { if !self.state.subgraphs.is_empty() { let unresolved_supergraph_config = UnresolvedSupergraphConfig::builder() .subgraphs(self.state.subgraphs.clone()) - .and_federation_version(self.state.federation_version.clone()) + .federation_version_resolver(self.state.federation_version_resolver.clone()) .build(); let resolved_supergraph_config = FullyResolvedSupergraphConfig::resolve( introspect_subgraph_impl, @@ -237,7 +239,7 @@ impl SupergraphConfigResolver { if !self.state.subgraphs.is_empty() { let unresolved_supergraph_config = UnresolvedSupergraphConfig::builder() .subgraphs(self.state.subgraphs.clone()) - .and_federation_version(self.state.federation_version.clone()) + .federation_version_resolver(self.state.federation_version_resolver.clone()) .build(); let resolved_supergraph_config = LazilyResolvedSupergraphConfig::resolve( supergraph_config_root, diff --git a/src/composition/supergraph/config/resolver/state.rs b/src/composition/supergraph/config/resolver/state.rs index f632f08ca..48b9d37f1 100644 --- a/src/composition/supergraph/config/resolver/state.rs +++ b/src/composition/supergraph/config/resolver/state.rs @@ -1,18 +1,22 @@ use std::collections::BTreeMap; -use apollo_federation_types::config::{FederationVersion, SubgraphConfig}; +use apollo_federation_types::config::SubgraphConfig; use camino::Utf8PathBuf; +use crate::composition::supergraph::config::federation::{ + FederationVersionResolverFromSubgraphs, FederationVersionResolverFromSupergraphConfig, +}; + /// In this stage, we await the caller to optionally load subgraphs from the Studio API using /// the contents of the `--graph-ref` flag pub struct LoadRemoteSubgraphs { - pub federation_version: Option, + pub federation_version_resolver: FederationVersionResolverFromSupergraphConfig, } /// In this stage, we await the caller to optionally load subgraphs and a specified federation /// version from a local supergraph config file pub struct LoadSupergraphConfig { - pub federation_version: Option, + pub federation_version_resolver: FederationVersionResolverFromSupergraphConfig, pub subgraphs: BTreeMap, } @@ -20,6 +24,6 @@ pub struct LoadSupergraphConfig { /// and exist) or fully: rendering the subgraph source down to an SDL pub struct ResolveSubgraphs { pub origin_path: Option, - pub federation_version: Option, + pub federation_version_resolver: FederationVersionResolverFromSubgraphs, pub subgraphs: BTreeMap, } diff --git a/src/composition/supergraph/config/unresolved/supergraph.rs b/src/composition/supergraph/config/unresolved/supergraph.rs index f0eafdf87..3333685ae 100644 --- a/src/composition/supergraph/config/unresolved/supergraph.rs +++ b/src/composition/supergraph/config/unresolved/supergraph.rs @@ -6,6 +6,8 @@ use buildstructor::buildstructor; use camino::Utf8PathBuf; use derive_getters::Getters; +use crate::composition::supergraph::config::federation::FederationVersionResolverFromSubgraphs; + use super::UnresolvedSubgraph; /// Object that represents a [`SupergraphConfig`] that requires resolution @@ -13,7 +15,7 @@ use super::UnresolvedSubgraph; pub struct UnresolvedSupergraphConfig { origin_path: Option, subgraphs: BTreeMap, - federation_version: Option, + federation_version_resolver: FederationVersionResolverFromSubgraphs, } #[buildstructor] @@ -23,7 +25,7 @@ impl UnresolvedSupergraphConfig { pub fn new( origin_path: Option, subgraphs: BTreeMap, - federation_version: Option, + federation_version_resolver: FederationVersionResolverFromSubgraphs, ) -> UnresolvedSupergraphConfig { let subgraphs = BTreeMap::from_iter( subgraphs @@ -33,9 +35,14 @@ impl UnresolvedSupergraphConfig { UnresolvedSupergraphConfig { origin_path, subgraphs, - federation_version, + federation_version_resolver, } } + + /// Provides the target federation version provided by the user + pub fn target_federation_version(&self) -> Option { + self.federation_version_resolver.target_federation_version() + } } #[cfg(test)] @@ -56,6 +63,7 @@ mod tests { use crate::{ composition::supergraph::config::{ + federation::FederationVersionResolverFromSubgraphs, full::{FullyResolvedSubgraph, FullyResolvedSupergraphConfig}, lazy::{LazilyResolvedSubgraph, LazilyResolvedSupergraphConfig}, resolver::ResolveSupergraphConfigError, @@ -279,7 +287,9 @@ mod tests { let unresolved_supergraph_config = UnresolvedSupergraphConfig { origin_path: None, subgraphs: unresolved_subgraphs, - federation_version: target_federation_version, + federation_version_resolver: FederationVersionResolverFromSubgraphs::new( + target_federation_version, + ), }; let RemoteSubgraphScenario { @@ -484,7 +494,9 @@ mod tests { let unresolved_supergraph_config = UnresolvedSupergraphConfig { origin_path: None, subgraphs: unresolved_subgraphs, - federation_version: Some(target_federation_version.clone()), + federation_version_resolver: FederationVersionResolverFromSubgraphs::new(Some( + target_federation_version.clone(), + )), }; let RemoteSubgraphScenario { @@ -576,13 +588,10 @@ mod tests { } let err = assert_that!(result).is_err().subject; - if let ResolveSupergraphConfigError::FederationVersionMismatch { - specified_federation_version, - subgraph_names, - } = err - { - let subgraph_names = HashSet::from_iter(subgraph_names.iter().cloned()); - assert_that!(specified_federation_version).is_equal_to(&target_federation_version); + if let ResolveSupergraphConfigError::FederationVersionMismatch(err) = err { + let subgraph_names = HashSet::from_iter(err.subgraph_names().iter().cloned()); + assert_that!(err.specified_federation_version()) + .is_equal_to(&target_federation_version); assert_that!(subgraph_names).is_equal_to(&fed_two_subgraph_names); } else { panic!("Result contains the wrong type of error: {:?}", err); @@ -631,7 +640,7 @@ mod tests { let unresolved_supergraph_config = UnresolvedSupergraphConfig { origin_path: Some(supergraph_config_origin_path), subgraphs: unresolved_subgraphs, - federation_version: None, + federation_version_resolver: FederationVersionResolverFromSubgraphs::new(None), }; let result = LazilyResolvedSupergraphConfig::resolve( From 11eccc63a95c2647297d8f95c92af9f9796d8c01 Mon Sep 17 00:00:00 2001 From: Brian George Date: Fri, 6 Dec 2024 20:09:48 -0500 Subject: [PATCH 2/3] Merge `skip_supergraph_resolution` into `from_supergraph_config` --- .../supergraph/config/federation.rs | 30 +++++++++---------- .../supergraph/config/resolver/mod.rs | 4 +-- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/composition/supergraph/config/federation.rs b/src/composition/supergraph/config/federation.rs index 03c9673db..85d24af93 100644 --- a/src/composition/supergraph/config/federation.rs +++ b/src/composition/supergraph/config/federation.rs @@ -69,22 +69,22 @@ impl FederationVersionResolver { /// from a [`SupergraphConfig`] (if it has one) pub fn from_supergraph_config( self, - supergraph_config: &SupergraphConfig, + supergraph_config: Option<&SupergraphConfig>, ) -> FederationVersionResolver { - let federation_version = self - .federation_version - .or(supergraph_config.get_federation_version()); - FederationVersionResolver { - state: PhantomData::, - federation_version, - } - } - - /// Skips [`SupergraphConfig`] resolution, presumably because there is none - pub fn skip_supergraph_resolution(self) -> FederationVersionResolver { - FederationVersionResolver { - state: PhantomData::, - federation_version: self.federation_version, + match supergraph_config { + Some(supergraph_config) => { + let federation_version = self + .federation_version + .or(supergraph_config.get_federation_version()); + FederationVersionResolver { + state: PhantomData::, + federation_version, + } + } + None => FederationVersionResolver { + state: PhantomData::, + federation_version: self.federation_version, + }, } } diff --git a/src/composition/supergraph/config/resolver/mod.rs b/src/composition/supergraph/config/resolver/mod.rs index 5c99ad99a..a71123d19 100644 --- a/src/composition/supergraph/config/resolver/mod.rs +++ b/src/composition/supergraph/config/resolver/mod.rs @@ -152,7 +152,7 @@ impl SupergraphConfigResolver { let federation_version_resolver = self .state .federation_version_resolver - .from_supergraph_config(&supergraph_config); + .from_supergraph_config(Some(&supergraph_config)); let mut merged_subgraphs = self.state.subgraphs; for (name, subgraph_config) in supergraph_config.into_iter() { let subgraph_config = SubgraphConfig { @@ -179,7 +179,7 @@ impl SupergraphConfigResolver { federation_version_resolver: self .state .federation_version_resolver - .skip_supergraph_resolution(), + .from_supergraph_config(None), subgraphs: self.state.subgraphs, }, }) From c7844ff75be936906814b7dbdd7ffc7a5f674fb0 Mon Sep 17 00:00:00 2001 From: Brian George Date: Tue, 10 Dec 2024 17:57:01 -0500 Subject: [PATCH 3/3] Extract fed resolution tests --- .../supergraph/config/federation.rs | 116 +++- .../supergraph/config/full/supergraph.rs | 2 +- src/composition/supergraph/config/mod.rs | 642 ----------------- .../supergraph/config/resolver/mod.rs | 645 ++++++++++++++++++ .../supergraph/config/unresolved/subgraph.rs | 9 + 5 files changed, 770 insertions(+), 644 deletions(-) diff --git a/src/composition/supergraph/config/federation.rs b/src/composition/supergraph/config/federation.rs index 85d24af93..40b1d4770 100644 --- a/src/composition/supergraph/config/federation.rs +++ b/src/composition/supergraph/config/federation.rs @@ -126,7 +126,7 @@ impl FederationVersionResolver { /// Resolves the [`FederationVersion`] against user input and the subgraph SDLs provided pub fn resolve<'a>( &self, - subgraphs: &'a mut impl Iterator, + subgraphs: impl Iterator, ) -> Result { let fed_two_subgraphs = subgraphs .filter_map(|(subgraph_name, subgraph)| { @@ -158,3 +158,117 @@ impl FederationVersionResolver { } } } + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + + use apollo_federation_types::config::{FederationVersion, SubgraphConfig, SupergraphConfig}; + use speculoos::prelude::*; + + use crate::composition::supergraph::config::{full::FullyResolvedSubgraph, scenario::*}; + + use super::FederationVersionResolverFromSupergraphConfig; + + /// Test showing that federation version is selected from the user-specified fed version + /// over local supergraph config or resolved subgraphs + #[test] + fn test_resolve_from_user_selection() { + let subgraph_name = subgraph_name(); + let subgraph_scenario = sdl_subgraph_scenario( + sdl(), + subgraph_name.to_string(), + SubgraphFederationVersion::One, + ); + let unresolved_subgraphs = BTreeMap::from_iter([( + subgraph_name.to_string(), + SubgraphConfig::from(subgraph_scenario.unresolved_subgraph.clone()), + )]); + let federation_version_resolver = + FederationVersionResolverFromSupergraphConfig::new(FederationVersion::LatestFedTwo); + let supergraph_config = + SupergraphConfig::new(unresolved_subgraphs, Some(FederationVersion::LatestFedOne)); + + let resolved_subgraphs = vec![( + subgraph_name.to_string(), + FullyResolvedSubgraph::builder() + .schema(subgraph_scenario.sdl) + .and_routing_url(subgraph_scenario.unresolved_subgraph.routing_url().clone()) + .is_fed_two(false) + .build(), + )]; + let federation_version = federation_version_resolver + .from_supergraph_config(Some(&supergraph_config)) + .resolve(resolved_subgraphs.iter().map(|(k, v)| (k, v))); + assert_that!(federation_version) + .is_ok() + .is_equal_to(FederationVersion::LatestFedTwo); + } + + /// Test showing that federation version is selected from the supergraph config + /// over resolved subgraphs when it is not specified by the user + #[test] + fn test_resolve_from_supergraph_config() { + let subgraph_name = subgraph_name(); + let subgraph_scenario = sdl_subgraph_scenario( + sdl(), + subgraph_name.to_string(), + SubgraphFederationVersion::One, + ); + let unresolved_subgraphs = BTreeMap::from_iter([( + subgraph_name.to_string(), + SubgraphConfig::from(subgraph_scenario.unresolved_subgraph.clone()), + )]); + let federation_version_resolver = FederationVersionResolverFromSupergraphConfig::default(); + let supergraph_config = + SupergraphConfig::new(unresolved_subgraphs, Some(FederationVersion::LatestFedTwo)); + + let resolved_subgraphs = vec![( + subgraph_name.to_string(), + FullyResolvedSubgraph::builder() + .schema(subgraph_scenario.sdl) + .and_routing_url(subgraph_scenario.unresolved_subgraph.routing_url().clone()) + .is_fed_two(false) + .build(), + )]; + let federation_version = federation_version_resolver + .from_supergraph_config(Some(&supergraph_config)) + .resolve(resolved_subgraphs.iter().map(|(k, v)| (k, v))); + assert_that!(federation_version) + .is_ok() + .is_equal_to(FederationVersion::LatestFedTwo); + } + + /// Test showing that federation version is selected from resolved subgraphs + /// when it is not specified by the user or in a supergraph config + #[test] + fn test_resolve_from_resolved_subgraphs() { + let subgraph_name = subgraph_name(); + let subgraph_scenario = sdl_subgraph_scenario( + sdl_fed2(sdl()), + subgraph_name.to_string(), + SubgraphFederationVersion::Two, + ); + let unresolved_subgraphs = BTreeMap::from_iter([( + subgraph_name.to_string(), + SubgraphConfig::from(subgraph_scenario.unresolved_subgraph.clone()), + )]); + let federation_version_resolver = FederationVersionResolverFromSupergraphConfig::default(); + let supergraph_config = SupergraphConfig::new(unresolved_subgraphs, None); + + let resolved_subgraphs = vec![( + subgraph_name.to_string(), + FullyResolvedSubgraph::builder() + .schema(subgraph_scenario.sdl) + .and_routing_url(subgraph_scenario.unresolved_subgraph.routing_url().clone()) + .is_fed_two(true) + .build(), + )]; + let federation_version = federation_version_resolver + .from_supergraph_config(Some(&supergraph_config)) + .resolve(resolved_subgraphs.iter().map(|(k, v)| (k, v))); + assert_that!(federation_version) + .is_ok() + .is_equal_to(FederationVersion::LatestFedTwo); + } +} diff --git a/src/composition/supergraph/config/full/supergraph.rs b/src/composition/supergraph/config/full/supergraph.rs index 01bd90256..2e1a8932a 100644 --- a/src/composition/supergraph/config/full/supergraph.rs +++ b/src/composition/supergraph/config/full/supergraph.rs @@ -67,7 +67,7 @@ impl FullyResolvedSupergraphConfig { let subgraphs = BTreeMap::from_iter(subgraphs); let federation_version = unresolved_supergraph_config .federation_version_resolver() - .resolve(&mut subgraphs.iter())?; + .resolve(subgraphs.iter())?; Ok(FullyResolvedSupergraphConfig { origin_path: unresolved_supergraph_config.origin_path().clone(), subgraphs, diff --git a/src/composition/supergraph/config/mod.rs b/src/composition/supergraph/config/mod.rs index f68a85bed..68b24b5c8 100644 --- a/src/composition/supergraph/config/mod.rs +++ b/src/composition/supergraph/config/mod.rs @@ -10,645 +10,3 @@ pub mod resolver; #[cfg(test)] pub mod scenario; pub mod unresolved; - -#[cfg(test)] -mod tests { - use std::{collections::BTreeMap, str::FromStr}; - - use anyhow::Result; - use apollo_federation_types::config::{ - FederationVersion, SchemaSource, SubgraphConfig, SupergraphConfig, - }; - use assert_fs::{ - prelude::{FileTouch, FileWriteStr, PathChild}, - TempDir, - }; - use camino::Utf8PathBuf; - use mockall::predicate; - use rstest::rstest; - use semver::Version; - use speculoos::prelude::*; - - use crate::utils::{ - effect::{ - fetch_remote_subgraph::{MockFetchRemoteSubgraph, RemoteSubgraph}, - fetch_remote_subgraphs::MockFetchRemoteSubgraphs, - introspect::MockIntrospectSubgraph, - read_stdin::MockReadStdin, - }, - parsers::FileDescriptorType, - }; - - use super::{resolver::SupergraphConfigResolver, scenario::*}; - - /// Test showing that federation version is selected from the user-specified fed version - /// over local supergraph config, remote composition version, or version inferred from - /// resolved SDLs - /// For these tests, we only need to test against a remote schema source and a local one. - /// The sdl schema source was chosen as local, since it's the easiest one to configure - #[rstest] - /// Case: both local and remote subgraphs exist with fed 1 SDLs - #[case( - Some(remote_subgraph_scenario( - sdl(), - subgraph_name(), - routing_url(), - SubgraphFederationVersion::One - )), - Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::One)) - )] - /// Case: only a remote subgraph exists with a fed 1 SDL - #[case( - Some(remote_subgraph_scenario( - sdl(), - subgraph_name(), - routing_url(), - SubgraphFederationVersion::One - )), - None - )] - /// Case: only a local subgraph exists with a fed 1 SDL - #[case( - None, - Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::One)) - )] - /// Case: both local and remote subgraphs exist with fed 2 SDLs - #[case( - Some(remote_subgraph_scenario( - sdl(), - subgraph_name(), - routing_url(), - SubgraphFederationVersion::Two - )), - Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::Two)) - )] - /// Case: only a remote subgraph exists with a fed 2 SDL - #[case( - Some(remote_subgraph_scenario( - sdl(), - subgraph_name(), - routing_url(), - SubgraphFederationVersion::Two - )), - None - )] - /// Case: only a local subgraph exists with a fed 2 SDL - #[case( - None, - Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::Two)) - )] - /// Case: both local and remote subgraphs exist with varying fed version SDLs - #[case( - Some(remote_subgraph_scenario( - sdl(), - subgraph_name(), - routing_url(), - SubgraphFederationVersion::One - )), - Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::Two)) - )] - /// This test further uses #[values] to make sure we have a matrix of tests - /// All possible combinations result in using the target federation version, - /// since that is the highest order of precedence - #[tokio::test] - async fn test_select_federation_version_from_user_selection( - #[case] remote_subgraph_scenario: Option, - #[case] sdl_subgraph_scenario: Option, - // Dictates whether to load the remote supergraph schema from a the local config or using the --graph_ref flag - #[values(true, false)] fetch_remote_subgraph_from_config: bool, - // Dictates whether to load the local supergraph schema from a file or stdin - #[values(true, false)] load_supergraph_config_from_file: bool, - // The optional fed version attached to a local supergraph config - #[values(Some(FederationVersion::LatestFedOne), None)] - local_supergraph_federation_version: Option, - ) -> Result<()> { - // user-specified federation version - let target_federation_version = - FederationVersion::ExactFedTwo(Version::from_str("2.7.1").unwrap()); - let mut subgraphs = BTreeMap::new(); - - let mut mock_fetch_remote_subgraphs = MockFetchRemoteSubgraphs::new(); - let mut mock_fetch_remote_subgraph = MockFetchRemoteSubgraph::new(); - - setup_remote_subgraph_scenario( - fetch_remote_subgraph_from_config, - remote_subgraph_scenario.as_ref(), - &mut subgraphs, - &mut mock_fetch_remote_subgraphs, - &mut mock_fetch_remote_subgraph, - ); - - setup_sdl_subgraph_scenario(sdl_subgraph_scenario.as_ref(), &mut subgraphs); - - let mut mock_read_stdin = MockReadStdin::new(); - - let local_supergraph_config = - SupergraphConfig::new(subgraphs, local_supergraph_federation_version); - let local_supergraph_config_str = serde_yaml::to_string(&local_supergraph_config)?; - let local_supergraph_config_dir = assert_fs::TempDir::new()?; - let local_supergraph_config_path = - Utf8PathBuf::from_path_buf(local_supergraph_config_dir.path().to_path_buf()).unwrap(); - - let file_descriptor_type = setup_file_descriptor( - load_supergraph_config_from_file, - &local_supergraph_config_dir, - &local_supergraph_config_str, - &mut mock_read_stdin, - )?; - - // we never introspect subgraphs in this test, but we still have to account for the effect - let mut mock_introspect_subgraph = MockIntrospectSubgraph::new(); - mock_introspect_subgraph - .expect_introspect_subgraph() - .times(0); - - // init resolver with a target fed version - let resolver = SupergraphConfigResolver::new(target_federation_version.clone()); - - // determine whether to try to load from graph refs - let graph_ref = remote_subgraph_scenario - .as_ref() - .and_then(|remote_subgraph_scenario| { - if fetch_remote_subgraph_from_config { - None - } else { - Some(remote_subgraph_scenario.graph_ref.clone()) - } - }); - - // load remote subgraphs - let resolver = resolver - .load_remote_subgraphs(&mock_fetch_remote_subgraphs, graph_ref.as_ref()) - .await?; - - // load from the file descriptor - let resolver = resolver - .load_from_file_descriptor(&mut mock_read_stdin, Some(&file_descriptor_type))?; - - // validate that the correct effect has been invoked - mock_fetch_remote_subgraphs.checkpoint(); - - // fully resolve subgraphs into their SDLs - let fully_resolved_supergraph_config = resolver - .fully_resolve_subgraphs( - &mock_introspect_subgraph, - &mock_fetch_remote_subgraph, - Some(&local_supergraph_config_path), - ) - .await?; - - // validate that the correct effects have been invoked - mock_introspect_subgraph.checkpoint(); - mock_fetch_remote_subgraph.checkpoint(); - - // validate that the federation version is correct - assert_that!(fully_resolved_supergraph_config.federation_version()) - .is_equal_to(&target_federation_version); - - Ok(()) - } - - /// Test showing that federation version is selected from the local supergraph config fed version - /// over remote composition version, or version inferred from resolved SDLs - /// For these tests, we only need to test against a remote schema source and a local one. - /// The sdl schema source was chosen as local, since it's the easiest one to configure - #[rstest] - /// Case: both local and remote subgraphs exist with fed 1 SDLs - #[case( - Some(remote_subgraph_scenario( - sdl(), - subgraph_name(), - routing_url(), - SubgraphFederationVersion::One - )), - Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::One)) - )] - /// Case: only a remote subgraph exists with a fed 1 SDL - #[case( - Some(remote_subgraph_scenario( - sdl(), - subgraph_name(), - routing_url(), - SubgraphFederationVersion::One - )), - None - )] - /// Case: only a local subgraph exists with a fed 1 SDL - #[case( - None, - Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::One)) - )] - /// Case: both local and remote subgraphs exist with fed 2 SDLs - #[case( - Some(remote_subgraph_scenario( - sdl(), - subgraph_name(), - routing_url(), - SubgraphFederationVersion::Two - )), - Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::Two)) - )] - /// Case: only a remote subgraph exists with a fed 2 SDL - #[case( - Some(remote_subgraph_scenario( - sdl(), - subgraph_name(), - routing_url(), - SubgraphFederationVersion::Two - )), - None - )] - /// Case: only a local subgraph exists with a fed 2 SDL - #[case( - None, - Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::Two)) - )] - /// Case: both local and remote subgraphs exist with varying fed version SDLs - #[case( - Some(remote_subgraph_scenario( - sdl(), - subgraph_name(), - routing_url(), - SubgraphFederationVersion::One - )), - Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::Two)) - )] - /// This test further uses #[values] to make sure we have a matrix of tests - /// All possible combinations result in using the remote federation version, - /// since that is the highest order of precedence in this socpe - #[trace] - #[tokio::test] - async fn test_select_federation_version_from_local_supergraph_config( - #[case] remote_subgraph_scenario: Option, - #[case] sdl_subgraph_scenario: Option, - // Dictates whether to load the remote supergraph schema from a the local config or using the --graph_ref flag - #[values(true, false)] fetch_remote_subgraph_from_config: bool, - // Dictates whether to load the local supergraph schema from a file or stdin - #[values(true, false)] load_supergraph_config_from_file: bool, - ) -> Result<()> { - // user-specified federation version (from local supergraph config) - let local_supergraph_federation_version = - FederationVersion::ExactFedTwo(Version::from_str("2.7.1").unwrap()); - - let mut subgraphs = BTreeMap::new(); - - let mut mock_fetch_remote_subgraphs = MockFetchRemoteSubgraphs::new(); - let mut mock_fetch_remote_subgraph = MockFetchRemoteSubgraph::new(); - - setup_remote_subgraph_scenario( - fetch_remote_subgraph_from_config, - remote_subgraph_scenario.as_ref(), - &mut subgraphs, - &mut mock_fetch_remote_subgraphs, - &mut mock_fetch_remote_subgraph, - ); - - setup_sdl_subgraph_scenario(sdl_subgraph_scenario.as_ref(), &mut subgraphs); - - let mut mock_read_stdin = MockReadStdin::new(); - - let local_supergraph_config = - SupergraphConfig::new(subgraphs, Some(local_supergraph_federation_version.clone())); - let local_supergraph_config_str = serde_yaml::to_string(&local_supergraph_config)?; - let local_supergraph_config_dir = assert_fs::TempDir::new()?; - let local_supergraph_config_path = - Utf8PathBuf::from_path_buf(local_supergraph_config_dir.path().to_path_buf()).unwrap(); - - let file_descriptor_type = setup_file_descriptor( - load_supergraph_config_from_file, - &local_supergraph_config_dir, - &local_supergraph_config_str, - &mut mock_read_stdin, - )?; - - // we never introspect subgraphs in this test, but we still have to account for the effect - let mut mock_introspect_subgraph = MockIntrospectSubgraph::new(); - mock_introspect_subgraph - .expect_introspect_subgraph() - .times(0); - - // init resolver with no target fed version - let resolver = SupergraphConfigResolver::default(); - - // determine whether to try to load from graph refs - let graph_ref = remote_subgraph_scenario - .as_ref() - .and_then(|remote_subgraph_scenario| { - if fetch_remote_subgraph_from_config { - None - } else { - Some(remote_subgraph_scenario.graph_ref.clone()) - } - }); - - // load remote subgraphs - let resolver = resolver - .load_remote_subgraphs(&mock_fetch_remote_subgraphs, graph_ref.as_ref()) - .await?; - - // load from the file descriptor - let resolver = resolver - .load_from_file_descriptor(&mut mock_read_stdin, Some(&file_descriptor_type))?; - - // validate that the correct effect has been invoked - mock_fetch_remote_subgraphs.checkpoint(); - - // fully resolve subgraphs into their SDLs - let fully_resolved_supergraph_config = resolver - .fully_resolve_subgraphs( - &mock_introspect_subgraph, - &mock_fetch_remote_subgraph, - Some(&local_supergraph_config_path), - ) - .await?; - - // validate that the correct effects have been invoked - mock_introspect_subgraph.checkpoint(); - mock_fetch_remote_subgraph.checkpoint(); - - // validate that the federation version is correct - assert_that!(fully_resolved_supergraph_config.federation_version()) - .is_equal_to(&local_supergraph_federation_version); - - Ok(()) - } - - /// Test showing that federation version is selected from the local supergraph config fed version - /// over remote composition version, or version inferred from resolved SDLs - /// For these tests, we only need to test against a remote schema source and a local one. - /// The sdl schema source was chosen as local, since it's the easiest one to configure - #[rstest] - /// Case: both local and remote subgraphs exist with fed 1 SDLs - #[case( - Some(remote_subgraph_scenario( - sdl(), - subgraph_name(), - routing_url(), - SubgraphFederationVersion::One - )), - Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::One)) - )] - /// Case: only a remote subgraph exists with a fed 1 SDL - #[case( - Some(remote_subgraph_scenario( - sdl(), - subgraph_name(), - routing_url(), - SubgraphFederationVersion::One - )), - None - )] - /// Case: only a local subgraph exists with a fed 1 SDL - #[case( - None, - Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::One)) - )] - /// Case: both local and remote subgraphs exist with fed 2 SDLs - #[case( - Some(remote_subgraph_scenario( - sdl(), - subgraph_name(), - routing_url(), - SubgraphFederationVersion::Two - )), - Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::Two)) - )] - /// Case: only a remote subgraph exists with a fed 2 SDL - #[case( - Some(remote_subgraph_scenario( - sdl(), - subgraph_name(), - routing_url(), - SubgraphFederationVersion::Two - )), - None - )] - /// Case: only a local subgraph exists with a fed 2 SDL - #[case( - None, - Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::Two)) - )] - /// Case: both local and remote subgraphs exist with varying fed version SDLs - #[case( - Some(remote_subgraph_scenario( - sdl(), - subgraph_name(), - routing_url(), - SubgraphFederationVersion::One - )), - Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::Two)) - )] - /// This test further uses #[values] to make sure we have a matrix of tests - /// All possible combinations result in using the remote federation version, - /// since that is the highest order of precedence in this socpe - #[trace] - #[tokio::test] - async fn test_select_federation_version_defaults_to_fed_two( - #[case] remote_subgraph_scenario: Option, - #[case] sdl_subgraph_scenario: Option, - // Dictates whether to load the remote supergraph schema from a the local config or using the --graph_ref flag - #[values(true, false)] fetch_remote_subgraph_from_config: bool, - // Dictates whether to load the local supergraph schema from a file or stdin - #[values(true, false)] load_supergraph_config_from_file: bool, - ) -> Result<()> { - let mut subgraphs = BTreeMap::new(); - - let mut mock_fetch_remote_subgraphs = MockFetchRemoteSubgraphs::new(); - let mut mock_fetch_remote_subgraph = MockFetchRemoteSubgraph::new(); - - setup_remote_subgraph_scenario( - fetch_remote_subgraph_from_config, - remote_subgraph_scenario.as_ref(), - &mut subgraphs, - &mut mock_fetch_remote_subgraphs, - &mut mock_fetch_remote_subgraph, - ); - - setup_sdl_subgraph_scenario(sdl_subgraph_scenario.as_ref(), &mut subgraphs); - - let mut mock_read_stdin = MockReadStdin::new(); - - let local_supergraph_config = SupergraphConfig::new(subgraphs, None); - let local_supergraph_config_str = serde_yaml::to_string(&local_supergraph_config)?; - let local_supergraph_config_dir = assert_fs::TempDir::new()?; - let local_supergraph_config_path = - Utf8PathBuf::from_path_buf(local_supergraph_config_dir.path().to_path_buf()).unwrap(); - - let file_descriptor_type = setup_file_descriptor( - load_supergraph_config_from_file, - &local_supergraph_config_dir, - &local_supergraph_config_str, - &mut mock_read_stdin, - )?; - - // we never introspect subgraphs in this test, but we still have to account for the effect - let mut mock_introspect_subgraph = MockIntrospectSubgraph::new(); - mock_introspect_subgraph - .expect_introspect_subgraph() - .times(0); - - // init resolver with no target fed version - let resolver = SupergraphConfigResolver::default(); - - // determine whether to try to load from graph refs - let graph_ref = remote_subgraph_scenario - .as_ref() - .and_then(|remote_subgraph_scenario| { - if fetch_remote_subgraph_from_config { - None - } else { - Some(remote_subgraph_scenario.graph_ref.clone()) - } - }); - - // load remote subgraphs - let resolver = resolver - .load_remote_subgraphs(&mock_fetch_remote_subgraphs, graph_ref.as_ref()) - .await?; - - // load from the file descriptor - let resolver = resolver - .load_from_file_descriptor(&mut mock_read_stdin, Some(&file_descriptor_type))?; - - // validate that the correct effect has been invoked - mock_fetch_remote_subgraphs.checkpoint(); - - // fully resolve subgraphs into their SDLs - let fully_resolved_supergraph_config = resolver - .fully_resolve_subgraphs( - &mock_introspect_subgraph, - &mock_fetch_remote_subgraph, - Some(&local_supergraph_config_path), - ) - .await?; - - // validate that the correct effects have been invoked - mock_introspect_subgraph.checkpoint(); - mock_fetch_remote_subgraph.checkpoint(); - - // validate that the federation version is correct - assert_that!(fully_resolved_supergraph_config.federation_version()) - .is_equal_to(&FederationVersion::LatestFedTwo); - - Ok(()) - } - - fn setup_sdl_subgraph_scenario( - sdl_subgraph_scenario: Option<&SdlSubgraphScenario>, - local_subgraphs: &mut BTreeMap, - ) { - // If the sdl subgraph scenario exists, add a SubgraphConfig for it to the supergraph config - if let Some(sdl_subgraph_scenario) = sdl_subgraph_scenario { - let schema_source = SchemaSource::Sdl { - sdl: sdl_subgraph_scenario.sdl.to_string(), - }; - let subgraph_config = SubgraphConfig { - routing_url: None, - schema: schema_source, - }; - local_subgraphs.insert("sdl-subgraph".to_string(), subgraph_config); - } - } - - fn setup_remote_subgraph_scenario( - fetch_remote_subgraph_from_config: bool, - remote_subgraph_scenario: Option<&RemoteSubgraphScenario>, - local_subgraphs: &mut BTreeMap, - mock_fetch_remote_subgraphs: &mut MockFetchRemoteSubgraphs, - mock_fetch_remote_subgraph: &mut MockFetchRemoteSubgraph, - ) { - if let Some(remote_subgraph_scenario) = remote_subgraph_scenario { - let schema_source = SchemaSource::Subgraph { - graphref: remote_subgraph_scenario.graph_ref.to_string(), - subgraph: remote_subgraph_scenario.subgraph_name.to_string(), - }; - let subgraph_config = SubgraphConfig { - routing_url: Some(remote_subgraph_scenario.routing_url.clone()), - schema: schema_source, - }; - // If the remote subgraph scenario exists, add a SubgraphConfig for it to the supergraph config - if fetch_remote_subgraph_from_config { - local_subgraphs.insert("remote-subgraph".to_string(), subgraph_config); - mock_fetch_remote_subgraphs - .expect_fetch_remote_subgraphs() - .times(0); - } - // Otherwise, fetch it by --graph_ref - else { - mock_fetch_remote_subgraphs - .expect_fetch_remote_subgraphs() - .times(1) - .with(predicate::eq(remote_subgraph_scenario.graph_ref.clone())) - .returning({ - let subgraph_name = remote_subgraph_scenario.subgraph_name.to_string(); - move |_| { - Ok(BTreeMap::from_iter([( - subgraph_name.to_string(), - subgraph_config.clone(), - )])) - } - }); - } - - // we always fetch the SDLs from remote - mock_fetch_remote_subgraph - .expect_fetch_remote_subgraph() - .times(1) - .with( - predicate::eq(remote_subgraph_scenario.graph_ref.clone()), - predicate::eq(remote_subgraph_scenario.subgraph_name.clone()), - ) - .returning({ - let subgraph_name = remote_subgraph_scenario.subgraph_name.to_string(); - let routing_url = remote_subgraph_scenario.routing_url.to_string(); - let sdl = remote_subgraph_scenario.sdl.to_string(); - move |_, _| { - Ok(RemoteSubgraph::builder() - .name(subgraph_name.to_string()) - .routing_url(routing_url.to_string()) - .schema(sdl.to_string()) - .build()) - } - }); - } else { - // if no remote subgraph schemas exist, don't expect them to fetched - mock_fetch_remote_subgraphs - .expect_fetch_remote_subgraphs() - .times(0); - mock_fetch_remote_subgraph - .expect_fetch_remote_subgraph() - .times(0); - } - } - - fn setup_file_descriptor( - load_supergraph_config_from_file: bool, - local_supergraph_config_dir: &TempDir, - local_supergraph_config_str: &str, - mock_read_stdin: &mut MockReadStdin, - ) -> Result { - let file_descriptor_type = if load_supergraph_config_from_file { - // if we should be loading the supergraph config from a file, set up the temp files to do so - let local_supergraph_config_file = local_supergraph_config_dir.child("supergraph.yaml"); - local_supergraph_config_file.touch()?; - local_supergraph_config_file.write_str(local_supergraph_config_str)?; - let path = - Utf8PathBuf::from_path_buf(local_supergraph_config_file.path().to_path_buf()) - .unwrap(); - mock_read_stdin.expect_read_stdin().times(0); - FileDescriptorType::File(path) - } else { - // otherwise, mock read_stdin to provide the string back - mock_read_stdin - .expect_read_stdin() - .times(1) - .with(predicate::eq("supergraph config")) - .returning({ - let local_supergraph_config_str = local_supergraph_config_str.to_string(); - move |_| Ok(local_supergraph_config_str.to_string()) - }); - FileDescriptorType::Stdin - }; - Ok(file_descriptor_type) - } -} diff --git a/src/composition/supergraph/config/resolver/mod.rs b/src/composition/supergraph/config/resolver/mod.rs index a71123d19..ea8b53ac2 100644 --- a/src/composition/supergraph/config/resolver/mod.rs +++ b/src/composition/supergraph/config/resolver/mod.rs @@ -253,3 +253,648 @@ impl SupergraphConfigResolver { } } } + +#[cfg(test)] +mod tests { + use std::{collections::BTreeMap, str::FromStr}; + + use anyhow::Result; + use apollo_federation_types::config::{ + FederationVersion, SchemaSource, SubgraphConfig, SupergraphConfig, + }; + use assert_fs::{ + prelude::{FileTouch, FileWriteStr, PathChild}, + TempDir, + }; + use camino::Utf8PathBuf; + use mockall::predicate; + use rstest::rstest; + use semver::Version; + use speculoos::prelude::*; + + use crate::{ + composition::supergraph::config::scenario::*, + utils::{ + effect::{ + fetch_remote_subgraph::{MockFetchRemoteSubgraph, RemoteSubgraph}, + fetch_remote_subgraphs::MockFetchRemoteSubgraphs, + introspect::MockIntrospectSubgraph, + read_stdin::MockReadStdin, + }, + parsers::FileDescriptorType, + }, + }; + + use super::SupergraphConfigResolver; + + /// Test showing that federation version is selected from the user-specified fed version + /// over local supergraph config, remote composition version, or version inferred from + /// resolved SDLs + /// For these tests, we only need to test against a remote schema source and a local one. + /// The sdl schema source was chosen as local, since it's the easiest one to configure + #[rstest] + /// Case: both local and remote subgraphs exist with fed 1 SDLs + #[case( + Some(remote_subgraph_scenario( + sdl(), + subgraph_name(), + routing_url(), + SubgraphFederationVersion::One + )), + Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::One)) + )] + /// Case: only a remote subgraph exists with a fed 1 SDL + #[case( + Some(remote_subgraph_scenario( + sdl(), + subgraph_name(), + routing_url(), + SubgraphFederationVersion::One + )), + None + )] + /// Case: only a local subgraph exists with a fed 1 SDL + #[case( + None, + Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::One)) + )] + /// Case: both local and remote subgraphs exist with fed 2 SDLs + #[case( + Some(remote_subgraph_scenario( + sdl(), + subgraph_name(), + routing_url(), + SubgraphFederationVersion::Two + )), + Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::Two)) + )] + /// Case: only a remote subgraph exists with a fed 2 SDL + #[case( + Some(remote_subgraph_scenario( + sdl(), + subgraph_name(), + routing_url(), + SubgraphFederationVersion::Two + )), + None + )] + /// Case: only a local subgraph exists with a fed 2 SDL + #[case( + None, + Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::Two)) + )] + /// Case: both local and remote subgraphs exist with varying fed version SDLs + #[case( + Some(remote_subgraph_scenario( + sdl(), + subgraph_name(), + routing_url(), + SubgraphFederationVersion::One + )), + Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::Two)) + )] + /// This test further uses #[values] to make sure we have a matrix of tests + /// All possible combinations result in using the target federation version, + /// since that is the highest order of precedence + #[tokio::test] + async fn test_select_federation_version_from_user_selection( + #[case] remote_subgraph_scenario: Option, + #[case] sdl_subgraph_scenario: Option, + // Dictates whether to load the remote supergraph schema from a the local config or using the --graph_ref flag + #[values(true, false)] fetch_remote_subgraph_from_config: bool, + // Dictates whether to load the local supergraph schema from a file or stdin + #[values(true, false)] load_supergraph_config_from_file: bool, + // The optional fed version attached to a local supergraph config + #[values(Some(FederationVersion::LatestFedOne), None)] + local_supergraph_federation_version: Option, + ) -> Result<()> { + // user-specified federation version + let target_federation_version = + FederationVersion::ExactFedTwo(Version::from_str("2.7.1").unwrap()); + let mut subgraphs = BTreeMap::new(); + + let mut mock_fetch_remote_subgraphs = MockFetchRemoteSubgraphs::new(); + let mut mock_fetch_remote_subgraph = MockFetchRemoteSubgraph::new(); + + setup_remote_subgraph_scenario( + fetch_remote_subgraph_from_config, + remote_subgraph_scenario.as_ref(), + &mut subgraphs, + &mut mock_fetch_remote_subgraphs, + &mut mock_fetch_remote_subgraph, + ); + + setup_sdl_subgraph_scenario(sdl_subgraph_scenario.as_ref(), &mut subgraphs); + + let mut mock_read_stdin = MockReadStdin::new(); + + let local_supergraph_config = + SupergraphConfig::new(subgraphs, local_supergraph_federation_version); + let local_supergraph_config_str = serde_yaml::to_string(&local_supergraph_config)?; + let local_supergraph_config_dir = assert_fs::TempDir::new()?; + let local_supergraph_config_path = + Utf8PathBuf::from_path_buf(local_supergraph_config_dir.path().to_path_buf()).unwrap(); + + let file_descriptor_type = setup_file_descriptor( + load_supergraph_config_from_file, + &local_supergraph_config_dir, + &local_supergraph_config_str, + &mut mock_read_stdin, + )?; + + // we never introspect subgraphs in this test, but we still have to account for the effect + let mut mock_introspect_subgraph = MockIntrospectSubgraph::new(); + mock_introspect_subgraph + .expect_introspect_subgraph() + .times(0); + + // init resolver with a target fed version + let resolver = SupergraphConfigResolver::new(target_federation_version.clone()); + + // determine whether to try to load from graph refs + let graph_ref = remote_subgraph_scenario + .as_ref() + .and_then(|remote_subgraph_scenario| { + if fetch_remote_subgraph_from_config { + None + } else { + Some(remote_subgraph_scenario.graph_ref.clone()) + } + }); + + // load remote subgraphs + let resolver = resolver + .load_remote_subgraphs(&mock_fetch_remote_subgraphs, graph_ref.as_ref()) + .await?; + + // load from the file descriptor + let resolver = resolver + .load_from_file_descriptor(&mut mock_read_stdin, Some(&file_descriptor_type))?; + + // validate that the correct effect has been invoked + mock_fetch_remote_subgraphs.checkpoint(); + + // fully resolve subgraphs into their SDLs + let fully_resolved_supergraph_config = resolver + .fully_resolve_subgraphs( + &mock_introspect_subgraph, + &mock_fetch_remote_subgraph, + Some(&local_supergraph_config_path), + ) + .await?; + + // validate that the correct effects have been invoked + mock_introspect_subgraph.checkpoint(); + mock_fetch_remote_subgraph.checkpoint(); + + // validate that the federation version is correct + assert_that!(fully_resolved_supergraph_config.federation_version()) + .is_equal_to(&target_federation_version); + + Ok(()) + } + + /// Test showing that federation version is selected from the local supergraph config fed version + /// over remote composition version, or version inferred from resolved SDLs + /// For these tests, we only need to test against a remote schema source and a local one. + /// The sdl schema source was chosen as local, since it's the easiest one to configure + #[rstest] + /// Case: both local and remote subgraphs exist with fed 1 SDLs + #[case( + Some(remote_subgraph_scenario( + sdl(), + subgraph_name(), + routing_url(), + SubgraphFederationVersion::One + )), + Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::One)) + )] + /// Case: only a remote subgraph exists with a fed 1 SDL + #[case( + Some(remote_subgraph_scenario( + sdl(), + subgraph_name(), + routing_url(), + SubgraphFederationVersion::One + )), + None + )] + /// Case: only a local subgraph exists with a fed 1 SDL + #[case( + None, + Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::One)) + )] + /// Case: both local and remote subgraphs exist with fed 2 SDLs + #[case( + Some(remote_subgraph_scenario( + sdl(), + subgraph_name(), + routing_url(), + SubgraphFederationVersion::Two + )), + Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::Two)) + )] + /// Case: only a remote subgraph exists with a fed 2 SDL + #[case( + Some(remote_subgraph_scenario( + sdl(), + subgraph_name(), + routing_url(), + SubgraphFederationVersion::Two + )), + None + )] + /// Case: only a local subgraph exists with a fed 2 SDL + #[case( + None, + Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::Two)) + )] + /// Case: both local and remote subgraphs exist with varying fed version SDLs + #[case( + Some(remote_subgraph_scenario( + sdl(), + subgraph_name(), + routing_url(), + SubgraphFederationVersion::One + )), + Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::Two)) + )] + /// This test further uses #[values] to make sure we have a matrix of tests + /// All possible combinations result in using the remote federation version, + /// since that is the highest order of precedence in this socpe + #[trace] + #[tokio::test] + async fn test_select_federation_version_from_local_supergraph_config( + #[case] remote_subgraph_scenario: Option, + #[case] sdl_subgraph_scenario: Option, + // Dictates whether to load the remote supergraph schema from a the local config or using the --graph_ref flag + #[values(true, false)] fetch_remote_subgraph_from_config: bool, + // Dictates whether to load the local supergraph schema from a file or stdin + #[values(true, false)] load_supergraph_config_from_file: bool, + ) -> Result<()> { + // user-specified federation version (from local supergraph config) + let local_supergraph_federation_version = + FederationVersion::ExactFedTwo(Version::from_str("2.7.1").unwrap()); + + let mut subgraphs = BTreeMap::new(); + + let mut mock_fetch_remote_subgraphs = MockFetchRemoteSubgraphs::new(); + let mut mock_fetch_remote_subgraph = MockFetchRemoteSubgraph::new(); + + setup_remote_subgraph_scenario( + fetch_remote_subgraph_from_config, + remote_subgraph_scenario.as_ref(), + &mut subgraphs, + &mut mock_fetch_remote_subgraphs, + &mut mock_fetch_remote_subgraph, + ); + + setup_sdl_subgraph_scenario(sdl_subgraph_scenario.as_ref(), &mut subgraphs); + + let mut mock_read_stdin = MockReadStdin::new(); + + let local_supergraph_config = + SupergraphConfig::new(subgraphs, Some(local_supergraph_federation_version.clone())); + let local_supergraph_config_str = serde_yaml::to_string(&local_supergraph_config)?; + let local_supergraph_config_dir = assert_fs::TempDir::new()?; + let local_supergraph_config_path = + Utf8PathBuf::from_path_buf(local_supergraph_config_dir.path().to_path_buf()).unwrap(); + + let file_descriptor_type = setup_file_descriptor( + load_supergraph_config_from_file, + &local_supergraph_config_dir, + &local_supergraph_config_str, + &mut mock_read_stdin, + )?; + + // we never introspect subgraphs in this test, but we still have to account for the effect + let mut mock_introspect_subgraph = MockIntrospectSubgraph::new(); + mock_introspect_subgraph + .expect_introspect_subgraph() + .times(0); + + // init resolver with no target fed version + let resolver = SupergraphConfigResolver::default(); + + // determine whether to try to load from graph refs + let graph_ref = remote_subgraph_scenario + .as_ref() + .and_then(|remote_subgraph_scenario| { + if fetch_remote_subgraph_from_config { + None + } else { + Some(remote_subgraph_scenario.graph_ref.clone()) + } + }); + + // load remote subgraphs + let resolver = resolver + .load_remote_subgraphs(&mock_fetch_remote_subgraphs, graph_ref.as_ref()) + .await?; + + // load from the file descriptor + let resolver = resolver + .load_from_file_descriptor(&mut mock_read_stdin, Some(&file_descriptor_type))?; + + // validate that the correct effect has been invoked + mock_fetch_remote_subgraphs.checkpoint(); + + // fully resolve subgraphs into their SDLs + let fully_resolved_supergraph_config = resolver + .fully_resolve_subgraphs( + &mock_introspect_subgraph, + &mock_fetch_remote_subgraph, + Some(&local_supergraph_config_path), + ) + .await?; + + // validate that the correct effects have been invoked + mock_introspect_subgraph.checkpoint(); + mock_fetch_remote_subgraph.checkpoint(); + + // validate that the federation version is correct + assert_that!(fully_resolved_supergraph_config.federation_version()) + .is_equal_to(&local_supergraph_federation_version); + + Ok(()) + } + + /// Test showing that federation version is selected from the local supergraph config fed version + /// over remote composition version, or version inferred from resolved SDLs + /// For these tests, we only need to test against a remote schema source and a local one. + /// The sdl schema source was chosen as local, since it's the easiest one to configure + #[rstest] + /// Case: both local and remote subgraphs exist with fed 1 SDLs + #[case( + Some(remote_subgraph_scenario( + sdl(), + subgraph_name(), + routing_url(), + SubgraphFederationVersion::One + )), + Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::One)) + )] + /// Case: only a remote subgraph exists with a fed 1 SDL + #[case( + Some(remote_subgraph_scenario( + sdl(), + subgraph_name(), + routing_url(), + SubgraphFederationVersion::One + )), + None + )] + /// Case: only a local subgraph exists with a fed 1 SDL + #[case( + None, + Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::One)) + )] + /// Case: both local and remote subgraphs exist with fed 2 SDLs + #[case( + Some(remote_subgraph_scenario( + sdl(), + subgraph_name(), + routing_url(), + SubgraphFederationVersion::Two + )), + Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::Two)) + )] + /// Case: only a remote subgraph exists with a fed 2 SDL + #[case( + Some(remote_subgraph_scenario( + sdl(), + subgraph_name(), + routing_url(), + SubgraphFederationVersion::Two + )), + None + )] + /// Case: only a local subgraph exists with a fed 2 SDL + #[case( + None, + Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::Two)) + )] + /// Case: both local and remote subgraphs exist with varying fed version SDLs + #[case( + Some(remote_subgraph_scenario( + sdl(), + subgraph_name(), + routing_url(), + SubgraphFederationVersion::One + )), + Some(sdl_subgraph_scenario(sdl(), subgraph_name(), SubgraphFederationVersion::Two)) + )] + /// This test further uses #[values] to make sure we have a matrix of tests + /// All possible combinations result in using the remote federation version, + /// since that is the highest order of precedence in this socpe + #[trace] + #[tokio::test] + async fn test_select_federation_version_defaults_to_fed_two( + #[case] remote_subgraph_scenario: Option, + #[case] sdl_subgraph_scenario: Option, + // Dictates whether to load the remote supergraph schema from a the local config or using the --graph_ref flag + #[values(true, false)] fetch_remote_subgraph_from_config: bool, + // Dictates whether to load the local supergraph schema from a file or stdin + #[values(true, false)] load_supergraph_config_from_file: bool, + ) -> Result<()> { + let mut subgraphs = BTreeMap::new(); + + let mut mock_fetch_remote_subgraphs = MockFetchRemoteSubgraphs::new(); + let mut mock_fetch_remote_subgraph = MockFetchRemoteSubgraph::new(); + + setup_remote_subgraph_scenario( + fetch_remote_subgraph_from_config, + remote_subgraph_scenario.as_ref(), + &mut subgraphs, + &mut mock_fetch_remote_subgraphs, + &mut mock_fetch_remote_subgraph, + ); + + setup_sdl_subgraph_scenario(sdl_subgraph_scenario.as_ref(), &mut subgraphs); + + let mut mock_read_stdin = MockReadStdin::new(); + + let local_supergraph_config = SupergraphConfig::new(subgraphs, None); + let local_supergraph_config_str = serde_yaml::to_string(&local_supergraph_config)?; + let local_supergraph_config_dir = assert_fs::TempDir::new()?; + let local_supergraph_config_path = + Utf8PathBuf::from_path_buf(local_supergraph_config_dir.path().to_path_buf()).unwrap(); + + let file_descriptor_type = setup_file_descriptor( + load_supergraph_config_from_file, + &local_supergraph_config_dir, + &local_supergraph_config_str, + &mut mock_read_stdin, + )?; + + // we never introspect subgraphs in this test, but we still have to account for the effect + let mut mock_introspect_subgraph = MockIntrospectSubgraph::new(); + mock_introspect_subgraph + .expect_introspect_subgraph() + .times(0); + + // init resolver with no target fed version + let resolver = SupergraphConfigResolver::default(); + + // determine whether to try to load from graph refs + let graph_ref = remote_subgraph_scenario + .as_ref() + .and_then(|remote_subgraph_scenario| { + if fetch_remote_subgraph_from_config { + None + } else { + Some(remote_subgraph_scenario.graph_ref.clone()) + } + }); + + // load remote subgraphs + let resolver = resolver + .load_remote_subgraphs(&mock_fetch_remote_subgraphs, graph_ref.as_ref()) + .await?; + + // load from the file descriptor + let resolver = resolver + .load_from_file_descriptor(&mut mock_read_stdin, Some(&file_descriptor_type))?; + + // validate that the correct effect has been invoked + mock_fetch_remote_subgraphs.checkpoint(); + + // fully resolve subgraphs into their SDLs + let fully_resolved_supergraph_config = resolver + .fully_resolve_subgraphs( + &mock_introspect_subgraph, + &mock_fetch_remote_subgraph, + Some(&local_supergraph_config_path), + ) + .await?; + + // validate that the correct effects have been invoked + mock_introspect_subgraph.checkpoint(); + mock_fetch_remote_subgraph.checkpoint(); + + // validate that the federation version is correct + assert_that!(fully_resolved_supergraph_config.federation_version()) + .is_equal_to(&FederationVersion::LatestFedTwo); + + Ok(()) + } + + fn setup_sdl_subgraph_scenario( + sdl_subgraph_scenario: Option<&SdlSubgraphScenario>, + local_subgraphs: &mut BTreeMap, + ) { + // If the sdl subgraph scenario exists, add a SubgraphConfig for it to the supergraph config + if let Some(sdl_subgraph_scenario) = sdl_subgraph_scenario { + let schema_source = SchemaSource::Sdl { + sdl: sdl_subgraph_scenario.sdl.to_string(), + }; + let subgraph_config = SubgraphConfig { + routing_url: None, + schema: schema_source, + }; + local_subgraphs.insert("sdl-subgraph".to_string(), subgraph_config); + } + } + + fn setup_remote_subgraph_scenario( + fetch_remote_subgraph_from_config: bool, + remote_subgraph_scenario: Option<&RemoteSubgraphScenario>, + local_subgraphs: &mut BTreeMap, + mock_fetch_remote_subgraphs: &mut MockFetchRemoteSubgraphs, + mock_fetch_remote_subgraph: &mut MockFetchRemoteSubgraph, + ) { + if let Some(remote_subgraph_scenario) = remote_subgraph_scenario { + let schema_source = SchemaSource::Subgraph { + graphref: remote_subgraph_scenario.graph_ref.to_string(), + subgraph: remote_subgraph_scenario.subgraph_name.to_string(), + }; + let subgraph_config = SubgraphConfig { + routing_url: Some(remote_subgraph_scenario.routing_url.clone()), + schema: schema_source, + }; + // If the remote subgraph scenario exists, add a SubgraphConfig for it to the supergraph config + if fetch_remote_subgraph_from_config { + local_subgraphs.insert("remote-subgraph".to_string(), subgraph_config); + mock_fetch_remote_subgraphs + .expect_fetch_remote_subgraphs() + .times(0); + } + // Otherwise, fetch it by --graph_ref + else { + mock_fetch_remote_subgraphs + .expect_fetch_remote_subgraphs() + .times(1) + .with(predicate::eq(remote_subgraph_scenario.graph_ref.clone())) + .returning({ + let subgraph_name = remote_subgraph_scenario.subgraph_name.to_string(); + move |_| { + Ok(BTreeMap::from_iter([( + subgraph_name.to_string(), + subgraph_config.clone(), + )])) + } + }); + } + + // we always fetch the SDLs from remote + mock_fetch_remote_subgraph + .expect_fetch_remote_subgraph() + .times(1) + .with( + predicate::eq(remote_subgraph_scenario.graph_ref.clone()), + predicate::eq(remote_subgraph_scenario.subgraph_name.clone()), + ) + .returning({ + let subgraph_name = remote_subgraph_scenario.subgraph_name.to_string(); + let routing_url = remote_subgraph_scenario.routing_url.to_string(); + let sdl = remote_subgraph_scenario.sdl.to_string(); + move |_, _| { + Ok(RemoteSubgraph::builder() + .name(subgraph_name.to_string()) + .routing_url(routing_url.to_string()) + .schema(sdl.to_string()) + .build()) + } + }); + } else { + // if no remote subgraph schemas exist, don't expect them to fetched + mock_fetch_remote_subgraphs + .expect_fetch_remote_subgraphs() + .times(0); + mock_fetch_remote_subgraph + .expect_fetch_remote_subgraph() + .times(0); + } + } + + fn setup_file_descriptor( + load_supergraph_config_from_file: bool, + local_supergraph_config_dir: &TempDir, + local_supergraph_config_str: &str, + mock_read_stdin: &mut MockReadStdin, + ) -> Result { + let file_descriptor_type = if load_supergraph_config_from_file { + // if we should be loading the supergraph config from a file, set up the temp files to do so + let local_supergraph_config_file = local_supergraph_config_dir.child("supergraph.yaml"); + local_supergraph_config_file.touch()?; + local_supergraph_config_file.write_str(local_supergraph_config_str)?; + let path = + Utf8PathBuf::from_path_buf(local_supergraph_config_file.path().to_path_buf()) + .unwrap(); + mock_read_stdin.expect_read_stdin().times(0); + FileDescriptorType::File(path) + } else { + // otherwise, mock read_stdin to provide the string back + mock_read_stdin + .expect_read_stdin() + .times(1) + .with(predicate::eq("supergraph config")) + .returning({ + let local_supergraph_config_str = local_supergraph_config_str.to_string(); + move |_| Ok(local_supergraph_config_str.to_string()) + }); + FileDescriptorType::Stdin + }; + Ok(file_descriptor_type) + } +} diff --git a/src/composition/supergraph/config/unresolved/subgraph.rs b/src/composition/supergraph/config/unresolved/subgraph.rs index c55aaccf8..ce699a9b4 100644 --- a/src/composition/supergraph/config/unresolved/subgraph.rs +++ b/src/composition/supergraph/config/unresolved/subgraph.rs @@ -41,3 +41,12 @@ impl UnresolvedSubgraph { } } } + +impl From for SubgraphConfig { + fn from(value: UnresolvedSubgraph) -> Self { + SubgraphConfig { + routing_url: value.routing_url, + schema: value.schema, + } + } +}