diff --git a/src/command/lsp/errors.rs b/src/command/lsp/errors.rs index 611acb07f..304c4696e 100644 --- a/src/command/lsp/errors.rs +++ b/src/command/lsp/errors.rs @@ -1,21 +1,11 @@ -use crate::composition::supergraph::config::resolver::{ - LoadRemoteSubgraphsError, LoadSupergraphConfigError, ResolveSupergraphConfigError, -}; -use anyhow::Error; -use std::path::PathBuf; +use camino::Utf8PathBuf; + +use crate::composition::runner::errors::RunCompositionError; #[derive(thiserror::Error, Debug)] -pub enum SupergraphConfigLazyResolutionError { - #[error("Could not instantiate Studio Client")] - StudioClientInitialisationFailed(#[from] Error), - #[error("Could not load remote subgraphs")] - LoadRemoteSubgraphsFailed(#[from] LoadRemoteSubgraphsError), - #[error("Could not load supergraph config from local file")] - LoadLocalSupergraphConfigFailed(#[from] LoadSupergraphConfigError), - #[error("Could not resolve local and remote elements into complete SupergraphConfig")] - ResolveSupergraphConfigFailed(#[from] ResolveSupergraphConfigError), - #[error("Path `{0}` does not point to a file")] - PathDoesNotPointToAFile(PathBuf), +pub enum StartCompositionError { + #[error("Could not convert Supergraph path to URL")] + SupergraphYamlUrlConversionFailed(Utf8PathBuf), + #[error("Could not run initial composition")] + InitialCompositionFailed(#[from] RunCompositionError), } -#[derive(thiserror::Error, Debug)] -pub enum CompositionError {} diff --git a/src/command/lsp/mod.rs b/src/command/lsp/mod.rs index 7d48d0f6e..e9993e3e8 100644 --- a/src/command/lsp/mod.rs +++ b/src/command/lsp/mod.rs @@ -4,8 +4,8 @@ use std::collections::HashMap; use std::env::temp_dir; use std::io::stdin; -use anyhow::{anyhow, Error}; -use apollo_federation_types::config::FederationVersion; +use anyhow::Error; +use apollo_federation_types::config::FederationVersion::LatestFedTwo; use apollo_language_server::{ApolloLanguageServer, Config}; use camino::Utf8PathBuf; use clap::Parser; @@ -17,8 +17,8 @@ use tower_lsp::Server; use tracing::debug; use url::Url; -use crate::command::lsp::errors::SupergraphConfigLazyResolutionError; -use crate::command::lsp::errors::SupergraphConfigLazyResolutionError::PathDoesNotPointToAFile; +use crate::command::lsp::errors::StartCompositionError; +use crate::command::lsp::errors::StartCompositionError::SupergraphYamlUrlConversionFailed; use crate::composition::events::CompositionEvent; use crate::composition::runner::Runner; use crate::composition::supergraph::binary::OutputTarget; @@ -27,6 +27,8 @@ use crate::composition::supergraph::config::resolver::{ ResolveSupergraphConfigError, SupergraphConfigResolver, }; use crate::composition::supergraph::install::InstallSupergraph; +use crate::composition::SupergraphConfigResolutionError; +use crate::composition::SupergraphConfigResolutionError::PathDoesNotPointToAFile; use crate::composition::{ CompositionError, CompositionSubgraphAdded, CompositionSubgraphRemoved, CompositionSuccess, }; @@ -101,16 +103,18 @@ async fn run_lsp(client_config: StudioClientConfig, lsp_opts: LspOpts) -> RoverR let studio_client = client_config.get_authenticated_client(&lsp_opts.plugin_opts.profile)?; // Resolve Supergraph Config -> Lazy - let (lazily_resolved_supergraph_config, supergraph_content_root) = - generate_lazily_resolved_supergraph_config( - &studio_client, - supergraph_yaml_path.clone(), - ) - .await?; + let lazily_resolved_supergraph_config = generate_lazily_resolved_supergraph_config( + &studio_client, + supergraph_yaml_path.clone(), + ) + .await?; + let supergraph_yaml_url = Url::from_file_path(supergraph_yaml_path.clone()) + .map_err(|_| SupergraphYamlUrlConversionFailed(supergraph_yaml_path.clone()))?; + debug!("Supergraph Config Root: {:?}", supergraph_yaml_url); // Generate the config needed to spin up the Language Server let (service, socket, _receiver) = ApolloLanguageServer::build_service( Config { - root_uri: supergraph_content_root, + root_uri: String::from(supergraph_yaml_url.clone()), enable_auto_composition: false, force_federation: false, disable_telemetry: false, @@ -122,8 +126,6 @@ async fn run_lsp(client_config: StudioClientConfig, lsp_opts: LspOpts) -> RoverR .map(|(a, b)| (a.to_string(), b.schema().clone())), ), ); - let supergraph_yaml_url = Url::from_file_path(supergraph_yaml_path) - .map_err(|_| anyhow!("Failed to convert supergraph yaml path to url"))?; // Start running composition start_composition( lazily_resolved_supergraph_config, @@ -133,7 +135,7 @@ async fn run_lsp(client_config: StudioClientConfig, lsp_opts: LspOpts) -> RoverR lsp_opts, service.inner().to_owned(), ) - .await; + .await?; (service, socket) } }; @@ -148,7 +150,7 @@ async fn run_lsp(client_config: StudioClientConfig, lsp_opts: LspOpts) -> RoverR async fn generate_lazily_resolved_supergraph_config( studio_client: &StudioClient, supergraph_yaml_path: Utf8PathBuf, -) -> Result<(LazilyResolvedSupergraphConfig, String), SupergraphConfigLazyResolutionError> { +) -> Result { // Get the SupergraphConfig in a form we can use let supergraph_config = SupergraphConfigResolver::default() .load_remote_subgraphs(studio_client, None) @@ -158,12 +160,9 @@ async fn generate_lazily_resolved_supergraph_config( Some(&FileDescriptorType::File(supergraph_yaml_path.clone())), )?; if let Some(parent) = supergraph_yaml_path.parent() { - Ok(( - supergraph_config - .lazily_resolve_subgraphs(&parent.to_owned()) - .await?, - parent.to_string(), - )) + Ok(supergraph_config + .lazily_resolve_subgraphs(&parent.to_owned(), &supergraph_yaml_path) + .await?) } else { Err(PathDoesNotPointToAFile( supergraph_yaml_path.into_std_path_buf(), @@ -178,16 +177,14 @@ async fn start_composition( studio_client: StudioClient, lsp_opts: LspOpts, language_server: ApolloLanguageServer, -) { +) -> Result<(), StartCompositionError> { + let federation_version = lazily_resolved_supergraph_config + .federation_version() + .clone() + .unwrap_or(LatestFedTwo); + // Spawn a separate thread to handle composition and passing that data to the language server tokio::spawn(async move { - // Create a supergraph binary - // TODO: Check defaulting behaviour here and see if we need to centralise - let federation_version = lazily_resolved_supergraph_config - .federation_version() - .clone() - .unwrap_or(FederationVersion::LatestFedTwo); - // TODO: Let the supergraph binary exist inside its own task that can respond to being re-installed etc. let supergraph_binary = InstallSupergraph::new(federation_version.clone(), client_config.clone()) @@ -218,6 +215,7 @@ async fn start_composition( FsWriteFile::default(), OutputTarget::Stdout, Utf8PathBuf::try_from(temp_dir())?, + true, ) .run(); @@ -288,4 +286,5 @@ async fn start_composition( } Ok::<(), Error>(()) }); + Ok(()) } diff --git a/src/composition/mod.rs b/src/composition/mod.rs index a66bf2073..ab518b96a 100644 --- a/src/composition/mod.rs +++ b/src/composition/mod.rs @@ -1,5 +1,7 @@ use std::fmt::Debug; +use std::path::PathBuf; +use anyhow::Error; use apollo_federation_types::config::SchemaSource; use apollo_federation_types::{ config::FederationVersion, @@ -7,6 +9,10 @@ use apollo_federation_types::{ }; use camino::Utf8PathBuf; +use crate::composition::supergraph::config::resolver::{ + LoadRemoteSubgraphsError, LoadSupergraphConfigError, ResolveSupergraphConfigError, +}; + pub mod events; pub mod runner; pub mod supergraph; @@ -26,6 +32,10 @@ pub struct CompositionSuccess { #[derive(thiserror::Error, Debug, Eq, PartialEq)] pub enum CompositionError { + #[error("Failed serialise supergraph schema to YAML")] + SupergraphYamlSerialisationFailed { error: String }, + #[error("Failed to write supergraph schema to temporary file")] + SupergraphSchemaTemporaryFileWriteFailed { error: String }, #[error("Failed to run the composition binary")] Binary { error: String }, #[error("Failed to parse output of `{binary} compose`")] @@ -48,3 +58,17 @@ pub struct CompositionSubgraphAdded { pub struct CompositionSubgraphRemoved { pub(crate) name: String, } + +#[derive(thiserror::Error, Debug)] +pub enum SupergraphConfigResolutionError { + #[error("Could not instantiate Studio Client")] + StudioClientInitialisationFailed(#[from] Error), + #[error("Could not load remote subgraphs")] + LoadRemoteSubgraphsFailed(#[from] LoadRemoteSubgraphsError), + #[error("Could not load supergraph config from local file")] + LoadLocalSupergraphConfigFailed(#[from] LoadSupergraphConfigError), + #[error("Could not resolve local and remote elements into complete SupergraphConfig")] + ResolveSupergraphConfigFailed(#[from] ResolveSupergraphConfigError), + #[error("Path `{0}` does not point to a file")] + PathDoesNotPointToAFile(PathBuf), +} diff --git a/src/composition/runner/errors.rs b/src/composition/runner/errors.rs new file mode 100644 index 000000000..7731aa9dc --- /dev/null +++ b/src/composition/runner/errors.rs @@ -0,0 +1,40 @@ +//! A collection of errors that may occur when the runner executes, mainly wrapping up other +//! more disparate errors into standard types that are easier to pattern match against + +use std::io; + +use apollo_federation_types::config::FederationVersion; +use rover_std::RoverStdError; +use serde_yaml::Error; + +use crate::composition::supergraph::install::InstallSupergraphError; +use crate::composition::CompositionError; +use crate::composition::SupergraphConfigResolutionError; + +/// Error to encompass everything that could go wrong when running through the process of doing +/// composition. +#[derive(thiserror::Error, Debug)] +pub enum RunCompositionError { + /// Error if we cannot successfully resolve the SupergraphConfig for whatever reason + #[error("Could not resolve Supergraph Config")] + SupergraphConfigResolutionError(#[from] SupergraphConfigResolutionError), + /// Error if we cannot successfully serialise the SupergraphConfig to disk + #[error("Could not serialise Supergraph Config")] + SupergraphConfigSerialisationError(#[from] Error), + /// Error if the temporary directory that we create to house the temporarily serialised + /// version of the `supergraph.yaml` file cannot be created + #[error("Could not create temporary directory for Supergraph Config")] + TemporaryDirectoryCreationFailed(#[from] io::Error), + /// Error if we cannot write the temporary `supergraph.yaml` file + #[error("Could not write temporary Supergraph Config to disk")] + WritingTemporarySupergraphConfigFailed(#[from] RoverStdError), + /// Error if we cannot parse an exact version from the given version of Federation + #[error("Could not parse exact Federation Version from '{0}'")] + ParsingFederationVersionFailed(FederationVersion), + /// Error if we cannot install the given version of the supergraph binary + #[error("Could not install supergraph binary'")] + SupergraphBinaryInstallFailed(#[from] InstallSupergraphError), + /// Generic error type if we get an error from the process of composition itself + #[error("Composition error")] + CompositionError(#[from] CompositionError), +} diff --git a/src/composition/runner/mod.rs b/src/composition/runner/mod.rs index 59a39ffe4..7af8c4214 100644 --- a/src/composition/runner/mod.rs +++ b/src/composition/runner/mod.rs @@ -3,11 +3,37 @@ #![warn(missing_docs)] +use std::env::current_dir; +use std::io::stdin; +use std::{collections::BTreeMap, fmt::Debug}; + +use apollo_federation_types::config::{FederationVersion, SupergraphConfig}; +use buildstructor::Builder; +use camino::Utf8PathBuf; +use futures::stream::{BoxStream, StreamExt}; +use rover_client::shared::GraphRef; +use rover_std::warnln; +use tempfile::tempdir; +use tracing::debug; + use self::state::SetupSubgraphWatchers; -use crate::command::supergraph::compose::CompositionOutput; -use crate::composition::supergraph::config::full::FullyResolvedSubgraph; +use super::{ + events::CompositionEvent, + supergraph::{ + binary::{OutputTarget, SupergraphBinary}, + config::lazy::{LazilyResolvedSubgraph, LazilyResolvedSupergraphConfig}, + }, + watchers::{composition::CompositionWatcher, subgraphs::SubgraphWatchers}, + CompositionSuccess, +}; +use crate::composition::runner::errors::RunCompositionError; +use crate::composition::runner::errors::RunCompositionError::ParsingFederationVersionFailed; +use crate::composition::supergraph::config::full::{ + FullyResolvedSubgraph, FullyResolvedSupergraphConfig, +}; use crate::composition::supergraph::config::resolver::SupergraphConfigResolver; use crate::composition::supergraph::install::InstallSupergraph; +use crate::composition::SupergraphConfigResolutionError; use crate::options::LicenseAccepter; use crate::utils::effect::exec::TokioCommand; use crate::utils::effect::install::InstallBinary; @@ -24,29 +50,9 @@ use crate::{ client::StudioClientConfig, effect::{exec::ExecCommand, read_file::ReadFile, write_file::WriteFile}, }, - RoverError, RoverResult, -}; -use anyhow::anyhow; -use apollo_federation_types::config::{FederationVersion, SupergraphConfig}; -use buildstructor::Builder; -use camino::Utf8PathBuf; -use futures::stream::{BoxStream, StreamExt}; -use rover_client::shared::GraphRef; -use rover_std::warnln; -use std::env::current_dir; -use std::io::stdin; -use std::{collections::BTreeMap, fmt::Debug}; -use tempfile::tempdir; - -use super::{ - events::CompositionEvent, - supergraph::{ - binary::{OutputTarget, SupergraphBinary}, - config::lazy::{LazilyResolvedSubgraph, LazilyResolvedSupergraphConfig}, - }, - watchers::{composition::CompositionWatcher, subgraphs::SubgraphWatchers}, }; +pub mod errors; mod state; /// A struct for configuring and running subtasks for watching for both supergraph and subgraph @@ -80,47 +86,12 @@ pub struct OneShotComposition { impl OneShotComposition { /// Runs composition - pub async fn compose(self) -> RoverResult { - let mut stdin = stdin(); + pub async fn compose(self) -> Result { let write_file = FsWriteFile::default(); let read_file = FsReadFile::default(); let exec_command = TokioCommand::default(); - let supergraph_root = self.supergraph_yaml.clone().and_then(|file| match file { - FileDescriptorType::File(file) => { - let mut current_dir = current_dir().expect("Unable to get current directory path"); - - current_dir.push(file); - let path = Utf8PathBuf::from_path_buf(current_dir).unwrap(); - let parent = path.parent().unwrap().to_path_buf(); - Some(parent) - } - FileDescriptorType::Stdin => None, - }); - - let studio_client = self - .client_config - .get_authenticated_client(&self.profile.clone())?; - - // Get a FullyResolvedSupergraphConfig from first loading in any remote subgraphs and then - // a local supergraph config (if present) and then combining them into a fully resolved - // supergraph config - let resolver = SupergraphConfigResolver::default() - .load_remote_subgraphs(&studio_client, self.graph_ref.as_ref()) - .await? - .load_from_file_descriptor(&mut stdin, self.supergraph_yaml.as_ref())? - .fully_resolve_subgraphs( - &self.client_config, - &studio_client, - supergraph_root.as_ref(), - ) - .await?; - - // We convert the FullyResolvedSupergraphConfig into a Supergraph because it makes using - // Serde easier (said differently: we're using the Federation-rs types here for - // compatability with Federation-rs tooling later on when we use their supergraph binary to - // actually run composition) - let supergraph_config: SupergraphConfig = resolver.clone().into(); + let (resolver, supergraph_config) = self.create_supergraph_config().await?; // Convert the FullyResolvedSupergraphConfig to yaml before we save it let supergraph_config_yaml = serde_yaml::to_string(&supergraph_config)?; @@ -147,14 +118,12 @@ impl OneShotComposition { .unwrap_or(resolver.federation_version()); // We care about the exact version of the federation version because certain options aren't - // available before 2.9.0 and we gate on that version below + // available before 2.9.0, and we gate on that version below let exact_version = fed_version .get_exact() // This should be impossible to get to because we convert to a FederationVersion a few // lines above and so _should_ have an exact version - .ok_or(RoverError::new(anyhow!( - "failed to get exact Federation version" - )))?; + .ok_or(ParsingFederationVersionFailed(fed_version.clone()))?; // Making the output file mutable allows us to change it if we're using a version of the // supergraph binary that can't write to file (ie, anything pre-2.9.0) @@ -190,7 +159,50 @@ impl OneShotComposition { ) .await?; - Ok(result.into()) + Ok(result) + } + + async fn create_supergraph_config( + &self, + ) -> Result<(FullyResolvedSupergraphConfig, SupergraphConfig), SupergraphConfigResolutionError> + { + let mut stdin = stdin(); + let supergraph_root = self.supergraph_yaml.clone().and_then(|file| match file { + FileDescriptorType::File(file) => { + let mut current_dir = current_dir().expect("Unable to get current directory path"); + + current_dir.push(file); + let path = Utf8PathBuf::from_path_buf(current_dir).unwrap(); + let parent = path.parent().unwrap().to_path_buf(); + Some(parent) + } + FileDescriptorType::Stdin => None, + }); + + let studio_client = self + .client_config + .get_authenticated_client(&self.profile.clone())?; + + // Get a FullyResolvedSupergraphConfig from first loading in any remote subgraphs and then + // a local supergraph config (if present) and then combining them into a fully resolved + // supergraph config + let resolver = SupergraphConfigResolver::default() + .load_remote_subgraphs(&studio_client, self.graph_ref.as_ref()) + .await? + .load_from_file_descriptor(&mut stdin, self.supergraph_yaml.as_ref())? + .fully_resolve_subgraphs( + &self.client_config, + &studio_client, + supergraph_root.as_ref(), + ) + .await?; + + // We convert the FullyResolvedSupergraphConfig into a Supergraph because it makes using + // Serde easier (said differently: we're using the Federation-rs types here for + // compatability with Federation-rs tooling later on when we use their supergraph binary to + // actually run composition) + let supergraph_config: SupergraphConfig = resolver.clone().into(); + Ok((resolver, supergraph_config)) } } @@ -234,10 +246,12 @@ impl Runner { // We could return None here if we received a supergraph config directly from stdin. In // that case, we don't want to configure a watcher. let supergraph_config_watcher = if let Some(origin_path) = supergraph_config.origin_path() { + debug!("Configuring supergraph file watcher for: {}", origin_path); let f = FileWatcher::new(origin_path.clone()); let watcher = SupergraphConfigWatcher::new(f, supergraph_config); Some(watcher) } else { + debug!("Not configuring supergraph file watcher"); None }; Runner { @@ -261,6 +275,7 @@ impl Runner { write_file: WriteF, output_target: OutputTarget, temp_dir: Utf8PathBuf, + compose_on_initialisation: bool, ) -> Runner> where ReadF: ReadFile + Debug + Eq + PartialEq + Send + Sync + 'static, @@ -276,6 +291,7 @@ impl Runner { .write_file(write_file) .output_target(output_target) .temp_dir(temp_dir) + .compose_on_initialisation(compose_on_initialisation) .build(); Runner { state: state::Run { diff --git a/src/composition/supergraph/config/resolver/mod.rs b/src/composition/supergraph/config/resolver/mod.rs index ea8b53ac2..16ce3cbed 100644 --- a/src/composition/supergraph/config/resolver/mod.rs +++ b/src/composition/supergraph/config/resolver/mod.rs @@ -20,6 +20,15 @@ use apollo_federation_types::config::{ use camino::Utf8PathBuf; use rover_client::shared::GraphRef; +use self::state::ResolveSubgraphs; +use super::{ + error::ResolveSubgraphError, full::FullyResolvedSupergraphConfig, + lazy::LazilyResolvedSupergraphConfig, unresolved::UnresolvedSupergraphConfig, +}; +use crate::composition::supergraph::config::federation::{ + FederationVersionMismatch, FederationVersionResolver, + FederationVersionResolverFromSupergraphConfig, +}; use crate::{ utils::{ effect::{ @@ -32,19 +41,6 @@ use crate::{ RoverError, }; -use self::state::ResolveSubgraphs; - -use super::{ - error::ResolveSubgraphError, - federation::{ - FederationVersionMismatch, FederationVersionResolver, - FederationVersionResolverFromSupergraphConfig, - }, - full::FullyResolvedSupergraphConfig, - lazy::LazilyResolvedSupergraphConfig, - unresolved::UnresolvedSupergraphConfig, -}; - mod state; /// This is a state-based resolver for the different stages of resolving a supergraph config @@ -235,11 +231,13 @@ impl SupergraphConfigResolver { pub async fn lazily_resolve_subgraphs( &self, supergraph_config_root: &Utf8PathBuf, + supergraph_yaml_path: &Utf8PathBuf, ) -> Result { if !self.state.subgraphs.is_empty() { let unresolved_supergraph_config = UnresolvedSupergraphConfig::builder() .subgraphs(self.state.subgraphs.clone()) .federation_version_resolver(self.state.federation_version_resolver.clone()) + .origin_path(supergraph_yaml_path) .build(); let resolved_supergraph_config = LazilyResolvedSupergraphConfig::resolve( supergraph_config_root, @@ -272,6 +270,7 @@ mod tests { use semver::Version; use speculoos::prelude::*; + use super::SupergraphConfigResolver; use crate::{ composition::supergraph::config::scenario::*, utils::{ @@ -285,8 +284,6 @@ mod tests { }, }; - 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 diff --git a/src/composition/watchers/composition.rs b/src/composition/watchers/composition.rs index fbed101ab..bb1669432 100644 --- a/src/composition/watchers/composition.rs +++ b/src/composition/watchers/composition.rs @@ -1,16 +1,20 @@ +use std::collections::BTreeMap; + use apollo_federation_types::config::SchemaSource::Sdl; use apollo_federation_types::config::SupergraphConfig; use buildstructor::Builder; use camino::Utf8PathBuf; use futures::stream::BoxStream; use rover_std::errln; -use std::collections::BTreeMap; use tap::TapFallible; use tokio::{sync::mpsc::UnboundedSender, task::AbortHandle}; use tokio_stream::StreamExt; +use tracing::error; use crate::composition::supergraph::config::full::FullyResolvedSubgraph; -use crate::composition::{CompositionSubgraphAdded, CompositionSubgraphRemoved}; +use crate::composition::{ + CompositionError, CompositionSubgraphAdded, CompositionSubgraphRemoved, CompositionSuccess, +}; use crate::{ composition::{ events::CompositionEvent, @@ -30,6 +34,7 @@ pub struct CompositionWatcher { read_file: ReadF, write_file: WriteF, temp_dir: Utf8PathBuf, + compose_on_initialisation: bool, } impl SubtaskHandleStream for CompositionWatcher @@ -49,7 +54,33 @@ where tokio::task::spawn({ let mut subgraphs = self.subgraphs.clone(); let target_file = self.temp_dir.join("supergraph.yaml"); + async move { + if self.compose_on_initialisation { + if let Err(err) = self + .setup_temporary_supergraph_yaml(subgraphs.clone(), &target_file) + .await + { + error!("Could not setup initial supergraph schema: {}", err); + }; + let _ = sender + .send(CompositionEvent::Started) + .tap_err(|err| error!("{:?}", err)); + let output = self.run_composition(&target_file).await; + match output { + Ok(success) => { + let _ = sender + .send(CompositionEvent::Success(success)) + .tap_err(|err| error!("{:?}", err)); + } + Err(err) => { + let _ = sender + .send(CompositionEvent::Error(err)) + .tap_err(|err| error!("{:?}", err)); + } + } + } + while let Some(event) = input.next().await { match event { SubgraphEvent::SubgraphChanged(subgraph_schema_changed) => { @@ -102,44 +133,17 @@ where .tap_err(|err| tracing::error!("{:?}", err)); } } - - let supergraph_config = dbg!(convert_to_supergraph_config(subgraphs.clone())); - let supergraph_config_yaml = serde_yaml::to_string(&supergraph_config); - - let supergraph_config_yaml = match supergraph_config_yaml { - Ok(supergraph_config_yaml) => supergraph_config_yaml, - Err(err) => { - errln!("Failed to serialize supergraph config into yaml"); - tracing::error!("{:?}", err); - continue; - } - }; - - let write_file_result = self - .write_file - .write_file(&target_file, supergraph_config_yaml.as_bytes()) - .await; - - if let Err(err) = write_file_result { - errln!("Failed to write the supergraph config to disk"); - tracing::error!("{:?}", err); + if let Err(err) = self + .setup_temporary_supergraph_yaml(subgraphs.clone(), &target_file) + .await + { + error!("Could not setup supergraph schema: {}", err); continue; - } - + }; let _ = sender .send(CompositionEvent::Started) - .tap_err(|err| tracing::error!("{:?}", err)); - - let output = self - .supergraph_binary - .compose( - &self.exec_command, - &self.read_file, - &self.output_target, - target_file.clone(), - ) - .await; - + .tap_err(|err| error!("{:?}", err)); + let output = self.run_composition(&target_file).await; match output { Ok(success) => { let _ = sender @@ -159,6 +163,62 @@ where } } +impl CompositionWatcher +where + ExecC: 'static + ExecCommand + Send + Sync, + ReadF: 'static + ReadFile + Send + Sync, + WriteF: 'static + Send + Sync + WriteFile, +{ + async fn run_composition( + &self, + target_file: &Utf8PathBuf, + ) -> Result { + self.supergraph_binary + .compose( + &self.exec_command, + &self.read_file, + &self.output_target, + target_file.clone(), + ) + .await + } + + async fn setup_temporary_supergraph_yaml( + &self, + subgraphs: BTreeMap, + target_file: &Utf8PathBuf, + ) -> Result<(), CompositionError> { + let supergraph_config = convert_to_supergraph_config(subgraphs.clone()); + let supergraph_config_yaml = serde_yaml::to_string(&supergraph_config); + + let supergraph_config_yaml = match supergraph_config_yaml { + Ok(supergraph_config_yaml) => supergraph_config_yaml, + Err(err) => { + errln!("Failed to serialize supergraph config into yaml"); + tracing::error!("{:?}", err); + return Err(CompositionError::SupergraphYamlSerialisationFailed { + error: err.to_string(), + }); + } + }; + + let write_file_result = self + .write_file + .write_file(target_file, supergraph_config_yaml.as_bytes()) + .await; + + if let Err(err) = write_file_result { + errln!("Failed to write the supergraph config to disk"); + tracing::error!("{:?}", err); + Err(CompositionError::SupergraphSchemaTemporaryFileWriteFailed { + error: err.to_string(), + }) + } else { + Ok(()) + } + } +} + fn convert_to_supergraph_config( _subgraphs: BTreeMap, ) -> SupergraphConfig { @@ -279,6 +339,7 @@ mod tests { .write_file(mock_write_file) .temp_dir(temp_dir_path) .output_target(OutputTarget::Stdout) + .compose_on_initialisation(false) .build(); let subgraph_change_events: BoxStream = once(async {