From ffeb2ad1db153485b22d8b508c274c738dec80a9 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Thu, 20 May 2021 14:58:54 -0500 Subject: [PATCH 01/17] chore: refactor subgraph check This commit does a lot of heavy lifting for the pending rebase. 1) Creates new input and output types in rover-client for subgraph check 2) Moves GitContext out of rover::utils to rover-client::utils 3) Creates error code E029 for composition errors 4) Styles cloud-composition errors like harmonizer --- Cargo.lock | 2 + crates/rover-client/Cargo.toml | 2 + crates/rover-client/src/error.rs | 24 +++ crates/rover-client/src/lib.rs | 3 + .../check_query.graphql} | 19 +- .../src/query/subgraph/check/mod.rs | 3 + .../{check.rs => check/query_runner.rs} | 99 +++++----- .../src/query/subgraph/check/types.rs | 101 +++++++++++ {src => crates/rover-client/src}/utils/git.rs | 171 +++++++++--------- crates/rover-client/src/utils/mod.rs | 3 + docs/source/errors.md | 8 + src/cli.rs | 11 +- src/command/graph/check.rs | 2 +- src/command/graph/mod.rs | 4 +- src/command/graph/publish.rs | 2 +- src/command/output.rs | 33 +++- src/command/subgraph/check.rs | 117 ++---------- src/command/subgraph/mod.rs | 4 +- src/command/subgraph/publish.rs | 2 +- src/error/metadata/code.rs | 2 + src/error/metadata/codes/E028.md | 2 +- src/error/metadata/codes/E029.md | 5 + src/error/metadata/mod.rs | 21 +++ src/error/metadata/suggestion.rs | 4 + src/utils/mod.rs | 1 - 25 files changed, 385 insertions(+), 260 deletions(-) rename crates/rover-client/src/query/subgraph/{check.graphql => check/check_query.graphql} (63%) create mode 100644 crates/rover-client/src/query/subgraph/check/mod.rs rename crates/rover-client/src/query/subgraph/{check.rs => check/query_runner.rs} (50%) create mode 100644 crates/rover-client/src/query/subgraph/check/types.rs rename {src => crates/rover-client/src}/utils/git.rs (68%) create mode 100644 crates/rover-client/src/utils/mod.rs create mode 100644 src/error/metadata/codes/E029.md diff --git a/Cargo.lock b/Cargo.lock index 370594611..9a2e9d7b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1974,6 +1974,8 @@ dependencies = [ "anyhow", "camino", "chrono", + "git-url-parse", + "git2", "graphql-parser 0.3.0", "graphql_client", "houston", diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index 846bd54ab..7c3ee3dc0 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -14,6 +14,8 @@ houston = {path = "../houston"} anyhow = "1" camino = "1" chrono = "0.4" +git-url-parse = "0.3.1" +git2 = { version = "0.13.20", default-features = false, features = ["vendored-openssl"] } graphql-parser = "0.3.0" graphql_client = "0.9" http = "0.2" diff --git a/crates/rover-client/src/error.rs b/crates/rover-client/src/error.rs index 3f92d95bd..09941f3f0 100644 --- a/crates/rover-client/src/error.rs +++ b/crates/rover-client/src/error.rs @@ -1,6 +1,8 @@ use reqwest::Url; use thiserror::Error; +use crate::query::subgraph::check::types::CompositionError; + /// RoverClientError represents all possible failures that can occur during a client request. #[derive(Error, Debug)] pub enum RoverClientError { @@ -104,6 +106,12 @@ pub enum RoverClientError { composition_errors: Vec, }, + #[error("{}", subgraph_composition_error_msg(.composition_errors))] + SubgraphCompositionErrors { + graph_name: String, + composition_errors: Vec, + }, + /// This error occurs when the Studio API returns no implementing services for a graph /// This response shouldn't be possible! #[error("The response from Apollo Studio was malformed. Response body contains `null` value for \"{null_field}\"")] @@ -141,3 +149,19 @@ pub enum RoverClientError { #[error("This endpoint doesn't support subgraph introspection via the Query._service field")] SubgraphIntrospectionNotAvailable, } + +fn subgraph_composition_error_msg(composition_errors: &[CompositionError]) -> String { + let num_failures = composition_errors.len(); + if num_failures == 0 { + unreachable!("No composition errors were encountered while composing the supergraph."); + } + let mut msg = String::new(); + msg.push_str(&match num_failures { + 1 => "Encountered 1 composition error while composing the supergraph.".to_string(), + _ => format!( + "Encountered {} composition errors while composing the supergraph.", + num_failures + ), + }); + msg +} diff --git a/crates/rover-client/src/lib.rs b/crates/rover-client/src/lib.rs index 0b59cf52f..f206d4c0d 100644 --- a/crates/rover-client/src/lib.rs +++ b/crates/rover-client/src/lib.rs @@ -21,3 +21,6 @@ pub mod query; /// Module for getting release info pub mod releases; + +/// Module for shared functionality +pub mod utils; diff --git a/crates/rover-client/src/query/subgraph/check.graphql b/crates/rover-client/src/query/subgraph/check/check_query.graphql similarity index 63% rename from crates/rover-client/src/query/subgraph/check.graphql rename to crates/rover-client/src/query/subgraph/check/check_query.graphql index 5037bd561..4afaecbcc 100644 --- a/crates/rover-client/src/query/subgraph/check.graphql +++ b/crates/rover-client/src/query/subgraph/check/check_query.graphql @@ -1,22 +1,27 @@ - mutation CheckPartialSchemaQuery( + mutation SubgraphCheckQuery( $graph_id: ID! $variant: String! - $implementingServiceName: String! - $partialSchema: PartialSchemaInput! - $gitContext: GitContextInput! + $subgraph: String! + $proposed_schema: PartialSchemaInput! + $git_context: GitContextInput! $config: HistoricQueryParameters! ) { service(id: $graph_id) { checkPartialSchema( graphVariant: $variant - implementingServiceName: $implementingServiceName - partialSchema: $partialSchema - gitContext: $gitContext + implementingServiceName: $subgraph + partialSchema: $proposed_schema + gitContext: $git_context historicParameters: $config ) { compositionValidationResult { errors { message + code + locations { + line + column + } } } checkSchemaResult { diff --git a/crates/rover-client/src/query/subgraph/check/mod.rs b/crates/rover-client/src/query/subgraph/check/mod.rs new file mode 100644 index 000000000..4fd7b2ce8 --- /dev/null +++ b/crates/rover-client/src/query/subgraph/check/mod.rs @@ -0,0 +1,3 @@ +pub mod query_runner; +pub(crate) mod types; +pub use types::{SubgraphCheckConfig, SubgraphCheckInput, SubgraphCheckResponse}; diff --git a/crates/rover-client/src/query/subgraph/check.rs b/crates/rover-client/src/query/subgraph/check/query_runner.rs similarity index 50% rename from crates/rover-client/src/query/subgraph/check.rs rename to crates/rover-client/src/query/subgraph/check/query_runner.rs index 7291bde2b..b602595ea 100644 --- a/crates/rover-client/src/query/subgraph/check.rs +++ b/crates/rover-client/src/query/subgraph/check/query_runner.rs @@ -1,39 +1,36 @@ +use super::types::*; use crate::blocking::StudioClient; use crate::query::config::is_federated; use crate::RoverClientError; - use graphql_client::*; -use reqwest::Url; - -type Timestamp = String; #[derive(GraphQLQuery)] // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/query/subgraph/check.graphql", + query_path = "src/query/subgraph/check/check_query.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. check_partial_schema_query -pub struct CheckPartialSchemaQuery; +/// Snake case of this name is the mod name. i.e. subgraph_check_query +pub struct SubgraphCheckQuery; /// The main function to be used from this module. /// This function takes a proposed schema and validates it against a published /// schema. pub fn run( - variables: check_partial_schema_query::Variables, + input: SubgraphCheckInput, client: &StudioClient, -) -> Result { - let graph = variables.graph_id.clone(); +) -> Result { + let graph = input.graph_id.clone(); // This response is used to check whether or not the current graph is federated. let is_federated = is_federated::run( is_federated::is_federated_graph::Variables { - graph_id: variables.graph_id.clone(), - graph_variant: variables.variant.clone(), + graph_id: input.graph_id.clone(), + graph_variant: input.variant.clone(), }, &client, )?; @@ -43,77 +40,71 @@ pub fn run( can_operation_convert: false, }); } - let data = client.post::(variables)?; + let variables = input.into(); + let data = client.post::(variables)?; get_check_response_from_data(data, graph) } -pub enum CheckResponse { - CompositionErrors(Vec), - CheckResult(CheckResult) -} - -#[derive(Debug)] -pub struct CheckResult { - pub target_url: Option, - pub number_of_checked_operations: i64, - pub change_severity: check_partial_schema_query::ChangeSeverity, - pub changes: Vec, -} - fn get_check_response_from_data( - data: check_partial_schema_query::ResponseData, - graph: String, -) -> Result { - let service = data.service.ok_or(RoverClientError::NoService { graph })?; + data: subgraph_check_query::ResponseData, + graph_name: String, +) -> Result { + let service = data.service.ok_or(RoverClientError::NoService { + graph: graph_name.clone(), + })?; // for some reason this is a `Vec>` // we convert this to just `Vec` because the `None` // errors would be useless. - let composition_errors: Vec = service + let query_composition_errors: Vec = service .check_partial_schema .composition_validation_result .errors; - if composition_errors.is_empty() { + if query_composition_errors.is_empty() { let check_schema_result = service.check_partial_schema.check_schema_result.ok_or( RoverClientError::MalformedResponse { null_field: "service.check_partial_schema.check_schema_result".to_string(), }, )?; - let target_url = get_url(check_schema_result.target_url); - let diff_to_previous = check_schema_result.diff_to_previous; let number_of_checked_operations = diff_to_previous.number_of_checked_operations.unwrap_or(0); - let change_severity = diff_to_previous.severity; - let changes = diff_to_previous.changes; + let change_severity = diff_to_previous.severity.into(); - let check_result = CheckResult { - target_url, + let mut changes = Vec::with_capacity(diff_to_previous.changes.len()); + for change in diff_to_previous.changes { + changes.push(SchemaChange { + code: change.code, + severity: change.severity.into(), + description: change.description, + }); + } + + let check_result = SubgraphCheckResponse { + target_url: check_schema_result.target_url, number_of_checked_operations, - change_severity, changes, + change_severity, }; - Ok(CheckResponse::CheckResult(check_result)) + Ok(check_result) } else { - Ok(CheckResponse::CompositionErrors(composition_errors)) - } -} - -fn get_url(url: Option) -> Option { - match url { - Some(url) => { - let url = Url::parse(&url); - match url { - Ok(url) => Some(url), - // if the API returns an invalid URL, don't put it in the response - Err(_) => None, - } + let num_failures = query_composition_errors.len(); + + let mut composition_errors = Vec::with_capacity(num_failures); + for query_composition_error in query_composition_errors { + composition_errors.push(CompositionError { + message: query_composition_error.message, + code: query_composition_error.code, + }); } - None => None, + Err(RoverClientError::SubgraphCompositionErrors { + graph_name, + composition_errors, + }) } } diff --git a/crates/rover-client/src/query/subgraph/check/types.rs b/crates/rover-client/src/query/subgraph/check/types.rs new file mode 100644 index 000000000..457095e53 --- /dev/null +++ b/crates/rover-client/src/query/subgraph/check/types.rs @@ -0,0 +1,101 @@ +use std::fmt; + +use crate::utils::GitContext; + +use super::query_runner::subgraph_check_query; + +pub(crate) type Timestamp = String; +type QueryVariables = subgraph_check_query::Variables; +type QueryChangeSeverity = subgraph_check_query::ChangeSeverity; +type QuerySchema = subgraph_check_query::PartialSchemaInput; +type QueryConfig = subgraph_check_query::HistoricQueryParameters; + +#[derive(Debug, Clone, PartialEq)] +pub struct SubgraphCheckInput { + pub graph_id: String, + pub variant: String, + pub subgraph: String, + pub proposed_schema: String, + pub git_context: GitContext, + pub config: SubgraphCheckConfig, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SubgraphCheckConfig { + pub query_count_threshold: Option, + pub query_count_threshold_percentage: Option, + pub validation_period_from: Option, + pub validation_period_to: Option, +} + +impl From for QueryVariables { + fn from(input: SubgraphCheckInput) -> Self { + Self { + graph_id: input.graph_id, + variant: input.variant, + subgraph: input.subgraph, + proposed_schema: QuerySchema { + sdl: Some(input.proposed_schema), + hash: None, + }, + config: QueryConfig { + query_count_threshold: input.config.query_count_threshold, + query_count_threshold_percentage: input.config.query_count_threshold_percentage, + from: input.config.validation_period_from, + to: input.config.validation_period_to, + // we don't support configuring these, but we can't leave them out + excluded_clients: None, + ignored_operations: None, + included_variants: None, + }, + git_context: input.git_context.into(), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SubgraphCheckResponse { + pub target_url: Option, + pub number_of_checked_operations: i64, + pub changes: Vec, + pub change_severity: ChangeSeverity, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ChangeSeverity { + PASS, + FAIL, +} + +impl fmt::Display for ChangeSeverity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let msg = match self { + ChangeSeverity::PASS => "PASS", + ChangeSeverity::FAIL => "FAIL", + }; + write!(f, "{}", msg) + } +} + +impl From for ChangeSeverity { + fn from(severity: QueryChangeSeverity) -> Self { + match severity { + QueryChangeSeverity::NOTICE => ChangeSeverity::PASS, + QueryChangeSeverity::FAILURE => ChangeSeverity::FAIL, + _ => unreachable!("Unknown change severity"), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SchemaChange { + pub code: String, + pub description: String, + pub severity: ChangeSeverity, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct CompositionError { + pub message: String, + pub code: Option, +} diff --git a/src/utils/git.rs b/crates/rover-client/src/utils/git.rs similarity index 68% rename from src/utils/git.rs rename to crates/rover-client/src/utils/git.rs index 48b4ffd37..558be9d4a 100644 --- a/src/utils/git.rs +++ b/crates/rover-client/src/utils/git.rs @@ -1,95 +1,101 @@ -use crate::utils::env::{RoverEnv, RoverEnvKey}; -use crate::Result; -use rover_client::query::{graph, subgraph}; +use crate::query::{graph, subgraph}; use std::env; use git2::{Reference, Repository}; use git_url_parse::GitUrl; -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct GitContext { pub branch: Option, pub author: Option, pub commit: Option, - pub message: Option, pub remote_url: Option, } impl GitContext { - pub fn try_from_rover_env(env: &RoverEnv) -> Result { - let repo = Repository::discover(env::current_dir()?); - match repo { - Ok(repo) => { - let head = repo.head().ok(); - Ok(Self { - branch: GitContext::get_branch(env, head.as_ref())?, - commit: GitContext::get_commit(env, head.as_ref())?, - author: GitContext::get_author(env, head.as_ref())?, - remote_url: GitContext::get_remote_url(env, Some(&repo))?, - message: None, - }) + pub fn new_with_override(override_git_context: GitContext) -> Self { + let repo = GitContext::get_repo(); + + let mut remote_url = override_git_context.remote_url; + + if let Some(repo) = repo { + remote_url = remote_url.or_else(|| GitContext::get_remote_url(&repo)); + if let Ok(head) = repo.head() { + let branch = override_git_context + .branch + .or_else(|| GitContext::get_branch(&head)); + + let author = override_git_context + .author + .or_else(|| GitContext::get_author(&head)); + + let commit = override_git_context + .commit + .or_else(|| GitContext::get_commit(&head)); + + return GitContext { + branch, + author, + commit, + remote_url, + }; } - Err(_) => Ok(Self { - branch: GitContext::get_branch(env, None)?, - commit: GitContext::get_commit(env, None)?, - author: GitContext::get_author(env, None)?, - remote_url: GitContext::get_remote_url(env, None)?, - message: None, - }), + } + + GitContext { + branch: override_git_context.branch, + author: override_git_context.author, + commit: override_git_context.commit, + remote_url, } } - fn get_branch(env: &RoverEnv, head: Option<&Reference>) -> Result> { - Ok(env.get(RoverEnvKey::VcsBranch)?.or_else(|| { - let mut branch = None; - if let Some(head) = head { - branch = head.shorthand().map(|s| s.to_string()) - } - branch - })) + pub fn default() -> Self { + GitContext::new_with_override(GitContext { + author: None, + branch: None, + commit: None, + remote_url: None, + }) } - fn get_commit(env: &RoverEnv, head: Option<&Reference>) -> Result> { - Ok(env.get(RoverEnvKey::VcsCommit)?.or_else(|| { - let mut commit = None; - if let Some(head) = head { - if let Ok(head_commit) = head.peel_to_commit() { - commit = Some(head_commit.id().to_string()) - } - } - commit - })) + fn get_repo() -> Option { + env::current_dir() + .map(|d| Repository::discover(d).ok()) + .ok() + .flatten() } - fn get_author(env: &RoverEnv, head: Option<&Reference>) -> Result> { - Ok(env.get(RoverEnvKey::VcsAuthor)?.or_else(|| { - let mut author = None; - if let Some(head) = head { - if let Ok(head_commit) = head.peel_to_commit() { - author = Some(head_commit.author().to_string()) - } - } - author - })) + fn get_branch(head: &Reference) -> Option { + head.shorthand().map(|s| s.to_string()) } - fn get_remote_url(env: &RoverEnv, repo: Option<&Repository>) -> Result> { - let remote_url = env.get(RoverEnvKey::VcsRemoteUrl)?.or_else(|| { - let mut remote_url = None; - if let Some(repo) = repo { - if let Ok(remote) = repo.find_remote("origin") { - remote_url = remote.url().map(|r| r.to_string()) - } - } - remote_url - }); + fn get_commit(head: &Reference) -> Option { + if let Ok(head_commit) = head.peel_to_commit() { + Some(head_commit.id().to_string()) + } else { + None + } + } - Ok(if let Some(remote_url) = remote_url { - GitContext::sanitize_remote_url(&remote_url) + fn get_author(head: &Reference) -> Option { + if let Ok(head_commit) = head.peel_to_commit() { + Some(head_commit.author().to_string()) } else { None - }) + } + } + + fn get_remote_url(repo: &Repository) -> Option { + let remote_url = if let Ok(remote) = repo.find_remote("origin") { + remote.url().map(|r| r.to_string()) + } else { + None + }; + remote_url + .map(|r| GitContext::sanitize_remote_url(&r)) + .flatten() } // Parses and sanitizes git remote urls according to the same rules as @@ -139,7 +145,7 @@ impl From for GraphPublishContextInput { commit: git_context.commit, committer: git_context.author, remote_url: git_context.remote_url, - message: git_context.message, + message: None, } } } @@ -152,7 +158,7 @@ impl From for GraphCheckContextInput { commit: git_context.commit, committer: git_context.author, remote_url: git_context.remote_url, - message: git_context.message, + message: None, } } } @@ -166,12 +172,13 @@ impl From for SubgraphPublishContextInput { commit: git_context.commit, committer: git_context.author, remote_url: git_context.remote_url, - message: git_context.message, + message: None, } } } -type SubgraphCheckContextInput = subgraph::check::check_partial_schema_query::GitContextInput; +type SubgraphCheckContextInput = + subgraph::check::query_runner::subgraph_check_query::GitContextInput; impl From for SubgraphCheckContextInput { fn from(git_context: GitContext) -> SubgraphCheckContextInput { SubgraphCheckContextInput { @@ -179,7 +186,7 @@ impl From for SubgraphCheckContextInput { commit: git_context.commit, committer: git_context.author, remote_url: git_context.remote_url, - message: git_context.message, + message: None, } } } @@ -187,7 +194,6 @@ impl From for SubgraphCheckContextInput { #[cfg(test)] mod tests { use super::*; - use crate::PKG_NAME; #[test] fn removed_user_from_remote_with_only_user() { @@ -318,30 +324,21 @@ mod tests { let commit = "f84b32caddddfdd9fa87d7ce2140d56eabe805ee".to_string(); let remote_url = "git@bitbucket.org:roku/theworstremoteintheworld.git".to_string(); - let mut rover_env = RoverEnv::new(); - rover_env.insert(RoverEnvKey::VcsBranch, &branch); - rover_env.insert(RoverEnvKey::VcsAuthor, &author); - rover_env.insert(RoverEnvKey::VcsCommit, &commit); - rover_env.insert(RoverEnvKey::VcsRemoteUrl, &remote_url); - - let expected_git_context = GitContext { + let override_git_context = GitContext { branch: Some(branch), author: Some(author), commit: Some(commit), - message: None, remote_url: Some(remote_url), }; - let actual_git_context = GitContext::try_from_rover_env(&rover_env) - .expect("Could not create GitContext from RoverEnv"); + let actual_git_context = GitContext::new_with_override(override_git_context.clone()); - assert_eq!(expected_git_context, actual_git_context); + assert_eq!(override_git_context, actual_git_context); } #[test] - fn it_can_create_git_context_committ_author_remote_url() { - let git_context = - GitContext::try_from_rover_env(&RoverEnv::new()).expect("Could not create git context"); + fn it_can_create_git_context_commit_author_remote_url() { + let git_context = GitContext::default(); assert!(git_context.branch.is_some()); assert!(git_context.author.is_some()); @@ -352,10 +349,8 @@ mod tests { panic!("Could not find the commit hash"); } - assert!(git_context.message.is_none()); - if let Some(remote_url) = git_context.remote_url { - assert!(remote_url.contains(PKG_NAME)); + assert!(remote_url.contains("apollographql")); } else { panic!("GitContext could not find the remote url"); } diff --git a/crates/rover-client/src/utils/mod.rs b/crates/rover-client/src/utils/mod.rs new file mode 100644 index 000000000..2c5601aed --- /dev/null +++ b/crates/rover-client/src/utils/mod.rs @@ -0,0 +1,3 @@ +mod git; + +pub use git::GitContext; diff --git a/docs/source/errors.md b/docs/source/errors.md index f3a4f4f15..b5a98a192 100644 --- a/docs/source/errors.md +++ b/docs/source/errors.md @@ -233,4 +233,12 @@ This error occurs when Rover could not connect to an HTTP endpoint. If you encountered this error while running introspection, you'll want to make sure that you typed the endpoint correctly, your Internet connection is stable, and that your server is responding to requests. You may wish to run the command again with `--log=debug`. +### E029 + +This error occurs when you propose a subgraph schema that could not be composed. + +There are many reasons why you may run into composition errors. This error should include information about _why_ the proposed subgraph schema could not be composed. Error code references can be found [here](https://www.apollographql.com/docs/federation/errors/). + +Some composition errors are part of normal workflows. For instance, you may need to publish a subgraph that does not compose if you are trying to [migrate an entity or field](https://www.apollographql.com/docs/federation/entities/#migrating-entities-and-fields-advanced). + diff --git a/src/cli.rs b/src/cli.rs index fa25d3dfd..1820dbce3 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -5,13 +5,13 @@ use crate::command::{self, RoverStdout}; use crate::utils::{ client::StudioClientConfig, env::{RoverEnv, RoverEnvKey}, - git::GitContext, stringify::from_display, version, }; use crate::Result; use config::Config; use houston as config; +use rover_client::utils::GitContext; use timber::{Level, LEVELS}; use camino::Utf8PathBuf; @@ -87,7 +87,14 @@ impl Rover { pub(crate) fn get_git_context(&self) -> Result { // constructing GitContext with a set of overrides from env vars - let git_context = GitContext::try_from_rover_env(&self.env_store)?; + let override_git_context = GitContext { + branch: self.env_store.get(RoverEnvKey::VcsBranch).ok().flatten(), + commit: self.env_store.get(RoverEnvKey::VcsCommit).ok().flatten(), + author: self.env_store.get(RoverEnvKey::VcsAuthor).ok().flatten(), + remote_url: self.env_store.get(RoverEnvKey::VcsRemoteUrl).ok().flatten(), + }; + + let git_context = GitContext::new_with_override(override_git_context); tracing::debug!(?git_context); Ok(git_context) } diff --git a/src/command/graph/check.rs b/src/command/graph/check.rs index e3433a9c9..7964e349c 100644 --- a/src/command/graph/check.rs +++ b/src/command/graph/check.rs @@ -2,10 +2,10 @@ use serde::Serialize; use structopt::StructOpt; use rover_client::query::graph::check; +use rover_client::utils::GitContext; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; -use crate::utils::git::GitContext; use crate::utils::loaders::load_schema_from_flag; use crate::utils::parsers::{ parse_graph_ref, parse_query_count_threshold, parse_query_percentage_threshold, diff --git a/src/command/graph/mod.rs b/src/command/graph/mod.rs index 92e9a3c86..4ec72fbfe 100644 --- a/src/command/graph/mod.rs +++ b/src/command/graph/mod.rs @@ -7,9 +7,11 @@ use serde::Serialize; use structopt::StructOpt; use crate::command::RoverStdout; -use crate::utils::{client::StudioClientConfig, git::GitContext}; +use crate::utils::client::StudioClientConfig; use crate::Result; +use rover_client::utils::GitContext; + #[derive(Debug, Serialize, StructOpt)] pub struct Graph { #[structopt(subcommand)] diff --git a/src/command/graph/publish.rs b/src/command/graph/publish.rs index 06f66860c..361608242 100644 --- a/src/command/graph/publish.rs +++ b/src/command/graph/publish.rs @@ -3,10 +3,10 @@ use serde::Serialize; use structopt::StructOpt; use rover_client::query::graph::publish; +use rover_client::utils::GitContext; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; -use crate::utils::git::GitContext; use crate::utils::loaders::load_schema_from_flag; use crate::utils::parsers::{parse_graph_ref, parse_schema_source, GraphRef, SchemaSource}; use crate::Result; diff --git a/src/command/output.rs b/src/command/output.rs index eac986a73..c1c8fe3e9 100644 --- a/src/command/output.rs +++ b/src/command/output.rs @@ -6,7 +6,7 @@ use crate::utils::table::{self, cell, row}; use ansi_term::{Colour::Yellow, Style}; use atty::Stream; use crossterm::style::Attribute::Underlined; -use rover_client::query::subgraph::list::ListDetails; +use rover_client::query::subgraph::{check::SubgraphCheckResponse, list::ListDetails}; use termimad::MadSkin; /// RoverStdout defines all of the different types of data that are printed @@ -25,6 +25,7 @@ pub enum RoverStdout { CoreSchema(String), SchemaHash(String), SubgraphList(ListDetails), + SubgraphCheck(SubgraphCheckResponse), VariantList(Vec), Profiles(Vec), Introspection(String), @@ -98,6 +99,36 @@ impl RoverStdout { details.root_url, details.graph_name ); } + RoverStdout::SubgraphCheck(check_response) => { + let num_changes = check_response.changes.len(); + + let msg = match num_changes { + 0 => "There were no changes detected in the composed schema.".to_string(), + _ => format!( + "Compared {} schema changes against {} operations", + check_response.changes.len(), + check_response.number_of_checked_operations + ), + }; + + eprintln!("{}", &msg); + + if !check_response.changes.is_empty() { + let mut table = table::get_table(); + + // bc => sets top row to be bold and center + table.add_row(row![bc => "Change", "Code", "Description"]); + for check in &check_response.changes { + table.add_row(row![check.severity, check.code, check.description]); + } + + print_content(table.to_string()); + } + + if let Some(url) = &check_response.target_url { + eprintln!("View full details at {}", url); + } + } RoverStdout::VariantList(variants) => { print_descriptor("Variants"); for variant in variants { diff --git a/src/command/subgraph/check.rs b/src/command/subgraph/check.rs index d4e85770a..f6c3bc3b1 100644 --- a/src/command/subgraph/check.rs +++ b/src/command/subgraph/check.rs @@ -1,18 +1,16 @@ -use ansi_term::Colour::Red; use serde::Serialize; use structopt::StructOpt; -use rover_client::query::subgraph::check; +use rover_client::query::subgraph::check::{query_runner, SubgraphCheckConfig, SubgraphCheckInput}; +use rover_client::utils::GitContext; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; -use crate::utils::git::GitContext; use crate::utils::loaders::load_schema_from_flag; use crate::utils::parsers::{ parse_graph_ref, parse_query_count_threshold, parse_query_percentage_threshold, parse_schema_source, parse_validation_period, GraphRef, SchemaSource, ValidationPeriod, }; -use crate::utils::table::{self, cell, row}; use crate::Result; #[derive(Debug, Serialize, StructOpt)] @@ -63,113 +61,30 @@ impl Check { ) -> Result { let client = client_config.get_client(&self.profile_name)?; - let sdl = load_schema_from_flag(&self.schema, std::io::stdin())?; + let proposed_schema = load_schema_from_flag(&self.schema, std::io::stdin())?; - let partial_schema = check::check_partial_schema_query::PartialSchemaInput { - sdl: Some(sdl), - // we never need to send the hash since the back end computes it from SDL - hash: None, - }; + eprintln!( + "Checking the proposed schema for subgraph {} against {}", + &self.subgraph, &self.graph + ); - let res = check::run( - check::check_partial_schema_query::Variables { + let res = query_runner::run( + SubgraphCheckInput { graph_id: self.graph.name.clone(), variant: self.graph.variant.clone(), - partial_schema, - implementing_service_name: self.subgraph.clone(), - git_context: git_context.into(), - config: check::check_partial_schema_query::HistoricQueryParameters { + proposed_schema, + subgraph: self.subgraph.clone(), + git_context, + config: SubgraphCheckConfig { query_count_threshold: self.query_count_threshold, query_count_threshold_percentage: self.query_percentage_threshold, - from: self.validation_period.clone().unwrap_or_default().from, - to: self.validation_period.clone().unwrap_or_default().to, - // we don't support configuring these, but we can't leave them out - excluded_clients: None, - ignored_operations: None, - included_variants: None, + validation_period_from: self.validation_period.clone().unwrap_or_default().from, + validation_period_to: self.validation_period.clone().unwrap_or_default().to, }, }, &client, )?; - eprintln!("Checked the proposed subgraph against {}", &self.graph); - - match res { - check::CheckResponse::CompositionErrors(composition_errors) => { - handle_composition_errors(&composition_errors) - } - check::CheckResponse::CheckResult(check_result) => handle_checks(check_result), - } - } -} - -fn handle_checks(check_result: check::CheckResult) -> Result { - let num_changes = check_result.changes.len(); - - let msg = match num_changes { - 0 => "There were no changes detected in the composed schema.".to_string(), - _ => format!( - "Compared {} schema changes against {} operations", - check_result.changes.len(), - check_result.number_of_checked_operations - ), - }; - - eprintln!("{}", &msg); - - let mut num_failures = 0; - - if !check_result.changes.is_empty() { - let mut table = table::get_table(); - - // bc => sets top row to be bold and center - table.add_row(row![bc => "Change", "Code", "Description"]); - for check in check_result.changes { - let change = match check.severity { - check::check_partial_schema_query::ChangeSeverity::NOTICE => "PASS", - check::check_partial_schema_query::ChangeSeverity::FAILURE => { - num_failures += 1; - "FAIL" - } - _ => unreachable!("Unknown change severity"), - }; - table.add_row(row![change, check.code, check.description]); - } - - eprintln!("{}", table); - } - - if let Some(url) = check_result.target_url { - eprintln!("View full details at {}", &url); - } - - match num_failures { - 0 => Ok(RoverStdout::None), - 1 => Err(anyhow::anyhow!("Encountered 1 failure while checking your subgraph.").into()), - _ => Err(anyhow::anyhow!( - "Encountered {} failures while checking your subgraph.", - num_failures - ) - .into()), - } -} - -fn handle_composition_errors( - composition_errors: &[check::check_partial_schema_query::CheckPartialSchemaQueryServiceCheckPartialSchemaCompositionValidationResultErrors], -) -> Result { - let num_failures = composition_errors.len(); - for error in composition_errors { - eprintln!("{} {}", Red.bold().paint("error:"), &error.message); - } - match num_failures { - 0 => Ok(RoverStdout::None), - 1 => Err( - anyhow::anyhow!("Encountered 1 composition error while composing the subgraph.").into(), - ), - _ => Err(anyhow::anyhow!( - "Encountered {} composition errors while composing the subgraph.", - num_failures - ) - .into()), + Ok(RoverStdout::SubgraphCheck(res)) } } diff --git a/src/command/subgraph/mod.rs b/src/command/subgraph/mod.rs index dd0173a44..6dbf262e3 100644 --- a/src/command/subgraph/mod.rs +++ b/src/command/subgraph/mod.rs @@ -9,9 +9,11 @@ use serde::Serialize; use structopt::StructOpt; use crate::command::RoverStdout; -use crate::utils::{client::StudioClientConfig, git::GitContext}; +use crate::utils::client::StudioClientConfig; use crate::Result; +use rover_client::utils::GitContext; + #[derive(Debug, Serialize, StructOpt)] pub struct Subgraph { #[structopt(subcommand)] diff --git a/src/command/subgraph/publish.rs b/src/command/subgraph/publish.rs index 6781d50c4..7312be054 100644 --- a/src/command/subgraph/publish.rs +++ b/src/command/subgraph/publish.rs @@ -5,13 +5,13 @@ use structopt::StructOpt; use crate::command::RoverStdout; use crate::utils::{ client::StudioClientConfig, - git::GitContext, loaders::load_schema_from_flag, parsers::{parse_graph_ref, parse_schema_source, GraphRef, SchemaSource}, }; use crate::Result; use rover_client::query::subgraph::publish::{self, PublishPartialSchemaResponse}; +use rover_client::utils::GitContext; #[derive(Debug, Serialize, StructOpt)] pub struct Publish { diff --git a/src/error/metadata/code.rs b/src/error/metadata/code.rs index 07e09efb7..c50932b4b 100644 --- a/src/error/metadata/code.rs +++ b/src/error/metadata/code.rs @@ -34,6 +34,7 @@ pub enum Code { E026, E027, E028, + E029, } impl Display for Code { @@ -75,6 +76,7 @@ impl Code { (Code::E026, include_str!("./codes/E026.md").to_string()), (Code::E027, include_str!("./codes/E027.md").to_string()), (Code::E028, include_str!("./codes/E028.md").to_string()), + (Code::E029, include_str!("./codes/E029.md").to_string()), ]; contents.into_iter().collect() } diff --git a/src/error/metadata/codes/E028.md b/src/error/metadata/codes/E028.md index 52b1dd800..c31e8ea2f 100644 --- a/src/error/metadata/codes/E028.md +++ b/src/error/metadata/codes/E028.md @@ -1,3 +1,3 @@ This error occurs when Rover could not connect to an HTTP endpoint. -If you encountered this error while running introspection, you'll want to make sure that you typed the endpoint correctly, your Internet connection is stable, and that your server is responding to requests. You may wish to run the command again with `--log=debug`. +If you encountered this error while running introspection, you'll want to make sure that you typed the endpoint correctly, your Internet connection is stable, and that your server is responding to requests. You may wish to run the command again with `--log=debug`. \ No newline at end of file diff --git a/src/error/metadata/codes/E029.md b/src/error/metadata/codes/E029.md new file mode 100644 index 000000000..793b1022b --- /dev/null +++ b/src/error/metadata/codes/E029.md @@ -0,0 +1,5 @@ +This error occurs when you propose a subgraph schema that could not be composed. + +There are many reasons why you may run into composition errors. This error should include information about _why_ the proposed subgraph schema could not be composed. Error code references can be found [here](https://www.apollographql.com/docs/federation/errors/). + +Some composition errors are part of normal workflows. For instance, you may need to publish a subgraph that does not compose if you are trying to [migrate an entity or field](https://www.apollographql.com/docs/federation/entities/#migrating-entities-and-fields-advanced). diff --git a/src/error/metadata/mod.rs b/src/error/metadata/mod.rs index 8bc0d67ad..32649ab6c 100644 --- a/src/error/metadata/mod.rs +++ b/src/error/metadata/mod.rs @@ -58,6 +58,27 @@ impl From<&mut anyhow::Error> for Metadata { RoverClientError::InvalidSeverity => { (Some(Suggestion::SubmitIssue), Some(Code::E006)) } + RoverClientError::SubgraphCompositionErrors { + graph_name, + composition_errors, + } => { + for composition_error in composition_errors { + let mut error = format!("{} ", Red.bold().paint("error:")); + if let Some(code) = &composition_error.code { + error.push_str(&format!("{}: ", code)); + } else { + error.push_str("UNKNOWN: "); + } + error.push_str(&composition_error.message); + eprintln!("{}", &error); + } + ( + Some(Suggestion::FixSubgraphSchema { + graph_name: graph_name.clone(), + }), + Some(Code::E029), + ) + } RoverClientError::SubgraphIntrospectionNotAvailable => { (Some(Suggestion::UseFederatedGraph), Some(Code::E007)) } diff --git a/src/error/metadata/suggestion.rs b/src/error/metadata/suggestion.rs index 36a234eb4..5599eefa4 100644 --- a/src/error/metadata/suggestion.rs +++ b/src/error/metadata/suggestion.rs @@ -32,6 +32,9 @@ pub enum Suggestion { CheckServerConnection, ConvertGraphToSubgraph, CheckGnuVersion, + FixSubgraphSchema { + graph_name: String, + }, } impl Display for Suggestion { @@ -129,6 +132,7 @@ impl Display for Suggestion { Suggestion::CheckServerConnection => "Make sure the endpoint is accepting connections and is spelled correctly".to_string(), Suggestion::ConvertGraphToSubgraph => "If you are sure you want to convert a non-federated graph to a subgraph, you can re-run the same command with a `--convert` flag.".to_string(), Suggestion::CheckGnuVersion => "This is likely an issue with your current version of `glibc`. Try running `ldd --version`, and if the version >= 2.18, we suggest installing the Rover binary built for `x86_64-unknown-linux-gnu`".to_string(), + Suggestion::FixSubgraphSchema { graph_name } => format!("The changes in the schema you proposed are incompatible with graph {}. See {} for more information on resolving composition errors.", Yellow.normal().paint(graph_name), Cyan.normal().paint("https://www.apollographql.com/docs/federation/errors/")) }; write!(formatter, "{}", &suggestion) } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 989333fed..61b7c1331 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,6 +1,5 @@ pub mod client; pub mod env; -pub mod git; pub mod loaders; pub mod parsers; pub mod pkg; From 42f0bc82d10cde97caa747e7ab86a47451f709f7 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Wed, 23 Jun 2021 10:23:06 -0500 Subject: [PATCH 02/17] chore: refactor subgraph fetch (#575) --- .../fetch_query.graphql} | 4 +- .../src/query/subgraph/fetch/mod.rs | 3 + .../{fetch.rs => fetch/query_runner.rs} | 78 ++++++++++--------- .../src/query/subgraph/fetch/types.rs | 42 ++++++++++ src/command/subgraph/fetch.rs | 10 +-- src/command/supergraph/compose/do_compose.rs | 9 ++- 6 files changed, 97 insertions(+), 49 deletions(-) rename crates/rover-client/src/query/subgraph/{fetch.graphql => fetch/fetch_query.graphql} (72%) create mode 100644 crates/rover-client/src/query/subgraph/fetch/mod.rs rename crates/rover-client/src/query/subgraph/{fetch.rs => fetch/query_runner.rs} (69%) create mode 100644 crates/rover-client/src/query/subgraph/fetch/types.rs diff --git a/crates/rover-client/src/query/subgraph/fetch.graphql b/crates/rover-client/src/query/subgraph/fetch/fetch_query.graphql similarity index 72% rename from crates/rover-client/src/query/subgraph/fetch.graphql rename to crates/rover-client/src/query/subgraph/fetch/fetch_query.graphql index 8b4e600c0..631177641 100644 --- a/crates/rover-client/src/query/subgraph/fetch.graphql +++ b/crates/rover-client/src/query/subgraph/fetch/fetch_query.graphql @@ -1,5 +1,5 @@ -query FetchSubgraphQuery($variant: String!, $graphID: ID!) { - service(id: $graphID) { +query SubgraphFetchQuery($graph_id: ID!, $variant: String!) { + service(id: $graph_id) { implementingServices(graphVariant: $variant) { __typename ... on FederatedImplementingServices { diff --git a/crates/rover-client/src/query/subgraph/fetch/mod.rs b/crates/rover-client/src/query/subgraph/fetch/mod.rs new file mode 100644 index 000000000..7c1ea61cc --- /dev/null +++ b/crates/rover-client/src/query/subgraph/fetch/mod.rs @@ -0,0 +1,3 @@ +pub mod query_runner; +pub(crate) mod types; +pub use types::{SubgraphFetchInput, SubgraphFetchResponse}; diff --git a/crates/rover-client/src/query/subgraph/fetch.rs b/crates/rover-client/src/query/subgraph/fetch/query_runner.rs similarity index 69% rename from crates/rover-client/src/query/subgraph/fetch.rs rename to crates/rover-client/src/query/subgraph/fetch/query_runner.rs index 9509d3951..b365f45f4 100644 --- a/crates/rover-client/src/query/subgraph/fetch.rs +++ b/crates/rover-client/src/query/subgraph/fetch/query_runner.rs @@ -1,3 +1,4 @@ +use super::types::*; use crate::blocking::StudioClient; use crate::RoverClientError; use graphql_client::*; @@ -6,66 +7,69 @@ use graphql_client::*; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/query/subgraph/fetch.graphql", + query_path = "src/query/subgraph/fetch/fetch_query.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. fetch_subgraph_query -pub struct FetchSubgraphQuery; +/// Snake case of this name is the mod name. i.e. subgraph_fetch_query +pub(crate) struct SubgraphFetchQuery; /// Fetches a schema from apollo studio and returns its SDL (String) pub fn run( - variables: fetch_subgraph_query::Variables, + input: SubgraphFetchInput, client: &StudioClient, - // we can't specify this as a variable in the op, so we have to filter the - // operation response by this name - subgraph: &str, -) -> Result { - let graph = variables.graph_id.clone(); - let response_data = client.post::(variables)?; - let services = get_services_from_response_data(response_data, graph)?; - get_sdl_for_service(services, subgraph) - // if we want json, we can parse & serialize it here +) -> Result { + let variables: SubgraphFetchVariables = input.clone().into(); + let response_data = client.post::(variables.into())?; + get_sdl_from_response_data(input, response_data) +} + +fn get_sdl_from_response_data( + input: SubgraphFetchInput, + response_data: SubgraphFetchResponseData, +) -> Result { + let service_list = get_services_from_response_data(&input.graph_id, response_data)?; + let sdl = get_sdl_for_service(&input.subgraph, service_list)?; + Ok(SubgraphFetchResponse { sdl }) } -type ServiceList = Vec; fn get_services_from_response_data( - response_data: fetch_subgraph_query::ResponseData, - graph: String, + graph_id: &str, + response_data: SubgraphFetchResponseData, ) -> Result { let service_data = response_data.service.ok_or(RoverClientError::NoService { - graph: graph.clone(), + graph: graph_id.to_string(), })?; // get list of services let services = match service_data.implementing_services { Some(services) => Ok(services), - // this case may be removable in the near future as unreachable, since - // you should still get an `implementingServices` response in the case - // of a non-federated graph. Fow now, this case still exists, but - // wont' for long. Check on this later (Jake) :) None => Err(RoverClientError::ExpectedFederatedGraph { - graph: graph.clone(), + graph: graph_id.to_string(), can_operation_convert: false, }), }?; match services { - fetch_subgraph_query::FetchSubgraphQueryServiceImplementingServices::FederatedImplementingServices (services) => { - Ok(services.services) - }, - fetch_subgraph_query::FetchSubgraphQueryServiceImplementingServices::NonFederatedImplementingService => { - Err(RoverClientError::ExpectedFederatedGraph { graph, can_operation_convert: false }) + Services::FederatedImplementingServices(services) => Ok(services.services), + Services::NonFederatedImplementingService => { + Err(RoverClientError::ExpectedFederatedGraph { + graph: graph_id.to_string(), + can_operation_convert: false, + }) } } } -fn get_sdl_for_service(services: ServiceList, subgraph: &str) -> Result { +fn get_sdl_for_service( + subgraph_name: &str, + services: ServiceList, +) -> Result { // find the right service by name - let service = services.iter().find(|svc| svc.name == subgraph); + let service = services.iter().find(|svc| svc.name == subgraph_name); // if there is a service, get it's active sdl, otherwise, error and list // available services to fetch @@ -75,7 +79,7 @@ fn get_sdl_for_service(services: ServiceList, subgraph: &str) -> Result = services.iter().map(|svc| svc.name.clone()).collect(); Err(RoverClientError::NoSubgraphInGraph { - invalid_subgraph: subgraph.to_string(), + invalid_subgraph: subgraph_name.to_string(), valid_subgraphs, }) } @@ -109,9 +113,8 @@ mod tests { } } }); - let data: fetch_subgraph_query::ResponseData = - serde_json::from_value(json_response).unwrap(); - let output = get_services_from_response_data(data, "mygraph".to_string()); + let data: SubgraphFetchResponseData = serde_json::from_value(json_response).unwrap(); + let output = get_services_from_response_data("mygraph", data); let expected_json = json!([ { @@ -140,9 +143,8 @@ mod tests { "implementingServices": null } }); - let data: fetch_subgraph_query::ResponseData = - serde_json::from_value(json_response).unwrap(); - let output = get_services_from_response_data(data, "mygraph".to_string()); + let data: SubgraphFetchResponseData = serde_json::from_value(json_response).unwrap(); + let output = get_services_from_response_data("mygraph", data); assert!(output.is_err()); } @@ -163,7 +165,7 @@ mod tests { } ]); let service_list: ServiceList = serde_json::from_value(json_service_list).unwrap(); - let output = get_sdl_for_service(service_list, "accounts2"); + let output = get_sdl_for_service("accounts2", service_list); assert_eq!( output.unwrap(), "extend type User @key(fields: \"id\") {\n id: ID! @external\n age: Int\n}\n" @@ -188,7 +190,7 @@ mod tests { } ]); let service_list: ServiceList = serde_json::from_value(json_service_list).unwrap(); - let output = get_sdl_for_service(service_list, "harambe-was-an-inside-job"); + let output = get_sdl_for_service("harambe-was-an-inside-job", service_list); assert!(output.is_err()); } } diff --git a/crates/rover-client/src/query/subgraph/fetch/types.rs b/crates/rover-client/src/query/subgraph/fetch/types.rs new file mode 100644 index 000000000..d3a118892 --- /dev/null +++ b/crates/rover-client/src/query/subgraph/fetch/types.rs @@ -0,0 +1,42 @@ +use super::query_runner::subgraph_fetch_query; + +pub(crate) type ServiceList = Vec; +pub(crate) type SubgraphFetchResponseData = subgraph_fetch_query::ResponseData; +pub(crate) type Services = subgraph_fetch_query::SubgraphFetchQueryServiceImplementingServices; +pub(crate) type QueryVariables = subgraph_fetch_query::Variables; + +#[derive(Debug, Clone, PartialEq)] +pub struct SubgraphFetchInput { + pub graph_id: String, + pub variant: String, + pub subgraph: String, +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct SubgraphFetchVariables { + graph_id: String, + variant: String, +} + +impl From for SubgraphFetchVariables { + fn from(input: SubgraphFetchInput) -> Self { + Self { + graph_id: input.graph_id, + variant: input.variant, + } + } +} + +impl From for QueryVariables { + fn from(fetch_variables: SubgraphFetchVariables) -> Self { + Self { + graph_id: fetch_variables.graph_id, + variant: fetch_variables.variant, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SubgraphFetchResponse { + pub sdl: String, +} diff --git a/src/command/subgraph/fetch.rs b/src/command/subgraph/fetch.rs index e51ae6bc1..7319fadd2 100644 --- a/src/command/subgraph/fetch.rs +++ b/src/command/subgraph/fetch.rs @@ -2,7 +2,7 @@ use ansi_term::Colour::{Cyan, Yellow}; use serde::Serialize; use structopt::StructOpt; -use rover_client::query::subgraph::fetch; +use rover_client::query::subgraph::fetch::{query_runner, SubgraphFetchInput}; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; @@ -39,15 +39,15 @@ impl Fetch { Yellow.normal().paint(&self.profile_name) ); - let sdl = fetch::run( - fetch::fetch_subgraph_query::Variables { + let result = query_runner::run( + SubgraphFetchInput { graph_id: self.graph.name.clone(), variant: self.graph.variant.clone(), + subgraph: self.subgraph.clone(), }, &client, - &self.subgraph, )?; - Ok(RoverStdout::Sdl(sdl)) + Ok(RoverStdout::Sdl(result.sdl)) } } diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index 781845afb..b816c3e7a 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -5,6 +5,7 @@ use crate::{anyhow, command::RoverStdout, error::RoverError, Result, Suggestion} use ansi_term::Colour::Red; use camino::Utf8PathBuf; +use rover_client::query::subgraph::fetch::SubgraphFetchInput; use rover_client::{ blocking::GraphQLClient, query::subgraph::{fetch, introspect}, @@ -122,13 +123,13 @@ pub(crate) fn get_subgraph_definitions( // obtain SDL and add it to subgraph_definition. let client = client_config.get_client(&profile_name)?; let graphref = parse_graph_ref(graphref)?; - let schema = fetch::run( - fetch::fetch_subgraph_query::Variables { + let result = fetch::query_runner::run( + SubgraphFetchInput { graph_id: graphref.name.clone(), variant: graphref.variant.clone(), + subgraph: subgraph.clone(), }, &client, - subgraph, )?; // We don't require a routing_url for this variant of a schema, @@ -138,7 +139,7 @@ pub(crate) fn get_subgraph_definitions( // and use that when no routing_url is provided. let url = &subgraph_data.routing_url.clone().unwrap_or_default(); - let subgraph_definition = SubgraphDefinition::new(subgraph_name, url, &schema); + let subgraph_definition = SubgraphDefinition::new(subgraph_name, url, &result.sdl); subgraphs.push(subgraph_definition); } } From b5db7ba4cf557b924eb53e28c7c6549de7ece5be Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Fri, 25 Jun 2021 12:10:23 -0500 Subject: [PATCH 03/17] chore: refactor subgraph publish (#630) --- .../src/query/subgraph/check/types.rs | 13 +++++ .../src/query/subgraph/publish/mod.rs | 3 + .../mutation_runner.rs} | 47 ++++++--------- .../publish_mutation.graphql} | 14 ++--- .../src/query/subgraph/publish/types.rs | 58 +++++++++++++++++++ crates/rover-client/src/utils/git.rs | 30 +--------- src/command/subgraph/publish.rs | 33 +++++------ 7 files changed, 114 insertions(+), 84 deletions(-) create mode 100644 crates/rover-client/src/query/subgraph/publish/mod.rs rename crates/rover-client/src/query/subgraph/{publish.rs => publish/mutation_runner.rs} (78%) rename crates/rover-client/src/query/subgraph/{publish.graphql => publish/publish_mutation.graphql} (65%) create mode 100644 crates/rover-client/src/query/subgraph/publish/types.rs diff --git a/crates/rover-client/src/query/subgraph/check/types.rs b/crates/rover-client/src/query/subgraph/check/types.rs index 457095e53..f339a6b1e 100644 --- a/crates/rover-client/src/query/subgraph/check/types.rs +++ b/crates/rover-client/src/query/subgraph/check/types.rs @@ -9,6 +9,7 @@ type QueryVariables = subgraph_check_query::Variables; type QueryChangeSeverity = subgraph_check_query::ChangeSeverity; type QuerySchema = subgraph_check_query::PartialSchemaInput; type QueryConfig = subgraph_check_query::HistoricQueryParameters; +type GitContextInput = subgraph_check_query::GitContextInput; #[derive(Debug, Clone, PartialEq)] pub struct SubgraphCheckInput { @@ -99,3 +100,15 @@ pub struct CompositionError { pub message: String, pub code: Option, } + +impl From for GitContextInput { + fn from(git_context: GitContext) -> GitContextInput { + GitContextInput { + branch: git_context.branch, + commit: git_context.commit, + committer: git_context.author, + remote_url: git_context.remote_url, + message: None, + } + } +} diff --git a/crates/rover-client/src/query/subgraph/publish/mod.rs b/crates/rover-client/src/query/subgraph/publish/mod.rs new file mode 100644 index 000000000..b0504e084 --- /dev/null +++ b/crates/rover-client/src/query/subgraph/publish/mod.rs @@ -0,0 +1,3 @@ +pub mod mutation_runner; +pub(crate) mod types; +pub use types::{SubgraphPublishInput, SubgraphPublishResponse}; diff --git a/crates/rover-client/src/query/subgraph/publish.rs b/crates/rover-client/src/query/subgraph/publish/mutation_runner.rs similarity index 78% rename from crates/rover-client/src/query/subgraph/publish.rs rename to crates/rover-client/src/query/subgraph/publish/mutation_runner.rs index 3b08f088a..0b3ffe42c 100644 --- a/crates/rover-client/src/query/subgraph/publish.rs +++ b/crates/rover-client/src/query/subgraph/publish/mutation_runner.rs @@ -1,4 +1,4 @@ -// PublishPartialSchemaMutation +use super::types::*; use crate::blocking::StudioClient; use crate::query::config::is_federated; use crate::RoverClientError; @@ -8,38 +8,30 @@ use graphql_client::*; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/query/subgraph/publish.graphql", + query_path = "src/query/subgraph/publish/publish_mutation.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. publish_partial_schema_mutation -pub struct PublishPartialSchemaMutation; - -#[derive(Debug, PartialEq)] -pub struct PublishPartialSchemaResponse { - pub schema_hash: Option, - pub did_update_gateway: bool, - pub service_was_created: bool, - pub composition_errors: Option>, -} +/// Snake case of this name is the mod name. i.e. subgraph_publish_mutation +pub struct SubgraphPublishMutation; pub fn run( - variables: publish_partial_schema_mutation::Variables, + input: SubgraphPublishInput, client: &StudioClient, - convert_to_federated_graph: bool, -) -> Result { - let graph = variables.graph_id.clone(); +) -> Result { + let variables: MutationVariables = input.clone().into(); + let graph = input.graph_id.clone(); // We don't want to implicitly convert non-federated graph to supergraphs. // Error here if no --convert flag is passed _and_ the current context // is non-federated. Add a suggestion to require a --convert flag. - if !convert_to_federated_graph { + if !input.convert_to_federated_graph { let is_federated = is_federated::run( is_federated::is_federated_graph::Variables { - graph_id: variables.graph_id.clone(), - graph_variant: variables.graph_variant.clone(), + graph_id: input.graph_id.clone(), + graph_variant: input.variant, }, &client, )?; @@ -51,16 +43,13 @@ pub fn run( }); } } - let data = client.post::(variables)?; + let data = client.post::(variables)?; let publish_response = get_publish_response_from_data(data, graph)?; Ok(build_response(publish_response)) } -// alias this return type since it's disgusting -type UpdateResponse = publish_partial_schema_mutation::PublishPartialSchemaMutationServiceUpsertImplementingServiceAndTriggerComposition; - fn get_publish_response_from_data( - data: publish_partial_schema_mutation::ResponseData, + data: ResponseData, graph: String, ) -> Result { let service_data = data.service.ok_or(RoverClientError::NoService { graph })?; @@ -68,7 +57,7 @@ fn get_publish_response_from_data( Ok(service_data.upsert_implementing_service_and_trigger_composition) } -fn build_response(publish_response: UpdateResponse) -> PublishPartialSchemaResponse { +fn build_response(publish_response: UpdateResponse) -> SubgraphPublishResponse { let composition_errors: Vec = publish_response .errors .iter() @@ -82,7 +71,7 @@ fn build_response(publish_response: UpdateResponse) -> PublishPartialSchemaRespo None }; - PublishPartialSchemaResponse { + SubgraphPublishResponse { schema_hash: match publish_response.composition_config { Some(config) => Some(config.schema_hash), None => None, @@ -114,7 +103,7 @@ mod tests { assert_eq!( output, - PublishPartialSchemaResponse { + SubgraphPublishResponse { schema_hash: Some("5gf564".to_string()), composition_errors: Some(vec![ "[Accounts] User -> composition error".to_string(), @@ -139,7 +128,7 @@ mod tests { assert_eq!( output, - PublishPartialSchemaResponse { + SubgraphPublishResponse { schema_hash: Some("5gf564".to_string()), composition_errors: None, did_update_gateway: true, @@ -163,7 +152,7 @@ mod tests { assert_eq!( output, - PublishPartialSchemaResponse { + SubgraphPublishResponse { schema_hash: None, composition_errors: Some( vec!["[Accounts] -> Things went really wrong".to_string()] diff --git a/crates/rover-client/src/query/subgraph/publish.graphql b/crates/rover-client/src/query/subgraph/publish/publish_mutation.graphql similarity index 65% rename from crates/rover-client/src/query/subgraph/publish.graphql rename to crates/rover-client/src/query/subgraph/publish/publish_mutation.graphql index 6acd42bcf..956de4ba2 100644 --- a/crates/rover-client/src/query/subgraph/publish.graphql +++ b/crates/rover-client/src/query/subgraph/publish/publish_mutation.graphql @@ -1,19 +1,19 @@ -mutation PublishPartialSchemaMutation( +mutation SubgraphPublishMutation( $graphId: ID! - $graphVariant: String! - $name: String! + $variant: String! + $subgraph: String! $url: String $revision: String! - $activePartialSchema: PartialSchemaInput! + $schema: PartialSchemaInput! $gitContext: GitContextInput! ) { service(id: $graphId) { upsertImplementingServiceAndTriggerComposition( - name: $name + name: $subgraph url: $url revision: $revision - activePartialSchema: $activePartialSchema - graphVariant: $graphVariant + activePartialSchema: $schema + graphVariant: $variant gitContext: $gitContext ) { compositionConfig { diff --git a/crates/rover-client/src/query/subgraph/publish/types.rs b/crates/rover-client/src/query/subgraph/publish/types.rs new file mode 100644 index 000000000..26738cf78 --- /dev/null +++ b/crates/rover-client/src/query/subgraph/publish/types.rs @@ -0,0 +1,58 @@ +use super::mutation_runner::subgraph_publish_mutation; + +use crate::utils::GitContext; + +pub(crate) type ResponseData = subgraph_publish_mutation::ResponseData; +pub(crate) type MutationVariables = subgraph_publish_mutation::Variables; +pub(crate) type UpdateResponse = subgraph_publish_mutation::SubgraphPublishMutationServiceUpsertImplementingServiceAndTriggerComposition; + +type SchemaInput = subgraph_publish_mutation::PartialSchemaInput; +type GitContextInput = subgraph_publish_mutation::GitContextInput; + +#[derive(Debug, Clone, PartialEq)] +pub struct SubgraphPublishInput { + pub graph_id: String, + pub variant: String, + pub subgraph: String, + pub url: Option, + pub schema: String, + pub git_context: GitContext, + pub convert_to_federated_graph: bool, +} + +#[derive(Debug, PartialEq)] +pub struct SubgraphPublishResponse { + pub schema_hash: Option, + pub did_update_gateway: bool, + pub service_was_created: bool, + pub composition_errors: Option>, +} + +impl From for MutationVariables { + fn from(publish_input: SubgraphPublishInput) -> Self { + Self { + graph_id: publish_input.graph_id, + variant: publish_input.variant, + subgraph: publish_input.subgraph, + url: publish_input.url, + schema: SchemaInput { + sdl: Some(publish_input.schema), + hash: None, + }, + git_context: publish_input.git_context.into(), + revision: "".to_string(), + } + } +} + +impl From for GitContextInput { + fn from(git_context: GitContext) -> GitContextInput { + GitContextInput { + branch: git_context.branch, + commit: git_context.commit, + committer: git_context.author, + remote_url: git_context.remote_url, + message: None, + } + } +} diff --git a/crates/rover-client/src/utils/git.rs b/crates/rover-client/src/utils/git.rs index 558be9d4a..22f2fc4d2 100644 --- a/crates/rover-client/src/utils/git.rs +++ b/crates/rover-client/src/utils/git.rs @@ -1,4 +1,4 @@ -use crate::query::{graph, subgraph}; +use crate::query::graph; use std::env; @@ -163,34 +163,6 @@ impl From for GraphCheckContextInput { } } -type SubgraphPublishContextInput = - subgraph::publish::publish_partial_schema_mutation::GitContextInput; -impl From for SubgraphPublishContextInput { - fn from(git_context: GitContext) -> SubgraphPublishContextInput { - SubgraphPublishContextInput { - branch: git_context.branch, - commit: git_context.commit, - committer: git_context.author, - remote_url: git_context.remote_url, - message: None, - } - } -} - -type SubgraphCheckContextInput = - subgraph::check::query_runner::subgraph_check_query::GitContextInput; -impl From for SubgraphCheckContextInput { - fn from(git_context: GitContext) -> SubgraphCheckContextInput { - SubgraphCheckContextInput { - branch: git_context.branch, - commit: git_context.commit, - committer: git_context.author, - remote_url: git_context.remote_url, - message: None, - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/command/subgraph/publish.rs b/src/command/subgraph/publish.rs index 7312be054..5314d4646 100644 --- a/src/command/subgraph/publish.rs +++ b/src/command/subgraph/publish.rs @@ -10,7 +10,7 @@ use crate::utils::{ }; use crate::Result; -use rover_client::query::subgraph::publish::{self, PublishPartialSchemaResponse}; +use rover_client::query::subgraph::publish::{self, SubgraphPublishInput, SubgraphPublishResponse}; use rover_client::utils::GitContext; #[derive(Debug, Serialize, StructOpt)] @@ -64,26 +64,21 @@ impl Publish { Yellow.normal().paint(&self.profile_name) ); - let schema_document = load_schema_from_flag(&self.schema, std::io::stdin())?; + let schema = load_schema_from_flag(&self.schema, std::io::stdin())?; - tracing::debug!("Publishing \n{}", &schema_document); + tracing::debug!("Publishing \n{}", &schema); - let publish_response = publish::run( - publish::publish_partial_schema_mutation::Variables { + let publish_response = publish::mutation_runner::run( + SubgraphPublishInput { graph_id: self.graph.name.clone(), - graph_variant: self.graph.variant.clone(), - name: self.subgraph.clone(), - active_partial_schema: - publish::publish_partial_schema_mutation::PartialSchemaInput { - sdl: Some(schema_document), - hash: None, - }, - revision: "".to_string(), + variant: self.graph.variant.clone(), + subgraph: self.subgraph.clone(), url: self.routing_url.clone(), - git_context: git_context.into(), + schema, + git_context, + convert_to_federated_graph: self.convert, }, &client, - self.convert, )?; handle_publish_response(publish_response, &self.subgraph, &self.graph.name); @@ -91,7 +86,7 @@ impl Publish { } } -fn handle_publish_response(response: PublishPartialSchemaResponse, subgraph: &str, graph: &str) { +fn handle_publish_response(response: SubgraphPublishResponse, subgraph: &str, graph: &str) { if response.service_was_created { eprintln!( "A new subgraph called '{}' for the '{}' graph was created", @@ -125,13 +120,13 @@ fn handle_publish_response(response: PublishPartialSchemaResponse, subgraph: &st #[cfg(test)] mod tests { - use super::{handle_publish_response, PublishPartialSchemaResponse}; + use super::{handle_publish_response, SubgraphPublishResponse}; // this test is a bit weird, since we can't test the output. We just verify it // doesn't error #[test] fn handle_response_doesnt_error_with_all_successes() { - let response = PublishPartialSchemaResponse { + let response = SubgraphPublishResponse { schema_hash: Some("123456".to_string()), did_update_gateway: true, service_was_created: true, @@ -143,7 +138,7 @@ mod tests { #[test] fn handle_response_doesnt_error_with_all_failures() { - let response = PublishPartialSchemaResponse { + let response = SubgraphPublishResponse { schema_hash: None, did_update_gateway: false, service_was_created: false, From 26eda8b82963905b9bff6589d7594638c40e0005 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Mon, 28 Jun 2021 11:34:17 -0500 Subject: [PATCH 04/17] chore: refactor config whoami (#633) --- crates/rover-client/src/blocking/client.rs | 29 ++++++++- crates/rover-client/src/blocking/mod.rs | 2 + .../src/blocking/studio_client.rs | 49 +++++++++++++-- crates/rover-client/src/headers.rs | 61 ------------------- crates/rover-client/src/lib.rs | 3 - crates/rover-client/src/query/config/mod.rs | 2 +- .../src/query/config/who_am_i/mod.rs | 4 ++ .../{whoami.rs => who_am_i/query_runner.rs} | 46 ++++++-------- .../src/query/config/who_am_i/types.rs | 31 ++++++++++ .../who_am_i_query.graphql} | 2 +- src/command/config/whoami.rs | 10 +-- 11 files changed, 134 insertions(+), 105 deletions(-) delete mode 100644 crates/rover-client/src/headers.rs create mode 100644 crates/rover-client/src/query/config/who_am_i/mod.rs rename crates/rover-client/src/query/config/{whoami.rs => who_am_i/query_runner.rs} (76%) create mode 100644 crates/rover-client/src/query/config/who_am_i/types.rs rename crates/rover-client/src/query/config/{whoami.graphql => who_am_i/who_am_i_query.graphql} (80%) diff --git a/crates/rover-client/src/blocking/client.rs b/crates/rover-client/src/blocking/client.rs index 8cde1bb23..f9a472a03 100644 --- a/crates/rover-client/src/blocking/client.rs +++ b/crates/rover-client/src/blocking/client.rs @@ -1,13 +1,16 @@ -use crate::{headers, RoverClientError}; +use crate::RoverClientError; use graphql_client::{Error as GraphQLError, GraphQLQuery, Response as GraphQLResponse}; use reqwest::{ blocking::{Client as ReqwestClient, Response}, - header::HeaderMap, + header::{HeaderMap, HeaderName, HeaderValue}, Error as ReqwestError, StatusCode, }; use std::collections::HashMap; +pub(crate) const JSON_CONTENT_TYPE: &str = "application/json"; +pub(crate) const CLIENT_NAME: &str = "rover-client"; + /// Represents a generic GraphQL client for making http requests. pub struct GraphQLClient { client: ReqwestClient, @@ -35,7 +38,7 @@ impl GraphQLClient { variables: Q::Variables, header_map: &HashMap, ) -> Result { - let header_map = headers::build(header_map)?; + let header_map = build_headers(header_map)?; let response = self.execute::(variables, header_map)?; GraphQLClient::handle_response::(response) } @@ -125,6 +128,26 @@ fn handle_graphql_body_errors(errors: Vec) -> Result<(), RoverClie } } +/// Function for building a [HeaderMap] for making http requests. Use for +/// Generic requests to any graphql endpoint. +/// +/// Takes a single argument, list of header key/value pairs +fn build_headers(header_map: &HashMap) -> Result { + let mut headers = HeaderMap::new(); + + // this should be consistent for any graphql requests + let content_type = HeaderValue::from_str(JSON_CONTENT_TYPE)?; + headers.append("Content-Type", content_type); + + for (key, value) in header_map { + let header_key = HeaderName::from_bytes(key.as_bytes())?; + let header_value = HeaderValue::from_str(&value)?; + headers.append(header_key, header_value); + } + + Ok(headers) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/rover-client/src/blocking/mod.rs b/crates/rover-client/src/blocking/mod.rs index e51e31143..d89a42510 100644 --- a/crates/rover-client/src/blocking/mod.rs +++ b/crates/rover-client/src/blocking/mod.rs @@ -3,3 +3,5 @@ mod studio_client; pub use client::GraphQLClient; pub use studio_client::StudioClient; + +pub(crate) use client::{CLIENT_NAME, JSON_CONTENT_TYPE}; diff --git a/crates/rover-client/src/blocking/studio_client.rs b/crates/rover-client/src/blocking/studio_client.rs index 22b2d15b1..12f23011e 100644 --- a/crates/rover-client/src/blocking/studio_client.rs +++ b/crates/rover-client/src/blocking/studio_client.rs @@ -1,12 +1,17 @@ -use crate::{blocking::GraphQLClient, headers, RoverClientError}; -use houston::Credential; +use crate::{ + blocking::{GraphQLClient, CLIENT_NAME, JSON_CONTENT_TYPE}, + RoverClientError, +}; + +use houston::{Credential, CredentialOrigin}; use graphql_client::GraphQLQuery; +use reqwest::header::{HeaderMap, HeaderValue}; use reqwest::Error as ReqwestError; /// Represents a client for making GraphQL requests to Apollo Studio. pub struct StudioClient { - pub credential: Credential, + credential: Credential, client: GraphQLClient, version: String, } @@ -33,8 +38,44 @@ impl StudioClient { &self, variables: Q::Variables, ) -> Result { - let header_map = headers::build_studio_headers(&self.credential.api_key, &self.version)?; + let header_map = self.build_studio_headers()?; let response = self.client.execute::(variables, header_map)?; GraphQLClient::handle_response::(response) } + + /// Function for building a [HeaderMap] for making http requests. Use for making + /// requests to Apollo Studio. We're leaving this separate from `build` since we + /// need to be able to mark the api_key as sensitive (at the bottom) + /// + /// Takes an `api_key` and a `client_version`, and returns a [HeaderMap]. + pub fn build_studio_headers(&self) -> Result { + let mut headers = HeaderMap::new(); + + let content_type = HeaderValue::from_str(JSON_CONTENT_TYPE)?; + headers.insert("Content-Type", content_type); + + // The headers "apollographql-client-name" and "apollographql-client-version" + // are used for client identification in Apollo Studio. + + // This provides metrics in Studio that help keep track of what parts of the schema + // Rover uses, which ensures future changes to the API do not break Rover users. + // more info here: + // https://www.apollographql.com/docs/studio/client-awareness/#using-apollo-server-and-apollo-client + + let client_name = HeaderValue::from_str(CLIENT_NAME)?; + headers.insert("apollographql-client-name", client_name); + tracing::debug!(?self.version); + let client_version = HeaderValue::from_str(&self.version)?; + headers.insert("apollographql-client-version", client_version); + + let mut api_key = HeaderValue::from_str(&self.credential.api_key)?; + api_key.set_sensitive(true); + headers.insert("x-api-key", api_key); + + Ok(headers) + } + + pub fn get_credential_origin(&self) -> CredentialOrigin { + self.credential.origin.clone() + } } diff --git a/crates/rover-client/src/headers.rs b/crates/rover-client/src/headers.rs deleted file mode 100644 index 6bcf47762..000000000 --- a/crates/rover-client/src/headers.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::RoverClientError; -use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; -use std::collections::HashMap; - -const JSON_CONTENT_TYPE: &str = "application/json"; -const CLIENT_NAME: &str = "rover-client"; - -/// Function for building a [HeaderMap] for making http requests. Use for -/// Generic requests to any graphql endpoint. -/// -/// Takes a single argument, list of header key/value pairs -pub fn build(header_map: &HashMap) -> Result { - let mut headers = HeaderMap::new(); - - // this should be consistent for any graphql requests - let content_type = HeaderValue::from_str(JSON_CONTENT_TYPE)?; - headers.append("Content-Type", content_type); - - for (key, value) in header_map { - let header_key = HeaderName::from_bytes(key.as_bytes())?; - let header_value = HeaderValue::from_str(&value)?; - headers.append(header_key, header_value); - } - - Ok(headers) -} - -/// Function for building a [HeaderMap] for making http requests. Use for making -/// requests to Apollo Studio. We're leaving this separate from `build` since we -/// need to be able to mark the api_key as sensitive (at the bottom) -/// -/// Takes an `api_key` and a `client_version`, and returns a [HeaderMap]. -pub fn build_studio_headers( - api_key: &str, - client_version: &str, -) -> Result { - let mut headers = HeaderMap::new(); - - let content_type = HeaderValue::from_str(JSON_CONTENT_TYPE)?; - headers.insert("Content-Type", content_type); - - // The headers "apollographql-client-name" and "apollographql-client-version" - // are used for client identification in Apollo Studio. - - // This provides metrics in Studio that help keep track of what parts of the schema - // Rover uses, which ensures future changes to the API do not break Rover users. - // more info here: - // https://www.apollographql.com/docs/studio/client-awareness/#using-apollo-server-and-apollo-client - - let client_name = HeaderValue::from_str(CLIENT_NAME)?; - headers.insert("apollographql-client-name", client_name); - tracing::debug!(?client_version); - let client_version = HeaderValue::from_str(&client_version)?; - headers.insert("apollographql-client-version", client_version); - - let mut api_key = HeaderValue::from_str(api_key)?; - api_key.set_sensitive(true); - headers.insert("x-api-key", api_key); - - Ok(headers) -} diff --git a/crates/rover-client/src/lib.rs b/crates/rover-client/src/lib.rs index f206d4c0d..d650fc35d 100644 --- a/crates/rover-client/src/lib.rs +++ b/crates/rover-client/src/lib.rs @@ -6,9 +6,6 @@ pub mod blocking; mod error; -/// Module related to constructing request headers. -pub mod headers; - /// Module related to building an SDL from an introspection response. pub mod introspection; diff --git a/crates/rover-client/src/query/config/mod.rs b/crates/rover-client/src/query/config/mod.rs index ab814a569..e5e9f78ca 100644 --- a/crates/rover-client/src/query/config/mod.rs +++ b/crates/rover-client/src/query/config/mod.rs @@ -1,5 +1,5 @@ /// runner for rover config whoami -pub mod whoami; +pub mod who_am_i; /// runner is_federated check pub mod is_federated; diff --git a/crates/rover-client/src/query/config/who_am_i/mod.rs b/crates/rover-client/src/query/config/who_am_i/mod.rs new file mode 100644 index 000000000..30535495a --- /dev/null +++ b/crates/rover-client/src/query/config/who_am_i/mod.rs @@ -0,0 +1,4 @@ +mod types; + +pub mod query_runner; +pub use types::{Actor, ConfigWhoAmIInput, RegistryIdentity}; diff --git a/crates/rover-client/src/query/config/whoami.rs b/crates/rover-client/src/query/config/who_am_i/query_runner.rs similarity index 76% rename from crates/rover-client/src/query/config/whoami.rs rename to crates/rover-client/src/query/config/who_am_i/query_runner.rs index 238440eb1..0bc7eba7e 100644 --- a/crates/rover-client/src/query/config/whoami.rs +++ b/crates/rover-client/src/query/config/who_am_i/query_runner.rs @@ -1,5 +1,10 @@ use crate::blocking::StudioClient; +use crate::query::config::who_am_i::{ + types::{QueryActorType, QueryResponseData, RegistryIdentity}, + Actor, ConfigWhoAmIInput, +}; use crate::RoverClientError; + use houston::CredentialOrigin; use graphql_client::*; @@ -8,43 +13,28 @@ use graphql_client::*; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/query/config/whoami.graphql", + query_path = "src/query/config/who_am_i/who_am_i_query.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. who_am_i_query -pub struct WhoAmIQuery; - -#[derive(Debug, PartialEq)] -pub struct RegistryIdentity { - pub id: String, - pub graph_title: Option, - pub key_actor_type: Actor, - pub credential_origin: CredentialOrigin, -} - -#[derive(Debug, PartialEq)] -pub enum Actor { - GRAPH, - USER, - OTHER, -} +/// Snake case of this name is the mod name. i.e. config_who_am_i_query +pub struct ConfigWhoAmIQuery; /// Get info from the registry about an API key, i.e. the name/id of the /// user/graph and what kind of key it is (GRAPH/USER/Other) pub fn run( - variables: who_am_i_query::Variables, + input: ConfigWhoAmIInput, client: &StudioClient, ) -> Result { - let response_data = client.post::(variables)?; - get_identity_from_response_data(response_data, client.credential.origin.clone()) + let response_data = client.post::(input.into())?; + get_identity_from_response_data(response_data, client.get_credential_origin()) } fn get_identity_from_response_data( - response_data: who_am_i_query::ResponseData, + response_data: QueryResponseData, credential_origin: CredentialOrigin, ) -> Result { if let Some(me) = response_data.me { @@ -54,13 +44,13 @@ fn get_identity_from_response_data( // more here: https://studio-staging.apollographql.com/graph/engine/schema/reference/enums/ActorType?variant=prod let key_actor_type = match me.as_actor.type_ { - who_am_i_query::ActorType::GRAPH => Actor::GRAPH, - who_am_i_query::ActorType::USER => Actor::USER, + QueryActorType::GRAPH => Actor::GRAPH, + QueryActorType::USER => Actor::USER, _ => Actor::OTHER, }; let graph_title = match me.on { - who_am_i_query::WhoAmIQueryMeOn::Service(s) => Some(s.title), + config_who_am_i_query::ConfigWhoAmIQueryMeOn::Service(s) => Some(s.title), _ => None, }; @@ -91,7 +81,8 @@ mod tests { }, } }); - let data: who_am_i_query::ResponseData = serde_json::from_value(json_response).unwrap(); + let data: config_who_am_i_query::ResponseData = + serde_json::from_value(json_response).unwrap(); let output = get_identity_from_response_data(data, CredentialOrigin::EnvVar); let expected_identity = RegistryIdentity { @@ -116,7 +107,8 @@ mod tests { }, } }); - let data: who_am_i_query::ResponseData = serde_json::from_value(json_response).unwrap(); + let data: config_who_am_i_query::ResponseData = + serde_json::from_value(json_response).unwrap(); let output = get_identity_from_response_data(data, CredentialOrigin::EnvVar); let expected_identity = RegistryIdentity { diff --git a/crates/rover-client/src/query/config/who_am_i/types.rs b/crates/rover-client/src/query/config/who_am_i/types.rs new file mode 100644 index 000000000..c9cfeafa2 --- /dev/null +++ b/crates/rover-client/src/query/config/who_am_i/types.rs @@ -0,0 +1,31 @@ +use super::query_runner::config_who_am_i_query; + +use houston::CredentialOrigin; + +pub(crate) type QueryResponseData = config_who_am_i_query::ResponseData; +pub(crate) type QueryVariables = config_who_am_i_query::Variables; +pub(crate) type QueryActorType = config_who_am_i_query::ActorType; + +#[derive(Debug, PartialEq)] +pub struct RegistryIdentity { + pub id: String, + pub graph_title: Option, + pub key_actor_type: Actor, + pub credential_origin: CredentialOrigin, +} + +#[derive(Debug, PartialEq)] +pub enum Actor { + GRAPH, + USER, + OTHER, +} + +#[derive(Debug, PartialEq)] +pub struct ConfigWhoAmIInput {} + +impl From for QueryVariables { + fn from(_input: ConfigWhoAmIInput) -> Self { + Self {} + } +} diff --git a/crates/rover-client/src/query/config/whoami.graphql b/crates/rover-client/src/query/config/who_am_i/who_am_i_query.graphql similarity index 80% rename from crates/rover-client/src/query/config/whoami.graphql rename to crates/rover-client/src/query/config/who_am_i/who_am_i_query.graphql index 9bf0e18f6..4f131f2fa 100644 --- a/crates/rover-client/src/query/config/whoami.graphql +++ b/crates/rover-client/src/query/config/who_am_i/who_am_i_query.graphql @@ -1,4 +1,4 @@ -query WhoAmIQuery { +query ConfigWhoAmIQuery { me { __typename ... on Service { diff --git a/src/command/config/whoami.rs b/src/command/config/whoami.rs index 2c343c16e..1c77dea5a 100644 --- a/src/command/config/whoami.rs +++ b/src/command/config/whoami.rs @@ -1,9 +1,9 @@ use ansi_term::Colour::Green; +use rover_client::query::config::who_am_i::{query_runner, Actor, ConfigWhoAmIInput}; use serde::Serialize; use structopt::StructOpt; use houston::CredentialOrigin; -use rover_client::query::config::whoami; use crate::anyhow; use crate::command::RoverStdout; @@ -26,7 +26,7 @@ impl WhoAmI { let client = client_config.get_client(&self.profile_name)?; eprintln!("Checking identity of your API key against the registry."); - let identity = whoami::run(whoami::who_am_i_query::Variables {}, &client)?; + let identity = query_runner::run(ConfigWhoAmIInput {}, &client)?; let mut message = format!( "{}: {:?}\n", @@ -35,7 +35,7 @@ impl WhoAmI { ); match identity.key_actor_type { - whoami::Actor::GRAPH => { + Actor::GRAPH => { if let Some(graph_title) = identity.graph_title { message.push_str(&format!( "{}: {}\n", @@ -50,7 +50,7 @@ impl WhoAmI { )); Ok(()) } - whoami::Actor::USER => { + Actor::USER => { message.push_str(&format!( "{}: {}\n", Green.normal().paint("User ID"), @@ -63,7 +63,7 @@ impl WhoAmI { )), }?; - let origin = match client.credential.origin { + let origin = match client.get_credential_origin() { CredentialOrigin::ConfigFile(path) => format!("--profile {}", &path), CredentialOrigin::EnvVar => format!("${}", &RoverEnvKey::Key), }; From 82aed51265a2180352e9a99c281a3111c1224ac1 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Mon, 28 Jun 2021 14:16:50 -0500 Subject: [PATCH 05/17] chore: refactor subgraph delete (#639) --- .../delete_mutation.graphql} | 10 ++-- .../src/query/subgraph/delete/mod.rs | 6 ++ .../{delete.rs => delete/mutation_runner.rs} | 57 ++++++++----------- .../src/query/subgraph/delete/types.rs | 36 ++++++++++++ src/command/subgraph/delete.rs | 24 ++++---- 5 files changed, 82 insertions(+), 51 deletions(-) rename crates/rover-client/src/query/subgraph/{delete.graphql => delete/delete_mutation.graphql} (63%) create mode 100644 crates/rover-client/src/query/subgraph/delete/mod.rs rename crates/rover-client/src/query/subgraph/{delete.rs => delete/mutation_runner.rs} (65%) create mode 100644 crates/rover-client/src/query/subgraph/delete/types.rs diff --git a/crates/rover-client/src/query/subgraph/delete.graphql b/crates/rover-client/src/query/subgraph/delete/delete_mutation.graphql similarity index 63% rename from crates/rover-client/src/query/subgraph/delete.graphql rename to crates/rover-client/src/query/subgraph/delete/delete_mutation.graphql index 0b6cf25a5..f9ba68241 100644 --- a/crates/rover-client/src/query/subgraph/delete.graphql +++ b/crates/rover-client/src/query/subgraph/delete/delete_mutation.graphql @@ -1,13 +1,13 @@ -mutation DeleteServiceMutation( +mutation SubgraphDeleteMutation( $graphId: ID! - $graphVariant: String! - $name: String! + $variant: String! + $subgraph: String! $dryRun: Boolean! ) { service(id: $graphId) { removeImplementingServiceAndTriggerComposition( - graphVariant: $graphVariant - name: $name + graphVariant: $variant + name: $subgraph dryRun: $dryRun ) { errors { diff --git a/crates/rover-client/src/query/subgraph/delete/mod.rs b/crates/rover-client/src/query/subgraph/delete/mod.rs new file mode 100644 index 000000000..788f96dd6 --- /dev/null +++ b/crates/rover-client/src/query/subgraph/delete/mod.rs @@ -0,0 +1,6 @@ +mod mutation_runner; + +pub(crate) mod types; + +pub use mutation_runner::run; +pub use types::{SubgraphDeleteInput, SubgraphDeleteResponse}; diff --git a/crates/rover-client/src/query/subgraph/delete.rs b/crates/rover-client/src/query/subgraph/delete/mutation_runner.rs similarity index 65% rename from crates/rover-client/src/query/subgraph/delete.rs rename to crates/rover-client/src/query/subgraph/delete/mutation_runner.rs index 7e735b2ea..f9ddddde0 100644 --- a/crates/rover-client/src/query/subgraph/delete.rs +++ b/crates/rover-client/src/query/subgraph/delete/mutation_runner.rs @@ -1,48 +1,39 @@ use crate::blocking::StudioClient; +use crate::query::subgraph::delete::types::*; use crate::RoverClientError; + use graphql_client::*; #[derive(GraphQLQuery)] // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/query/subgraph/delete.graphql", + query_path = "src/query/subgraph/delete/delete_mutation.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. delete_service_mutation -pub struct DeleteServiceMutation; -type RawMutationResponse = delete_service_mutation::DeleteServiceMutationServiceRemoveImplementingServiceAndTriggerComposition; - -/// this struct contains all the info needed to print the result of the delete. -/// `updated_gateway` is true when composition succeeds and the gateway config -/// is updated for the gateway to consume. `composition_errors` is just a list -/// of strings for when there are composition errors as a result of the delete. -#[derive(Debug, PartialEq)] -pub struct DeleteServiceResponse { - pub updated_gateway: bool, - pub composition_errors: Option>, -} +/// Snake case of this name is the mod name. i.e. subgraph_delete_mutation +pub(crate) struct SubgraphDeleteMutation; /// The main function to be used from this module. This function fetches a /// schema from apollo studio and returns it in either sdl (default) or json format pub fn run( - variables: delete_service_mutation::Variables, + input: SubgraphDeleteInput, client: &StudioClient, -) -> Result { - let graph = variables.graph_id.clone(); - let response_data = client.post::(variables)?; +) -> Result { + let graph = input.graph_id.clone(); + let response_data = client.post::(input.into())?; let data = get_delete_data_from_response(response_data, graph)?; Ok(build_response(data)) } fn get_delete_data_from_response( - response_data: delete_service_mutation::ResponseData, + response_data: subgraph_delete_mutation::ResponseData, graph: String, -) -> Result { +) -> Result { let service_data = response_data .service .ok_or(RoverClientError::NoService { graph })?; @@ -50,7 +41,7 @@ fn get_delete_data_from_response( Ok(service_data.remove_implementing_service_and_trigger_composition) } -fn build_response(response: RawMutationResponse) -> DeleteServiceResponse { +fn build_response(response: MutationComposition) -> SubgraphDeleteResponse { let composition_errors: Vec = response .errors .iter() @@ -64,7 +55,7 @@ fn build_response(response: RawMutationResponse) -> DeleteServiceResponse { None }; - DeleteServiceResponse { + SubgraphDeleteResponse { updated_gateway: response.updated_gateway, composition_errors, } @@ -75,8 +66,6 @@ mod tests { use super::*; use serde_json::json; - type RawCompositionErrors = delete_service_mutation::DeleteServiceMutationServiceRemoveImplementingServiceAndTriggerCompositionErrors; - #[test] fn get_delete_data_from_response_works() { let json_response = json!({ @@ -91,19 +80,19 @@ mod tests { } } }); - let data: delete_service_mutation::ResponseData = + let data: subgraph_delete_mutation::ResponseData = serde_json::from_value(json_response).unwrap(); let output = get_delete_data_from_response(data, "mygraph".to_string()); assert!(output.is_ok()); - let expected_response = RawMutationResponse { + let expected_response = MutationComposition { errors: vec![ - Some(RawCompositionErrors { + Some(MutationCompositionErrors { message: "wow".to_string(), }), None, - Some(RawCompositionErrors { + Some(MutationCompositionErrors { message: "boo".to_string(), }), ], @@ -114,13 +103,13 @@ mod tests { #[test] fn build_response_works_with_successful_responses() { - let response = RawMutationResponse { + let response = MutationComposition { errors: vec![ - Some(RawCompositionErrors { + Some(MutationCompositionErrors { message: "wow".to_string(), }), None, - Some(RawCompositionErrors { + Some(MutationCompositionErrors { message: "boo".to_string(), }), ], @@ -130,7 +119,7 @@ mod tests { let parsed = build_response(response); assert_eq!( parsed, - DeleteServiceResponse { + SubgraphDeleteResponse { composition_errors: Some(vec!["wow".to_string(), "boo".to_string()]), updated_gateway: false, } @@ -139,7 +128,7 @@ mod tests { #[test] fn build_response_works_with_failure_responses() { - let response = RawMutationResponse { + let response = MutationComposition { errors: vec![], updated_gateway: true, }; @@ -147,7 +136,7 @@ mod tests { let parsed = build_response(response); assert_eq!( parsed, - DeleteServiceResponse { + SubgraphDeleteResponse { composition_errors: None, updated_gateway: true, } diff --git a/crates/rover-client/src/query/subgraph/delete/types.rs b/crates/rover-client/src/query/subgraph/delete/types.rs new file mode 100644 index 000000000..82a82358f --- /dev/null +++ b/crates/rover-client/src/query/subgraph/delete/types.rs @@ -0,0 +1,36 @@ +use crate::query::subgraph::delete::mutation_runner::subgraph_delete_mutation; + +pub(crate) type MutationComposition = subgraph_delete_mutation::SubgraphDeleteMutationServiceRemoveImplementingServiceAndTriggerComposition; +pub(crate) type MutationVariables = subgraph_delete_mutation::Variables; + +#[cfg(test)] +pub(crate) type MutationCompositionErrors = subgraph_delete_mutation::SubgraphDeleteMutationServiceRemoveImplementingServiceAndTriggerCompositionErrors; + +#[derive(Debug, Clone, PartialEq)] +pub struct SubgraphDeleteInput { + pub graph_id: String, + pub variant: String, + pub subgraph: String, + pub dry_run: bool, +} + +/// this struct contains all the info needed to print the result of the delete. +/// `updated_gateway` is true when composition succeeds and the gateway config +/// is updated for the gateway to consume. `composition_errors` is just a list +/// of strings for when there are composition errors as a result of the delete. +#[derive(Debug, PartialEq)] +pub struct SubgraphDeleteResponse { + pub updated_gateway: bool, + pub composition_errors: Option>, +} + +impl From for MutationVariables { + fn from(input: SubgraphDeleteInput) -> Self { + Self { + graph_id: input.graph_id, + variant: input.variant, + subgraph: input.subgraph, + dry_run: input.dry_run, + } + } +} diff --git a/src/command/subgraph/delete.rs b/src/command/subgraph/delete.rs index f66a24485..bcf7c61d4 100644 --- a/src/command/subgraph/delete.rs +++ b/src/command/subgraph/delete.rs @@ -7,7 +7,7 @@ use crate::utils::client::StudioClientConfig; use crate::utils::parsers::{parse_graph_ref, GraphRef}; use crate::Result; -use rover_client::query::subgraph::delete::{self, DeleteServiceResponse}; +use rover_client::query::subgraph::delete::{self, SubgraphDeleteInput, SubgraphDeleteResponse}; #[derive(Debug, Serialize, StructOpt)] pub struct Delete { @@ -50,10 +50,10 @@ impl Delete { if !self.confirm { // run delete with dryRun, so we can preview composition errors let delete_dry_run_response = delete::run( - delete::delete_service_mutation::Variables { + SubgraphDeleteInput { graph_id: self.graph.name.clone(), - graph_variant: self.graph.variant.clone(), - name: self.subgraph.clone(), + variant: self.graph.variant.clone(), + subgraph: self.subgraph.clone(), dry_run: true, }, &client, @@ -69,10 +69,10 @@ impl Delete { } let delete_response = delete::run( - delete::delete_service_mutation::Variables { + SubgraphDeleteInput { graph_id: self.graph.name.clone(), - graph_variant: self.graph.variant.clone(), - name: self.subgraph.clone(), + variant: self.graph.variant.clone(), + subgraph: self.subgraph.clone(), dry_run: false, }, &client, @@ -83,7 +83,7 @@ impl Delete { } } -fn handle_dry_run_response(response: DeleteServiceResponse, subgraph: &str, graph_ref: &str) { +fn handle_dry_run_response(response: SubgraphDeleteResponse, subgraph: &str, graph_ref: &str) { let warn_prefix = Red.normal().paint("WARN:"); if let Some(errors) = response.composition_errors { eprintln!( @@ -111,7 +111,7 @@ fn confirm_delete() -> Result { } } -fn handle_response(response: DeleteServiceResponse, subgraph: &str, graph_ref: &str) { +fn handle_response(response: SubgraphDeleteResponse, subgraph: &str, graph_ref: &str) { let warn_prefix = Red.normal().paint("WARN:"); if response.updated_gateway { eprintln!( @@ -138,11 +138,11 @@ fn handle_response(response: DeleteServiceResponse, subgraph: &str, graph_ref: & #[cfg(test)] mod tests { - use super::{handle_response, DeleteServiceResponse}; + use super::{handle_response, SubgraphDeleteResponse}; #[test] fn handle_response_doesnt_error_with_all_successes() { - let response = DeleteServiceResponse { + let response = SubgraphDeleteResponse { composition_errors: None, updated_gateway: true, }; @@ -152,7 +152,7 @@ mod tests { #[test] fn handle_response_doesnt_error_with_all_failures() { - let response = DeleteServiceResponse { + let response = SubgraphDeleteResponse { composition_errors: Some(vec![ "a bad thing happened".to_string(), "another bad thing".to_string(), From d74307bb2c7859cb54a205bf3f5b0a1ae8fb2b39 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Mon, 28 Jun 2021 15:07:00 -0500 Subject: [PATCH 06/17] chore: refactor subgraph list (#640) --- .../{list.graphql => list/list_query.graphql} | 2 +- .../src/query/subgraph/list/mod.rs | 6 ++ .../{list.rs => list/query_runner.rs} | 64 +++++++------------ .../src/query/subgraph/list/types.rs | 38 +++++++++++ src/command/output.rs | 4 +- src/command/subgraph/list.rs | 4 +- 6 files changed, 73 insertions(+), 45 deletions(-) rename crates/rover-client/src/query/subgraph/{list.graphql => list/list_query.graphql} (80%) create mode 100644 crates/rover-client/src/query/subgraph/list/mod.rs rename crates/rover-client/src/query/subgraph/{list.rs => list/query_runner.rs} (72%) create mode 100644 crates/rover-client/src/query/subgraph/list/types.rs diff --git a/crates/rover-client/src/query/subgraph/list.graphql b/crates/rover-client/src/query/subgraph/list/list_query.graphql similarity index 80% rename from crates/rover-client/src/query/subgraph/list.graphql rename to crates/rover-client/src/query/subgraph/list/list_query.graphql index d733b65aa..69ec2f182 100644 --- a/crates/rover-client/src/query/subgraph/list.graphql +++ b/crates/rover-client/src/query/subgraph/list/list_query.graphql @@ -1,4 +1,4 @@ -query ListSubgraphsQuery($graphId: ID!, $variant: String!) { +query SubgraphListQuery($graphId: ID!, $variant: String!) { frontendUrlRoot service(id: $graphId) { implementingServices(graphVariant: $variant) { diff --git a/crates/rover-client/src/query/subgraph/list/mod.rs b/crates/rover-client/src/query/subgraph/list/mod.rs new file mode 100644 index 000000000..07495e228 --- /dev/null +++ b/crates/rover-client/src/query/subgraph/list/mod.rs @@ -0,0 +1,6 @@ +mod query_runner; + +pub(crate) mod types; + +pub use query_runner::run; +pub use types::{SubgraphListInput, SubgraphListResponse}; diff --git a/crates/rover-client/src/query/subgraph/list.rs b/crates/rover-client/src/query/subgraph/list/query_runner.rs similarity index 72% rename from crates/rover-client/src/query/subgraph/list.rs rename to crates/rover-client/src/query/subgraph/list/query_runner.rs index a7752d5f7..368744417 100644 --- a/crates/rover-client/src/query/subgraph/list.rs +++ b/crates/rover-client/src/query/subgraph/list/query_runner.rs @@ -1,59 +1,44 @@ use crate::blocking::StudioClient; +use crate::query::subgraph::list::types::*; use crate::RoverClientError; -use chrono::prelude::*; + use graphql_client::*; type Timestamp = String; - #[derive(GraphQLQuery)] // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/query/subgraph/list.graphql", + query_path = "src/query/subgraph/list/list_query.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. list_subgraphs_query -pub struct ListSubgraphsQuery; - -#[derive(Clone, PartialEq, Debug)] -pub struct SubgraphInfo { - pub name: String, - pub url: Option, // optional, and may not be a real url - pub updated_at: Option>, -} - -#[derive(Clone, PartialEq, Debug)] -pub struct ListDetails { - pub subgraphs: Vec, - pub root_url: String, - pub graph_name: String, -} +/// Snake case of this name is the mod name. i.e. subgraph_list_query +pub struct SubgraphListQuery; /// Fetches list of subgraphs for a given graph, returns name & url of each pub fn run( - variables: list_subgraphs_query::Variables, + input: SubgraphListInput, client: &StudioClient, -) -> Result { - let graph = variables.graph_id.clone(); - let response_data = client.post::(variables)?; +) -> Result { + let graph = input.graph_id.clone(); + let response_data = client.post::(input.into())?; let root_url = response_data.frontend_url_root.clone(); let subgraphs = get_subgraphs_from_response_data(response_data, graph.clone())?; - Ok(ListDetails { + Ok(SubgraphListResponse { subgraphs: format_subgraphs(&subgraphs), root_url, graph_name: graph, }) } -type RawSubgraphInfo = list_subgraphs_query::ListSubgraphsQueryServiceImplementingServicesOnFederatedImplementingServicesServices; fn get_subgraphs_from_response_data( - response_data: list_subgraphs_query::ResponseData, + response_data: QueryResponseData, graph: String, -) -> Result, RoverClientError> { +) -> Result, RoverClientError> { let service_data = response_data.service.ok_or(RoverClientError::NoService { graph: graph.clone(), })?; @@ -72,24 +57,25 @@ fn get_subgraphs_from_response_data( // implementing_services.services match services { - list_subgraphs_query::ListSubgraphsQueryServiceImplementingServices::FederatedImplementingServices (services) => { - Ok(services.services) - }, - list_subgraphs_query::ListSubgraphsQueryServiceImplementingServices::NonFederatedImplementingService => { - Err(RoverClientError::ExpectedFederatedGraph { graph, can_operation_convert: false }) + QueryGraphType::FederatedImplementingServices(services) => Ok(services.services), + QueryGraphType::NonFederatedImplementingService => { + Err(RoverClientError::ExpectedFederatedGraph { + graph, + can_operation_convert: false, + }) } } } /// puts the subgraphs into a vec of SubgraphInfo, sorted by updated_at /// timestamp. Newer updated services will show at top of list -fn format_subgraphs(subgraphs: &[RawSubgraphInfo]) -> Vec { +fn format_subgraphs(subgraphs: &[QuerySubgraphInfo]) -> Vec { let mut subgraphs: Vec = subgraphs .iter() .map(|subgraph| SubgraphInfo { name: subgraph.name.clone(), url: subgraph.url.clone(), - updated_at: subgraph.updated_at.clone().parse::>().ok(), + updated_at: subgraph.updated_at.clone().parse().ok(), }) .collect(); @@ -128,8 +114,7 @@ mod tests { } } }); - let data: list_subgraphs_query::ResponseData = - serde_json::from_value(json_response).unwrap(); + let data: QueryResponseData = serde_json::from_value(json_response).unwrap(); let output = get_subgraphs_from_response_data(data, "mygraph".to_string()); let expected_json = json!([ @@ -144,7 +129,7 @@ mod tests { "updatedAt": "2020-09-16T19:22:06.420Z" } ]); - let expected_service_list: Vec = + let expected_service_list: Vec = serde_json::from_value(expected_json).unwrap(); assert!(output.is_ok()); @@ -159,8 +144,7 @@ mod tests { "implementingServices": null } }); - let data: list_subgraphs_query::ResponseData = - serde_json::from_value(json_response).unwrap(); + let data: QueryResponseData = serde_json::from_value(json_response).unwrap(); let output = get_subgraphs_from_response_data(data, "mygraph".to_string()); assert!(output.is_err()); } @@ -184,7 +168,7 @@ mod tests { "updatedAt": "2020-09-16T19:22:06.420Z" } ]); - let raw_subgraph_list: Vec = + let raw_subgraph_list: Vec = serde_json::from_value(raw_info_json).unwrap(); let formatted = format_subgraphs(&raw_subgraph_list); assert_eq!(formatted[0].name, "accounts".to_string()); diff --git a/crates/rover-client/src/query/subgraph/list/types.rs b/crates/rover-client/src/query/subgraph/list/types.rs new file mode 100644 index 000000000..ef69c1cb7 --- /dev/null +++ b/crates/rover-client/src/query/subgraph/list/types.rs @@ -0,0 +1,38 @@ +use crate::query::subgraph::list::query_runner::subgraph_list_query; + +pub(crate) type QuerySubgraphInfo = subgraph_list_query::SubgraphListQueryServiceImplementingServicesOnFederatedImplementingServicesServices; +pub(crate) type QueryResponseData = subgraph_list_query::ResponseData; +pub(crate) type QueryGraphType = subgraph_list_query::SubgraphListQueryServiceImplementingServices; + +type QueryVariables = subgraph_list_query::Variables; + +use chrono::{DateTime, Local}; + +#[derive(Clone, PartialEq, Debug)] +pub struct SubgraphListInput { + pub graph_id: String, + pub variant: String, +} + +impl From for QueryVariables { + fn from(input: SubgraphListInput) -> Self { + Self { + graph_id: input.graph_id, + variant: input.variant, + } + } +} + +#[derive(Clone, PartialEq, Debug)] +pub struct SubgraphListResponse { + pub subgraphs: Vec, + pub root_url: String, + pub graph_name: String, +} + +#[derive(Clone, PartialEq, Debug)] +pub struct SubgraphInfo { + pub name: String, + pub url: Option, // optional, and may not be a real url + pub updated_at: Option>, +} diff --git a/src/command/output.rs b/src/command/output.rs index c1c8fe3e9..c2870532f 100644 --- a/src/command/output.rs +++ b/src/command/output.rs @@ -6,7 +6,7 @@ use crate::utils::table::{self, cell, row}; use ansi_term::{Colour::Yellow, Style}; use atty::Stream; use crossterm::style::Attribute::Underlined; -use rover_client::query::subgraph::{check::SubgraphCheckResponse, list::ListDetails}; +use rover_client::query::subgraph::{check::SubgraphCheckResponse, list::SubgraphListResponse}; use termimad::MadSkin; /// RoverStdout defines all of the different types of data that are printed @@ -24,7 +24,7 @@ pub enum RoverStdout { Sdl(String), CoreSchema(String), SchemaHash(String), - SubgraphList(ListDetails), + SubgraphList(SubgraphListResponse), SubgraphCheck(SubgraphCheckResponse), VariantList(Vec), Profiles(Vec), diff --git a/src/command/subgraph/list.rs b/src/command/subgraph/list.rs index 27843938d..28c122a48 100644 --- a/src/command/subgraph/list.rs +++ b/src/command/subgraph/list.rs @@ -2,7 +2,7 @@ use ansi_term::Colour::Cyan; use serde::Serialize; use structopt::StructOpt; -use rover_client::query::subgraph::list; +use rover_client::query::subgraph::list::{self, SubgraphListInput}; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; @@ -34,7 +34,7 @@ impl List { ); let list_details = list::run( - list::list_subgraphs_query::Variables { + SubgraphListInput { graph_id: self.graph.name.clone(), variant: self.graph.variant.clone(), }, From 8d0aabf86bbb72be8464df8bf8560bcbd3605110 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Mon, 28 Jun 2021 16:50:14 -0500 Subject: [PATCH 07/17] chore: refactor subgraph introspect (#641) --- .../{ => introspect}/introspect_query.graphql | 2 +- .../introspect_schema.graphql | 0 .../src/query/subgraph/introspect/mod.rs | 5 ++++ .../query_runner.rs} | 28 ++++++++----------- .../src/query/subgraph/introspect/types.rs | 22 +++++++++++++++ src/command/subgraph/introspect.rs | 7 +++-- src/command/supergraph/compose/do_compose.rs | 8 +++++- 7 files changed, 51 insertions(+), 21 deletions(-) rename crates/rover-client/src/query/subgraph/{ => introspect}/introspect_query.graphql (52%) rename crates/rover-client/src/query/subgraph/{ => introspect}/introspect_schema.graphql (100%) create mode 100644 crates/rover-client/src/query/subgraph/introspect/mod.rs rename crates/rover-client/src/query/subgraph/{introspect.rs => introspect/query_runner.rs} (57%) create mode 100644 crates/rover-client/src/query/subgraph/introspect/types.rs diff --git a/crates/rover-client/src/query/subgraph/introspect_query.graphql b/crates/rover-client/src/query/subgraph/introspect/introspect_query.graphql similarity index 52% rename from crates/rover-client/src/query/subgraph/introspect_query.graphql rename to crates/rover-client/src/query/subgraph/introspect/introspect_query.graphql index b29c43696..4d6269dfe 100644 --- a/crates/rover-client/src/query/subgraph/introspect_query.graphql +++ b/crates/rover-client/src/query/subgraph/introspect/introspect_query.graphql @@ -1,4 +1,4 @@ -query IntrospectionQuery{ +query SubgraphIntrospectQuery{ _service { sdl } diff --git a/crates/rover-client/src/query/subgraph/introspect_schema.graphql b/crates/rover-client/src/query/subgraph/introspect/introspect_schema.graphql similarity index 100% rename from crates/rover-client/src/query/subgraph/introspect_schema.graphql rename to crates/rover-client/src/query/subgraph/introspect/introspect_schema.graphql diff --git a/crates/rover-client/src/query/subgraph/introspect/mod.rs b/crates/rover-client/src/query/subgraph/introspect/mod.rs new file mode 100644 index 000000000..e16e29777 --- /dev/null +++ b/crates/rover-client/src/query/subgraph/introspect/mod.rs @@ -0,0 +1,5 @@ +pub(crate) mod query_runner; +pub(crate) mod types; + +pub use query_runner::run; +pub use types::{SubgraphIntrospectInput, SubgraphIntrospectResponse}; diff --git a/crates/rover-client/src/query/subgraph/introspect.rs b/crates/rover-client/src/query/subgraph/introspect/query_runner.rs similarity index 57% rename from crates/rover-client/src/query/subgraph/introspect.rs rename to crates/rover-client/src/query/subgraph/introspect/query_runner.rs index 8a2a0b206..a46ce5084 100644 --- a/crates/rover-client/src/query/subgraph/introspect.rs +++ b/crates/rover-client/src/query/subgraph/introspect/query_runner.rs @@ -1,29 +1,25 @@ use crate::blocking::GraphQLClient; +use crate::query::subgraph::introspect::types::*; use crate::RoverClientError; + use graphql_client::*; -use std::collections::HashMap; #[derive(GraphQLQuery)] #[graphql( - query_path = "src/query/subgraph/introspect_query.graphql", - schema_path = "src/query/subgraph/introspect_schema.graphql", + query_path = "src/query/subgraph/introspect/introspect_query.graphql", + schema_path = "src/query/subgraph/introspect/introspect_schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] -pub struct IntrospectionQuery; - -#[derive(Debug, PartialEq)] -pub struct IntrospectionResponse { - pub result: String, -} +pub(crate) struct SubgraphIntrospectQuery; pub fn run( + input: SubgraphIntrospectInput, client: &GraphQLClient, - headers: &HashMap, -) -> Result { - let variables = introspection_query::Variables {}; - let response_data = client.post::(variables, headers); +) -> Result { + let response_data = + client.post::(input.clone().into(), &input.headers); match response_data { Ok(data) => build_response(data), @@ -39,14 +35,12 @@ pub fn run( } } -fn build_response( - data: introspection_query::ResponseData, -) -> Result { +fn build_response(data: QueryResponseData) -> Result { let service_data = data.service.ok_or(RoverClientError::IntrospectionError { msg: "No introspection response available.".to_string(), })?; - Ok(IntrospectionResponse { + Ok(SubgraphIntrospectResponse { result: service_data.sdl, }) } diff --git a/crates/rover-client/src/query/subgraph/introspect/types.rs b/crates/rover-client/src/query/subgraph/introspect/types.rs new file mode 100644 index 000000000..6f3301964 --- /dev/null +++ b/crates/rover-client/src/query/subgraph/introspect/types.rs @@ -0,0 +1,22 @@ +use crate::query::subgraph::introspect::query_runner::subgraph_introspect_query; + +pub(crate) type QueryVariables = subgraph_introspect_query::Variables; +pub(crate) type QueryResponseData = subgraph_introspect_query::ResponseData; + +use std::collections::HashMap; + +#[derive(Debug, Clone, PartialEq)] +pub struct SubgraphIntrospectInput { + pub headers: HashMap, +} + +impl From for QueryVariables { + fn from(_input: SubgraphIntrospectInput) -> Self { + Self {} + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SubgraphIntrospectResponse { + pub result: String, +} diff --git a/src/command/subgraph/introspect.rs b/src/command/subgraph/introspect.rs index 4b69876f0..884cfe5c8 100644 --- a/src/command/subgraph/introspect.rs +++ b/src/command/subgraph/introspect.rs @@ -3,7 +3,10 @@ use std::collections::HashMap; use structopt::StructOpt; use url::Url; -use rover_client::{blocking::GraphQLClient, query::subgraph::introspect}; +use rover_client::{ + blocking::GraphQLClient, + query::subgraph::introspect::{self, SubgraphIntrospectInput}, +}; use crate::command::RoverStdout; use crate::utils::parsers::parse_header; @@ -43,7 +46,7 @@ impl Introspect { } } - let introspection_response = introspect::run(&client, &headers)?; + let introspection_response = introspect::run(SubgraphIntrospectInput { headers }, &client)?; Ok(RoverStdout::Introspection(introspection_response.result)) } diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index b816c3e7a..d775e627b 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -6,6 +6,7 @@ use ansi_term::Colour::Red; use camino::Utf8PathBuf; use rover_client::query::subgraph::fetch::SubgraphFetchInput; +use rover_client::query::subgraph::introspect::SubgraphIntrospectInput; use rover_client::{ blocking::GraphQLClient, query::subgraph::{fetch, introspect}, @@ -105,7 +106,12 @@ pub(crate) fn get_subgraph_definitions( // obtain SDL and add it to subgraph_definition. let client = GraphQLClient::new(&subgraph_url.to_string())?; - let introspection_response = introspect::run(&client, &HashMap::new())?; + let introspection_response = introspect::run( + SubgraphIntrospectInput { + headers: HashMap::new(), + }, + &client, + )?; let schema = introspection_response.result; // We don't require a routing_url for this variant of a schema, From ca2cddecfa43e8f7d8922933582d914eeac6bf31 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Wed, 30 Jun 2021 14:48:10 -0500 Subject: [PATCH 08/17] chore: refactor graph introspect (#643) --- crates/rover-client/src/introspection/mod.rs | 2 - .../rover-client/src/introspection/schema.rs | 271 ------------- crates/rover-client/src/lib.rs | 3 - .../src/query/graph/introspect.rs | 129 ------ .../introspect}/fixtures/interfaces.json | 0 .../graph/introspect}/fixtures/simple.json | 0 .../graph/introspect}/fixtures/swapi.json | 0 .../{ => introspect}/introspect_query.graphql | 2 +- .../introspect_schema.graphql | 0 .../src/query/graph/introspect/mod.rs | 7 + .../query/graph/introspect/query_runner.rs | 41 ++ .../query/graph/introspect}/schema.rs | 376 +++++++++++++++++- .../src/query/graph/introspect/types.rs | 22 + src/command/graph/introspect.rs | 11 +- 14 files changed, 444 insertions(+), 420 deletions(-) delete mode 100644 crates/rover-client/src/introspection/mod.rs delete mode 100644 crates/rover-client/src/introspection/schema.rs delete mode 100644 crates/rover-client/src/query/graph/introspect.rs rename crates/rover-client/{tests => src/query/graph/introspect}/fixtures/interfaces.json (100%) rename crates/rover-client/{tests => src/query/graph/introspect}/fixtures/simple.json (100%) rename crates/rover-client/{tests => src/query/graph/introspect}/fixtures/swapi.json (100%) rename crates/rover-client/src/query/graph/{ => introspect}/introspect_query.graphql (97%) rename crates/rover-client/src/query/graph/{ => introspect}/introspect_schema.graphql (100%) create mode 100644 crates/rover-client/src/query/graph/introspect/mod.rs create mode 100644 crates/rover-client/src/query/graph/introspect/query_runner.rs rename crates/rover-client/{tests => src/query/graph/introspect}/schema.rs (78%) create mode 100644 crates/rover-client/src/query/graph/introspect/types.rs diff --git a/crates/rover-client/src/introspection/mod.rs b/crates/rover-client/src/introspection/mod.rs deleted file mode 100644 index dd9d7b111..000000000 --- a/crates/rover-client/src/introspection/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod schema; -pub use schema::Schema; diff --git a/crates/rover-client/src/introspection/schema.rs b/crates/rover-client/src/introspection/schema.rs deleted file mode 100644 index c5744f894..000000000 --- a/crates/rover-client/src/introspection/schema.rs +++ /dev/null @@ -1,271 +0,0 @@ -//! Schema encoding module used to work with Introspection result. -//! -//! More information on Schema Definition language(SDL) can be found in [this -//! documentation](https://www.apollographql.com/docs/apollo-server/schema/schema/). -//! -use crate::query::graph::introspect; -use sdl_encoder::{ - Directive, EnumDef, EnumValue, Field, InputField, InputObjectDef, InputValue, InterfaceDef, - ObjectDef, ScalarDef, Schema as SDL, SchemaDef, Type_, UnionDef, -}; -use serde::Deserialize; -use std::convert::TryFrom; - -pub type FullTypeField = introspect::introspection_query::FullTypeFields; -pub type FullTypeInputField = introspect::introspection_query::FullTypeInputFields; -pub type FullTypeFieldArg = introspect::introspection_query::FullTypeFieldsArgs; -pub type IntrospectionResult = introspect::introspection_query::ResponseData; -pub type SchemaMutationType = introspect::introspection_query::IntrospectionQuerySchemaMutationType; -pub type SchemaQueryType = introspect::introspection_query::IntrospectionQuerySchemaQueryType; -pub type SchemaType = introspect::introspection_query::IntrospectionQuerySchemaTypes; -pub type SchemaDirective = introspect::introspection_query::IntrospectionQuerySchemaDirectives; -pub type SchemaSubscriptionType = - introspect::introspection_query::IntrospectionQuerySchemaSubscriptionType; -pub type __TypeKind = introspect::introspection_query::__TypeKind; - -// Represents GraphQL types we will not be encoding to SDL. -const GRAPHQL_NAMED_TYPES: [&str; 12] = [ - "__Schema", - "__Type", - "__TypeKind", - "__Field", - "__InputValue", - "__EnumValue", - "__DirectiveLocation", - "__Directive", - "Boolean", - "String", - "Int", - "ID", -]; - -// Represents GraphQL directives we will not be encoding to SDL. -const SPECIFIED_DIRECTIVES: [&str; 3] = ["skip", "include", "deprecated"]; - -/// A representation of a GraphQL Schema. -/// -/// Contains Schema Types and Directives. -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Schema { - types: Vec, - directives: Vec, - mutation_type: Option, - query_type: SchemaQueryType, - subscription_type: Option, -} - -impl Schema { - /// Encode Schema into an SDL. - pub fn encode(self) -> String { - let mut sdl = SDL::new(); - - // When we have a defined mutation and subscription, we record - // everything to Schema Definition. - // https://www.apollographql.com/docs/graphql-subscriptions/subscriptions-to-schema/ - if self.mutation_type.is_some() | self.subscription_type.is_some() { - let mut schema_def = SchemaDef::new(); - if let Some(mutation_type) = self.mutation_type { - schema_def.mutation(mutation_type.name.unwrap()); - } - if let Some(subscription_type) = self.subscription_type { - schema_def.subscription(subscription_type.name.unwrap()); - } - if let Some(name) = self.query_type.name { - schema_def.query(name); - } - sdl.schema(schema_def); - } else if let Some(name) = self.query_type.name { - // If we don't have a mutation or a subscription, but do have a - // query type, only create a Schema Definition when it's something - // other than `Query`. - if name != "Query" { - let mut schema_def = SchemaDef::new(); - schema_def.query(name); - sdl.schema(schema_def); - } - } - - // Exclude GraphQL directives like 'skip' and 'include' before encoding directives. - self.directives - .into_iter() - .filter(|directive| !SPECIFIED_DIRECTIVES.contains(&directive.name.as_str())) - .for_each(|directive| Self::encode_directives(directive, &mut sdl)); - - // Exclude GraphQL named types like __Schema before encoding full type. - self.types - .into_iter() - .filter(|type_| match type_.full_type.name.as_deref() { - Some(name) => !GRAPHQL_NAMED_TYPES.contains(&name), - None => false, - }) - .for_each(|type_| Self::encode_full_type(type_, &mut sdl)); - - sdl.finish() - } - - fn encode_directives(directive: SchemaDirective, sdl: &mut SDL) { - let mut directive_ = Directive::new(directive.name); - directive_.description(directive.description); - for location in directive.locations { - // Location is of a __DirectiveLocation enum that doesn't implement - // Display (meaning we can't just do .to_string). This next line - // just forces it into a String with format! debug. - directive_.location(format!("{:?}", location)); - } - - sdl.directive(directive_) - } - - fn encode_full_type(type_: SchemaType, sdl: &mut SDL) { - let ty = type_.full_type; - - match ty.kind { - __TypeKind::OBJECT => { - let mut object_def = ObjectDef::new(ty.name.unwrap_or_else(String::new)); - object_def.description(ty.description); - if let Some(interfaces) = ty.interfaces { - for interface in interfaces { - object_def.interface(interface.type_ref.name.unwrap_or_else(String::new)); - } - } - if let Some(field) = ty.fields { - for f in field { - let field_def = Self::encode_field(f); - object_def.field(field_def); - } - sdl.object(object_def); - } - } - __TypeKind::INPUT_OBJECT => { - let mut input_def = InputObjectDef::new(ty.name.unwrap_or_else(String::new)); - input_def.description(ty.description); - if let Some(field) = ty.input_fields { - for f in field { - let input_field_def = Self::encode_input_field(f); - input_def.field(input_field_def); - } - sdl.input(input_def); - } - } - __TypeKind::INTERFACE => { - let mut interface_def = InterfaceDef::new(ty.name.unwrap_or_else(String::new)); - interface_def.description(ty.description); - if let Some(interfaces) = ty.interfaces { - for interface in interfaces { - interface_def - .interface(interface.type_ref.name.unwrap_or_else(String::new)); - } - } - if let Some(field) = ty.fields { - for f in field { - let field_def = Self::encode_field(f); - interface_def.field(field_def); - } - sdl.interface(interface_def); - } - } - __TypeKind::SCALAR => { - let mut scalar_def = ScalarDef::new(ty.name.unwrap_or_else(String::new)); - scalar_def.description(ty.description); - sdl.scalar(scalar_def); - } - __TypeKind::UNION => { - let mut union_def = UnionDef::new(ty.name.unwrap_or_else(String::new)); - union_def.description(ty.description); - if let Some(possible_types) = ty.possible_types { - for possible_type in possible_types { - union_def.member(possible_type.type_ref.name.unwrap_or_else(String::new)); - } - } - sdl.union(union_def); - } - __TypeKind::ENUM => { - let mut enum_def = EnumDef::new(ty.name.unwrap_or_else(String::new)); - if let Some(enums) = ty.enum_values { - for enum_ in enums { - let mut enum_value = EnumValue::new(enum_.name); - enum_value.description(enum_.description); - - if enum_.is_deprecated { - enum_value.deprecated(enum_.deprecation_reason); - } - - enum_def.value(enum_value); - } - } - sdl.enum_(enum_def); - } - _ => (), - } - } - - fn encode_field(field: FullTypeField) -> Field { - let ty = Self::encode_type(field.type_.type_ref); - let mut field_def = Field::new(field.name, ty); - - for value in field.args { - let field_value = Self::encode_arg(value); - field_def.arg(field_value); - } - - if field.is_deprecated { - field_def.deprecated(field.deprecation_reason); - } - field_def.description(field.description); - field_def - } - - fn encode_input_field(field: FullTypeInputField) -> InputField { - let ty = Self::encode_type(field.input_value.type_.type_ref); - let mut field_def = InputField::new(field.input_value.name, ty); - - field_def.default(field.input_value.default_value); - field_def.description(field.input_value.description); - field_def - } - - fn encode_arg(value: FullTypeFieldArg) -> InputValue { - let ty = Self::encode_type(value.input_value.type_.type_ref); - let mut value_def = InputValue::new(value.input_value.name, ty); - - value_def.default(value.input_value.default_value); - value_def.description(value.input_value.description); - value_def - } - - fn encode_type(ty: impl introspect::OfType) -> Type_ { - use introspect::introspection_query::__TypeKind::*; - match ty.kind() { - SCALAR | OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT => Type_::NamedType { - name: ty.name().unwrap().to_string(), - }, - NON_NULL => { - let ty = Self::encode_type(ty.of_type().unwrap()); - Type_::NonNull { ty: Box::new(ty) } - } - LIST => { - let ty = Self::encode_type(ty.of_type().unwrap()); - Type_::List { ty: Box::new(ty) } - } - Other(ty) => panic!("Unknown type: {}", ty), - } - } -} - -impl TryFrom for Schema { - type Error = &'static str; - - fn try_from(src: IntrospectionResult) -> Result { - match src.schema { - Some(s) => Ok(Self { - types: s.types, - directives: s.directives, - mutation_type: s.mutation_type, - query_type: s.query_type, - subscription_type: s.subscription_type, - }), - None => Err("Schema not found in Introspection Result."), - } - } -} diff --git a/crates/rover-client/src/lib.rs b/crates/rover-client/src/lib.rs index d650fc35d..943267dce 100644 --- a/crates/rover-client/src/lib.rs +++ b/crates/rover-client/src/lib.rs @@ -6,9 +6,6 @@ pub mod blocking; mod error; -/// Module related to building an SDL from an introspection response. -pub mod introspection; - /// Module for client related errors. pub use error::RoverClientError; diff --git a/crates/rover-client/src/query/graph/introspect.rs b/crates/rover-client/src/query/graph/introspect.rs deleted file mode 100644 index 1e88db228..000000000 --- a/crates/rover-client/src/query/graph/introspect.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::{collections::HashMap, convert::TryFrom}; - -use crate::blocking::GraphQLClient; -use crate::introspection::Schema; -use crate::RoverClientError; -use graphql_client::*; - -#[derive(GraphQLQuery)] -#[graphql( - query_path = "src/query/graph/introspect_query.graphql", - schema_path = "src/query/graph/introspect_schema.graphql", - response_derives = "PartialEq, Debug, Serialize, Deserialize", - deprecated = "warn" -)] - -/// This struct is used to generate the module containing `Variables` and -/// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. introspection_query -pub struct IntrospectionQuery; - -#[derive(Debug, PartialEq)] -pub struct IntrospectionResponse { - pub result: String, -} - -/// The main function to be used from this module. This function fetches a -/// schema from apollo studio and returns it in either sdl (default) or json format -pub fn run( - client: &GraphQLClient, - headers: &HashMap, -) -> Result { - let variables = introspection_query::Variables {}; - let response_data = client.post::(variables, headers)?; - build_response(response_data) -} - -fn build_response( - response: introspection_query::ResponseData, -) -> Result { - match Schema::try_from(response) { - Ok(schema) => Ok(IntrospectionResponse { - result: schema.encode(), - }), - Err(msg) => Err(RoverClientError::IntrospectionError { msg: msg.into() }), - } -} - -/// This trait is used to be able to iterate over ofType fields in -/// IntrospectionResponse. -pub trait OfType { - type TypeRef: OfType; - - fn kind(&self) -> &introspection_query::__TypeKind; - fn name(&self) -> Option<&str>; - fn of_type(self) -> Option; -} - -macro_rules! impl_of_type { - ($target:ty, $assoc:ty) => { - impl OfType for $target { - type TypeRef = $assoc; - - fn kind(&self) -> &introspection_query::__TypeKind { - &self.kind - } - - fn name(&self) -> Option<&str> { - self.name.as_deref() - } - - fn of_type(self) -> Option { - self.of_type - } - } - }; -} - -impl_of_type!( - introspection_query::TypeRef, - introspection_query::TypeRefOfType -); - -impl_of_type!( - introspection_query::TypeRefOfType, - introspection_query::TypeRefOfTypeOfType -); - -impl_of_type!( - introspection_query::TypeRefOfTypeOfType, - introspection_query::TypeRefOfTypeOfTypeOfType -); - -impl_of_type!( - introspection_query::TypeRefOfTypeOfTypeOfType, - introspection_query::TypeRefOfTypeOfTypeOfTypeOfType -); - -impl_of_type!( - introspection_query::TypeRefOfTypeOfTypeOfTypeOfType, - introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfType -); - -impl_of_type!( - introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfType, - introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfType -); - -impl_of_type!( - introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfType, - introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfTypeOfType -); - -// NOTE(lrlna): This is a **hack**. This makes sure that the last possible -// generated ofType by graphql_client can return a None for of_type method. -impl OfType for introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfTypeOfType { - type TypeRef = introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfTypeOfType; - - fn kind(&self) -> &introspection_query::__TypeKind { - &self.kind - } - - fn name(&self) -> Option<&str> { - self.name.as_deref() - } - - fn of_type(self) -> Option { - None - } -} diff --git a/crates/rover-client/tests/fixtures/interfaces.json b/crates/rover-client/src/query/graph/introspect/fixtures/interfaces.json similarity index 100% rename from crates/rover-client/tests/fixtures/interfaces.json rename to crates/rover-client/src/query/graph/introspect/fixtures/interfaces.json diff --git a/crates/rover-client/tests/fixtures/simple.json b/crates/rover-client/src/query/graph/introspect/fixtures/simple.json similarity index 100% rename from crates/rover-client/tests/fixtures/simple.json rename to crates/rover-client/src/query/graph/introspect/fixtures/simple.json diff --git a/crates/rover-client/tests/fixtures/swapi.json b/crates/rover-client/src/query/graph/introspect/fixtures/swapi.json similarity index 100% rename from crates/rover-client/tests/fixtures/swapi.json rename to crates/rover-client/src/query/graph/introspect/fixtures/swapi.json diff --git a/crates/rover-client/src/query/graph/introspect_query.graphql b/crates/rover-client/src/query/graph/introspect/introspect_query.graphql similarity index 97% rename from crates/rover-client/src/query/graph/introspect_query.graphql rename to crates/rover-client/src/query/graph/introspect/introspect_query.graphql index 996742faf..c1c64031a 100644 --- a/crates/rover-client/src/query/graph/introspect_query.graphql +++ b/crates/rover-client/src/query/graph/introspect/introspect_query.graphql @@ -1,4 +1,4 @@ -query IntrospectionQuery { +query GraphIntrospectQuery { __schema { queryType { name diff --git a/crates/rover-client/src/query/graph/introspect_schema.graphql b/crates/rover-client/src/query/graph/introspect/introspect_schema.graphql similarity index 100% rename from crates/rover-client/src/query/graph/introspect_schema.graphql rename to crates/rover-client/src/query/graph/introspect/introspect_schema.graphql diff --git a/crates/rover-client/src/query/graph/introspect/mod.rs b/crates/rover-client/src/query/graph/introspect/mod.rs new file mode 100644 index 000000000..bc5028d54 --- /dev/null +++ b/crates/rover-client/src/query/graph/introspect/mod.rs @@ -0,0 +1,7 @@ +mod query_runner; +mod schema; +mod types; + +pub use query_runner::run; +pub use schema::Schema; +pub use types::{GraphIntrospectInput, GraphIntrospectResponse}; diff --git a/crates/rover-client/src/query/graph/introspect/query_runner.rs b/crates/rover-client/src/query/graph/introspect/query_runner.rs new file mode 100644 index 000000000..8188f18b2 --- /dev/null +++ b/crates/rover-client/src/query/graph/introspect/query_runner.rs @@ -0,0 +1,41 @@ +use crate::blocking::GraphQLClient; +use crate::query::graph::introspect::{types::*, Schema}; +use crate::RoverClientError; +use graphql_client::*; + +use std::convert::TryFrom; + +#[derive(GraphQLQuery)] +#[graphql( + query_path = "src/query/graph/introspect/introspect_query.graphql", + schema_path = "src/query/graph/introspect/introspect_schema.graphql", + response_derives = "PartialEq, Debug, Serialize, Deserialize", + deprecated = "warn" +)] + +/// This struct is used to generate the module containing `Variables` and +/// `ResponseData` structs. +/// Snake case of this name is the mod name. i.e. graph_introspect_query +pub(crate) struct GraphIntrospectQuery; + +/// The main function to be used from this module. This function fetches a +/// schema from apollo studio and returns it in either sdl (default) or json format +pub fn run( + input: GraphIntrospectInput, + client: &GraphQLClient, +) -> Result { + let variables = input.clone().into(); + let response_data = client.post::(variables, &input.headers)?; + build_response(response_data) +} + +fn build_response( + response: QueryResponseData, +) -> Result { + match Schema::try_from(response) { + Ok(schema) => Ok(GraphIntrospectResponse { + schema_sdl: schema.encode(), + }), + Err(msg) => Err(RoverClientError::IntrospectionError { msg: msg.into() }), + } +} diff --git a/crates/rover-client/tests/schema.rs b/crates/rover-client/src/query/graph/introspect/schema.rs similarity index 78% rename from crates/rover-client/tests/schema.rs rename to crates/rover-client/src/query/graph/introspect/schema.rs index 4678ec852..d7f014a73 100644 --- a/crates/rover-client/tests/schema.rs +++ b/crates/rover-client/src/query/graph/introspect/schema.rs @@ -1,20 +1,374 @@ -use graphql_client::Response; -use rover_client::introspection::Schema; +//! Schema encoding module used to work with Introspection result. +//! +//! More information on Schema Definition language(SDL) can be found in [this +//! documentation](https://www.apollographql.com/docs/apollo-server/schema/schema/). +//! +use sdl_encoder::{ + Directive, EnumDef, EnumValue, Field, InputField, InputObjectDef, InputValue, InterfaceDef, + ObjectDef, ScalarDef, Schema as SDL, SchemaDef, Type_, UnionDef, +}; +use serde::Deserialize; use std::convert::TryFrom; -use std::fs::File; + +use crate::query::graph::introspect::query_runner::graph_introspect_query; + +type FullTypeField = graph_introspect_query::FullTypeFields; +type FullTypeInputField = graph_introspect_query::FullTypeInputFields; +type FullTypeFieldArg = graph_introspect_query::FullTypeFieldsArgs; +type IntrospectionResult = graph_introspect_query::ResponseData; +type SchemaMutationType = graph_introspect_query::GraphIntrospectQuerySchemaMutationType; +type SchemaQueryType = graph_introspect_query::GraphIntrospectQuerySchemaQueryType; +type SchemaType = graph_introspect_query::GraphIntrospectQuerySchemaTypes; +type SchemaDirective = graph_introspect_query::GraphIntrospectQuerySchemaDirectives; +type SchemaSubscriptionType = graph_introspect_query::GraphIntrospectQuerySchemaSubscriptionType; +type __TypeKind = graph_introspect_query::__TypeKind; + +// Represents GraphQL types we will not be encoding to SDL. +const GRAPHQL_NAMED_TYPES: [&str; 12] = [ + "__Schema", + "__Type", + "__TypeKind", + "__Field", + "__InputValue", + "__EnumValue", + "__DirectiveLocation", + "__Directive", + "Boolean", + "String", + "Int", + "ID", +]; + +// Represents GraphQL directives we will not be encoding to SDL. +const SPECIFIED_DIRECTIVES: [&str; 3] = ["skip", "include", "deprecated"]; + +/// A representation of a GraphQL Schema. +/// +/// Contains Schema Types and Directives. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Schema { + types: Vec, + directives: Vec, + mutation_type: Option, + query_type: SchemaQueryType, + subscription_type: Option, +} + +impl Schema { + /// Encode Schema into an SDL. + pub fn encode(self) -> String { + let mut sdl = SDL::new(); + + // When we have a defined mutation and subscription, we record + // everything to Schema Definition. + // https://www.apollographql.com/docs/graphql-subscriptions/subscriptions-to-schema/ + if self.mutation_type.is_some() | self.subscription_type.is_some() { + let mut schema_def = SchemaDef::new(); + if let Some(mutation_type) = self.mutation_type { + schema_def.mutation(mutation_type.name.unwrap()); + } + if let Some(subscription_type) = self.subscription_type { + schema_def.subscription(subscription_type.name.unwrap()); + } + if let Some(name) = self.query_type.name { + schema_def.query(name); + } + sdl.schema(schema_def); + } else if let Some(name) = self.query_type.name { + // If we don't have a mutation or a subscription, but do have a + // query type, only create a Schema Definition when it's something + // other than `Query`. + if name != "Query" { + let mut schema_def = SchemaDef::new(); + schema_def.query(name); + sdl.schema(schema_def); + } + } + + // Exclude GraphQL directives like 'skip' and 'include' before encoding directives. + self.directives + .into_iter() + .filter(|directive| !SPECIFIED_DIRECTIVES.contains(&directive.name.as_str())) + .for_each(|directive| Self::encode_directives(directive, &mut sdl)); + + // Exclude GraphQL named types like __Schema before encoding full type. + self.types + .into_iter() + .filter(|type_| match type_.full_type.name.as_deref() { + Some(name) => !GRAPHQL_NAMED_TYPES.contains(&name), + None => false, + }) + .for_each(|type_| Self::encode_full_type(type_, &mut sdl)); + + sdl.finish() + } + + fn encode_directives(directive: SchemaDirective, sdl: &mut SDL) { + let mut directive_ = Directive::new(directive.name); + directive_.description(directive.description); + for location in directive.locations { + // Location is of a __DirectiveLocation enum that doesn't implement + // Display (meaning we can't just do .to_string). This next line + // just forces it into a String with format! debug. + directive_.location(format!("{:?}", location)); + } + + sdl.directive(directive_) + } + + fn encode_full_type(type_: SchemaType, sdl: &mut SDL) { + let ty = type_.full_type; + + match ty.kind { + __TypeKind::OBJECT => { + let mut object_def = ObjectDef::new(ty.name.unwrap_or_else(String::new)); + object_def.description(ty.description); + if let Some(interfaces) = ty.interfaces { + for interface in interfaces { + object_def.interface(interface.type_ref.name.unwrap_or_else(String::new)); + } + } + if let Some(field) = ty.fields { + for f in field { + let field_def = Self::encode_field(f); + object_def.field(field_def); + } + sdl.object(object_def); + } + } + __TypeKind::INPUT_OBJECT => { + let mut input_def = InputObjectDef::new(ty.name.unwrap_or_else(String::new)); + input_def.description(ty.description); + if let Some(field) = ty.input_fields { + for f in field { + let input_field_def = Self::encode_input_field(f); + input_def.field(input_field_def); + } + sdl.input(input_def); + } + } + __TypeKind::INTERFACE => { + let mut interface_def = InterfaceDef::new(ty.name.unwrap_or_else(String::new)); + interface_def.description(ty.description); + if let Some(interfaces) = ty.interfaces { + for interface in interfaces { + interface_def + .interface(interface.type_ref.name.unwrap_or_else(String::new)); + } + } + if let Some(field) = ty.fields { + for f in field { + let field_def = Self::encode_field(f); + interface_def.field(field_def); + } + sdl.interface(interface_def); + } + } + __TypeKind::SCALAR => { + let mut scalar_def = ScalarDef::new(ty.name.unwrap_or_else(String::new)); + scalar_def.description(ty.description); + sdl.scalar(scalar_def); + } + __TypeKind::UNION => { + let mut union_def = UnionDef::new(ty.name.unwrap_or_else(String::new)); + union_def.description(ty.description); + if let Some(possible_types) = ty.possible_types { + for possible_type in possible_types { + union_def.member(possible_type.type_ref.name.unwrap_or_else(String::new)); + } + } + sdl.union(union_def); + } + __TypeKind::ENUM => { + let mut enum_def = EnumDef::new(ty.name.unwrap_or_else(String::new)); + if let Some(enums) = ty.enum_values { + for enum_ in enums { + let mut enum_value = EnumValue::new(enum_.name); + enum_value.description(enum_.description); + + if enum_.is_deprecated { + enum_value.deprecated(enum_.deprecation_reason); + } + + enum_def.value(enum_value); + } + } + sdl.enum_(enum_def); + } + _ => (), + } + } + + fn encode_field(field: FullTypeField) -> Field { + let ty = Self::encode_type(field.type_.type_ref); + let mut field_def = Field::new(field.name, ty); + + for value in field.args { + let field_value = Self::encode_arg(value); + field_def.arg(field_value); + } + + if field.is_deprecated { + field_def.deprecated(field.deprecation_reason); + } + field_def.description(field.description); + field_def + } + + fn encode_input_field(field: FullTypeInputField) -> InputField { + let ty = Self::encode_type(field.input_value.type_.type_ref); + let mut field_def = InputField::new(field.input_value.name, ty); + + field_def.default(field.input_value.default_value); + field_def.description(field.input_value.description); + field_def + } + + fn encode_arg(value: FullTypeFieldArg) -> InputValue { + let ty = Self::encode_type(value.input_value.type_.type_ref); + let mut value_def = InputValue::new(value.input_value.name, ty); + + value_def.default(value.input_value.default_value); + value_def.description(value.input_value.description); + value_def + } + + fn encode_type(ty: impl OfType) -> Type_ { + use graph_introspect_query::__TypeKind::*; + match ty.kind() { + SCALAR | OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT => Type_::NamedType { + name: ty.name().unwrap().to_string(), + }, + NON_NULL => { + let ty = Self::encode_type(ty.of_type().unwrap()); + Type_::NonNull { ty: Box::new(ty) } + } + LIST => { + let ty = Self::encode_type(ty.of_type().unwrap()); + Type_::List { ty: Box::new(ty) } + } + Other(ty) => panic!("Unknown type: {}", ty), + } + } +} + +impl TryFrom for Schema { + type Error = &'static str; + + fn try_from(src: IntrospectionResult) -> Result { + match src.schema { + Some(s) => Ok(Self { + types: s.types, + directives: s.directives, + mutation_type: s.mutation_type, + query_type: s.query_type, + subscription_type: s.subscription_type, + }), + None => Err("Schema not found in Introspection Result."), + } + } +} + +/// This trait is used to be able to iterate over ofType fields in +/// IntrospectionResponse. +pub trait OfType { + type TypeRef: OfType; + + fn kind(&self) -> &__TypeKind; + fn name(&self) -> Option<&str>; + fn of_type(self) -> Option; +} + +macro_rules! impl_of_type { + ($target:ty, $assoc:ty) => { + impl OfType for $target { + type TypeRef = $assoc; + + fn kind(&self) -> &__TypeKind { + &self.kind + } + + fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + fn of_type(self) -> Option { + self.of_type + } + } + }; +} + +impl_of_type!( + graph_introspect_query::TypeRef, + graph_introspect_query::TypeRefOfType +); + +impl_of_type!( + graph_introspect_query::TypeRefOfType, + graph_introspect_query::TypeRefOfTypeOfType +); + +impl_of_type!( + graph_introspect_query::TypeRefOfTypeOfType, + graph_introspect_query::TypeRefOfTypeOfTypeOfType +); + +impl_of_type!( + graph_introspect_query::TypeRefOfTypeOfTypeOfType, + graph_introspect_query::TypeRefOfTypeOfTypeOfTypeOfType +); + +impl_of_type!( + graph_introspect_query::TypeRefOfTypeOfTypeOfTypeOfType, + graph_introspect_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfType +); + +impl_of_type!( + graph_introspect_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfType, + graph_introspect_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfType +); + +impl_of_type!( + graph_introspect_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfType, + graph_introspect_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfTypeOfType +); + +// NOTE(lrlna): This is a **hack**. This makes sure that the last possible +// generated ofType by graphql_client can return a None for of_type method. +impl OfType for graph_introspect_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfTypeOfType { + type TypeRef = graph_introspect_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfTypeOfType; + + fn kind(&self) -> &graph_introspect_query::__TypeKind { + &self.kind + } + + fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + fn of_type(self) -> Option { + None + } +} #[cfg(test)] mod tests { use super::*; + + use graphql_client::Response; use indoc::indoc; use pretty_assertions::assert_eq; - use rover_client::query::graph::introspect; + use std::convert::TryFrom; + use std::fs::File; + + use crate::query::graph::introspect::types::QueryResponseData; - pub type IntrospectionResult = introspect::introspection_query::ResponseData; #[test] fn it_builds_simple_schema() { - let file = File::open("tests/fixtures/simple.json").unwrap(); - let res: Response = serde_json::from_reader(file).unwrap(); + let file = File::open("src/query/graph/introspect/fixtures/simple.json").unwrap(); + let res: Response = serde_json::from_reader(file).unwrap(); let data = res.data.unwrap(); let schema = Schema::try_from(data).unwrap(); @@ -47,8 +401,8 @@ mod tests { #[test] fn it_builds_swapi_schema() { - let file = File::open("tests/fixtures/swapi.json").unwrap(); - let res: Response = serde_json::from_reader(file).unwrap(); + let file = File::open("src/query/graph/introspect/fixtures/swapi.json").unwrap(); + let res: Response = serde_json::from_reader(file).unwrap(); let data = res.data.unwrap(); let schema = Schema::try_from(data).unwrap(); @@ -1010,8 +1364,8 @@ mod tests { #[test] fn it_builds_schema_with_interfaces() { - let file = File::open("tests/fixtures/interfaces.json").unwrap(); - let res: Response = serde_json::from_reader(file).unwrap(); + let file = File::open("src/query/graph/introspect/fixtures/interfaces.json").unwrap(); + let res: Response = serde_json::from_reader(file).unwrap(); let data = res.data.unwrap(); let schema = Schema::try_from(data).unwrap(); diff --git a/crates/rover-client/src/query/graph/introspect/types.rs b/crates/rover-client/src/query/graph/introspect/types.rs new file mode 100644 index 000000000..aa9089cd5 --- /dev/null +++ b/crates/rover-client/src/query/graph/introspect/types.rs @@ -0,0 +1,22 @@ +use std::collections::HashMap; + +use crate::query::graph::introspect::query_runner::graph_introspect_query; + +pub(crate) type QueryResponseData = graph_introspect_query::ResponseData; +pub(crate) type QueryVariables = graph_introspect_query::Variables; + +#[derive(Debug, Clone, PartialEq)] +pub struct GraphIntrospectInput { + pub headers: HashMap, +} + +impl From for QueryVariables { + fn from(_input: GraphIntrospectInput) -> Self { + Self {} + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct GraphIntrospectResponse { + pub schema_sdl: String, +} diff --git a/src/command/graph/introspect.rs b/src/command/graph/introspect.rs index 25e270bef..c76fa7512 100644 --- a/src/command/graph/introspect.rs +++ b/src/command/graph/introspect.rs @@ -4,7 +4,10 @@ use std::collections::HashMap; use structopt::StructOpt; use url::Url; -use rover_client::{blocking::GraphQLClient, query::graph::introspect}; +use rover_client::{ + blocking::GraphQLClient, + query::graph::introspect::{self, GraphIntrospectInput}, +}; use crate::command::RoverStdout; use crate::utils::parsers::parse_header; @@ -38,8 +41,10 @@ impl Introspect { } } - let introspection_response = introspect::run(&client, &headers)?; + let introspection_response = introspect::run(GraphIntrospectInput { headers }, &client)?; - Ok(RoverStdout::Introspection(introspection_response.result)) + Ok(RoverStdout::Introspection( + introspection_response.schema_sdl, + )) } } From 7555b91ceb6c843e27684cc3e6dce6e8713aee25 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Thu, 1 Jul 2021 15:11:55 -0500 Subject: [PATCH 09/17] chore: refactor release update checker (#646) --- Cargo.lock | 3 +-- Cargo.toml | 1 - crates/rover-client/Cargo.toml | 2 +- crates/rover-client/src/error.rs | 10 +++++--- crates/rover-client/src/lib.rs | 3 ++- crates/rover-client/src/releases.rs | 40 +++++++++++++++-------------- installers/binstall/src/error.rs | 7 ++++- installers/binstall/src/install.rs | 12 +++++---- src/error/metadata/mod.rs | 3 ++- src/utils/version.rs | 18 ++++--------- 10 files changed, 52 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a2e9d7b2..4e40a7a99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1949,7 +1949,6 @@ dependencies = [ "rover-client", "rustversion", "sdl-encoder", - "semver 1.0.3", "serde", "serde_json", "serde_yaml", @@ -1983,9 +1982,9 @@ dependencies = [ "indoc", "online", "pretty_assertions", - "regex", "reqwest", "sdl-encoder", + "semver 1.0.3", "serde", "serde_json", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 0d56929eb..50ac88946 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,6 @@ opener = "0.5.0" os_info = "3.0" prettytable-rs = "0.8.0" regex = "1" -semver = "1" serde = "1.0" serde_json = "1.0" serde_yaml = "0.8" diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index 7c3ee3dc0..eb1289364 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -19,9 +19,9 @@ git2 = { version = "0.13.20", default-features = false, features = ["vendored-op graphql-parser = "0.3.0" graphql_client = "0.9" http = "0.2" -regex = "1.5.4" reqwest = {version = "0.11", default-features = false, features = ["json", "blocking", "rustls-tls", "gzip"]} sdl-encoder = {path = "../sdl-encoder"} +semver = "1" serde = "1" serde_json = "1" thiserror = "1" diff --git a/crates/rover-client/src/error.rs b/crates/rover-client/src/error.rs index 09941f3f0..f2ddd2a61 100644 --- a/crates/rover-client/src/error.rs +++ b/crates/rover-client/src/error.rs @@ -142,9 +142,13 @@ pub enum RoverClientError { #[error("The registry did not recognize the provided API key")] InvalidKey, - /// could not parse the latest version - #[error("Could not get the latest release version")] - UnparseableReleaseVersion, + /// Could not parse the latest version + #[error("Could not parse the latest release version")] + UnparseableReleaseVersion { source: semver::Error }, + + /// Encountered an error while processing the request for the latest version + #[error("There's something wrong with the latest GitHub release URL")] + BadReleaseUrl, #[error("This endpoint doesn't support subgraph introspection via the Query._service field")] SubgraphIntrospectionNotAvailable, diff --git a/crates/rover-client/src/lib.rs b/crates/rover-client/src/lib.rs index 943267dce..aa15757c7 100644 --- a/crates/rover-client/src/lib.rs +++ b/crates/rover-client/src/lib.rs @@ -2,9 +2,10 @@ //! HTTP client for making GraphQL requests for the Rover CLI tool. +mod error; + /// Module related to blocking http client. pub mod blocking; -mod error; /// Module for client related errors. pub use error::RoverClientError; diff --git a/crates/rover-client/src/releases.rs b/crates/rover-client/src/releases.rs index 365648169..306bf9b45 100644 --- a/crates/rover-client/src/releases.rs +++ b/crates/rover-client/src/releases.rs @@ -1,26 +1,28 @@ use crate::RoverClientError; -use regex::Regex; -use reqwest::blocking; + +use reqwest::blocking::Client; +pub use semver::Version; const LATEST_RELEASE_URL: &str = "https://github.com/apollographql/rover/releases/latest"; -/// Looks up the latest release version, and returns it as a string -pub fn get_latest_release() -> Result { - let res = blocking::Client::new().head(LATEST_RELEASE_URL).send()?; +/// Looks up and parses the latest release version +pub fn get_latest_release() -> Result { + // send a request to the latest GitHub release + let response = Client::new().head(LATEST_RELEASE_URL).send()?; + + // this will return a response with a redirect to the latest tagged release + let url_path_segments = response + .url() + .path_segments() + .ok_or(RoverClientError::BadReleaseUrl)?; - let release_url = res.url().to_string(); - let release_url_parts: Vec<&str> = release_url.split('/').collect(); + // the last section of the URL will have the latest version in `v0.1.1` format + let version_string = url_path_segments + .last() + .ok_or(RoverClientError::BadReleaseUrl)? + .to_string(); - match release_url_parts.last() { - Some(version) => { - // Parse out the semver version (ex. v1.0.0 -> 1.0.0) - let re = Regex::new(r"^v[0-9]*\.[0-9]*\.[0-9]*$").unwrap(); - if re.is_match(version) { - Ok(version.to_string().replace('v', "")) - } else { - Err(RoverClientError::UnparseableReleaseVersion) - } - } - None => Err(RoverClientError::UnparseableReleaseVersion), - } + // strip the `v` prefix from the last section of the URL before parsing + Version::parse(&version_string[1..]) + .map_err(|source| RoverClientError::UnparseableReleaseVersion { source }) } diff --git a/installers/binstall/src/error.rs b/installers/binstall/src/error.rs index 26ca51377..dc6f92353 100644 --- a/installers/binstall/src/error.rs +++ b/installers/binstall/src/error.rs @@ -2,21 +2,26 @@ use thiserror::Error; use std::io; -/// InstallerError is the type of Error that occured while installing. +/// InstallerError is the type of Error that occurred while installing. #[derive(Error, Debug)] pub enum InstallerError { + /// Something went wrong with system I/O #[error(transparent)] IoError(#[from] io::Error), + /// Couldn't find a valid install location on Unix #[error("Could not find the home directory of the current user")] NoHomeUnix, + /// Couldn't find a valid install location on Windows #[error("Could not find the user profile folder")] NoHomeWindows, + /// Something went wrong while adding the executable to zsh config #[error("Zsh setup failed")] ZshSetup, + /// A specified path was not valid UTF-8 #[error(transparent)] PathNotUtf8(#[from] camino::FromPathBufError), } diff --git a/installers/binstall/src/install.rs b/installers/binstall/src/install.rs index ecf494167..4ea6e51b9 100644 --- a/installers/binstall/src/install.rs +++ b/installers/binstall/src/install.rs @@ -15,12 +15,19 @@ pub struct Installer { } impl Installer { + /// Installs the executable and returns the location it was installed. pub fn install(&self) -> Result, InstallerError> { let install_path = self.do_install()?; Ok(install_path) } + /// Gets the location the executable will be installed to + pub fn get_bin_dir_path(&self) -> Result { + let bin_dir = self.get_base_dir_path()?.join("bin"); + Ok(bin_dir) + } + fn do_install(&self) -> Result, InstallerError> { let bin_destination = self.get_bin_path()?; @@ -52,11 +59,6 @@ impl Installer { Ok(base_dir.join(&format!(".{}", &self.binary_name))) } - pub fn get_bin_dir_path(&self) -> Result { - let bin_dir = self.get_base_dir_path()?.join("bin"); - Ok(bin_dir) - } - fn create_bin_dir(&self) -> Result<(), InstallerError> { fs::create_dir_all(self.get_bin_dir_path()?)?; Ok(()) diff --git a/src/error/metadata/mod.rs b/src/error/metadata/mod.rs index 32649ab6c..911b44b43 100644 --- a/src/error/metadata/mod.rs +++ b/src/error/metadata/mod.rs @@ -121,9 +121,10 @@ impl From<&mut anyhow::Error> for Metadata { RoverClientError::ClientError { msg: _ } => (None, Some(Code::E012)), RoverClientError::InvalidKey => (Some(Suggestion::CheckKey), Some(Code::E013)), RoverClientError::MalformedKey => (Some(Suggestion::ProperKey), Some(Code::E014)), - RoverClientError::UnparseableReleaseVersion => { + RoverClientError::UnparseableReleaseVersion { source: _ } => { (Some(Suggestion::SubmitIssue), Some(Code::E015)) } + RoverClientError::BadReleaseUrl => (Some(Suggestion::SubmitIssue), None), RoverClientError::NoCompositionPublishes { graph: _, composition_errors, diff --git a/src/utils/version.rs b/src/utils/version.rs index 9de822804..8076e81ce 100644 --- a/src/utils/version.rs +++ b/src/utils/version.rs @@ -3,11 +3,10 @@ use std::{fs, time::SystemTime}; use ansi_term::Colour::{Cyan, Yellow}; use billboard::{Alignment, Billboard}; use camino::Utf8PathBuf; -use semver::Version; use crate::{Result, PKG_VERSION}; use houston as config; -use rover_client::releases::get_latest_release; +use rover_client::releases::{get_latest_release, Version}; const ONE_HOUR: u64 = 60 * 60; const ONE_DAY: u64 = ONE_HOUR * 24; @@ -50,12 +49,11 @@ pub fn check_for_update(config: config::Config, force: bool) -> Result<()> { } fn do_update_check(checked: &mut bool, should_output_if_updated: bool) -> Result<()> { - let latest = get_latest_release()?; - let pretty_latest = Cyan.normal().paint(format!("v{}", latest)); - let update_available = is_latest_newer(&latest, PKG_VERSION)?; - if update_available { + let latest_version = get_latest_release()?; + let pretty_latest = Cyan.normal().paint(format!("v{}", latest_version)); + if latest_version > Version::parse(PKG_VERSION)? { let message = format!( - "There is a newer version of Rover available: {} (currently running v{})\n\nFor instructions on how to install, run {}", + "There is a newer version of Rover available: {} (currently running v{})\n\nFor instructions on how to install, run {}", &pretty_latest, PKG_VERSION, Yellow.normal().paint("`rover docs open start`") @@ -90,9 +88,3 @@ fn get_last_checked_time_from_disk(version_file: &Utf8PathBuf) -> Option Result { - let latest = Version::parse(latest)?; - let running = Version::parse(running)?; - Ok(latest > running) -} From e29b87be2395c2d48da30cf348f63c9a57c831e7 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Tue, 6 Jul 2021 15:00:31 -0500 Subject: [PATCH 10/17] chore: begin adding shared types and consolidate check operations (#652) --- ARCHITECTURE.md | 30 +++-- crates/rover-client/src/error.rs | 19 ++- crates/rover-client/src/lib.rs | 4 +- .../config/is_federated.graphql | 0 .../config/is_federated.rs | 2 +- .../src/{query => operations}/config/mod.rs | 0 .../config/who_am_i/mod.rs | 2 +- .../config/who_am_i/runner.rs} | 4 +- .../config/who_am_i/types.rs | 2 +- .../config/who_am_i/who_am_i_query.graphql | 0 .../graph/check/check_mutation.graphql} | 10 +- .../src/operations/graph/check/mod.rs | 5 + .../graph/check/runner.rs} | 63 +++++----- .../src/operations/graph/check/types.rs | 97 +++++++++++++++ .../{query => operations}/graph/fetch.graphql | 0 .../src/{query => operations}/graph/fetch.rs | 2 +- .../graph/introspect/fixtures/interfaces.json | 0 .../graph/introspect/fixtures/simple.json | 0 .../graph/introspect/fixtures/swapi.json | 0 .../graph/introspect/introspect_query.graphql | 0 .../introspect/introspect_schema.graphql | 0 .../graph/introspect/mod.rs | 4 +- .../graph/introspect/runner.rs} | 6 +- .../graph/introspect/schema.rs | 10 +- .../graph/introspect/types.rs | 2 +- .../src/{query => operations}/graph/mod.rs | 0 .../graph/publish.graphql | 0 .../{query => operations}/graph/publish.rs | 2 +- .../src/{query => operations}/mod.rs | 0 .../subgraph/check/check_mutation.graphql} | 2 +- .../src/operations/subgraph/check/mod.rs | 3 + .../subgraph/check/runner.rs} | 31 +++-- .../src/operations/subgraph/check/types.rs | 76 ++++++++++++ .../subgraph/delete/delete_mutation.graphql | 0 .../subgraph/delete/mod.rs | 4 +- .../subgraph/delete/runner.rs} | 4 +- .../subgraph/delete/types.rs | 2 +- .../subgraph/fetch/fetch_query.graphql | 0 .../subgraph/fetch/mod.rs | 2 +- .../subgraph/fetch/runner.rs} | 2 +- .../subgraph/fetch/types.rs | 2 +- .../introspect/introspect_query.graphql | 0 .../introspect/introspect_schema.graphql | 0 .../subgraph/introspect/mod.rs | 4 +- .../subgraph/introspect/runner.rs} | 6 +- .../subgraph/introspect/types.rs | 2 +- .../subgraph/list/list_query.graphql | 0 .../subgraph/list/mod.rs | 4 +- .../subgraph/list/runner.rs} | 4 +- .../subgraph/list/types.rs | 2 +- .../src/{query => operations}/subgraph/mod.rs | 0 .../subgraph/publish/mod.rs | 2 +- .../subgraph/publish/publish_mutation.graphql | 0 .../subgraph/publish/runner.rs} | 4 +- .../subgraph/publish/types.rs | 4 +- .../supergraph/fetch.graphql | 0 .../{query => operations}/supergraph/fetch.rs | 2 +- .../{query => operations}/supergraph/mod.rs | 0 .../src/query/subgraph/check/mod.rs | 3 - .../src/query/subgraph/check/types.rs | 114 ------------------ .../rover-client/src/shared/check_response.rs | 104 ++++++++++++++++ .../{utils/git.rs => shared/git_context.rs} | 15 +-- crates/rover-client/src/shared/mod.rs | 5 + crates/rover-client/src/utils/mod.rs | 3 - src/cli.rs | 2 +- src/command/config/whoami.rs | 4 +- src/command/graph/check.rs | 85 +++---------- src/command/graph/fetch.rs | 2 +- src/command/graph/introspect.rs | 2 +- src/command/graph/mod.rs | 2 +- src/command/graph/publish.rs | 4 +- src/command/mod.rs | 3 +- src/command/output.rs | 67 +++++----- src/command/subgraph/check.rs | 10 +- src/command/subgraph/delete.rs | 4 +- src/command/subgraph/fetch.rs | 4 +- src/command/subgraph/introspect.rs | 2 +- src/command/subgraph/list.rs | 2 +- src/command/subgraph/mod.rs | 2 +- src/command/subgraph/publish.rs | 8 +- src/command/supergraph/compose/do_compose.rs | 8 +- src/command/supergraph/fetch.rs | 2 +- src/error/metadata/code.rs | 6 +- src/error/metadata/codes/E030.md | 1 + src/error/metadata/mod.rs | 11 +- src/error/metadata/suggestion.rs | 6 +- 86 files changed, 531 insertions(+), 376 deletions(-) rename crates/rover-client/src/{query => operations}/config/is_federated.graphql (100%) rename crates/rover-client/src/{query => operations}/config/is_federated.rs (96%) rename crates/rover-client/src/{query => operations}/config/mod.rs (100%) rename crates/rover-client/src/{query => operations}/config/who_am_i/mod.rs (76%) rename crates/rover-client/src/{query/config/who_am_i/query_runner.rs => operations/config/who_am_i/runner.rs} (97%) rename crates/rover-client/src/{query => operations}/config/who_am_i/types.rs (93%) rename crates/rover-client/src/{query => operations}/config/who_am_i/who_am_i_query.graphql (100%) rename crates/rover-client/src/{query/graph/check.graphql => operations/graph/check/check_mutation.graphql} (80%) create mode 100644 crates/rover-client/src/operations/graph/check/mod.rs rename crates/rover-client/src/{query/graph/check.rs => operations/graph/check/runner.rs} (52%) create mode 100644 crates/rover-client/src/operations/graph/check/types.rs rename crates/rover-client/src/{query => operations}/graph/fetch.graphql (100%) rename crates/rover-client/src/{query => operations}/graph/fetch.rs (98%) rename crates/rover-client/src/{query => operations}/graph/introspect/fixtures/interfaces.json (100%) rename crates/rover-client/src/{query => operations}/graph/introspect/fixtures/simple.json (100%) rename crates/rover-client/src/{query => operations}/graph/introspect/fixtures/swapi.json (100%) rename crates/rover-client/src/{query => operations}/graph/introspect/introspect_query.graphql (100%) rename crates/rover-client/src/{query => operations}/graph/introspect/introspect_schema.graphql (100%) rename crates/rover-client/src/{query => operations}/graph/introspect/mod.rs (71%) rename crates/rover-client/src/{query/graph/introspect/query_runner.rs => operations/graph/introspect/runner.rs} (85%) rename crates/rover-client/src/{query => operations}/graph/introspect/schema.rs (99%) rename crates/rover-client/src/{query => operations}/graph/introspect/types.rs (87%) rename crates/rover-client/src/{query => operations}/graph/mod.rs (100%) rename crates/rover-client/src/{query => operations}/graph/publish.graphql (100%) rename crates/rover-client/src/{query => operations}/graph/publish.rs (99%) rename crates/rover-client/src/{query => operations}/mod.rs (100%) rename crates/rover-client/src/{query/subgraph/check/check_query.graphql => operations/subgraph/check/check_mutation.graphql} (96%) create mode 100644 crates/rover-client/src/operations/subgraph/check/mod.rs rename crates/rover-client/src/{query/subgraph/check/query_runner.rs => operations/subgraph/check/runner.rs} (80%) create mode 100644 crates/rover-client/src/operations/subgraph/check/types.rs rename crates/rover-client/src/{query => operations}/subgraph/delete/delete_mutation.graphql (100%) rename crates/rover-client/src/{query => operations}/subgraph/delete/mod.rs (62%) rename crates/rover-client/src/{query/subgraph/delete/mutation_runner.rs => operations/subgraph/delete/runner.rs} (97%) rename crates/rover-client/src/{query => operations}/subgraph/delete/types.rs (94%) rename crates/rover-client/src/{query => operations}/subgraph/fetch/fetch_query.graphql (100%) rename crates/rover-client/src/{query => operations}/subgraph/fetch/mod.rs (78%) rename crates/rover-client/src/{query/subgraph/fetch/query_runner.rs => operations/subgraph/fetch/runner.rs} (98%) rename crates/rover-client/src/{query => operations}/subgraph/fetch/types.rs (96%) rename crates/rover-client/src/{query => operations}/subgraph/introspect/introspect_query.graphql (100%) rename crates/rover-client/src/{query => operations}/subgraph/introspect/introspect_schema.graphql (100%) rename crates/rover-client/src/{query => operations}/subgraph/introspect/mod.rs (62%) rename crates/rover-client/src/{query/subgraph/introspect/query_runner.rs => operations/subgraph/introspect/runner.rs} (85%) rename crates/rover-client/src/{query => operations}/subgraph/introspect/types.rs (86%) rename crates/rover-client/src/{query => operations}/subgraph/list/list_query.graphql (100%) rename crates/rover-client/src/{query => operations}/subgraph/list/mod.rs (64%) rename crates/rover-client/src/{query/subgraph/list/query_runner.rs => operations/subgraph/list/runner.rs} (98%) rename crates/rover-client/src/{query => operations}/subgraph/list/types.rs (94%) rename crates/rover-client/src/{query => operations}/subgraph/mod.rs (100%) rename crates/rover-client/src/{query => operations}/subgraph/publish/mod.rs (77%) rename crates/rover-client/src/{query => operations}/subgraph/publish/publish_mutation.graphql (100%) rename crates/rover-client/src/{query/subgraph/publish/mutation_runner.rs => operations/subgraph/publish/runner.rs} (97%) rename crates/rover-client/src/{query => operations}/subgraph/publish/types.rs (95%) rename crates/rover-client/src/{query => operations}/supergraph/fetch.graphql (100%) rename crates/rover-client/src/{query => operations}/supergraph/fetch.rs (99%) rename crates/rover-client/src/{query => operations}/supergraph/mod.rs (100%) delete mode 100644 crates/rover-client/src/query/subgraph/check/mod.rs delete mode 100644 crates/rover-client/src/query/subgraph/check/types.rs create mode 100644 crates/rover-client/src/shared/check_response.rs rename crates/rover-client/src/{utils/git.rs => shared/git_context.rs} (95%) create mode 100644 crates/rover-client/src/shared/mod.rs delete mode 100644 crates/rover-client/src/utils/mod.rs create mode 100644 src/error/metadata/codes/E030.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 2932cfe2b..d81252fce 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -228,13 +228,13 @@ For more information try --help ##### Setting up a command to work with `rover-client` -Most of Rover's commands make requests to Apollo Studio's API. Rather than handling the request logic in the repository's main package, Rover is structured so that this logic lives in `crates/rover-client`. This is helpful for separation of concerns and testing. +Most of Rover's commands make requests to Apollo Studio's API, or to another GraphQL API. Rather than handling the request logic in the repository's main package, Rover is structured so that this logic lives in `crates/rover-client`. This is helpful for separation of concerns and testing. To access functionality from `rover-client` in our `rover graph hello` command, we'll need to pass down a client from the entry to our command in `src/command/graph/mod.rs`. You can do this by changing the `Command::Hello(command) => command.run(),` line to `Command::Hello(command) => command.run(client_config),`. -Then you'll need to change `Hello::run` to accept a `client_config: StudioClientConfig` parameter in `src/command/graph/hello.rs`, and add a `use crate::utils::client::StudioClientConfig` import statement. Then, at the top of the run function, you can create a `StudioClient` by adding `let client = client_config.get_client(&self.profile_name)?;`. You can see examples of this in the other commands. +Then you'll need to change `Hello::run` to accept a `client_config: StudioClientConfig` parameter in `src/command/graph/hello.rs`, and add a `use crate::utils::client::StudioClientConfig` import statement. Then, at the top of the run function, you can create a `StudioClient` by adding `let client = client_config.get_authenticated_client(&self.profile_name)?;`. You can see examples of this in the other commands. ##### Auto-generated help command @@ -271,15 +271,15 @@ Whenever you create a new command, make sure to add `#[serde(skip_serializing)]` ##### Adding a query to Apollo Studio -The only piece of the `rover-client` crate that we need to be concerned with for now is the `src/query` directory. This is where all the queries to Apollo Studio live. This directory is roughly organized by the command names as well, but there might be some queries in these directories that are used by multiple commands. +The only piece of the `rover-client` crate that we need to be concerned with for now is the `src/operations` directory. This is where all the queries to Apollo Studio live. This directory is roughly organized by the command names as well, but there might be some queries in these directories that are used by multiple commands. -You can see in the `src/query/graph` directory a number of `.rs` files paired with `.graphql` files. The `.graphql` files are the files where the GraphQL operations live, and the matching `.rs` files contain the logic needed to execute those operations. +You can see in the `src/operations/graph` directory a number of `.rs` files paired with `.graphql` files. The `.graphql` files are the files where the GraphQL operations live, and the matching `.rs` files contain the logic needed to execute those operations. ##### Writing a GraphQL operation For our basic `graph hello` command, we're going to make a request to Apollo Studio that inquires about the existence of a particular graph, and nothing else. For this, we can use the `Query.service` field. -Create a `hello.graphql` file in `crates/rover-client/src/query/graph` and paste the following into it: +Create a `hello.graphql` file in `crates/rover-client/src/operations/graph` and paste the following into it: ```graphql query GraphHello($graphId: ID!) { @@ -295,17 +295,19 @@ This basic GraphQL operation uses a graph's unique ID (which we get from the `Gr This project uses [graphql-client](https://docs.rs/graphql_client/latest/graphql_client/) to generate types for each raw `.graphql` query that we write. -First, create an empty file at `crates/rover-client/src/query/graph/hello.rs`. +First, create an empty directory at `crates/rover-client/src/operations/graph/hello`, and then in that directory, create a `mod.rs` file to initialize the module. -To start compiling this file, we need to export the module in `crates/rover-client/src/query/graph/mod.rs`: +To start compiling this file, we need to export the module in `crates/rover-client/src/operations/graph/mod.rs`: ```rust ... -/// "Graph hello" command execution +/// "graph hello" command execution pub mod hello; ``` -Back in `hello.rs`, we'll import the following types: +Back in our `hello` module, we'll create a `runner.rs`, and add `mod runner` to our `mod.rs` file. + +Then, in `runner.rs`, import the following types: ```rust use crate::blocking::StudioClient; @@ -320,7 +322,7 @@ Then, we'll create a new struct that will have auto-generated types for the `hel // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/query/graph/hello.graphql", + query_path = "src/operations/graph/hello/hello_query.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" @@ -395,7 +397,13 @@ fn build_response( } ``` -This should get you to the point where you can run `rover graph hello ` and see if and when the last graph was deleted. From here, you should be able to follow the examples of other commands to write out tests for the `build_response` function. This is left as an exercise for the reader. +This should get you to the point where you can run `rover graph hello ` and see if and when the last graph was deleted. From here, you should be able to follow the examples of other commands to write out tests for the `build_response` function. + +##### Clean up the API + +Unfortunately this is not the cleanest API and doesn't match the pattern set by the rest of the commands. Each `rover-client` operation has an input type and an output type, along with a `run` function that takes in a `reqwest::blocking::Client`. + +You'll want to define all of the types scoped to this command in `types.rs`, and re-export them from the top level `hello` module, and nothing else. ##### `RoverStdout` diff --git a/crates/rover-client/src/error.rs b/crates/rover-client/src/error.rs index f2ddd2a61..220cf319e 100644 --- a/crates/rover-client/src/error.rs +++ b/crates/rover-client/src/error.rs @@ -1,7 +1,7 @@ use reqwest::Url; use thiserror::Error; -use crate::query::subgraph::check::types::CompositionError; +use crate::{operations::subgraph::check::types::CompositionError, shared::CheckResponse}; /// RoverClientError represents all possible failures that can occur during a client request. #[derive(Error, Debug)] @@ -132,6 +132,12 @@ pub enum RoverClientError { #[error("Invalid ChangeSeverity.")] InvalidSeverity, + /// While checking the proposed schema, we encountered changes that would break existing operations + // we nest the CheckResponse here because we want to print the entire response even + // if there were failures + #[error("{}", check_response_error_msg(.check_response))] + OperationCheckFailure { check_response: CheckResponse }, + /// This error occurs when a user has a malformed API key #[error( "The API key you provided is malformed. An API key must have three parts separated by a colon." @@ -169,3 +175,14 @@ fn subgraph_composition_error_msg(composition_errors: &[CompositionError]) -> St }); msg } + +fn check_response_error_msg(check_response: &CheckResponse) -> String { + let plural = match check_response.num_failures { + 1 => "", + _ => "s", + }; + format!( + "This operation has encountered {} change{} that would break existing clients.", + check_response.num_failures, plural + ) +} diff --git a/crates/rover-client/src/lib.rs b/crates/rover-client/src/lib.rs index aa15757c7..5c8f3e490 100644 --- a/crates/rover-client/src/lib.rs +++ b/crates/rover-client/src/lib.rs @@ -12,10 +12,10 @@ pub use error::RoverClientError; #[allow(clippy::upper_case_acronyms)] /// Module for actually querying studio -pub mod query; +pub mod operations; /// Module for getting release info pub mod releases; /// Module for shared functionality -pub mod utils; +pub mod shared; diff --git a/crates/rover-client/src/query/config/is_federated.graphql b/crates/rover-client/src/operations/config/is_federated.graphql similarity index 100% rename from crates/rover-client/src/query/config/is_federated.graphql rename to crates/rover-client/src/operations/config/is_federated.graphql diff --git a/crates/rover-client/src/query/config/is_federated.rs b/crates/rover-client/src/operations/config/is_federated.rs similarity index 96% rename from crates/rover-client/src/query/config/is_federated.rs rename to crates/rover-client/src/operations/config/is_federated.rs index b8fb68fa6..4175e1f2f 100644 --- a/crates/rover-client/src/query/config/is_federated.rs +++ b/crates/rover-client/src/operations/config/is_federated.rs @@ -7,7 +7,7 @@ use graphql_client::*; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/query/config/is_federated.graphql", + query_path = "src/operations/config/is_federated.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" diff --git a/crates/rover-client/src/query/config/mod.rs b/crates/rover-client/src/operations/config/mod.rs similarity index 100% rename from crates/rover-client/src/query/config/mod.rs rename to crates/rover-client/src/operations/config/mod.rs diff --git a/crates/rover-client/src/query/config/who_am_i/mod.rs b/crates/rover-client/src/operations/config/who_am_i/mod.rs similarity index 76% rename from crates/rover-client/src/query/config/who_am_i/mod.rs rename to crates/rover-client/src/operations/config/who_am_i/mod.rs index 30535495a..9e6871fdd 100644 --- a/crates/rover-client/src/query/config/who_am_i/mod.rs +++ b/crates/rover-client/src/operations/config/who_am_i/mod.rs @@ -1,4 +1,4 @@ mod types; -pub mod query_runner; +pub mod runner; pub use types::{Actor, ConfigWhoAmIInput, RegistryIdentity}; diff --git a/crates/rover-client/src/query/config/who_am_i/query_runner.rs b/crates/rover-client/src/operations/config/who_am_i/runner.rs similarity index 97% rename from crates/rover-client/src/query/config/who_am_i/query_runner.rs rename to crates/rover-client/src/operations/config/who_am_i/runner.rs index 0bc7eba7e..d63bf176c 100644 --- a/crates/rover-client/src/query/config/who_am_i/query_runner.rs +++ b/crates/rover-client/src/operations/config/who_am_i/runner.rs @@ -1,5 +1,5 @@ use crate::blocking::StudioClient; -use crate::query::config::who_am_i::{ +use crate::operations::config::who_am_i::{ types::{QueryActorType, QueryResponseData, RegistryIdentity}, Actor, ConfigWhoAmIInput, }; @@ -13,7 +13,7 @@ use graphql_client::*; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/query/config/who_am_i/who_am_i_query.graphql", + query_path = "src/operations/config/who_am_i/who_am_i_query.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" diff --git a/crates/rover-client/src/query/config/who_am_i/types.rs b/crates/rover-client/src/operations/config/who_am_i/types.rs similarity index 93% rename from crates/rover-client/src/query/config/who_am_i/types.rs rename to crates/rover-client/src/operations/config/who_am_i/types.rs index c9cfeafa2..d2d3c4e93 100644 --- a/crates/rover-client/src/query/config/who_am_i/types.rs +++ b/crates/rover-client/src/operations/config/who_am_i/types.rs @@ -1,4 +1,4 @@ -use super::query_runner::config_who_am_i_query; +use super::runner::config_who_am_i_query; use houston::CredentialOrigin; diff --git a/crates/rover-client/src/query/config/who_am_i/who_am_i_query.graphql b/crates/rover-client/src/operations/config/who_am_i/who_am_i_query.graphql similarity index 100% rename from crates/rover-client/src/query/config/who_am_i/who_am_i_query.graphql rename to crates/rover-client/src/operations/config/who_am_i/who_am_i_query.graphql diff --git a/crates/rover-client/src/query/graph/check.graphql b/crates/rover-client/src/operations/graph/check/check_mutation.graphql similarity index 80% rename from crates/rover-client/src/query/graph/check.graphql rename to crates/rover-client/src/operations/graph/check/check_mutation.graphql index a891ba7eb..e936d2993 100644 --- a/crates/rover-client/src/query/graph/check.graphql +++ b/crates/rover-client/src/operations/graph/check/check_mutation.graphql @@ -1,8 +1,8 @@ -mutation CheckSchemaQuery( - $graphId: ID! +mutation GraphCheckMutation( + $graph_id: ID! $variant: String - $schema: String - $gitContext: GitContextInput! + $proposed_schema: String + $git_context: GitContextInput! $config: HistoricQueryParameters! ) { service(id: $graphId) { @@ -24,4 +24,4 @@ mutation CheckSchemaQuery( } } } -} \ No newline at end of file +} diff --git a/crates/rover-client/src/operations/graph/check/mod.rs b/crates/rover-client/src/operations/graph/check/mod.rs new file mode 100644 index 000000000..23c23b7e8 --- /dev/null +++ b/crates/rover-client/src/operations/graph/check/mod.rs @@ -0,0 +1,5 @@ +mod runner; +mod types; + +pub use runner::run; +pub use types::GraphCheckInput; diff --git a/crates/rover-client/src/query/graph/check.rs b/crates/rover-client/src/operations/graph/check/runner.rs similarity index 52% rename from crates/rover-client/src/query/graph/check.rs rename to crates/rover-client/src/operations/graph/check/runner.rs index c57dad4ca..190dec094 100644 --- a/crates/rover-client/src/query/graph/check.rs +++ b/crates/rover-client/src/operations/graph/check/runner.rs @@ -1,76 +1,67 @@ use crate::blocking::StudioClient; +use crate::operations::graph::check::types::{ + GraphCheckInput, MutationChangeSeverity, MutationResponseData, +}; +use crate::shared::CheckResponse; use crate::RoverClientError; -use graphql_client::*; -use reqwest::Url; +use graphql_client::*; type Timestamp = String; #[derive(GraphQLQuery)] // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/query/graph/check.graphql", + query_path = "src/operations/graph/check/check_mutation.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. check_schema_query -pub struct CheckSchemaQuery; +/// Snake case of this name is the mod name. i.e. graph_check_mutation +pub(crate) struct GraphCheckMutation; /// The main function to be used from this module. /// This function takes a proposed schema and validates it against a published /// schema. pub fn run( - variables: check_schema_query::Variables, + input: GraphCheckInput, client: &StudioClient, ) -> Result { - let graph = variables.graph_id.clone(); - let data = client.post::(variables)?; + let graph = input.graph_id.clone(); + let data = client.post::(input.into())?; get_check_response_from_data(data, graph) } -#[derive(Debug)] -pub struct CheckResponse { - pub target_url: Option, - pub number_of_checked_operations: i64, - pub change_severity: check_schema_query::ChangeSeverity, - pub changes: Vec, -} - fn get_check_response_from_data( - data: check_schema_query::ResponseData, + data: MutationResponseData, graph: String, ) -> Result { let service = data.service.ok_or(RoverClientError::NoService { graph })?; - let target_url = get_url(service.check_schema.target_url); + let target_url = service.check_schema.target_url; let diff_to_previous = service.check_schema.diff_to_previous; let number_of_checked_operations = diff_to_previous.number_of_checked_operations.unwrap_or(0); - let change_severity = diff_to_previous.severity; - let changes = diff_to_previous.changes; + let change_severity = diff_to_previous.severity.into(); + let mut changes = Vec::with_capacity(diff_to_previous.changes.len()); + let mut num_failures = 0; + for change in diff_to_previous.changes { + if let MutationChangeSeverity::FAILURE = change.severity { + num_failures += 1; + } + changes.push(change.into()); + } - Ok(CheckResponse { + let check_response = CheckResponse { target_url, number_of_checked_operations, - change_severity, changes, - }) -} + change_severity, + num_failures, + }; -fn get_url(url: Option) -> Option { - match url { - Some(url) => { - let url = Url::parse(&url); - match url { - Ok(url) => Some(url), - // if the API returns an invalid URL, don't put it in the response - Err(_) => None, - } - } - None => None, - } + check_response.check_for_failures() } diff --git a/crates/rover-client/src/operations/graph/check/types.rs b/crates/rover-client/src/operations/graph/check/types.rs new file mode 100644 index 000000000..61c16efac --- /dev/null +++ b/crates/rover-client/src/operations/graph/check/types.rs @@ -0,0 +1,97 @@ +use crate::operations::graph::check::runner::graph_check_mutation; +use crate::shared::{ChangeSeverity, CheckConfig, GitContext, SchemaChange}; + +#[derive(Debug, Clone, PartialEq)] +pub struct GraphCheckInput { + pub graph_id: String, + pub variant: String, + pub proposed_schema: String, + pub git_context: GitContext, + pub config: CheckConfig, +} + +impl From for MutationVariables { + fn from(input: GraphCheckInput) -> Self { + Self { + graph_id: input.graph_id, + variant: Some(input.variant), + proposed_schema: Some(input.proposed_schema), + config: input.config.into(), + git_context: input.git_context.into(), + } + } +} + +type MutationConfig = graph_check_mutation::HistoricQueryParameters; +impl From for MutationConfig { + fn from(input: CheckConfig) -> Self { + Self { + query_count_threshold: input.query_count_threshold, + query_count_threshold_percentage: input.query_count_threshold_percentage, + from: input.validation_period_from, + to: input.validation_period_to, + // we don't support configuring these, but we can't leave them out + excluded_clients: None, + ignored_operations: None, + included_variants: None, + } + } +} + +type MutationVariables = graph_check_mutation::Variables; +pub(crate) type MutationResponseData = graph_check_mutation::ResponseData; + +pub(crate) type MutationChangeSeverity = graph_check_mutation::ChangeSeverity; +impl From for ChangeSeverity { + fn from(severity: MutationChangeSeverity) -> Self { + match severity { + MutationChangeSeverity::NOTICE => ChangeSeverity::PASS, + MutationChangeSeverity::FAILURE => ChangeSeverity::FAIL, + _ => ChangeSeverity::unreachable(), + } + } +} + +impl From for MutationChangeSeverity { + fn from(severity: ChangeSeverity) -> Self { + match severity { + ChangeSeverity::PASS => MutationChangeSeverity::NOTICE, + ChangeSeverity::FAIL => MutationChangeSeverity::FAILURE, + } + } +} + +type MutationSchemaChange = + graph_check_mutation::GraphCheckMutationServiceCheckSchemaDiffToPreviousChanges; +impl From for MutationSchemaChange { + fn from(schema_change: SchemaChange) -> MutationSchemaChange { + MutationSchemaChange { + severity: schema_change.severity.into(), + code: schema_change.code, + description: schema_change.description, + } + } +} + +impl From for SchemaChange { + fn from(schema_change: MutationSchemaChange) -> SchemaChange { + SchemaChange { + severity: schema_change.severity.into(), + code: schema_change.code, + description: schema_change.description, + } + } +} + +type MutationGitContextInput = graph_check_mutation::GitContextInput; +impl From for MutationGitContextInput { + fn from(git_context: GitContext) -> MutationGitContextInput { + MutationGitContextInput { + branch: git_context.branch, + commit: git_context.commit, + committer: git_context.author, + remote_url: git_context.remote_url, + message: None, + } + } +} diff --git a/crates/rover-client/src/query/graph/fetch.graphql b/crates/rover-client/src/operations/graph/fetch.graphql similarity index 100% rename from crates/rover-client/src/query/graph/fetch.graphql rename to crates/rover-client/src/operations/graph/fetch.graphql diff --git a/crates/rover-client/src/query/graph/fetch.rs b/crates/rover-client/src/operations/graph/fetch.rs similarity index 98% rename from crates/rover-client/src/query/graph/fetch.rs rename to crates/rover-client/src/operations/graph/fetch.rs index 2890334e0..3ac6f2aba 100644 --- a/crates/rover-client/src/query/graph/fetch.rs +++ b/crates/rover-client/src/operations/graph/fetch.rs @@ -10,7 +10,7 @@ type GraphQLDocument = String; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/query/graph/fetch.graphql", + query_path = "src/operations/graph/fetch.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" diff --git a/crates/rover-client/src/query/graph/introspect/fixtures/interfaces.json b/crates/rover-client/src/operations/graph/introspect/fixtures/interfaces.json similarity index 100% rename from crates/rover-client/src/query/graph/introspect/fixtures/interfaces.json rename to crates/rover-client/src/operations/graph/introspect/fixtures/interfaces.json diff --git a/crates/rover-client/src/query/graph/introspect/fixtures/simple.json b/crates/rover-client/src/operations/graph/introspect/fixtures/simple.json similarity index 100% rename from crates/rover-client/src/query/graph/introspect/fixtures/simple.json rename to crates/rover-client/src/operations/graph/introspect/fixtures/simple.json diff --git a/crates/rover-client/src/query/graph/introspect/fixtures/swapi.json b/crates/rover-client/src/operations/graph/introspect/fixtures/swapi.json similarity index 100% rename from crates/rover-client/src/query/graph/introspect/fixtures/swapi.json rename to crates/rover-client/src/operations/graph/introspect/fixtures/swapi.json diff --git a/crates/rover-client/src/query/graph/introspect/introspect_query.graphql b/crates/rover-client/src/operations/graph/introspect/introspect_query.graphql similarity index 100% rename from crates/rover-client/src/query/graph/introspect/introspect_query.graphql rename to crates/rover-client/src/operations/graph/introspect/introspect_query.graphql diff --git a/crates/rover-client/src/query/graph/introspect/introspect_schema.graphql b/crates/rover-client/src/operations/graph/introspect/introspect_schema.graphql similarity index 100% rename from crates/rover-client/src/query/graph/introspect/introspect_schema.graphql rename to crates/rover-client/src/operations/graph/introspect/introspect_schema.graphql diff --git a/crates/rover-client/src/query/graph/introspect/mod.rs b/crates/rover-client/src/operations/graph/introspect/mod.rs similarity index 71% rename from crates/rover-client/src/query/graph/introspect/mod.rs rename to crates/rover-client/src/operations/graph/introspect/mod.rs index bc5028d54..541b12ff2 100644 --- a/crates/rover-client/src/query/graph/introspect/mod.rs +++ b/crates/rover-client/src/operations/graph/introspect/mod.rs @@ -1,7 +1,7 @@ -mod query_runner; +mod runner; mod schema; mod types; -pub use query_runner::run; +pub use runner::run; pub use schema::Schema; pub use types::{GraphIntrospectInput, GraphIntrospectResponse}; diff --git a/crates/rover-client/src/query/graph/introspect/query_runner.rs b/crates/rover-client/src/operations/graph/introspect/runner.rs similarity index 85% rename from crates/rover-client/src/query/graph/introspect/query_runner.rs rename to crates/rover-client/src/operations/graph/introspect/runner.rs index 8188f18b2..c9516c10d 100644 --- a/crates/rover-client/src/query/graph/introspect/query_runner.rs +++ b/crates/rover-client/src/operations/graph/introspect/runner.rs @@ -1,5 +1,5 @@ use crate::blocking::GraphQLClient; -use crate::query::graph::introspect::{types::*, Schema}; +use crate::operations::graph::introspect::{types::*, Schema}; use crate::RoverClientError; use graphql_client::*; @@ -7,8 +7,8 @@ use std::convert::TryFrom; #[derive(GraphQLQuery)] #[graphql( - query_path = "src/query/graph/introspect/introspect_query.graphql", - schema_path = "src/query/graph/introspect/introspect_schema.graphql", + query_path = "src/operations/graph/introspect/introspect_query.graphql", + schema_path = "src/operations//graph/introspect/introspect_schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] diff --git a/crates/rover-client/src/query/graph/introspect/schema.rs b/crates/rover-client/src/operations/graph/introspect/schema.rs similarity index 99% rename from crates/rover-client/src/query/graph/introspect/schema.rs rename to crates/rover-client/src/operations/graph/introspect/schema.rs index d7f014a73..a6dbbe8f0 100644 --- a/crates/rover-client/src/query/graph/introspect/schema.rs +++ b/crates/rover-client/src/operations/graph/introspect/schema.rs @@ -10,7 +10,7 @@ use sdl_encoder::{ use serde::Deserialize; use std::convert::TryFrom; -use crate::query::graph::introspect::query_runner::graph_introspect_query; +use crate::operations::graph::introspect::runner::graph_introspect_query; type FullTypeField = graph_introspect_query::FullTypeFields; type FullTypeInputField = graph_introspect_query::FullTypeInputFields; @@ -363,11 +363,11 @@ mod tests { use std::convert::TryFrom; use std::fs::File; - use crate::query::graph::introspect::types::QueryResponseData; + use crate::operations::graph::introspect::types::QueryResponseData; #[test] fn it_builds_simple_schema() { - let file = File::open("src/query/graph/introspect/fixtures/simple.json").unwrap(); + let file = File::open("src/operations/graph/introspect/fixtures/simple.json").unwrap(); let res: Response = serde_json::from_reader(file).unwrap(); let data = res.data.unwrap(); @@ -401,7 +401,7 @@ mod tests { #[test] fn it_builds_swapi_schema() { - let file = File::open("src/query/graph/introspect/fixtures/swapi.json").unwrap(); + let file = File::open("src/operations/graph/introspect/fixtures/swapi.json").unwrap(); let res: Response = serde_json::from_reader(file).unwrap(); let data = res.data.unwrap(); @@ -1364,7 +1364,7 @@ mod tests { #[test] fn it_builds_schema_with_interfaces() { - let file = File::open("src/query/graph/introspect/fixtures/interfaces.json").unwrap(); + let file = File::open("src/operations/graph/introspect/fixtures/interfaces.json").unwrap(); let res: Response = serde_json::from_reader(file).unwrap(); let data = res.data.unwrap(); diff --git a/crates/rover-client/src/query/graph/introspect/types.rs b/crates/rover-client/src/operations/graph/introspect/types.rs similarity index 87% rename from crates/rover-client/src/query/graph/introspect/types.rs rename to crates/rover-client/src/operations/graph/introspect/types.rs index aa9089cd5..73fd29965 100644 --- a/crates/rover-client/src/query/graph/introspect/types.rs +++ b/crates/rover-client/src/operations/graph/introspect/types.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::query::graph::introspect::query_runner::graph_introspect_query; +use crate::operations::graph::introspect::runner::graph_introspect_query; pub(crate) type QueryResponseData = graph_introspect_query::ResponseData; pub(crate) type QueryVariables = graph_introspect_query::Variables; diff --git a/crates/rover-client/src/query/graph/mod.rs b/crates/rover-client/src/operations/graph/mod.rs similarity index 100% rename from crates/rover-client/src/query/graph/mod.rs rename to crates/rover-client/src/operations/graph/mod.rs diff --git a/crates/rover-client/src/query/graph/publish.graphql b/crates/rover-client/src/operations/graph/publish.graphql similarity index 100% rename from crates/rover-client/src/query/graph/publish.graphql rename to crates/rover-client/src/operations/graph/publish.graphql diff --git a/crates/rover-client/src/query/graph/publish.rs b/crates/rover-client/src/operations/graph/publish.rs similarity index 99% rename from crates/rover-client/src/query/graph/publish.rs rename to crates/rover-client/src/operations/graph/publish.rs index 3c5955600..d9b8b9787 100644 --- a/crates/rover-client/src/query/graph/publish.rs +++ b/crates/rover-client/src/operations/graph/publish.rs @@ -6,7 +6,7 @@ use graphql_client::*; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/query/graph/publish.graphql", + query_path = "src/operations/graph/publish.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" diff --git a/crates/rover-client/src/query/mod.rs b/crates/rover-client/src/operations/mod.rs similarity index 100% rename from crates/rover-client/src/query/mod.rs rename to crates/rover-client/src/operations/mod.rs diff --git a/crates/rover-client/src/query/subgraph/check/check_query.graphql b/crates/rover-client/src/operations/subgraph/check/check_mutation.graphql similarity index 96% rename from crates/rover-client/src/query/subgraph/check/check_query.graphql rename to crates/rover-client/src/operations/subgraph/check/check_mutation.graphql index 4afaecbcc..906dfd831 100644 --- a/crates/rover-client/src/query/subgraph/check/check_query.graphql +++ b/crates/rover-client/src/operations/subgraph/check/check_mutation.graphql @@ -1,4 +1,4 @@ - mutation SubgraphCheckQuery( + mutation SubgraphCheckMutation( $graph_id: ID! $variant: String! $subgraph: String! diff --git a/crates/rover-client/src/operations/subgraph/check/mod.rs b/crates/rover-client/src/operations/subgraph/check/mod.rs new file mode 100644 index 000000000..9734f57b4 --- /dev/null +++ b/crates/rover-client/src/operations/subgraph/check/mod.rs @@ -0,0 +1,3 @@ +pub mod runner; +pub(crate) mod types; +pub use types::SubgraphCheckInput; diff --git a/crates/rover-client/src/query/subgraph/check/query_runner.rs b/crates/rover-client/src/operations/subgraph/check/runner.rs similarity index 80% rename from crates/rover-client/src/query/subgraph/check/query_runner.rs rename to crates/rover-client/src/operations/subgraph/check/runner.rs index b602595ea..94d91bd85 100644 --- a/crates/rover-client/src/query/subgraph/check/query_runner.rs +++ b/crates/rover-client/src/operations/subgraph/check/runner.rs @@ -1,22 +1,25 @@ use super::types::*; use crate::blocking::StudioClient; -use crate::query::config::is_federated; +use crate::operations::{config::is_federated, subgraph::check::types::MutationResponseData}; +use crate::shared::{CheckResponse, SchemaChange}; use crate::RoverClientError; + use graphql_client::*; +type Timestamp = String; #[derive(GraphQLQuery)] // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/query/subgraph/check/check_query.graphql", + query_path = "src/operations/subgraph/check/check_mutation.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. subgraph_check_query -pub struct SubgraphCheckQuery; +/// Snake case of this name is the mod name. i.e. subgraph_check_mutation +pub(crate) struct SubgraphCheckMutation; /// The main function to be used from this module. /// This function takes a proposed schema and validates it against a published @@ -24,7 +27,7 @@ pub struct SubgraphCheckQuery; pub fn run( input: SubgraphCheckInput, client: &StudioClient, -) -> Result { +) -> Result { let graph = input.graph_id.clone(); // This response is used to check whether or not the current graph is federated. let is_federated = is_federated::run( @@ -41,14 +44,14 @@ pub fn run( }); } let variables = input.into(); - let data = client.post::(variables)?; + let data = client.post::(variables)?; get_check_response_from_data(data, graph) } fn get_check_response_from_data( - data: subgraph_check_query::ResponseData, + data: MutationResponseData, graph_name: String, -) -> Result { +) -> Result { let service = data.service.ok_or(RoverClientError::NoService { graph: graph_name.clone(), })?; @@ -56,7 +59,7 @@ fn get_check_response_from_data( // for some reason this is a `Vec>` // we convert this to just `Vec` because the `None` // errors would be useless. - let query_composition_errors: Vec = service + let query_composition_errors: Vec = service .check_partial_schema .composition_validation_result .errors; @@ -76,7 +79,11 @@ fn get_check_response_from_data( let change_severity = diff_to_previous.severity.into(); let mut changes = Vec::with_capacity(diff_to_previous.changes.len()); + let mut num_failures = 0; for change in diff_to_previous.changes { + if let MutationChangeSeverity::FAILURE = change.severity { + num_failures += 1; + } changes.push(SchemaChange { code: change.code, severity: change.severity.into(), @@ -84,14 +91,14 @@ fn get_check_response_from_data( }); } - let check_result = SubgraphCheckResponse { + let check_response = CheckResponse { + num_failures, target_url: check_schema_result.target_url, number_of_checked_operations, changes, change_severity, }; - - Ok(check_result) + check_response.check_for_failures() } else { let num_failures = query_composition_errors.len(); diff --git a/crates/rover-client/src/operations/subgraph/check/types.rs b/crates/rover-client/src/operations/subgraph/check/types.rs new file mode 100644 index 000000000..2fdfd258a --- /dev/null +++ b/crates/rover-client/src/operations/subgraph/check/types.rs @@ -0,0 +1,76 @@ +use crate::operations::subgraph::check::runner::subgraph_check_mutation; +use crate::shared::{ChangeSeverity, CheckConfig, GitContext}; + +type MutationVariables = subgraph_check_mutation::Variables; + +pub(crate) type MutationResponseData = subgraph_check_mutation::ResponseData; +pub(crate) type MutationCompositionErrors = + subgraph_check_mutation::SubgraphCheckMutationServiceCheckPartialSchemaCompositionValidationResultErrors; + +type MutationSchema = subgraph_check_mutation::PartialSchemaInput; +type MutationConfig = subgraph_check_mutation::HistoricQueryParameters; + +pub(crate) type MutationChangeSeverity = subgraph_check_mutation::ChangeSeverity; +impl From for ChangeSeverity { + fn from(severity: MutationChangeSeverity) -> Self { + match severity { + MutationChangeSeverity::NOTICE => ChangeSeverity::PASS, + MutationChangeSeverity::FAILURE => ChangeSeverity::FAIL, + _ => ChangeSeverity::unreachable(), + } + } +} + +type MutationGitContextInput = subgraph_check_mutation::GitContextInput; +impl From for MutationGitContextInput { + fn from(git_context: GitContext) -> MutationGitContextInput { + MutationGitContextInput { + branch: git_context.branch, + commit: git_context.commit, + committer: git_context.author, + remote_url: git_context.remote_url, + message: None, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SubgraphCheckInput { + pub graph_id: String, + pub variant: String, + pub subgraph: String, + pub proposed_schema: String, + pub git_context: GitContext, + pub config: CheckConfig, +} + +impl From for MutationVariables { + fn from(input: SubgraphCheckInput) -> Self { + Self { + graph_id: input.graph_id, + variant: input.variant, + subgraph: input.subgraph, + proposed_schema: MutationSchema { + sdl: Some(input.proposed_schema), + hash: None, + }, + config: MutationConfig { + query_count_threshold: input.config.query_count_threshold, + query_count_threshold_percentage: input.config.query_count_threshold_percentage, + from: input.config.validation_period_from, + to: input.config.validation_period_to, + // we don't support configuring these, but we can't leave them out + excluded_clients: None, + ignored_operations: None, + included_variants: None, + }, + git_context: input.git_context.into(), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct CompositionError { + pub message: String, + pub code: Option, +} diff --git a/crates/rover-client/src/query/subgraph/delete/delete_mutation.graphql b/crates/rover-client/src/operations/subgraph/delete/delete_mutation.graphql similarity index 100% rename from crates/rover-client/src/query/subgraph/delete/delete_mutation.graphql rename to crates/rover-client/src/operations/subgraph/delete/delete_mutation.graphql diff --git a/crates/rover-client/src/query/subgraph/delete/mod.rs b/crates/rover-client/src/operations/subgraph/delete/mod.rs similarity index 62% rename from crates/rover-client/src/query/subgraph/delete/mod.rs rename to crates/rover-client/src/operations/subgraph/delete/mod.rs index 788f96dd6..387f7cbc0 100644 --- a/crates/rover-client/src/query/subgraph/delete/mod.rs +++ b/crates/rover-client/src/operations/subgraph/delete/mod.rs @@ -1,6 +1,6 @@ -mod mutation_runner; +mod runner; pub(crate) mod types; -pub use mutation_runner::run; +pub use runner::run; pub use types::{SubgraphDeleteInput, SubgraphDeleteResponse}; diff --git a/crates/rover-client/src/query/subgraph/delete/mutation_runner.rs b/crates/rover-client/src/operations/subgraph/delete/runner.rs similarity index 97% rename from crates/rover-client/src/query/subgraph/delete/mutation_runner.rs rename to crates/rover-client/src/operations/subgraph/delete/runner.rs index f9ddddde0..694a7afc3 100644 --- a/crates/rover-client/src/query/subgraph/delete/mutation_runner.rs +++ b/crates/rover-client/src/operations/subgraph/delete/runner.rs @@ -1,5 +1,5 @@ use crate::blocking::StudioClient; -use crate::query::subgraph::delete::types::*; +use crate::operations::subgraph::delete::types::*; use crate::RoverClientError; use graphql_client::*; @@ -8,7 +8,7 @@ use graphql_client::*; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/query/subgraph/delete/delete_mutation.graphql", + query_path = "src/operations/subgraph/delete/delete_mutation.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" diff --git a/crates/rover-client/src/query/subgraph/delete/types.rs b/crates/rover-client/src/operations/subgraph/delete/types.rs similarity index 94% rename from crates/rover-client/src/query/subgraph/delete/types.rs rename to crates/rover-client/src/operations/subgraph/delete/types.rs index 82a82358f..04ce6c2e6 100644 --- a/crates/rover-client/src/query/subgraph/delete/types.rs +++ b/crates/rover-client/src/operations/subgraph/delete/types.rs @@ -1,4 +1,4 @@ -use crate::query::subgraph::delete::mutation_runner::subgraph_delete_mutation; +use crate::operations::subgraph::delete::runner::subgraph_delete_mutation; pub(crate) type MutationComposition = subgraph_delete_mutation::SubgraphDeleteMutationServiceRemoveImplementingServiceAndTriggerComposition; pub(crate) type MutationVariables = subgraph_delete_mutation::Variables; diff --git a/crates/rover-client/src/query/subgraph/fetch/fetch_query.graphql b/crates/rover-client/src/operations/subgraph/fetch/fetch_query.graphql similarity index 100% rename from crates/rover-client/src/query/subgraph/fetch/fetch_query.graphql rename to crates/rover-client/src/operations/subgraph/fetch/fetch_query.graphql diff --git a/crates/rover-client/src/query/subgraph/fetch/mod.rs b/crates/rover-client/src/operations/subgraph/fetch/mod.rs similarity index 78% rename from crates/rover-client/src/query/subgraph/fetch/mod.rs rename to crates/rover-client/src/operations/subgraph/fetch/mod.rs index 7c1ea61cc..57ec30cd3 100644 --- a/crates/rover-client/src/query/subgraph/fetch/mod.rs +++ b/crates/rover-client/src/operations/subgraph/fetch/mod.rs @@ -1,3 +1,3 @@ -pub mod query_runner; +pub mod runner; pub(crate) mod types; pub use types::{SubgraphFetchInput, SubgraphFetchResponse}; diff --git a/crates/rover-client/src/query/subgraph/fetch/query_runner.rs b/crates/rover-client/src/operations/subgraph/fetch/runner.rs similarity index 98% rename from crates/rover-client/src/query/subgraph/fetch/query_runner.rs rename to crates/rover-client/src/operations/subgraph/fetch/runner.rs index b365f45f4..e23032c57 100644 --- a/crates/rover-client/src/query/subgraph/fetch/query_runner.rs +++ b/crates/rover-client/src/operations/subgraph/fetch/runner.rs @@ -7,7 +7,7 @@ use graphql_client::*; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/query/subgraph/fetch/fetch_query.graphql", + query_path = "src/operations/subgraph/fetch/fetch_query.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" diff --git a/crates/rover-client/src/query/subgraph/fetch/types.rs b/crates/rover-client/src/operations/subgraph/fetch/types.rs similarity index 96% rename from crates/rover-client/src/query/subgraph/fetch/types.rs rename to crates/rover-client/src/operations/subgraph/fetch/types.rs index d3a118892..e0554de85 100644 --- a/crates/rover-client/src/query/subgraph/fetch/types.rs +++ b/crates/rover-client/src/operations/subgraph/fetch/types.rs @@ -1,4 +1,4 @@ -use super::query_runner::subgraph_fetch_query; +use super::runner::subgraph_fetch_query; pub(crate) type ServiceList = Vec; pub(crate) type SubgraphFetchResponseData = subgraph_fetch_query::ResponseData; diff --git a/crates/rover-client/src/query/subgraph/introspect/introspect_query.graphql b/crates/rover-client/src/operations/subgraph/introspect/introspect_query.graphql similarity index 100% rename from crates/rover-client/src/query/subgraph/introspect/introspect_query.graphql rename to crates/rover-client/src/operations/subgraph/introspect/introspect_query.graphql diff --git a/crates/rover-client/src/query/subgraph/introspect/introspect_schema.graphql b/crates/rover-client/src/operations/subgraph/introspect/introspect_schema.graphql similarity index 100% rename from crates/rover-client/src/query/subgraph/introspect/introspect_schema.graphql rename to crates/rover-client/src/operations/subgraph/introspect/introspect_schema.graphql diff --git a/crates/rover-client/src/query/subgraph/introspect/mod.rs b/crates/rover-client/src/operations/subgraph/introspect/mod.rs similarity index 62% rename from crates/rover-client/src/query/subgraph/introspect/mod.rs rename to crates/rover-client/src/operations/subgraph/introspect/mod.rs index e16e29777..047cf54f2 100644 --- a/crates/rover-client/src/query/subgraph/introspect/mod.rs +++ b/crates/rover-client/src/operations/subgraph/introspect/mod.rs @@ -1,5 +1,5 @@ -pub(crate) mod query_runner; +pub(crate) mod runner; pub(crate) mod types; -pub use query_runner::run; +pub use runner::run; pub use types::{SubgraphIntrospectInput, SubgraphIntrospectResponse}; diff --git a/crates/rover-client/src/query/subgraph/introspect/query_runner.rs b/crates/rover-client/src/operations/subgraph/introspect/runner.rs similarity index 85% rename from crates/rover-client/src/query/subgraph/introspect/query_runner.rs rename to crates/rover-client/src/operations/subgraph/introspect/runner.rs index a46ce5084..1a6fe7f13 100644 --- a/crates/rover-client/src/query/subgraph/introspect/query_runner.rs +++ b/crates/rover-client/src/operations/subgraph/introspect/runner.rs @@ -1,13 +1,13 @@ use crate::blocking::GraphQLClient; -use crate::query::subgraph::introspect::types::*; +use crate::operations::subgraph::introspect::types::*; use crate::RoverClientError; use graphql_client::*; #[derive(GraphQLQuery)] #[graphql( - query_path = "src/query/subgraph/introspect/introspect_query.graphql", - schema_path = "src/query/subgraph/introspect/introspect_schema.graphql", + query_path = "src/operations/subgraph/introspect/introspect_query.graphql", + schema_path = "src/operations//subgraph/introspect/introspect_schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] diff --git a/crates/rover-client/src/query/subgraph/introspect/types.rs b/crates/rover-client/src/operations/subgraph/introspect/types.rs similarity index 86% rename from crates/rover-client/src/query/subgraph/introspect/types.rs rename to crates/rover-client/src/operations/subgraph/introspect/types.rs index 6f3301964..0b1a933c7 100644 --- a/crates/rover-client/src/query/subgraph/introspect/types.rs +++ b/crates/rover-client/src/operations/subgraph/introspect/types.rs @@ -1,4 +1,4 @@ -use crate::query::subgraph::introspect::query_runner::subgraph_introspect_query; +use crate::operations::subgraph::introspect::runner::subgraph_introspect_query; pub(crate) type QueryVariables = subgraph_introspect_query::Variables; pub(crate) type QueryResponseData = subgraph_introspect_query::ResponseData; diff --git a/crates/rover-client/src/query/subgraph/list/list_query.graphql b/crates/rover-client/src/operations/subgraph/list/list_query.graphql similarity index 100% rename from crates/rover-client/src/query/subgraph/list/list_query.graphql rename to crates/rover-client/src/operations/subgraph/list/list_query.graphql diff --git a/crates/rover-client/src/query/subgraph/list/mod.rs b/crates/rover-client/src/operations/subgraph/list/mod.rs similarity index 64% rename from crates/rover-client/src/query/subgraph/list/mod.rs rename to crates/rover-client/src/operations/subgraph/list/mod.rs index 07495e228..aeb6b0a14 100644 --- a/crates/rover-client/src/query/subgraph/list/mod.rs +++ b/crates/rover-client/src/operations/subgraph/list/mod.rs @@ -1,6 +1,6 @@ -mod query_runner; +mod runner; pub(crate) mod types; -pub use query_runner::run; +pub use runner::run; pub use types::{SubgraphListInput, SubgraphListResponse}; diff --git a/crates/rover-client/src/query/subgraph/list/query_runner.rs b/crates/rover-client/src/operations/subgraph/list/runner.rs similarity index 98% rename from crates/rover-client/src/query/subgraph/list/query_runner.rs rename to crates/rover-client/src/operations/subgraph/list/runner.rs index 368744417..0b701d6be 100644 --- a/crates/rover-client/src/query/subgraph/list/query_runner.rs +++ b/crates/rover-client/src/operations/subgraph/list/runner.rs @@ -1,5 +1,5 @@ use crate::blocking::StudioClient; -use crate::query::subgraph::list::types::*; +use crate::operations::subgraph::list::types::*; use crate::RoverClientError; use graphql_client::*; @@ -9,7 +9,7 @@ type Timestamp = String; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/query/subgraph/list/list_query.graphql", + query_path = "src/operations/subgraph/list/list_query.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" diff --git a/crates/rover-client/src/query/subgraph/list/types.rs b/crates/rover-client/src/operations/subgraph/list/types.rs similarity index 94% rename from crates/rover-client/src/query/subgraph/list/types.rs rename to crates/rover-client/src/operations/subgraph/list/types.rs index ef69c1cb7..07235cfd0 100644 --- a/crates/rover-client/src/query/subgraph/list/types.rs +++ b/crates/rover-client/src/operations/subgraph/list/types.rs @@ -1,4 +1,4 @@ -use crate::query::subgraph::list::query_runner::subgraph_list_query; +use crate::operations::subgraph::list::runner::subgraph_list_query; pub(crate) type QuerySubgraphInfo = subgraph_list_query::SubgraphListQueryServiceImplementingServicesOnFederatedImplementingServicesServices; pub(crate) type QueryResponseData = subgraph_list_query::ResponseData; diff --git a/crates/rover-client/src/query/subgraph/mod.rs b/crates/rover-client/src/operations/subgraph/mod.rs similarity index 100% rename from crates/rover-client/src/query/subgraph/mod.rs rename to crates/rover-client/src/operations/subgraph/mod.rs diff --git a/crates/rover-client/src/query/subgraph/publish/mod.rs b/crates/rover-client/src/operations/subgraph/publish/mod.rs similarity index 77% rename from crates/rover-client/src/query/subgraph/publish/mod.rs rename to crates/rover-client/src/operations/subgraph/publish/mod.rs index b0504e084..8ad8f0747 100644 --- a/crates/rover-client/src/query/subgraph/publish/mod.rs +++ b/crates/rover-client/src/operations/subgraph/publish/mod.rs @@ -1,3 +1,3 @@ -pub mod mutation_runner; +pub mod runner; pub(crate) mod types; pub use types::{SubgraphPublishInput, SubgraphPublishResponse}; diff --git a/crates/rover-client/src/query/subgraph/publish/publish_mutation.graphql b/crates/rover-client/src/operations/subgraph/publish/publish_mutation.graphql similarity index 100% rename from crates/rover-client/src/query/subgraph/publish/publish_mutation.graphql rename to crates/rover-client/src/operations/subgraph/publish/publish_mutation.graphql diff --git a/crates/rover-client/src/query/subgraph/publish/mutation_runner.rs b/crates/rover-client/src/operations/subgraph/publish/runner.rs similarity index 97% rename from crates/rover-client/src/query/subgraph/publish/mutation_runner.rs rename to crates/rover-client/src/operations/subgraph/publish/runner.rs index 0b3ffe42c..b85154b5e 100644 --- a/crates/rover-client/src/query/subgraph/publish/mutation_runner.rs +++ b/crates/rover-client/src/operations/subgraph/publish/runner.rs @@ -1,6 +1,6 @@ use super::types::*; use crate::blocking::StudioClient; -use crate::query::config::is_federated; +use crate::operations::config::is_federated; use crate::RoverClientError; use graphql_client::*; @@ -8,7 +8,7 @@ use graphql_client::*; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/query/subgraph/publish/publish_mutation.graphql", + query_path = "src/operations/subgraph/publish/publish_mutation.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" diff --git a/crates/rover-client/src/query/subgraph/publish/types.rs b/crates/rover-client/src/operations/subgraph/publish/types.rs similarity index 95% rename from crates/rover-client/src/query/subgraph/publish/types.rs rename to crates/rover-client/src/operations/subgraph/publish/types.rs index 26738cf78..e51b32d67 100644 --- a/crates/rover-client/src/query/subgraph/publish/types.rs +++ b/crates/rover-client/src/operations/subgraph/publish/types.rs @@ -1,6 +1,6 @@ -use super::mutation_runner::subgraph_publish_mutation; +use super::runner::subgraph_publish_mutation; -use crate::utils::GitContext; +use crate::shared::GitContext; pub(crate) type ResponseData = subgraph_publish_mutation::ResponseData; pub(crate) type MutationVariables = subgraph_publish_mutation::Variables; diff --git a/crates/rover-client/src/query/supergraph/fetch.graphql b/crates/rover-client/src/operations/supergraph/fetch.graphql similarity index 100% rename from crates/rover-client/src/query/supergraph/fetch.graphql rename to crates/rover-client/src/operations/supergraph/fetch.graphql diff --git a/crates/rover-client/src/query/supergraph/fetch.rs b/crates/rover-client/src/operations/supergraph/fetch.rs similarity index 99% rename from crates/rover-client/src/query/supergraph/fetch.rs rename to crates/rover-client/src/operations/supergraph/fetch.rs index dc1b7b235..5e7a05b5d 100644 --- a/crates/rover-client/src/query/supergraph/fetch.rs +++ b/crates/rover-client/src/operations/supergraph/fetch.rs @@ -10,7 +10,7 @@ type GraphQLDocument = String; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/query/supergraph/fetch.graphql", + query_path = "src/operations/supergraph/fetch.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" diff --git a/crates/rover-client/src/query/supergraph/mod.rs b/crates/rover-client/src/operations/supergraph/mod.rs similarity index 100% rename from crates/rover-client/src/query/supergraph/mod.rs rename to crates/rover-client/src/operations/supergraph/mod.rs diff --git a/crates/rover-client/src/query/subgraph/check/mod.rs b/crates/rover-client/src/query/subgraph/check/mod.rs deleted file mode 100644 index 4fd7b2ce8..000000000 --- a/crates/rover-client/src/query/subgraph/check/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod query_runner; -pub(crate) mod types; -pub use types::{SubgraphCheckConfig, SubgraphCheckInput, SubgraphCheckResponse}; diff --git a/crates/rover-client/src/query/subgraph/check/types.rs b/crates/rover-client/src/query/subgraph/check/types.rs deleted file mode 100644 index f339a6b1e..000000000 --- a/crates/rover-client/src/query/subgraph/check/types.rs +++ /dev/null @@ -1,114 +0,0 @@ -use std::fmt; - -use crate::utils::GitContext; - -use super::query_runner::subgraph_check_query; - -pub(crate) type Timestamp = String; -type QueryVariables = subgraph_check_query::Variables; -type QueryChangeSeverity = subgraph_check_query::ChangeSeverity; -type QuerySchema = subgraph_check_query::PartialSchemaInput; -type QueryConfig = subgraph_check_query::HistoricQueryParameters; -type GitContextInput = subgraph_check_query::GitContextInput; - -#[derive(Debug, Clone, PartialEq)] -pub struct SubgraphCheckInput { - pub graph_id: String, - pub variant: String, - pub subgraph: String, - pub proposed_schema: String, - pub git_context: GitContext, - pub config: SubgraphCheckConfig, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct SubgraphCheckConfig { - pub query_count_threshold: Option, - pub query_count_threshold_percentage: Option, - pub validation_period_from: Option, - pub validation_period_to: Option, -} - -impl From for QueryVariables { - fn from(input: SubgraphCheckInput) -> Self { - Self { - graph_id: input.graph_id, - variant: input.variant, - subgraph: input.subgraph, - proposed_schema: QuerySchema { - sdl: Some(input.proposed_schema), - hash: None, - }, - config: QueryConfig { - query_count_threshold: input.config.query_count_threshold, - query_count_threshold_percentage: input.config.query_count_threshold_percentage, - from: input.config.validation_period_from, - to: input.config.validation_period_to, - // we don't support configuring these, but we can't leave them out - excluded_clients: None, - ignored_operations: None, - included_variants: None, - }, - git_context: input.git_context.into(), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct SubgraphCheckResponse { - pub target_url: Option, - pub number_of_checked_operations: i64, - pub changes: Vec, - pub change_severity: ChangeSeverity, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum ChangeSeverity { - PASS, - FAIL, -} - -impl fmt::Display for ChangeSeverity { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let msg = match self { - ChangeSeverity::PASS => "PASS", - ChangeSeverity::FAIL => "FAIL", - }; - write!(f, "{}", msg) - } -} - -impl From for ChangeSeverity { - fn from(severity: QueryChangeSeverity) -> Self { - match severity { - QueryChangeSeverity::NOTICE => ChangeSeverity::PASS, - QueryChangeSeverity::FAILURE => ChangeSeverity::FAIL, - _ => unreachable!("Unknown change severity"), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct SchemaChange { - pub code: String, - pub description: String, - pub severity: ChangeSeverity, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct CompositionError { - pub message: String, - pub code: Option, -} - -impl From for GitContextInput { - fn from(git_context: GitContext) -> GitContextInput { - GitContextInput { - branch: git_context.branch, - commit: git_context.commit, - committer: git_context.author, - remote_url: git_context.remote_url, - message: None, - } - } -} diff --git a/crates/rover-client/src/shared/check_response.rs b/crates/rover-client/src/shared/check_response.rs new file mode 100644 index 000000000..48dba4159 --- /dev/null +++ b/crates/rover-client/src/shared/check_response.rs @@ -0,0 +1,104 @@ +use std::cmp::Ordering; +use std::fmt; + +use crate::RoverClientError; + +/// CheckResponse is the return type of the +/// `graph` and `subgraph` check operations +#[derive(Debug, Clone, PartialEq)] +pub struct CheckResponse { + pub target_url: Option, + pub number_of_checked_operations: i64, + pub changes: Vec, + pub change_severity: ChangeSeverity, + pub num_failures: i64, +} + +impl CheckResponse { + pub fn new( + target_url: Option, + number_of_checked_operations: i64, + changes: Vec, + change_severity: ChangeSeverity, + ) -> CheckResponse { + let mut num_failures = 0; + for change in &changes { + if let ChangeSeverity::FAIL = change.severity { + num_failures += 1; + } + } + + CheckResponse { + target_url, + number_of_checked_operations, + changes, + change_severity, + num_failures, + } + } + + pub fn check_for_failures(&self) -> Result { + match &self.num_failures.cmp(&0) { + Ordering::Equal => Ok(self.clone()), + Ordering::Greater => Err(RoverClientError::OperationCheckFailure { + check_response: self.clone(), + }), + Ordering::Less => unreachable!("Somehow encountered a negative number of failures."), + } + } +} + +/// ChangeSeverity indicates whether a proposed change +/// in a GraphQL schema passed or failed the check +#[derive(Debug, Clone, PartialEq)] +pub enum ChangeSeverity { + /// The proposed schema has passed the checks + PASS, + + /// The proposed schema has failed the checks + FAIL, +} + +impl ChangeSeverity { + // This message should be used when matching on a + // ChangeSeverity originating from auto-generated + // types from graphql-client + // + // We want to panic in this situation so that we + // get bug reports if Rover doesn't know the proper type + pub(crate) fn unreachable() -> ! { + unreachable!("Unknown change severity") + } +} + +impl fmt::Display for ChangeSeverity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let msg = match self { + ChangeSeverity::PASS => "PASS", + ChangeSeverity::FAIL => "FAIL", + }; + write!(f, "{}", msg) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SchemaChange { + /// The code associated with a given change + /// e.g. 'TYPE_REMOVED' + pub code: String, + + /// Explanation of a given change + pub description: String, + + /// The severity of a given change + pub severity: ChangeSeverity, +} + +/// CheckConfig is used as an input to check operations +#[derive(Debug, Clone, PartialEq)] +pub struct CheckConfig { + pub query_count_threshold: Option, + pub query_count_threshold_percentage: Option, + pub validation_period_from: Option, + pub validation_period_to: Option, +} diff --git a/crates/rover-client/src/utils/git.rs b/crates/rover-client/src/shared/git_context.rs similarity index 95% rename from crates/rover-client/src/utils/git.rs rename to crates/rover-client/src/shared/git_context.rs index 22f2fc4d2..37fad6ba3 100644 --- a/crates/rover-client/src/utils/git.rs +++ b/crates/rover-client/src/shared/git_context.rs @@ -1,4 +1,4 @@ -use crate::query::graph; +use crate::operations::graph; use std::env; @@ -150,19 +150,6 @@ impl From for GraphPublishContextInput { } } -type GraphCheckContextInput = graph::check::check_schema_query::GitContextInput; -impl From for GraphCheckContextInput { - fn from(git_context: GitContext) -> GraphCheckContextInput { - GraphCheckContextInput { - branch: git_context.branch, - commit: git_context.commit, - committer: git_context.author, - remote_url: git_context.remote_url, - message: None, - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/rover-client/src/shared/mod.rs b/crates/rover-client/src/shared/mod.rs new file mode 100644 index 000000000..e4d6a6267 --- /dev/null +++ b/crates/rover-client/src/shared/mod.rs @@ -0,0 +1,5 @@ +mod check_response; +mod git_context; + +pub use check_response::{ChangeSeverity, CheckConfig, CheckResponse, SchemaChange}; +pub use git_context::GitContext; diff --git a/crates/rover-client/src/utils/mod.rs b/crates/rover-client/src/utils/mod.rs deleted file mode 100644 index 2c5601aed..000000000 --- a/crates/rover-client/src/utils/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod git; - -pub use git::GitContext; diff --git a/src/cli.rs b/src/cli.rs index 1820dbce3..c67f73b78 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -11,7 +11,7 @@ use crate::utils::{ use crate::Result; use config::Config; use houston as config; -use rover_client::utils::GitContext; +use rover_client::shared::GitContext; use timber::{Level, LEVELS}; use camino::Utf8PathBuf; diff --git a/src/command/config/whoami.rs b/src/command/config/whoami.rs index 1c77dea5a..b79104660 100644 --- a/src/command/config/whoami.rs +++ b/src/command/config/whoami.rs @@ -1,5 +1,5 @@ use ansi_term::Colour::Green; -use rover_client::query::config::who_am_i::{query_runner, Actor, ConfigWhoAmIInput}; +use rover_client::operations::config::who_am_i::{runner, Actor, ConfigWhoAmIInput}; use serde::Serialize; use structopt::StructOpt; @@ -26,7 +26,7 @@ impl WhoAmI { let client = client_config.get_client(&self.profile_name)?; eprintln!("Checking identity of your API key against the registry."); - let identity = query_runner::run(ConfigWhoAmIInput {}, &client)?; + let identity = runner::run(ConfigWhoAmIInput {}, &client)?; let mut message = format!( "{}: {:?}\n", diff --git a/src/command/graph/check.rs b/src/command/graph/check.rs index 7964e349c..ac2ca8e4d 100644 --- a/src/command/graph/check.rs +++ b/src/command/graph/check.rs @@ -1,8 +1,8 @@ use serde::Serialize; use structopt::StructOpt; -use rover_client::query::graph::check; -use rover_client::utils::GitContext; +use rover_client::operations::graph::check::{self, GraphCheckInput}; +use rover_client::shared::{CheckConfig, GitContext}; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; @@ -11,7 +11,6 @@ use crate::utils::parsers::{ parse_graph_ref, parse_query_count_threshold, parse_query_percentage_threshold, parse_schema_source, parse_validation_period, GraphRef, SchemaSource, ValidationPeriod, }; -use crate::utils::table::{self, cell, row}; use crate::Result; #[derive(Debug, Serialize, StructOpt)] @@ -56,79 +55,29 @@ impl Check { git_context: GitContext, ) -> Result { let client = client_config.get_client(&self.profile_name)?; - let sdl = load_schema_from_flag(&self.schema, std::io::stdin())?; + let proposed_schema = load_schema_from_flag(&self.schema, std::io::stdin())?; + + eprintln!( + "Checking the proposed schema against metrics from {}", + &self.graph + ); + let res = check::run( - check::check_schema_query::Variables { + GraphCheckInput { graph_id: self.graph.name.clone(), - variant: Some(self.graph.variant.clone()), - schema: Some(sdl), - git_context: git_context.into(), - config: check::check_schema_query::HistoricQueryParameters { + variant: self.graph.variant.clone(), + proposed_schema, + git_context, + config: CheckConfig { query_count_threshold: self.query_count_threshold, query_count_threshold_percentage: self.query_percentage_threshold, - from: self.validation_period.clone().unwrap_or_default().from, - to: self.validation_period.clone().unwrap_or_default().to, - // we don't support configuring these, but we can't leave them out - excluded_clients: None, - ignored_operations: None, - included_variants: None, + validation_period_from: self.validation_period.clone().unwrap_or_default().from, + validation_period_to: self.validation_period.clone().unwrap_or_default().to, }, }, &client, )?; - eprintln!( - "Validated the proposed subgraph against metrics from {}", - &self.graph - ); - - let num_changes = res.changes.len(); - - let msg = match num_changes { - 0 => "There is no difference between the proposed graph and the graph that already exists in the graph registry. Try making a change to your proposed graph before running this command.".to_string(), - _ => format!("Compared {} schema changes against {} operations", res.changes.len(), res.number_of_checked_operations), - }; - - eprintln!("{}", &msg); - - let num_failures = print_changes(&res.changes); - - if let Some(url) = res.target_url { - eprintln!("View full details at {}", &url); - } - - match num_failures { - 0 => Ok(RoverStdout::None), - 1 => Err(anyhow::anyhow!("Encountered 1 failure.").into()), - _ => Err(anyhow::anyhow!("Encountered {} failures.", num_failures).into()), - } - } -} - -fn print_changes( - checks: &[check::check_schema_query::CheckSchemaQueryServiceCheckSchemaDiffToPreviousChanges], -) -> u64 { - let mut num_failures = 0; - - if !checks.is_empty() { - let mut table = table::get_table(); - - // bc => sets top row to be bold and center - table.add_row(row![bc => "Change", "Code", "Description"]); - for check in checks { - let change = match check.severity { - check::check_schema_query::ChangeSeverity::NOTICE => "PASS", - check::check_schema_query::ChangeSeverity::FAILURE => { - num_failures += 1; - "FAIL" - } - _ => unreachable!("Unknown change severity"), - }; - table.add_row(row![change, check.code, check.description]); - } - - eprintln!("{}", table); + Ok(RoverStdout::CheckResponse(res)) } - - num_failures } diff --git a/src/command/graph/fetch.rs b/src/command/graph/fetch.rs index d3d4c1e38..e08383f0f 100644 --- a/src/command/graph/fetch.rs +++ b/src/command/graph/fetch.rs @@ -2,7 +2,7 @@ use ansi_term::Colour::{Cyan, Yellow}; use serde::Serialize; use structopt::StructOpt; -use rover_client::query::graph::fetch; +use rover_client::operations::graph::fetch; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; diff --git a/src/command/graph/introspect.rs b/src/command/graph/introspect.rs index c76fa7512..e674702dd 100644 --- a/src/command/graph/introspect.rs +++ b/src/command/graph/introspect.rs @@ -6,7 +6,7 @@ use url::Url; use rover_client::{ blocking::GraphQLClient, - query::graph::introspect::{self, GraphIntrospectInput}, + operations::graph::introspect::{self, GraphIntrospectInput}, }; use crate::command::RoverStdout; diff --git a/src/command/graph/mod.rs b/src/command/graph/mod.rs index 4ec72fbfe..e06f7999f 100644 --- a/src/command/graph/mod.rs +++ b/src/command/graph/mod.rs @@ -10,7 +10,7 @@ use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; use crate::Result; -use rover_client::utils::GitContext; +use rover_client::shared::GitContext; #[derive(Debug, Serialize, StructOpt)] pub struct Graph { diff --git a/src/command/graph/publish.rs b/src/command/graph/publish.rs index 361608242..1526f09f1 100644 --- a/src/command/graph/publish.rs +++ b/src/command/graph/publish.rs @@ -2,8 +2,8 @@ use ansi_term::Colour::{Cyan, Yellow}; use serde::Serialize; use structopt::StructOpt; -use rover_client::query::graph::publish; -use rover_client::utils::GitContext; +use rover_client::operations::graph::publish; +use rover_client::shared::GitContext; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; diff --git a/src/command/mod.rs b/src/command/mod.rs index dfc4d344b..7d0b6c04b 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -4,11 +4,12 @@ mod explain; mod graph; mod info; mod install; -mod output; mod subgraph; mod supergraph; mod update; +pub(crate) mod output; + pub use config::Config; pub use docs::Docs; pub use explain::Explain; diff --git a/src/command/output.rs b/src/command/output.rs index c2870532f..685876718 100644 --- a/src/command/output.rs +++ b/src/command/output.rs @@ -6,7 +6,8 @@ use crate::utils::table::{self, cell, row}; use ansi_term::{Colour::Yellow, Style}; use atty::Stream; use crossterm::style::Attribute::Underlined; -use rover_client::query::subgraph::{check::SubgraphCheckResponse, list::SubgraphListResponse}; +use rover_client::operations::subgraph::list::SubgraphListResponse; +use rover_client::shared::CheckResponse; use termimad::MadSkin; /// RoverStdout defines all of the different types of data that are printed @@ -25,7 +26,7 @@ pub enum RoverStdout { CoreSchema(String), SchemaHash(String), SubgraphList(SubgraphListResponse), - SubgraphCheck(SubgraphCheckResponse), + CheckResponse(CheckResponse), VariantList(Vec), Profiles(Vec), Introspection(String), @@ -99,35 +100,8 @@ impl RoverStdout { details.root_url, details.graph_name ); } - RoverStdout::SubgraphCheck(check_response) => { - let num_changes = check_response.changes.len(); - - let msg = match num_changes { - 0 => "There were no changes detected in the composed schema.".to_string(), - _ => format!( - "Compared {} schema changes against {} operations", - check_response.changes.len(), - check_response.number_of_checked_operations - ), - }; - - eprintln!("{}", &msg); - - if !check_response.changes.is_empty() { - let mut table = table::get_table(); - - // bc => sets top row to be bold and center - table.add_row(row![bc => "Change", "Code", "Description"]); - for check in &check_response.changes { - table.add_row(row![check.severity, check.code, check.description]); - } - - print_content(table.to_string()); - } - - if let Some(url) = &check_response.target_url { - eprintln!("View full details at {}", url); - } + RoverStdout::CheckResponse(check_response) => { + print_check_response(check_response); } RoverStdout::VariantList(variants) => { print_descriptor("Variants"); @@ -186,3 +160,34 @@ fn print_content(content: impl Display) { print!("{}", content) } } + +pub(crate) fn print_check_response(check_response: &CheckResponse) { + let num_changes = check_response.changes.len(); + + let msg = match num_changes { + 0 => "There were no changes detected in the composed schema.".to_string(), + _ => format!( + "Compared {} schema changes against {} operations", + check_response.changes.len(), + check_response.number_of_checked_operations + ), + }; + + eprintln!("{}", &msg); + + if !check_response.changes.is_empty() { + let mut table = table::get_table(); + + // bc => sets top row to be bold and center + table.add_row(row![bc => "Change", "Code", "Description"]); + for check in &check_response.changes { + table.add_row(row![check.severity, check.code, check.description]); + } + + print_content(&table); + } + + if let Some(url) = &check_response.target_url { + eprintln!("View full details at {}", url); + } +} diff --git a/src/command/subgraph/check.rs b/src/command/subgraph/check.rs index f6c3bc3b1..de6780d87 100644 --- a/src/command/subgraph/check.rs +++ b/src/command/subgraph/check.rs @@ -1,8 +1,8 @@ use serde::Serialize; use structopt::StructOpt; -use rover_client::query::subgraph::check::{query_runner, SubgraphCheckConfig, SubgraphCheckInput}; -use rover_client::utils::GitContext; +use rover_client::operations::subgraph::check::{runner, SubgraphCheckInput}; +use rover_client::shared::{CheckConfig, GitContext}; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; @@ -68,14 +68,14 @@ impl Check { &self.subgraph, &self.graph ); - let res = query_runner::run( + let res = runner::run( SubgraphCheckInput { graph_id: self.graph.name.clone(), variant: self.graph.variant.clone(), proposed_schema, subgraph: self.subgraph.clone(), git_context, - config: SubgraphCheckConfig { + config: CheckConfig { query_count_threshold: self.query_count_threshold, query_count_threshold_percentage: self.query_percentage_threshold, validation_period_from: self.validation_period.clone().unwrap_or_default().from, @@ -85,6 +85,6 @@ impl Check { &client, )?; - Ok(RoverStdout::SubgraphCheck(res)) + Ok(RoverStdout::CheckResponse(res)) } } diff --git a/src/command/subgraph/delete.rs b/src/command/subgraph/delete.rs index bcf7c61d4..09bcf5dd3 100644 --- a/src/command/subgraph/delete.rs +++ b/src/command/subgraph/delete.rs @@ -7,7 +7,9 @@ use crate::utils::client::StudioClientConfig; use crate::utils::parsers::{parse_graph_ref, GraphRef}; use crate::Result; -use rover_client::query::subgraph::delete::{self, SubgraphDeleteInput, SubgraphDeleteResponse}; +use rover_client::operations::subgraph::delete::{ + self, SubgraphDeleteInput, SubgraphDeleteResponse, +}; #[derive(Debug, Serialize, StructOpt)] pub struct Delete { diff --git a/src/command/subgraph/fetch.rs b/src/command/subgraph/fetch.rs index 7319fadd2..08ddada32 100644 --- a/src/command/subgraph/fetch.rs +++ b/src/command/subgraph/fetch.rs @@ -2,7 +2,7 @@ use ansi_term::Colour::{Cyan, Yellow}; use serde::Serialize; use structopt::StructOpt; -use rover_client::query::subgraph::fetch::{query_runner, SubgraphFetchInput}; +use rover_client::operations::subgraph::fetch::{runner, SubgraphFetchInput}; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; @@ -39,7 +39,7 @@ impl Fetch { Yellow.normal().paint(&self.profile_name) ); - let result = query_runner::run( + let result = runner::run( SubgraphFetchInput { graph_id: self.graph.name.clone(), variant: self.graph.variant.clone(), diff --git a/src/command/subgraph/introspect.rs b/src/command/subgraph/introspect.rs index 884cfe5c8..d514f5639 100644 --- a/src/command/subgraph/introspect.rs +++ b/src/command/subgraph/introspect.rs @@ -5,7 +5,7 @@ use url::Url; use rover_client::{ blocking::GraphQLClient, - query::subgraph::introspect::{self, SubgraphIntrospectInput}, + operations::subgraph::introspect::{self, SubgraphIntrospectInput}, }; use crate::command::RoverStdout; diff --git a/src/command/subgraph/list.rs b/src/command/subgraph/list.rs index 28c122a48..d24ece7e2 100644 --- a/src/command/subgraph/list.rs +++ b/src/command/subgraph/list.rs @@ -2,7 +2,7 @@ use ansi_term::Colour::Cyan; use serde::Serialize; use structopt::StructOpt; -use rover_client::query::subgraph::list::{self, SubgraphListInput}; +use rover_client::operations::subgraph::list::{self, SubgraphListInput}; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; diff --git a/src/command/subgraph/mod.rs b/src/command/subgraph/mod.rs index 6dbf262e3..d7a45ae94 100644 --- a/src/command/subgraph/mod.rs +++ b/src/command/subgraph/mod.rs @@ -12,7 +12,7 @@ use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; use crate::Result; -use rover_client::utils::GitContext; +use rover_client::shared::GitContext; #[derive(Debug, Serialize, StructOpt)] pub struct Subgraph { diff --git a/src/command/subgraph/publish.rs b/src/command/subgraph/publish.rs index 5314d4646..d84c6a0ae 100644 --- a/src/command/subgraph/publish.rs +++ b/src/command/subgraph/publish.rs @@ -10,8 +10,10 @@ use crate::utils::{ }; use crate::Result; -use rover_client::query::subgraph::publish::{self, SubgraphPublishInput, SubgraphPublishResponse}; -use rover_client::utils::GitContext; +use rover_client::operations::subgraph::publish::{ + self, SubgraphPublishInput, SubgraphPublishResponse, +}; +use rover_client::shared::GitContext; #[derive(Debug, Serialize, StructOpt)] pub struct Publish { @@ -68,7 +70,7 @@ impl Publish { tracing::debug!("Publishing \n{}", &schema); - let publish_response = publish::mutation_runner::run( + let publish_response = publish::runner::run( SubgraphPublishInput { graph_id: self.graph.name.clone(), variant: self.graph.variant.clone(), diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index d775e627b..99ad0b9fc 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -5,11 +5,11 @@ use crate::{anyhow, command::RoverStdout, error::RoverError, Result, Suggestion} use ansi_term::Colour::Red; use camino::Utf8PathBuf; -use rover_client::query::subgraph::fetch::SubgraphFetchInput; -use rover_client::query::subgraph::introspect::SubgraphIntrospectInput; +use rover_client::operations::subgraph::fetch::SubgraphFetchInput; +use rover_client::operations::subgraph::introspect::SubgraphIntrospectInput; use rover_client::{ blocking::GraphQLClient, - query::subgraph::{fetch, introspect}, + operations::subgraph::{fetch, introspect}, }; use serde::Serialize; use std::{collections::HashMap, fs}; @@ -129,7 +129,7 @@ pub(crate) fn get_subgraph_definitions( // obtain SDL and add it to subgraph_definition. let client = client_config.get_client(&profile_name)?; let graphref = parse_graph_ref(graphref)?; - let result = fetch::query_runner::run( + let result = fetch::runner::run( SubgraphFetchInput { graph_id: graphref.name.clone(), variant: graphref.variant.clone(), diff --git a/src/command/supergraph/fetch.rs b/src/command/supergraph/fetch.rs index 8176f0297..d55cdd384 100644 --- a/src/command/supergraph/fetch.rs +++ b/src/command/supergraph/fetch.rs @@ -2,7 +2,7 @@ use crate::utils::client::StudioClientConfig; use crate::utils::parsers::{parse_graph_ref, GraphRef}; use crate::{command::RoverStdout, Result}; -use rover_client::query::supergraph::fetch; +use rover_client::operations::supergraph::fetch; use ansi_term::Colour::{Cyan, Yellow}; use serde::Serialize; diff --git a/src/error/metadata/code.rs b/src/error/metadata/code.rs index c50932b4b..86782c40b 100644 --- a/src/error/metadata/code.rs +++ b/src/error/metadata/code.rs @@ -35,6 +35,7 @@ pub enum Code { E027, E028, E029, + E030, } impl Display for Code { @@ -77,6 +78,7 @@ impl Code { (Code::E027, include_str!("./codes/E027.md").to_string()), (Code::E028, include_str!("./codes/E028.md").to_string()), (Code::E029, include_str!("./codes/E029.md").to_string()), + (Code::E030, include_str!("./codes/E030.md").to_string()), ]; contents.into_iter().collect() } @@ -86,8 +88,8 @@ impl Code { pub fn explain(&self) -> String { let all_explanations = Code::explanations(); let explanation = all_explanations.get(self); - if let Some(expl) = explanation { - format!("**{}**\n\n{}\n\n", self.to_string(), expl.clone()) + if let Some(explanation) = explanation { + format!("**{}**\n\n{}\n\n", &self, &explanation) } else { "Explanation not available".to_string() } diff --git a/src/error/metadata/codes/E030.md b/src/error/metadata/codes/E030.md new file mode 100644 index 000000000..b761379ac --- /dev/null +++ b/src/error/metadata/codes/E030.md @@ -0,0 +1 @@ +This error occurs when an operation check fails. This means that you proposed a schema that would break operations in use by existing clients. You can configure this behavior in the Checks -> Configuration view in [Apollo Studio](https://studio.apollographql.com/), and you can read more about client checks [here](https://www.apollographql.com/docs/studio/schema-checks/). \ No newline at end of file diff --git a/src/error/metadata/mod.rs b/src/error/metadata/mod.rs index 911b44b43..ab4ecd1a8 100644 --- a/src/error/metadata/mod.rs +++ b/src/error/metadata/mod.rs @@ -7,7 +7,7 @@ pub use suggestion::Suggestion; use houston::HoustonProblem; use rover_client::RoverClientError; -use crate::utils::env::RoverEnvKey; +use crate::{command::output::print_check_response, utils::env::RoverEnvKey}; use std::{env, fmt::Display}; @@ -79,6 +79,15 @@ impl From<&mut anyhow::Error> for Metadata { Some(Code::E029), ) } + RoverClientError::OperationCheckFailure { check_response } => { + print_check_response(check_response); + ( + Some(Suggestion::Adhoc( + "TODO: make a new error code and markdown file linking to client checks".to_string(), + )), + None, + ) + } RoverClientError::SubgraphIntrospectionNotAvailable => { (Some(Suggestion::UseFederatedGraph), Some(Code::E007)) } diff --git a/src/error/metadata/suggestion.rs b/src/error/metadata/suggestion.rs index 5599eefa4..a1d25f949 100644 --- a/src/error/metadata/suggestion.rs +++ b/src/error/metadata/suggestion.rs @@ -35,6 +35,9 @@ pub enum Suggestion { FixSubgraphSchema { graph_name: String, }, + FixOperationsInSchema { + graph_name: String, + }, } impl Display for Suggestion { @@ -132,7 +135,8 @@ impl Display for Suggestion { Suggestion::CheckServerConnection => "Make sure the endpoint is accepting connections and is spelled correctly".to_string(), Suggestion::ConvertGraphToSubgraph => "If you are sure you want to convert a non-federated graph to a subgraph, you can re-run the same command with a `--convert` flag.".to_string(), Suggestion::CheckGnuVersion => "This is likely an issue with your current version of `glibc`. Try running `ldd --version`, and if the version >= 2.18, we suggest installing the Rover binary built for `x86_64-unknown-linux-gnu`".to_string(), - Suggestion::FixSubgraphSchema { graph_name } => format!("The changes in the schema you proposed are incompatible with graph {}. See {} for more information on resolving composition errors.", Yellow.normal().paint(graph_name), Cyan.normal().paint("https://www.apollographql.com/docs/federation/errors/")) + Suggestion::FixSubgraphSchema { graph_name } => format!("The changes in the schema you proposed are incompatible with graph {}. See {} for more information on resolving composition errors.", Yellow.normal().paint(graph_name), Cyan.normal().paint("https://www.apollographql.com/docs/federation/errors/")), + Suggestion::FixOperationsInSchema { graph_name } => format!("The changes in the schema you proposed are incompatible with graph {}. See {} for more information on resolving operation check errors.", Yellow.normal().paint(graph_name), Cyan.normal().paint("https://www.apollographql.com/docs/studio/schema-checks/")) }; write!(formatter, "{}", &suggestion) } From 6ac35e1742b6ef9aa4dc4e9da43010241b6e74a7 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Wed, 7 Jul 2021 13:56:24 -0500 Subject: [PATCH 11/17] chore: move GraphRef to rover-client (#664) --- Cargo.lock | 1 + Cargo.toml | 1 - crates/rover-client/Cargo.toml | 1 + crates/rover-client/src/error.rs | 6 +- .../src/operations/config/who_am_i/mod.rs | 3 +- .../src/operations/graph/check/runner.rs | 2 +- .../src/operations/graph/check/types.rs | 9 +- .../src/operations/subgraph/check/mod.rs | 6 +- .../src/operations/subgraph/check/runner.rs | 8 +- .../src/operations/subgraph/check/types.rs | 15 +-- .../subgraph/delete/delete_mutation.graphql | 1 + .../src/operations/subgraph/delete/mod.rs | 3 +- .../src/operations/subgraph/delete/runner.rs | 37 ++++++- .../src/operations/subgraph/delete/types.rs | 14 ++- .../src/operations/subgraph/fetch/mod.rs | 6 +- .../src/operations/subgraph/fetch/runner.rs | 8 +- .../src/operations/subgraph/fetch/types.rs | 26 +---- .../src/operations/subgraph/list/mod.rs | 3 +- .../src/operations/subgraph/list/runner.rs | 2 +- .../src/operations/subgraph/list/types.rs | 9 +- .../src/operations/subgraph/publish/mod.rs | 6 +- .../subgraph/publish/publish_mutation.graphql | 1 + .../src/operations/subgraph/publish/runner.rs | 56 +++++++--- .../src/operations/subgraph/publish/types.rs | 13 +-- .../src/shared/composition_error.rs | 18 +++ crates/rover-client/src/shared/graph_ref.rs | 104 ++++++++++++++++++ crates/rover-client/src/shared/mod.rs | 4 + src/command/config/whoami.rs | 4 +- src/command/graph/check.rs | 11 +- src/command/graph/fetch.rs | 4 +- src/command/graph/publish.rs | 6 +- src/command/subgraph/check.rs | 15 ++- src/command/subgraph/delete.rs | 44 +++++--- src/command/subgraph/fetch.rs | 11 +- src/command/subgraph/list.rs | 7 +- src/command/subgraph/publish.rs | 37 ++++--- src/command/supergraph/compose/do_compose.rs | 18 +-- src/command/supergraph/config.rs | 2 +- src/command/supergraph/fetch.rs | 4 +- src/error/metadata/mod.rs | 13 +-- src/utils/parsers.rs | 93 +--------------- 41 files changed, 358 insertions(+), 274 deletions(-) create mode 100644 crates/rover-client/src/shared/composition_error.rs create mode 100644 crates/rover-client/src/shared/graph_ref.rs diff --git a/Cargo.lock b/Cargo.lock index f2efddf95..e8c389322 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1977,6 +1977,7 @@ dependencies = [ "indoc", "online", "pretty_assertions", + "regex", "reqwest", "sdl-encoder", "semver 1.0.3", diff --git a/Cargo.toml b/Cargo.toml index 7b430cbd6..4e01982d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,7 +57,6 @@ humantime = "2.1.0" opener = "0.5.0" os_info = "3.0" prettytable-rs = "0.8.0" -regex = "1" serde = "1.0" serde_json = "1.0" serde_yaml = "0.8" diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index 341562437..a471d4c92 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -18,6 +18,7 @@ git2 = { version = "0.13.20", default-features = false, features = ["vendored-op graphql_client = "0.9" http = "0.2" reqwest = {version = "0.11", default-features = false, features = ["json", "blocking", "rustls-tls", "gzip"]} +regex = "1" sdl-encoder = {path = "../sdl-encoder"} semver = "1" serde = "1" diff --git a/crates/rover-client/src/error.rs b/crates/rover-client/src/error.rs index 220cf319e..68a34f1f1 100644 --- a/crates/rover-client/src/error.rs +++ b/crates/rover-client/src/error.rs @@ -1,7 +1,7 @@ use reqwest::Url; use thiserror::Error; -use crate::{operations::subgraph::check::types::CompositionError, shared::CheckResponse}; +use crate::shared::{CheckResponse, CompositionError}; /// RoverClientError represents all possible failures that can occur during a client request. #[derive(Error, Debug)] @@ -138,6 +138,10 @@ pub enum RoverClientError { #[error("{}", check_response_error_msg(.check_response))] OperationCheckFailure { check_response: CheckResponse }, + /// This error occurs when a user has a malformed Graph Ref + #[error("Graph IDs must be in the format or @, where can only contain letters, numbers, or the characters `-` or `_`, and must be 64 characters or less. must be 64 characters or less.")] + InvalidGraphRef, + /// This error occurs when a user has a malformed API key #[error( "The API key you provided is malformed. An API key must have three parts separated by a colon." diff --git a/crates/rover-client/src/operations/config/who_am_i/mod.rs b/crates/rover-client/src/operations/config/who_am_i/mod.rs index 9e6871fdd..ccb47268d 100644 --- a/crates/rover-client/src/operations/config/who_am_i/mod.rs +++ b/crates/rover-client/src/operations/config/who_am_i/mod.rs @@ -1,4 +1,5 @@ +mod runner; mod types; -pub mod runner; +pub use runner::run; pub use types::{Actor, ConfigWhoAmIInput, RegistryIdentity}; diff --git a/crates/rover-client/src/operations/graph/check/runner.rs b/crates/rover-client/src/operations/graph/check/runner.rs index 190dec094..69ec084f2 100644 --- a/crates/rover-client/src/operations/graph/check/runner.rs +++ b/crates/rover-client/src/operations/graph/check/runner.rs @@ -29,7 +29,7 @@ pub fn run( input: GraphCheckInput, client: &StudioClient, ) -> Result { - let graph = input.graph_id.clone(); + let graph = input.graph_ref.name.clone(); let data = client.post::(input.into())?; get_check_response_from_data(data, graph) } diff --git a/crates/rover-client/src/operations/graph/check/types.rs b/crates/rover-client/src/operations/graph/check/types.rs index 61c16efac..1a85b5207 100644 --- a/crates/rover-client/src/operations/graph/check/types.rs +++ b/crates/rover-client/src/operations/graph/check/types.rs @@ -1,10 +1,9 @@ use crate::operations::graph::check::runner::graph_check_mutation; -use crate::shared::{ChangeSeverity, CheckConfig, GitContext, SchemaChange}; +use crate::shared::{ChangeSeverity, CheckConfig, GitContext, GraphRef, SchemaChange}; #[derive(Debug, Clone, PartialEq)] pub struct GraphCheckInput { - pub graph_id: String, - pub variant: String, + pub graph_ref: GraphRef, pub proposed_schema: String, pub git_context: GitContext, pub config: CheckConfig, @@ -13,8 +12,8 @@ pub struct GraphCheckInput { impl From for MutationVariables { fn from(input: GraphCheckInput) -> Self { Self { - graph_id: input.graph_id, - variant: Some(input.variant), + graph_id: input.graph_ref.name, + variant: Some(input.graph_ref.variant), proposed_schema: Some(input.proposed_schema), config: input.config.into(), git_context: input.git_context.into(), diff --git a/crates/rover-client/src/operations/subgraph/check/mod.rs b/crates/rover-client/src/operations/subgraph/check/mod.rs index 9734f57b4..ebbc0d6e6 100644 --- a/crates/rover-client/src/operations/subgraph/check/mod.rs +++ b/crates/rover-client/src/operations/subgraph/check/mod.rs @@ -1,3 +1,5 @@ -pub mod runner; -pub(crate) mod types; +mod runner; +mod types; + +pub use runner::run; pub use types::SubgraphCheckInput; diff --git a/crates/rover-client/src/operations/subgraph/check/runner.rs b/crates/rover-client/src/operations/subgraph/check/runner.rs index 94d91bd85..fde0365bb 100644 --- a/crates/rover-client/src/operations/subgraph/check/runner.rs +++ b/crates/rover-client/src/operations/subgraph/check/runner.rs @@ -1,7 +1,7 @@ use super::types::*; use crate::blocking::StudioClient; use crate::operations::{config::is_federated, subgraph::check::types::MutationResponseData}; -use crate::shared::{CheckResponse, SchemaChange}; +use crate::shared::{CheckResponse, CompositionError, SchemaChange}; use crate::RoverClientError; use graphql_client::*; @@ -28,12 +28,12 @@ pub fn run( input: SubgraphCheckInput, client: &StudioClient, ) -> Result { - let graph = input.graph_id.clone(); + let graph = input.graph_ref.name.clone(); // This response is used to check whether or not the current graph is federated. let is_federated = is_federated::run( is_federated::is_federated_graph::Variables { - graph_id: input.graph_id.clone(), - graph_variant: input.variant.clone(), + graph_id: input.graph_ref.name.clone(), + graph_variant: input.graph_ref.variant.clone(), }, &client, )?; diff --git a/crates/rover-client/src/operations/subgraph/check/types.rs b/crates/rover-client/src/operations/subgraph/check/types.rs index 2fdfd258a..dd38b7f57 100644 --- a/crates/rover-client/src/operations/subgraph/check/types.rs +++ b/crates/rover-client/src/operations/subgraph/check/types.rs @@ -1,5 +1,5 @@ use crate::operations::subgraph::check::runner::subgraph_check_mutation; -use crate::shared::{ChangeSeverity, CheckConfig, GitContext}; +use crate::shared::{ChangeSeverity, CheckConfig, GitContext, GraphRef}; type MutationVariables = subgraph_check_mutation::Variables; @@ -36,8 +36,7 @@ impl From for MutationGitContextInput { #[derive(Debug, Clone, PartialEq)] pub struct SubgraphCheckInput { - pub graph_id: String, - pub variant: String, + pub graph_ref: GraphRef, pub subgraph: String, pub proposed_schema: String, pub git_context: GitContext, @@ -47,8 +46,8 @@ pub struct SubgraphCheckInput { impl From for MutationVariables { fn from(input: SubgraphCheckInput) -> Self { Self { - graph_id: input.graph_id, - variant: input.variant, + graph_id: input.graph_ref.name, + variant: input.graph_ref.variant, subgraph: input.subgraph, proposed_schema: MutationSchema { sdl: Some(input.proposed_schema), @@ -68,9 +67,3 @@ impl From for MutationVariables { } } } - -#[derive(Debug, Clone, PartialEq)] -pub struct CompositionError { - pub message: String, - pub code: Option, -} diff --git a/crates/rover-client/src/operations/subgraph/delete/delete_mutation.graphql b/crates/rover-client/src/operations/subgraph/delete/delete_mutation.graphql index f9ba68241..40d22c3d5 100644 --- a/crates/rover-client/src/operations/subgraph/delete/delete_mutation.graphql +++ b/crates/rover-client/src/operations/subgraph/delete/delete_mutation.graphql @@ -12,6 +12,7 @@ mutation SubgraphDeleteMutation( ) { errors { message + code } updatedGateway } diff --git a/crates/rover-client/src/operations/subgraph/delete/mod.rs b/crates/rover-client/src/operations/subgraph/delete/mod.rs index 387f7cbc0..db8cb0622 100644 --- a/crates/rover-client/src/operations/subgraph/delete/mod.rs +++ b/crates/rover-client/src/operations/subgraph/delete/mod.rs @@ -1,6 +1,5 @@ mod runner; - -pub(crate) mod types; +mod types; pub use runner::run; pub use types::{SubgraphDeleteInput, SubgraphDeleteResponse}; diff --git a/crates/rover-client/src/operations/subgraph/delete/runner.rs b/crates/rover-client/src/operations/subgraph/delete/runner.rs index 694a7afc3..4355bd228 100644 --- a/crates/rover-client/src/operations/subgraph/delete/runner.rs +++ b/crates/rover-client/src/operations/subgraph/delete/runner.rs @@ -1,5 +1,6 @@ use crate::blocking::StudioClient; use crate::operations::subgraph::delete::types::*; +use crate::shared::CompositionError; use crate::RoverClientError; use graphql_client::*; @@ -24,7 +25,7 @@ pub fn run( input: SubgraphDeleteInput, client: &StudioClient, ) -> Result { - let graph = input.graph_id.clone(); + let graph = input.graph_ref.name.clone(); let response_data = client.post::(input.into())?; let data = get_delete_data_from_response(response_data, graph)?; Ok(build_response(data)) @@ -42,10 +43,15 @@ fn get_delete_data_from_response( } fn build_response(response: MutationComposition) -> SubgraphDeleteResponse { - let composition_errors: Vec = response + let composition_errors: Vec = response .errors .iter() - .filter_map(|error| error.as_ref().map(|e| e.message.clone())) + .filter_map(|error| { + error.as_ref().map(|e| CompositionError { + message: e.message.clone(), + code: e.code.clone(), + }) + }) .collect(); // if there are no errors, just return None @@ -72,9 +78,15 @@ mod tests { "service": { "removeImplementingServiceAndTriggerComposition": { "errors": [ - { "message": "wow" }, + { + "message": "wow", + "code": null + }, null, - { "message": "boo" } + { + "message": "boo", + "code": "BOO" + } ], "updatedGateway": false, } @@ -90,10 +102,12 @@ mod tests { errors: vec![ Some(MutationCompositionErrors { message: "wow".to_string(), + code: None, }), None, Some(MutationCompositionErrors { message: "boo".to_string(), + code: Some("BOO".to_string()), }), ], updated_gateway: false, @@ -107,10 +121,12 @@ mod tests { errors: vec![ Some(MutationCompositionErrors { message: "wow".to_string(), + code: None, }), None, Some(MutationCompositionErrors { message: "boo".to_string(), + code: Some("BOO".to_string()), }), ], updated_gateway: false, @@ -120,7 +136,16 @@ mod tests { assert_eq!( parsed, SubgraphDeleteResponse { - composition_errors: Some(vec!["wow".to_string(), "boo".to_string()]), + composition_errors: Some(vec![ + CompositionError { + message: "wow".to_string(), + code: None + }, + CompositionError { + message: "boo".to_string(), + code: Some("BOO".to_string()) + } + ]), updated_gateway: false, } ); diff --git a/crates/rover-client/src/operations/subgraph/delete/types.rs b/crates/rover-client/src/operations/subgraph/delete/types.rs index 04ce6c2e6..0bb7b67d2 100644 --- a/crates/rover-client/src/operations/subgraph/delete/types.rs +++ b/crates/rover-client/src/operations/subgraph/delete/types.rs @@ -1,4 +1,7 @@ -use crate::operations::subgraph::delete::runner::subgraph_delete_mutation; +use crate::{ + operations::subgraph::delete::runner::subgraph_delete_mutation, + shared::{CompositionError, GraphRef}, +}; pub(crate) type MutationComposition = subgraph_delete_mutation::SubgraphDeleteMutationServiceRemoveImplementingServiceAndTriggerComposition; pub(crate) type MutationVariables = subgraph_delete_mutation::Variables; @@ -8,8 +11,7 @@ pub(crate) type MutationCompositionErrors = subgraph_delete_mutation::SubgraphDe #[derive(Debug, Clone, PartialEq)] pub struct SubgraphDeleteInput { - pub graph_id: String, - pub variant: String, + pub graph_ref: GraphRef, pub subgraph: String, pub dry_run: bool, } @@ -21,14 +23,14 @@ pub struct SubgraphDeleteInput { #[derive(Debug, PartialEq)] pub struct SubgraphDeleteResponse { pub updated_gateway: bool, - pub composition_errors: Option>, + pub composition_errors: Option>, } impl From for MutationVariables { fn from(input: SubgraphDeleteInput) -> Self { Self { - graph_id: input.graph_id, - variant: input.variant, + graph_id: input.graph_ref.name, + variant: input.graph_ref.variant, subgraph: input.subgraph, dry_run: input.dry_run, } diff --git a/crates/rover-client/src/operations/subgraph/fetch/mod.rs b/crates/rover-client/src/operations/subgraph/fetch/mod.rs index 57ec30cd3..6258a0615 100644 --- a/crates/rover-client/src/operations/subgraph/fetch/mod.rs +++ b/crates/rover-client/src/operations/subgraph/fetch/mod.rs @@ -1,3 +1,5 @@ -pub mod runner; -pub(crate) mod types; +mod runner; +mod types; + +pub use runner::run; pub use types::{SubgraphFetchInput, SubgraphFetchResponse}; diff --git a/crates/rover-client/src/operations/subgraph/fetch/runner.rs b/crates/rover-client/src/operations/subgraph/fetch/runner.rs index e23032c57..29423c600 100644 --- a/crates/rover-client/src/operations/subgraph/fetch/runner.rs +++ b/crates/rover-client/src/operations/subgraph/fetch/runner.rs @@ -22,16 +22,16 @@ pub fn run( input: SubgraphFetchInput, client: &StudioClient, ) -> Result { - let variables: SubgraphFetchVariables = input.clone().into(); - let response_data = client.post::(variables.into())?; - get_sdl_from_response_data(input, response_data) + let input_clone = input.clone(); + let response_data = client.post::(input.into())?; + get_sdl_from_response_data(input_clone, response_data) } fn get_sdl_from_response_data( input: SubgraphFetchInput, response_data: SubgraphFetchResponseData, ) -> Result { - let service_list = get_services_from_response_data(&input.graph_id, response_data)?; + let service_list = get_services_from_response_data(&input.graph_ref.name, response_data)?; let sdl = get_sdl_for_service(&input.subgraph, service_list)?; Ok(SubgraphFetchResponse { sdl }) } diff --git a/crates/rover-client/src/operations/subgraph/fetch/types.rs b/crates/rover-client/src/operations/subgraph/fetch/types.rs index e0554de85..304805e57 100644 --- a/crates/rover-client/src/operations/subgraph/fetch/types.rs +++ b/crates/rover-client/src/operations/subgraph/fetch/types.rs @@ -1,3 +1,5 @@ +use crate::shared::GraphRef; + use super::runner::subgraph_fetch_query; pub(crate) type ServiceList = Vec; @@ -7,31 +9,15 @@ pub(crate) type QueryVariables = subgraph_fetch_query::Variables; #[derive(Debug, Clone, PartialEq)] pub struct SubgraphFetchInput { - pub graph_id: String, - pub variant: String, + pub graph_ref: GraphRef, pub subgraph: String, } -#[derive(Debug, Clone, PartialEq)] -pub(crate) struct SubgraphFetchVariables { - graph_id: String, - variant: String, -} - -impl From for SubgraphFetchVariables { +impl From for QueryVariables { fn from(input: SubgraphFetchInput) -> Self { Self { - graph_id: input.graph_id, - variant: input.variant, - } - } -} - -impl From for QueryVariables { - fn from(fetch_variables: SubgraphFetchVariables) -> Self { - Self { - graph_id: fetch_variables.graph_id, - variant: fetch_variables.variant, + graph_id: input.graph_ref.name, + variant: input.graph_ref.variant, } } } diff --git a/crates/rover-client/src/operations/subgraph/list/mod.rs b/crates/rover-client/src/operations/subgraph/list/mod.rs index aeb6b0a14..912e0a83c 100644 --- a/crates/rover-client/src/operations/subgraph/list/mod.rs +++ b/crates/rover-client/src/operations/subgraph/list/mod.rs @@ -1,6 +1,5 @@ mod runner; - -pub(crate) mod types; +mod types; pub use runner::run; pub use types::{SubgraphListInput, SubgraphListResponse}; diff --git a/crates/rover-client/src/operations/subgraph/list/runner.rs b/crates/rover-client/src/operations/subgraph/list/runner.rs index 0b701d6be..1c1faed5e 100644 --- a/crates/rover-client/src/operations/subgraph/list/runner.rs +++ b/crates/rover-client/src/operations/subgraph/list/runner.rs @@ -24,7 +24,7 @@ pub fn run( input: SubgraphListInput, client: &StudioClient, ) -> Result { - let graph = input.graph_id.clone(); + let graph = input.graph_ref.name.clone(); let response_data = client.post::(input.into())?; let root_url = response_data.frontend_url_root.clone(); let subgraphs = get_subgraphs_from_response_data(response_data, graph.clone())?; diff --git a/crates/rover-client/src/operations/subgraph/list/types.rs b/crates/rover-client/src/operations/subgraph/list/types.rs index 07235cfd0..00b9da8b6 100644 --- a/crates/rover-client/src/operations/subgraph/list/types.rs +++ b/crates/rover-client/src/operations/subgraph/list/types.rs @@ -1,4 +1,4 @@ -use crate::operations::subgraph::list::runner::subgraph_list_query; +use crate::{operations::subgraph::list::runner::subgraph_list_query, shared::GraphRef}; pub(crate) type QuerySubgraphInfo = subgraph_list_query::SubgraphListQueryServiceImplementingServicesOnFederatedImplementingServicesServices; pub(crate) type QueryResponseData = subgraph_list_query::ResponseData; @@ -10,15 +10,14 @@ use chrono::{DateTime, Local}; #[derive(Clone, PartialEq, Debug)] pub struct SubgraphListInput { - pub graph_id: String, - pub variant: String, + pub graph_ref: GraphRef, } impl From for QueryVariables { fn from(input: SubgraphListInput) -> Self { Self { - graph_id: input.graph_id, - variant: input.variant, + graph_id: input.graph_ref.name, + variant: input.graph_ref.variant, } } } diff --git a/crates/rover-client/src/operations/subgraph/publish/mod.rs b/crates/rover-client/src/operations/subgraph/publish/mod.rs index 8ad8f0747..0cf673b54 100644 --- a/crates/rover-client/src/operations/subgraph/publish/mod.rs +++ b/crates/rover-client/src/operations/subgraph/publish/mod.rs @@ -1,3 +1,5 @@ -pub mod runner; -pub(crate) mod types; +mod runner; +mod types; + +pub use runner::run; pub use types::{SubgraphPublishInput, SubgraphPublishResponse}; diff --git a/crates/rover-client/src/operations/subgraph/publish/publish_mutation.graphql b/crates/rover-client/src/operations/subgraph/publish/publish_mutation.graphql index 956de4ba2..c64f45468 100644 --- a/crates/rover-client/src/operations/subgraph/publish/publish_mutation.graphql +++ b/crates/rover-client/src/operations/subgraph/publish/publish_mutation.graphql @@ -21,6 +21,7 @@ mutation SubgraphPublishMutation( } errors { message + code } didUpdateGateway: updatedGateway serviceWasCreated: wasCreated diff --git a/crates/rover-client/src/operations/subgraph/publish/runner.rs b/crates/rover-client/src/operations/subgraph/publish/runner.rs index b85154b5e..e1ed36987 100644 --- a/crates/rover-client/src/operations/subgraph/publish/runner.rs +++ b/crates/rover-client/src/operations/subgraph/publish/runner.rs @@ -1,6 +1,7 @@ use super::types::*; use crate::blocking::StudioClient; use crate::operations::config::is_federated; +use crate::shared::CompositionError; use crate::RoverClientError; use graphql_client::*; @@ -23,15 +24,15 @@ pub fn run( client: &StudioClient, ) -> Result { let variables: MutationVariables = input.clone().into(); - let graph = input.graph_id.clone(); + let graph = input.graph_ref.name.clone(); // We don't want to implicitly convert non-federated graph to supergraphs. // Error here if no --convert flag is passed _and_ the current context // is non-federated. Add a suggestion to require a --convert flag. if !input.convert_to_federated_graph { let is_federated = is_federated::run( is_federated::is_federated_graph::Variables { - graph_id: input.graph_id.clone(), - graph_variant: input.variant, + graph_id: input.graph_ref.name.clone(), + graph_variant: input.graph_ref.variant, }, &client, )?; @@ -58,10 +59,15 @@ fn get_publish_response_from_data( } fn build_response(publish_response: UpdateResponse) -> SubgraphPublishResponse { - let composition_errors: Vec = publish_response + let composition_errors: Vec = publish_response .errors .iter() - .filter_map(|error| error.as_ref().map(|e| e.message.clone())) + .filter_map(|error| { + error.as_ref().map(|e| CompositionError { + message: e.message.clone(), + code: e.code.clone(), + }) + }) .collect(); // if there are no errors, just return None @@ -77,7 +83,7 @@ fn build_response(publish_response: UpdateResponse) -> SubgraphPublishResponse { None => None, }, did_update_gateway: publish_response.did_update_gateway, - service_was_created: publish_response.service_was_created, + subgraph_was_created: publish_response.service_was_created, composition_errors, } } @@ -91,9 +97,15 @@ mod tests { let json_response = json!({ "compositionConfig": { "schemaHash": "5gf564" }, "errors": [ - {"message": "[Accounts] User -> composition error"}, + { + "message": "[Accounts] User -> composition error", + "code": null + }, null, // this is technically allowed in the types - {"message": "[Products] Product -> another one"} + { + "message": "[Products] Product -> another one", + "code": "ERROR" + } ], "didUpdateGateway": false, "serviceWasCreated": true @@ -106,11 +118,17 @@ mod tests { SubgraphPublishResponse { schema_hash: Some("5gf564".to_string()), composition_errors: Some(vec![ - "[Accounts] User -> composition error".to_string(), - "[Products] Product -> another one".to_string() + CompositionError { + message: "[Accounts] User -> composition error".to_string(), + code: None + }, + CompositionError { + message: "[Products] Product -> another one".to_string(), + code: Some("ERROR".to_string()) + } ]), did_update_gateway: false, - service_was_created: true, + subgraph_was_created: true, } ); } @@ -132,7 +150,7 @@ mod tests { schema_hash: Some("5gf564".to_string()), composition_errors: None, did_update_gateway: true, - service_was_created: true, + subgraph_was_created: true, } ); } @@ -143,7 +161,10 @@ mod tests { fn build_response_works_with_failure_and_no_hash() { let json_response = json!({ "compositionConfig": null, - "errors": [{ "message": "[Accounts] -> Things went really wrong" }], + "errors": [{ + "message": "[Accounts] -> Things went really wrong", + "code": null + }], "didUpdateGateway": false, "serviceWasCreated": false }); @@ -154,11 +175,12 @@ mod tests { output, SubgraphPublishResponse { schema_hash: None, - composition_errors: Some( - vec!["[Accounts] -> Things went really wrong".to_string()] - ), + composition_errors: Some(vec![CompositionError { + message: "[Accounts] -> Things went really wrong".to_string(), + code: None + }]), did_update_gateway: false, - service_was_created: false, + subgraph_was_created: false, } ); } diff --git a/crates/rover-client/src/operations/subgraph/publish/types.rs b/crates/rover-client/src/operations/subgraph/publish/types.rs index e51b32d67..6472a70e4 100644 --- a/crates/rover-client/src/operations/subgraph/publish/types.rs +++ b/crates/rover-client/src/operations/subgraph/publish/types.rs @@ -1,6 +1,6 @@ use super::runner::subgraph_publish_mutation; -use crate::shared::GitContext; +use crate::shared::{CompositionError, GitContext, GraphRef}; pub(crate) type ResponseData = subgraph_publish_mutation::ResponseData; pub(crate) type MutationVariables = subgraph_publish_mutation::Variables; @@ -11,8 +11,7 @@ type GitContextInput = subgraph_publish_mutation::GitContextInput; #[derive(Debug, Clone, PartialEq)] pub struct SubgraphPublishInput { - pub graph_id: String, - pub variant: String, + pub graph_ref: GraphRef, pub subgraph: String, pub url: Option, pub schema: String, @@ -24,15 +23,15 @@ pub struct SubgraphPublishInput { pub struct SubgraphPublishResponse { pub schema_hash: Option, pub did_update_gateway: bool, - pub service_was_created: bool, - pub composition_errors: Option>, + pub subgraph_was_created: bool, + pub composition_errors: Option>, } impl From for MutationVariables { fn from(publish_input: SubgraphPublishInput) -> Self { Self { - graph_id: publish_input.graph_id, - variant: publish_input.variant, + graph_id: publish_input.graph_ref.name, + variant: publish_input.graph_ref.variant, subgraph: publish_input.subgraph, url: publish_input.url, schema: SchemaInput { diff --git a/crates/rover-client/src/shared/composition_error.rs b/crates/rover-client/src/shared/composition_error.rs new file mode 100644 index 000000000..b45e51c5a --- /dev/null +++ b/crates/rover-client/src/shared/composition_error.rs @@ -0,0 +1,18 @@ +use std::fmt::{self, Display}; + +#[derive(Debug, Clone, PartialEq)] +pub struct CompositionError { + pub message: String, + pub code: Option, +} + +impl Display for CompositionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(code) = &self.code { + write!(f, "{}: ", code)?; + } else { + write!(f, "UNKNOWN: ")?; + } + write!(f, "{}", &self.message) + } +} diff --git a/crates/rover-client/src/shared/graph_ref.rs b/crates/rover-client/src/shared/graph_ref.rs new file mode 100644 index 000000000..5cad87c3e --- /dev/null +++ b/crates/rover-client/src/shared/graph_ref.rs @@ -0,0 +1,104 @@ +use std::fmt; +use std::str::FromStr; + +use crate::RoverClientError; + +use regex::Regex; + +#[derive(Debug, Clone, PartialEq)] +pub struct GraphRef { + pub name: String, + pub variant: String, +} + +impl fmt::Display for GraphRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}@{}", self.name, self.variant) + } +} + +impl FromStr for GraphRef { + type Err = RoverClientError; + + /// NOTE: THIS IS A TEMPORARY SOLUTION. IN THE FUTURE, ALL GRAPH ID PARSING + /// WILL HAPPEN IN THE BACKEND TO KEEP EVERYTHING CONSISTENT. THIS IS AN + /// INCOMPLETE PLACEHOLDER, AND MAY NOT COVER EVERY SINGLE VALID USE CASE + fn from_str(graph_id: &str) -> Result { + let pattern = Regex::new(r"^[a-zA-Z][a-zA-Z0-9_-]{0,63}$").unwrap(); + let variant_pattern = Regex::new(r"^([a-zA-Z][a-zA-Z0-9_-]{0,63})@(.{0,63})$").unwrap(); + + let valid_graph_name_only = pattern.is_match(graph_id); + let valid_graph_with_variant = variant_pattern.is_match(graph_id); + + if valid_graph_name_only { + Ok(GraphRef { + name: graph_id.to_string(), + variant: "current".to_string(), + }) + } else if valid_graph_with_variant { + let matches = variant_pattern.captures(graph_id).unwrap(); + let name = matches.get(1).unwrap().as_str(); + let variant = matches.get(2).unwrap().as_str(); + Ok(GraphRef { + name: name.to_string(), + variant: variant.to_string(), + }) + } else { + Err(RoverClientError::InvalidGraphRef) + } + } +} + +#[cfg(test)] +mod tests { + use super::GraphRef; + use std::str::FromStr; + + #[test] + fn from_str_works() { + assert!(GraphRef::from_str("engine#%^").is_err()); + assert!(GraphRef::from_str( + "1234567890123456789012345678901234567890123456789012345678901234567890" + ) + .is_err()); + assert!(GraphRef::from_str("1boi").is_err()); + assert!(GraphRef::from_str("_eng").is_err()); + assert!(GraphRef::from_str( + "engine@1234567890123456789012345678901234567890123456789012345678901234567890" + ) + .is_err()); + assert!(GraphRef::from_str( + "engine1234567890123456789012345678901234567890123456789012345678901234567890@prod" + ) + .is_err()); + + assert_eq!( + GraphRef::from_str("engine@okay").unwrap(), + GraphRef { + name: "engine".to_string(), + variant: "okay".to_string() + } + ); + assert_eq!( + GraphRef::from_str("studio").unwrap(), + GraphRef { + name: "studio".to_string(), + variant: "current".to_string() + } + ); + assert_eq!( + GraphRef::from_str("this_should_work").unwrap(), + GraphRef { + name: "this_should_work".to_string(), + variant: "current".to_string() + } + ); + assert_eq!( + GraphRef::from_str("it-is-cool@my-special/variant:from$hell").unwrap(), + GraphRef { + name: "it-is-cool".to_string(), + variant: "my-special/variant:from$hell".to_string() + } + ); + } +} diff --git a/crates/rover-client/src/shared/mod.rs b/crates/rover-client/src/shared/mod.rs index e4d6a6267..9a2f6f445 100644 --- a/crates/rover-client/src/shared/mod.rs +++ b/crates/rover-client/src/shared/mod.rs @@ -1,5 +1,9 @@ mod check_response; +mod composition_error; mod git_context; +mod graph_ref; pub use check_response::{ChangeSeverity, CheckConfig, CheckResponse, SchemaChange}; +pub use composition_error::CompositionError; pub use git_context::GitContext; +pub use graph_ref::GraphRef; diff --git a/src/command/config/whoami.rs b/src/command/config/whoami.rs index b79104660..332b2c637 100644 --- a/src/command/config/whoami.rs +++ b/src/command/config/whoami.rs @@ -1,5 +1,5 @@ use ansi_term::Colour::Green; -use rover_client::operations::config::who_am_i::{runner, Actor, ConfigWhoAmIInput}; +use rover_client::operations::config::who_am_i::{self, Actor, ConfigWhoAmIInput}; use serde::Serialize; use structopt::StructOpt; @@ -26,7 +26,7 @@ impl WhoAmI { let client = client_config.get_client(&self.profile_name)?; eprintln!("Checking identity of your API key against the registry."); - let identity = runner::run(ConfigWhoAmIInput {}, &client)?; + let identity = who_am_i::run(ConfigWhoAmIInput {}, &client)?; let mut message = format!( "{}: {:?}\n", diff --git a/src/command/graph/check.rs b/src/command/graph/check.rs index ac2ca8e4d..a8bf3b821 100644 --- a/src/command/graph/check.rs +++ b/src/command/graph/check.rs @@ -2,14 +2,14 @@ use serde::Serialize; use structopt::StructOpt; use rover_client::operations::graph::check::{self, GraphCheckInput}; -use rover_client::shared::{CheckConfig, GitContext}; +use rover_client::shared::{CheckConfig, GitContext, GraphRef}; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; use crate::utils::loaders::load_schema_from_flag; use crate::utils::parsers::{ - parse_graph_ref, parse_query_count_threshold, parse_query_percentage_threshold, - parse_schema_source, parse_validation_period, GraphRef, SchemaSource, ValidationPeriod, + parse_query_count_threshold, parse_query_percentage_threshold, parse_schema_source, + parse_validation_period, SchemaSource, ValidationPeriod, }; use crate::Result; @@ -17,7 +17,7 @@ use crate::Result; pub struct Check { /// @ of graph in Apollo Studio to validate. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] + #[structopt(name = "GRAPH_REF")] #[serde(skip_serializing)] graph: GraphRef, @@ -64,8 +64,7 @@ impl Check { let res = check::run( GraphCheckInput { - graph_id: self.graph.name.clone(), - variant: self.graph.variant.clone(), + graph_ref: self.graph.clone(), proposed_schema, git_context, config: CheckConfig { diff --git a/src/command/graph/fetch.rs b/src/command/graph/fetch.rs index e08383f0f..dd2c21d53 100644 --- a/src/command/graph/fetch.rs +++ b/src/command/graph/fetch.rs @@ -3,17 +3,17 @@ use serde::Serialize; use structopt::StructOpt; use rover_client::operations::graph::fetch; +use rover_client::shared::GraphRef; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; -use crate::utils::parsers::{parse_graph_ref, GraphRef}; use crate::Result; #[derive(Debug, Serialize, StructOpt)] pub struct Fetch { /// @ of graph in Apollo Studio to fetch from. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] + #[structopt(name = "GRAPH_REF")] #[serde(skip_serializing)] graph: GraphRef, diff --git a/src/command/graph/publish.rs b/src/command/graph/publish.rs index 1526f09f1..b20e53f34 100644 --- a/src/command/graph/publish.rs +++ b/src/command/graph/publish.rs @@ -3,19 +3,19 @@ use serde::Serialize; use structopt::StructOpt; use rover_client::operations::graph::publish; -use rover_client::shared::GitContext; +use rover_client::shared::{GitContext, GraphRef}; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; use crate::utils::loaders::load_schema_from_flag; -use crate::utils::parsers::{parse_graph_ref, parse_schema_source, GraphRef, SchemaSource}; +use crate::utils::parsers::{parse_schema_source, SchemaSource}; use crate::Result; #[derive(Debug, Serialize, StructOpt)] pub struct Publish { /// @ of graph in Apollo Studio to publish to. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] + #[structopt(name = "GRAPH_REF")] #[serde(skip_serializing)] graph: GraphRef, diff --git a/src/command/subgraph/check.rs b/src/command/subgraph/check.rs index de6780d87..642268efa 100644 --- a/src/command/subgraph/check.rs +++ b/src/command/subgraph/check.rs @@ -1,15 +1,15 @@ use serde::Serialize; use structopt::StructOpt; -use rover_client::operations::subgraph::check::{runner, SubgraphCheckInput}; -use rover_client::shared::{CheckConfig, GitContext}; +use rover_client::operations::subgraph::check::{self, SubgraphCheckInput}; +use rover_client::shared::{CheckConfig, GitContext, GraphRef}; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; use crate::utils::loaders::load_schema_from_flag; use crate::utils::parsers::{ - parse_graph_ref, parse_query_count_threshold, parse_query_percentage_threshold, - parse_schema_source, parse_validation_period, GraphRef, SchemaSource, ValidationPeriod, + parse_query_count_threshold, parse_query_percentage_threshold, parse_schema_source, + parse_validation_period, SchemaSource, ValidationPeriod, }; use crate::Result; @@ -17,7 +17,7 @@ use crate::Result; pub struct Check { /// @ of graph in Apollo Studio to validate. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] + #[structopt(name = "GRAPH_REF")] #[serde(skip_serializing)] graph: GraphRef, @@ -68,10 +68,9 @@ impl Check { &self.subgraph, &self.graph ); - let res = runner::run( + let res = check::run( SubgraphCheckInput { - graph_id: self.graph.name.clone(), - variant: self.graph.variant.clone(), + graph_ref: self.graph.clone(), proposed_schema, subgraph: self.subgraph.clone(), git_context, diff --git a/src/command/subgraph/delete.rs b/src/command/subgraph/delete.rs index 09bcf5dd3..675138b9e 100644 --- a/src/command/subgraph/delete.rs +++ b/src/command/subgraph/delete.rs @@ -4,18 +4,18 @@ use structopt::StructOpt; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; -use crate::utils::parsers::{parse_graph_ref, GraphRef}; use crate::Result; use rover_client::operations::subgraph::delete::{ self, SubgraphDeleteInput, SubgraphDeleteResponse, }; +use rover_client::shared::GraphRef; #[derive(Debug, Serialize, StructOpt)] pub struct Delete { /// @ of federated graph in Apollo Studio to delete subgraph from. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] + #[structopt(name = "GRAPH_REF")] #[serde(skip_serializing)] graph: GraphRef, @@ -53,8 +53,7 @@ impl Delete { // run delete with dryRun, so we can preview composition errors let delete_dry_run_response = delete::run( SubgraphDeleteInput { - graph_id: self.graph.name.clone(), - variant: self.graph.variant.clone(), + graph_ref: self.graph.clone(), subgraph: self.subgraph.clone(), dry_run: true, }, @@ -72,8 +71,7 @@ impl Delete { let delete_response = delete::run( SubgraphDeleteInput { - graph_id: self.graph.name.clone(), - variant: self.graph.variant.clone(), + graph_ref: self.graph.clone(), subgraph: self.subgraph.clone(), dry_run: false, }, @@ -89,12 +87,14 @@ fn handle_dry_run_response(response: SubgraphDeleteResponse, subgraph: &str, gra let warn_prefix = Red.normal().paint("WARN:"); if let Some(errors) = response.composition_errors { eprintln!( - "{} Deleting the {} subgraph from {} would result in the following composition errors: \n{}", - warn_prefix, - Cyan.normal().paint(subgraph), - Cyan.normal().paint(graph_ref), - errors.join("\n") - ); + "{} Deleting the {} subgraph from {} would result in the following composition errors:", + warn_prefix, + Cyan.normal().paint(subgraph), + Cyan.normal().paint(graph_ref), + ); + for error in errors { + eprintln!("{}", &error); + } eprintln!("{} This is only a prediction. If the graph changes before confirming, these errors could change.", warn_prefix); } else { eprintln!("{} At the time of checking, there would be no composition errors resulting from the deletion of this subgraph.", warn_prefix); @@ -131,16 +131,20 @@ fn handle_response(response: SubgraphDeleteResponse, subgraph: &str, graph_ref: if let Some(errors) = response.composition_errors { eprintln!( - "{} There were composition errors as a result of deleting the subgraph: \n{}", + "{} There were composition errors as a result of deleting the subgraph:", warn_prefix, - errors.join("\n") - ) + ); + + for error in errors { + eprintln!("{}", &error); + } } } #[cfg(test)] mod tests { use super::{handle_response, SubgraphDeleteResponse}; + use rover_client::shared::CompositionError; #[test] fn handle_response_doesnt_error_with_all_successes() { @@ -156,8 +160,14 @@ mod tests { fn handle_response_doesnt_error_with_all_failures() { let response = SubgraphDeleteResponse { composition_errors: Some(vec![ - "a bad thing happened".to_string(), - "another bad thing".to_string(), + CompositionError { + message: "a bad thing happened".to_string(), + code: None, + }, + CompositionError { + message: "another bad thing".to_string(), + code: None, + }, ]), updated_gateway: false, }; diff --git a/src/command/subgraph/fetch.rs b/src/command/subgraph/fetch.rs index 08ddada32..a5dafbe84 100644 --- a/src/command/subgraph/fetch.rs +++ b/src/command/subgraph/fetch.rs @@ -2,18 +2,18 @@ use ansi_term::Colour::{Cyan, Yellow}; use serde::Serialize; use structopt::StructOpt; -use rover_client::operations::subgraph::fetch::{runner, SubgraphFetchInput}; +use rover_client::operations::subgraph::fetch::{self, SubgraphFetchInput}; +use rover_client::shared::GraphRef; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; -use crate::utils::parsers::{parse_graph_ref, GraphRef}; use crate::Result; #[derive(Debug, Serialize, StructOpt)] pub struct Fetch { /// @ of graph in Apollo Studio to fetch from. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] + #[structopt(name = "GRAPH_REF")] #[serde(skip_serializing)] graph: GraphRef, @@ -39,10 +39,9 @@ impl Fetch { Yellow.normal().paint(&self.profile_name) ); - let result = runner::run( + let result = fetch::run( SubgraphFetchInput { - graph_id: self.graph.name.clone(), - variant: self.graph.variant.clone(), + graph_ref: self.graph.clone(), subgraph: self.subgraph.clone(), }, &client, diff --git a/src/command/subgraph/list.rs b/src/command/subgraph/list.rs index d24ece7e2..3944a8e51 100644 --- a/src/command/subgraph/list.rs +++ b/src/command/subgraph/list.rs @@ -3,17 +3,17 @@ use serde::Serialize; use structopt::StructOpt; use rover_client::operations::subgraph::list::{self, SubgraphListInput}; +use rover_client::shared::GraphRef; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; -use crate::utils::parsers::{parse_graph_ref, GraphRef}; use crate::Result; #[derive(Debug, Serialize, StructOpt)] pub struct List { /// @ of graph in Apollo Studio to list subgraphs from. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] + #[structopt(name = "GRAPH_REF")] #[serde(skip_serializing)] graph: GraphRef, @@ -35,8 +35,7 @@ impl List { let list_details = list::run( SubgraphListInput { - graph_id: self.graph.name.clone(), - variant: self.graph.variant.clone(), + graph_ref: self.graph.clone(), }, &client, )?; diff --git a/src/command/subgraph/publish.rs b/src/command/subgraph/publish.rs index d84c6a0ae..bd1e0284f 100644 --- a/src/command/subgraph/publish.rs +++ b/src/command/subgraph/publish.rs @@ -6,20 +6,20 @@ use crate::command::RoverStdout; use crate::utils::{ client::StudioClientConfig, loaders::load_schema_from_flag, - parsers::{parse_graph_ref, parse_schema_source, GraphRef, SchemaSource}, + parsers::{parse_schema_source, SchemaSource}, }; use crate::Result; use rover_client::operations::subgraph::publish::{ self, SubgraphPublishInput, SubgraphPublishResponse, }; -use rover_client::shared::GitContext; +use rover_client::shared::{GitContext, GraphRef}; #[derive(Debug, Serialize, StructOpt)] pub struct Publish { /// @ of federated graph in Apollo Studio to publish to. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] + #[structopt(name = "GRAPH_REF")] #[serde(skip_serializing)] graph: GraphRef, @@ -70,10 +70,9 @@ impl Publish { tracing::debug!("Publishing \n{}", &schema); - let publish_response = publish::runner::run( + let publish_response = publish::run( SubgraphPublishInput { - graph_id: self.graph.name.clone(), - variant: self.graph.variant.clone(), + graph_ref: self.graph.clone(), subgraph: self.subgraph.clone(), url: self.routing_url.clone(), schema, @@ -89,7 +88,7 @@ impl Publish { } fn handle_publish_response(response: SubgraphPublishResponse, subgraph: &str, graph: &str) { - if response.service_was_created { + if response.subgraph_was_created { eprintln!( "A new subgraph called '{}' for the '{}' graph was created", subgraph, graph @@ -112,17 +111,17 @@ fn handle_publish_response(response: SubgraphPublishResponse, subgraph: &str, gr if let Some(errors) = response.composition_errors { let warn_prefix = Red.normal().paint("WARN:"); - eprintln!( - "{} The following composition errors occurred: \n{}", - warn_prefix, - errors.join("\n") - ); + eprintln!("{} The following composition errors occurred:", warn_prefix,); + for error in errors { + eprintln!("{}", &error); + } } } #[cfg(test)] mod tests { use super::{handle_publish_response, SubgraphPublishResponse}; + use rover_client::shared::CompositionError; // this test is a bit weird, since we can't test the output. We just verify it // doesn't error @@ -131,7 +130,7 @@ mod tests { let response = SubgraphPublishResponse { schema_hash: Some("123456".to_string()), did_update_gateway: true, - service_was_created: true, + subgraph_was_created: true, composition_errors: None, }; @@ -143,10 +142,16 @@ mod tests { let response = SubgraphPublishResponse { schema_hash: None, did_update_gateway: false, - service_was_created: false, + subgraph_was_created: false, composition_errors: Some(vec![ - "a bad thing happened".to_string(), - "another bad thing".to_string(), + CompositionError { + message: "a bad thing happened".to_string(), + code: None, + }, + CompositionError { + message: "another bad thing".to_string(), + code: None, + }, ]), }; diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index 99ad0b9fc..ba76fd2ec 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -1,5 +1,5 @@ use crate::command::supergraph::config::{self, SchemaSource, SupergraphConfig}; -use crate::utils::{client::StudioClientConfig, parsers::parse_graph_ref}; +use crate::utils::client::StudioClientConfig; use crate::{anyhow, command::RoverStdout, error::RoverError, Result, Suggestion}; use ansi_term::Colour::Red; @@ -7,12 +7,13 @@ use camino::Utf8PathBuf; use rover_client::operations::subgraph::fetch::SubgraphFetchInput; use rover_client::operations::subgraph::introspect::SubgraphIntrospectInput; +use rover_client::shared::GraphRef; use rover_client::{ blocking::GraphQLClient, operations::subgraph::{fetch, introspect}, }; use serde::Serialize; -use std::{collections::HashMap, fs}; +use std::{collections::HashMap, fs, str::FromStr}; use structopt::StructOpt; use harmonizer::ServiceDefinition as SubgraphDefinition; @@ -124,15 +125,16 @@ pub(crate) fn get_subgraph_definitions( let subgraph_definition = SubgraphDefinition::new(subgraph_name, url, &schema); subgraphs.push(subgraph_definition); } - SchemaSource::Subgraph { graphref, subgraph } => { - // given a graphref and subgraph, run subgraph fetch to + SchemaSource::Subgraph { + graph_ref, + subgraph, + } => { + // given a graph_ref and subgraph, run subgraph fetch to // obtain SDL and add it to subgraph_definition. let client = client_config.get_client(&profile_name)?; - let graphref = parse_graph_ref(graphref)?; - let result = fetch::runner::run( + let result = fetch::run( SubgraphFetchInput { - graph_id: graphref.name.clone(), - variant: graphref.variant.clone(), + graph_ref: GraphRef::from_str(graph_ref)?, subgraph: subgraph.clone(), }, &client, diff --git a/src/command/supergraph/config.rs b/src/command/supergraph/config.rs index 59551a32b..50c08b55f 100644 --- a/src/command/supergraph/config.rs +++ b/src/command/supergraph/config.rs @@ -27,7 +27,7 @@ pub(crate) struct Subgraph { pub(crate) enum SchemaSource { File { file: Utf8PathBuf }, SubgraphIntrospection { subgraph_url: Url }, - Subgraph { graphref: String, subgraph: String }, + Subgraph { graph_ref: String, subgraph: String }, } #[cfg(feature = "composition-js")] diff --git a/src/command/supergraph/fetch.rs b/src/command/supergraph/fetch.rs index d55cdd384..97937af1e 100644 --- a/src/command/supergraph/fetch.rs +++ b/src/command/supergraph/fetch.rs @@ -1,8 +1,8 @@ use crate::utils::client::StudioClientConfig; -use crate::utils::parsers::{parse_graph_ref, GraphRef}; use crate::{command::RoverStdout, Result}; use rover_client::operations::supergraph::fetch; +use rover_client::shared::GraphRef; use ansi_term::Colour::{Cyan, Yellow}; use serde::Serialize; @@ -12,7 +12,7 @@ use structopt::StructOpt; pub struct Fetch { /// @ of graph in Apollo Studio to fetch from. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] + #[structopt(name = "GRAPH_REF")] #[serde(skip_serializing)] graph: GraphRef, diff --git a/src/error/metadata/mod.rs b/src/error/metadata/mod.rs index ab4ecd1a8..b2f33d9d4 100644 --- a/src/error/metadata/mod.rs +++ b/src/error/metadata/mod.rs @@ -63,14 +63,8 @@ impl From<&mut anyhow::Error> for Metadata { composition_errors, } => { for composition_error in composition_errors { - let mut error = format!("{} ", Red.bold().paint("error:")); - if let Some(code) = &composition_error.code { - error.push_str(&format!("{}: ", code)); - } else { - error.push_str("UNKNOWN: "); - } - error.push_str(&composition_error.message); - eprintln!("{}", &error); + let error_descriptor = format!("{} ", Red.bold().paint("error:")); + eprintln!("{} {}", &error_descriptor, &composition_error); } ( Some(Suggestion::FixSubgraphSchema { @@ -147,6 +141,9 @@ impl From<&mut anyhow::Error> for Metadata { RoverClientError::CouldNotConnect { .. } => { (Some(Suggestion::CheckServerConnection), Some(Code::E028)) } + RoverClientError::InvalidGraphRef { .. } => { + unreachable!("Graph ref parse errors should be caught via structopt") + } }; return Metadata { suggestion, diff --git a/src/utils/parsers.rs b/src/utils/parsers.rs index 0f6c31024..9ede9dba8 100644 --- a/src/utils/parsers.rs +++ b/src/utils/parsers.rs @@ -1,8 +1,7 @@ use camino::Utf8PathBuf; -use regex::Regex; use serde::Serialize; -use std::{convert::TryInto, fmt}; +use std::convert::TryInto; use crate::{error::RoverError, Result}; @@ -25,46 +24,6 @@ pub fn parse_schema_source(loc: &str) -> Result { } } -#[derive(Debug, Clone, PartialEq)] -pub struct GraphRef { - pub name: String, - pub variant: String, -} - -impl fmt::Display for GraphRef { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}@{}", self.name, self.variant) - } -} - -/// NOTE: THIS IS A TEMPORARY SOLUTION. IN THE FUTURE, ALL GRAPH ID PARSING -/// WILL HAPPEN IN THE BACKEND TO KEEP EVERYTHING CONSISTENT. THIS IS AN -/// INCOMPLETE PLACEHOLDER, AND MAY NOT COVER EVERY SINGLE VALID USE CASE -pub fn parse_graph_ref(graph_id: &str) -> Result { - let pattern = Regex::new(r"^[a-zA-Z][a-zA-Z0-9_-]{0,63}$").unwrap(); - let variant_pattern = Regex::new(r"^([a-zA-Z][a-zA-Z0-9_-]{0,63})@(.{0,63})$").unwrap(); - - let valid_graph_name_only = pattern.is_match(graph_id); - let valid_graph_with_variant = variant_pattern.is_match(graph_id); - - if valid_graph_name_only { - Ok(GraphRef { - name: graph_id.to_string(), - variant: "current".to_string(), - }) - } else if valid_graph_with_variant { - let matches = variant_pattern.captures(graph_id).unwrap(); - let name = matches.get(1).unwrap().as_str(); - let variant = matches.get(2).unwrap().as_str(); - Ok(GraphRef { - name: name.to_string(), - variant: variant.to_string(), - }) - } else { - Err(RoverError::parse_error("Graph IDs must be in the format or @, where can only contain letters, numbers, or the characters `-` or `_`, and must be 64 characters or less. must be 64 characters or less.")) - } -} - #[derive(Debug, Serialize, Default, Clone)] pub struct ValidationPeriod { // these timestamps could be represented as i64, but the API expects @@ -140,7 +99,7 @@ pub fn parse_header(header: &str) -> Result<(String, String)> { #[cfg(test)] mod tests { - use super::{parse_graph_ref, parse_schema_source, GraphRef, SchemaSource}; + use super::{parse_schema_source, SchemaSource}; #[test] fn it_correctly_parses_stdin_flag() { @@ -163,52 +122,4 @@ mod tests { let loc = parse_schema_source(""); assert!(loc.is_err()); } - - #[test] - fn parse_graph_ref_works() { - assert!(parse_graph_ref("engine#%^").is_err()); - assert!(parse_graph_ref( - "1234567890123456789012345678901234567890123456789012345678901234567890" - ) - .is_err()); - assert!(parse_graph_ref("1boi").is_err()); - assert!(parse_graph_ref("_eng").is_err()); - assert!(parse_graph_ref( - "engine@1234567890123456789012345678901234567890123456789012345678901234567890" - ) - .is_err()); - assert!(parse_graph_ref( - "engine1234567890123456789012345678901234567890123456789012345678901234567890@prod" - ) - .is_err()); - - assert_eq!( - parse_graph_ref("engine@okay").unwrap(), - GraphRef { - name: "engine".to_string(), - variant: "okay".to_string() - } - ); - assert_eq!( - parse_graph_ref("studio").unwrap(), - GraphRef { - name: "studio".to_string(), - variant: "current".to_string() - } - ); - assert_eq!( - parse_graph_ref("this_should_work").unwrap(), - GraphRef { - name: "this_should_work".to_string(), - variant: "current".to_string() - } - ); - assert_eq!( - parse_graph_ref("it-is-cool@my-special/variant:from$hell").unwrap(), - GraphRef { - name: "it-is-cool".to_string(), - variant: "my-special/variant:from$hell".to_string() - } - ); - } } From 9c9e487a235fa75a440a0f577620e25c03494cb9 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Thu, 15 Jul 2021 12:38:15 -0500 Subject: [PATCH 12/17] chore: refactor the rest of rover-client (#675) --- Cargo.lock | 2 +- Cargo.toml | 1 - crates/rover-client/Cargo.toml | 1 + crates/rover-client/src/error.rs | 37 +++-- .../operations/config/is_federated.graphql | 7 - .../is_federated/is_federated_query.graphql | 7 + .../src/operations/config/is_federated/mod.rs | 5 + .../runner.rs} | 20 ++- .../operations/config/is_federated/types.rs | 18 +++ .../src/operations/graph/check/runner.rs | 12 +- .../src/operations/graph/check/types.rs | 11 +- .../src/operations/graph/fetch.graphql | 11 -- .../graph/fetch/fetch_query.graphql | 11 ++ .../src/operations/graph/fetch/mod.rs | 5 + .../graph/{fetch.rs => fetch/runner.rs} | 71 +++++---- .../src/operations/graph/fetch/types.rs | 18 +++ .../src/operations/graph/introspect/runner.rs | 2 +- .../src/operations/graph/publish/mod.rs | 5 + .../publish_mutation.graphql} | 14 +- .../graph/{publish.rs => publish/runner.rs} | 78 +++++----- .../src/operations/graph/publish/types.rs | 40 +++++ .../src/operations/subgraph/check/runner.rs | 26 ++-- .../src/operations/subgraph/check/types.rs | 19 ++- .../src/operations/subgraph/delete/runner.rs | 19 ++- .../src/operations/subgraph/fetch/mod.rs | 2 +- .../src/operations/subgraph/fetch/runner.rs | 43 ++++-- .../src/operations/subgraph/fetch/types.rs | 5 - .../operations/subgraph/introspect/runner.rs | 2 +- .../src/operations/subgraph/list/runner.rs | 30 ++-- .../src/operations/subgraph/list/types.rs | 2 +- .../src/operations/subgraph/publish/runner.rs | 21 +-- .../fetch_query.graphql} | 5 +- .../src/operations/supergraph/fetch/mod.rs | 5 + .../supergraph/{fetch.rs => fetch/runner.rs} | 142 +++++++++++------- .../src/operations/supergraph/fetch/types.rs | 18 +++ .../rover-client/src/shared/check_response.rs | 38 ++++- .../rover-client/src/shared/fetch_response.rs | 17 +++ crates/rover-client/src/shared/git_context.rs | 15 -- crates/rover-client/src/shared/mod.rs | 6 +- src/command/graph/check.rs | 9 +- src/command/graph/fetch.rs | 12 +- src/command/graph/publish.rs | 21 ++- src/command/output.rs | 23 ++- src/command/subgraph/check.rs | 9 +- src/command/subgraph/fetch.rs | 4 +- src/command/supergraph/compose/do_compose.rs | 3 +- src/command/supergraph/fetch.rs | 11 +- src/error/metadata/mod.rs | 28 ++-- src/error/metadata/suggestion.rs | 30 ++-- src/utils/parsers.rs | 41 ----- 50 files changed, 598 insertions(+), 384 deletions(-) delete mode 100644 crates/rover-client/src/operations/config/is_federated.graphql create mode 100644 crates/rover-client/src/operations/config/is_federated/is_federated_query.graphql create mode 100644 crates/rover-client/src/operations/config/is_federated/mod.rs rename crates/rover-client/src/operations/config/{is_federated.rs => is_federated/runner.rs} (71%) create mode 100644 crates/rover-client/src/operations/config/is_federated/types.rs delete mode 100644 crates/rover-client/src/operations/graph/fetch.graphql create mode 100644 crates/rover-client/src/operations/graph/fetch/fetch_query.graphql create mode 100644 crates/rover-client/src/operations/graph/fetch/mod.rs rename crates/rover-client/src/operations/graph/{fetch.rs => fetch/runner.rs} (58%) create mode 100644 crates/rover-client/src/operations/graph/fetch/types.rs create mode 100644 crates/rover-client/src/operations/graph/publish/mod.rs rename crates/rover-client/src/operations/graph/{publish.graphql => publish/publish_mutation.graphql} (70%) rename crates/rover-client/src/operations/graph/{publish.rs => publish/runner.rs} (75%) create mode 100644 crates/rover-client/src/operations/graph/publish/types.rs rename crates/rover-client/src/operations/supergraph/{fetch.graphql => fetch/fetch_query.graphql} (78%) create mode 100644 crates/rover-client/src/operations/supergraph/fetch/mod.rs rename crates/rover-client/src/operations/supergraph/{fetch.rs => fetch/runner.rs} (68%) create mode 100644 crates/rover-client/src/operations/supergraph/fetch/types.rs create mode 100644 crates/rover-client/src/shared/fetch_response.rs diff --git a/Cargo.lock b/Cargo.lock index 8aba78530..340643ad1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1939,7 +1939,6 @@ dependencies = [ "harmonizer", "heck", "houston", - "humantime", "opener", "os_info", "predicates", @@ -1977,6 +1976,7 @@ dependencies = [ "graphql_client", "houston", "http", + "humantime", "indoc", "online", "pretty_assertions", diff --git a/Cargo.toml b/Cargo.toml index d07ea73cc..01dcb44c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,6 @@ git-url-parse = "0.3.1" git2 = { version = "0.13.20", default-features = false, features = ["vendored-openssl"] } harmonizer = { version = "0.26.0", optional = true } heck = "0.3.3" -humantime = "2.1.0" opener = "0.5.0" os_info = "3.0" prettytable-rs = "0.8.0" diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index 1fec5258c..99d56f702 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -17,6 +17,7 @@ git-url-parse = "0.3.1" git2 = { version = "0.13.20", default-features = false, features = ["vendored-openssl"] } graphql_client = "0.9" http = "0.2" +humantime = "2.1.0" reqwest = { version = "0.11", default-features = false, features = ["blocking", "brotli", "gzip", "json", "native-tls-vendored"] } regex = "1" sdl-encoder = {path = "../sdl-encoder"} diff --git a/crates/rover-client/src/error.rs b/crates/rover-client/src/error.rs index 68a34f1f1..2ea70a76f 100644 --- a/crates/rover-client/src/error.rs +++ b/crates/rover-client/src/error.rs @@ -1,7 +1,7 @@ use reqwest::Url; use thiserror::Error; -use crate::shared::{CheckResponse, CompositionError}; +use crate::shared::{CheckResponse, CompositionError, GraphRef}; /// RoverClientError represents all possible failures that can occur during a client request. #[derive(Error, Debug)] @@ -60,14 +60,11 @@ pub enum RoverClientError { /// The Studio API could not find a variant for a graph #[error( - "The graph registry does not contain variant \"{invalid_variant}\" for graph \"{graph}\"" + "The graph registry does not contain variant \"{}\" for graph \"{}\"", graph_ref.variant, graph_ref.name )] NoSchemaForVariant { - /// The name of the graph. - graph: String, - - /// The non-existent variant. - invalid_variant: String, + /// The graph ref. + graph_ref: GraphRef, /// Valid variants. valid_variants: Vec, @@ -95,20 +92,22 @@ pub enum RoverClientError { /// when someone provides a bad graph/variant combination or isn't /// validated properly, we don't know which reason is at fault for data.service /// being empty, so this error tells them to check both. - #[error("Could not find graph with name \"{graph}\"")] - NoService { graph: String }, + #[error("Could not find graph with name \"{graph_ref}\"")] + GraphNotFound { graph_ref: GraphRef }, /// if someone attempts to get a core schema from a supergraph that has /// no composition results we return this error. - #[error("No supergraph SDL exists for \"{graph}\" because its subgraphs failed to compose.")] + #[error( + "No supergraph SDL exists for \"{graph_ref}\" because its subgraphs failed to compose." + )] NoCompositionPublishes { - graph: String, - composition_errors: Vec, + graph_ref: GraphRef, + composition_errors: Vec, }, #[error("{}", subgraph_composition_error_msg(.composition_errors))] SubgraphCompositionErrors { - graph_name: String, + graph_ref: GraphRef, composition_errors: Vec, }, @@ -122,9 +121,9 @@ pub enum RoverClientError { /// `can_operation_convert` is only set to true when a non-federated graph /// was encountered during an operation that could potentially convert a non-federated graph /// to a federated graph. - #[error("The graph `{graph}` is a non-federated graph. This operation is only possible for federated graphs.")] + #[error("The graph `{graph_ref}` is a non-federated graph. This operation is only possible for federated graphs.")] ExpectedFederatedGraph { - graph: String, + graph_ref: GraphRef, can_operation_convert: bool, }, @@ -132,6 +131,14 @@ pub enum RoverClientError { #[error("Invalid ChangeSeverity.")] InvalidSeverity, + /// The user supplied an invalid validation period + #[error("You can only specify a duration as granular as seconds.")] + ValidationPeriodTooGranular, + + /// The user supplied an invalid validation period duration + #[error(transparent)] + InvalidValidationPeriodDuration(#[from] humantime::DurationError), + /// While checking the proposed schema, we encountered changes that would break existing operations // we nest the CheckResponse here because we want to print the entire response even // if there were failures diff --git a/crates/rover-client/src/operations/config/is_federated.graphql b/crates/rover-client/src/operations/config/is_federated.graphql deleted file mode 100644 index 77ede9055..000000000 --- a/crates/rover-client/src/operations/config/is_federated.graphql +++ /dev/null @@ -1,7 +0,0 @@ -query IsFederatedGraph($graphId: ID!, $graphVariant: String!) { - service(id: $graphId) { - implementingServices(graphVariant: $graphVariant) { - __typename - } - } -} \ No newline at end of file diff --git a/crates/rover-client/src/operations/config/is_federated/is_federated_query.graphql b/crates/rover-client/src/operations/config/is_federated/is_federated_query.graphql new file mode 100644 index 000000000..9f8d216c5 --- /dev/null +++ b/crates/rover-client/src/operations/config/is_federated/is_federated_query.graphql @@ -0,0 +1,7 @@ +query IsFederatedGraph($graph_id: ID!, $variant: String!) { + service(id: $graphId) { + implementingServices(graphVariant: $variant) { + __typename + } + } +} \ No newline at end of file diff --git a/crates/rover-client/src/operations/config/is_federated/mod.rs b/crates/rover-client/src/operations/config/is_federated/mod.rs new file mode 100644 index 000000000..36375e300 --- /dev/null +++ b/crates/rover-client/src/operations/config/is_federated/mod.rs @@ -0,0 +1,5 @@ +mod runner; +mod types; + +pub(crate) use runner::run; +pub(crate) use types::IsFederatedInput; diff --git a/crates/rover-client/src/operations/config/is_federated.rs b/crates/rover-client/src/operations/config/is_federated/runner.rs similarity index 71% rename from crates/rover-client/src/operations/config/is_federated.rs rename to crates/rover-client/src/operations/config/is_federated/runner.rs index 4175e1f2f..35084ce68 100644 --- a/crates/rover-client/src/operations/config/is_federated.rs +++ b/crates/rover-client/src/operations/config/is_federated/runner.rs @@ -1,13 +1,15 @@ -// PublishPartialSchemaMutation use crate::blocking::StudioClient; +use crate::operations::config::is_federated::IsFederatedInput; +use crate::shared::GraphRef; use crate::RoverClientError; + use graphql_client::*; #[derive(GraphQLQuery)] // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/operations/config/is_federated.graphql", + query_path = "src/operations/config/is_federated/is_federated_query.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" @@ -18,21 +20,23 @@ use graphql_client::*; pub(crate) struct IsFederatedGraph; pub(crate) fn run( - variables: is_federated_graph::Variables, + input: IsFederatedInput, client: &StudioClient, ) -> Result { - let graph = variables.graph_id.clone(); - let data = client.post::(variables)?; - build_response(data, graph) + let graph_ref = input.graph_ref.clone(); + let data = client.post::(input.into())?; + build_response(data, graph_ref) } type ImplementingServices = is_federated_graph::IsFederatedGraphServiceImplementingServices; fn build_response( data: is_federated_graph::ResponseData, - graph: String, + graph_ref: GraphRef, ) -> Result { - let service = data.service.ok_or(RoverClientError::NoService { graph })?; + let service = data + .service + .ok_or(RoverClientError::GraphNotFound { graph_ref })?; Ok(match service.implementing_services { Some(typename) => match typename { ImplementingServices::FederatedImplementingServices => true, diff --git a/crates/rover-client/src/operations/config/is_federated/types.rs b/crates/rover-client/src/operations/config/is_federated/types.rs new file mode 100644 index 000000000..3f6f22e52 --- /dev/null +++ b/crates/rover-client/src/operations/config/is_federated/types.rs @@ -0,0 +1,18 @@ +use crate::operations::config::is_federated::runner::is_federated_graph; +use crate::shared::GraphRef; + +type QueryVariables = is_federated_graph::Variables; + +#[derive(Debug, Clone, PartialEq)] +pub struct IsFederatedInput { + pub graph_ref: GraphRef, +} + +impl From for QueryVariables { + fn from(input: IsFederatedInput) -> Self { + Self { + graph_id: input.graph_ref.name, + variant: input.graph_ref.variant, + } + } +} diff --git a/crates/rover-client/src/operations/graph/check/runner.rs b/crates/rover-client/src/operations/graph/check/runner.rs index 69ec084f2..d2315b61f 100644 --- a/crates/rover-client/src/operations/graph/check/runner.rs +++ b/crates/rover-client/src/operations/graph/check/runner.rs @@ -2,7 +2,7 @@ use crate::blocking::StudioClient; use crate::operations::graph::check::types::{ GraphCheckInput, MutationChangeSeverity, MutationResponseData, }; -use crate::shared::CheckResponse; +use crate::shared::{CheckResponse, GraphRef}; use crate::RoverClientError; use graphql_client::*; @@ -29,16 +29,18 @@ pub fn run( input: GraphCheckInput, client: &StudioClient, ) -> Result { - let graph = input.graph_ref.name.clone(); + let graph_ref = input.graph_ref.clone(); let data = client.post::(input.into())?; - get_check_response_from_data(data, graph) + get_check_response_from_data(data, graph_ref) } fn get_check_response_from_data( data: MutationResponseData, - graph: String, + graph_ref: GraphRef, ) -> Result { - let service = data.service.ok_or(RoverClientError::NoService { graph })?; + let service = data + .service + .ok_or(RoverClientError::GraphNotFound { graph_ref })?; let target_url = service.check_schema.target_url; let diff_to_previous = service.check_schema.diff_to_previous; diff --git a/crates/rover-client/src/operations/graph/check/types.rs b/crates/rover-client/src/operations/graph/check/types.rs index 1a85b5207..9991eed14 100644 --- a/crates/rover-client/src/operations/graph/check/types.rs +++ b/crates/rover-client/src/operations/graph/check/types.rs @@ -27,8 +27,15 @@ impl From for MutationConfig { Self { query_count_threshold: input.query_count_threshold, query_count_threshold_percentage: input.query_count_threshold_percentage, - from: input.validation_period_from, - to: input.validation_period_to, + from: Some( + input + .validation_period + .clone() + .unwrap_or_default() + .from + .to_string(), + ), + to: Some(input.validation_period.unwrap_or_default().to.to_string()), // we don't support configuring these, but we can't leave them out excluded_clients: None, ignored_operations: None, diff --git a/crates/rover-client/src/operations/graph/fetch.graphql b/crates/rover-client/src/operations/graph/fetch.graphql deleted file mode 100644 index 697a17b23..000000000 --- a/crates/rover-client/src/operations/graph/fetch.graphql +++ /dev/null @@ -1,11 +0,0 @@ -query FetchSchemaQuery($variant: String, $graphId: ID!, $hash: ID) { - frontendUrlRoot, - service(id: $graphId) { - schema(tag: $variant, hash: $hash) { - document - } - variants { - name - } - } -} diff --git a/crates/rover-client/src/operations/graph/fetch/fetch_query.graphql b/crates/rover-client/src/operations/graph/fetch/fetch_query.graphql new file mode 100644 index 000000000..8a4cc851e --- /dev/null +++ b/crates/rover-client/src/operations/graph/fetch/fetch_query.graphql @@ -0,0 +1,11 @@ +query GraphFetchQuery($graph_id: ID!, $variant: String) { + frontendUrlRoot, + service(id: $graph_id) { + schema(tag: $variant) { + document + } + variants { + name + } + } +} diff --git a/crates/rover-client/src/operations/graph/fetch/mod.rs b/crates/rover-client/src/operations/graph/fetch/mod.rs new file mode 100644 index 000000000..7713aca96 --- /dev/null +++ b/crates/rover-client/src/operations/graph/fetch/mod.rs @@ -0,0 +1,5 @@ +mod runner; +mod types; + +pub use runner::run; +pub use types::GraphFetchInput; diff --git a/crates/rover-client/src/operations/graph/fetch.rs b/crates/rover-client/src/operations/graph/fetch/runner.rs similarity index 58% rename from crates/rover-client/src/operations/graph/fetch.rs rename to crates/rover-client/src/operations/graph/fetch/runner.rs index 3ac6f2aba..fc72a3b53 100644 --- a/crates/rover-client/src/operations/graph/fetch.rs +++ b/crates/rover-client/src/operations/graph/fetch/runner.rs @@ -1,5 +1,8 @@ use crate::blocking::StudioClient; +use crate::operations::graph::fetch::GraphFetchInput; +use crate::shared::{FetchResponse, GraphRef, Sdl, SdlType}; use crate::RoverClientError; + use graphql_client::*; // I'm not sure where this should live long-term @@ -10,40 +13,42 @@ type GraphQLDocument = String; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/operations/graph/fetch.graphql", + query_path = "src/operations/graph/fetch/fetch_query.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. fetch_schema_query -pub struct FetchSchemaQuery; +/// Snake case of this name is the mod name. i.e. graph_fetch_query +pub(crate) struct GraphFetchQuery; /// The main function to be used from this module. This function fetches a /// schema from apollo studio and returns it in either sdl (default) or json format pub fn run( - variables: fetch_schema_query::Variables, + input: GraphFetchInput, client: &StudioClient, -) -> Result { - let graph = variables.graph_id.clone(); - let invalid_variant = variables - .variant - .clone() - .unwrap_or_else(|| "current".to_string()); - let response_data = client.post::(variables)?; - get_schema_from_response_data(response_data, graph, invalid_variant) - // if we want json, we can parse & serialize it here +) -> Result { + let graph_ref = input.graph_ref.clone(); + let response_data = client.post::(input.into())?; + let sdl_contents = get_schema_from_response_data(response_data, graph_ref)?; + Ok(FetchResponse { + sdl: Sdl { + contents: sdl_contents, + r#type: SdlType::Graph, + }, + }) } fn get_schema_from_response_data( - response_data: fetch_schema_query::ResponseData, - graph: String, - invalid_variant: String, + response_data: graph_fetch_query::ResponseData, + graph_ref: GraphRef, ) -> Result { - let service_data = response_data.service.ok_or(RoverClientError::NoService { - graph: graph.clone(), - })?; + let service_data = response_data + .service + .ok_or(RoverClientError::GraphNotFound { + graph_ref: graph_ref.clone(), + })?; let mut valid_variants = Vec::new(); @@ -55,8 +60,7 @@ fn get_schema_from_response_data( Ok(schema.document) } else { Err(RoverClientError::NoSchemaForVariant { - graph, - invalid_variant, + graph_ref, valid_variants, frontend_url_root: response_data.frontend_url_root, }) @@ -78,9 +82,9 @@ mod tests { "variants": [] } }); - let data: fetch_schema_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let (graph, invalid_variant) = mock_vars(); - let output = get_schema_from_response_data(data, graph, invalid_variant); + let data: graph_fetch_query::ResponseData = serde_json::from_value(json_response).unwrap(); + let graph_ref = mock_graph_ref(); + let output = get_schema_from_response_data(data, graph_ref); assert!(output.is_ok()); assert_eq!(output.unwrap(), "type Query { hello: String }".to_string()); @@ -90,9 +94,9 @@ mod tests { fn get_schema_from_response_data_errs_on_no_service() { let json_response = json!({ "service": null, "frontendUrlRoot": "https://studio.apollographql.com" }); - let data: fetch_schema_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let (graph, invalid_variant) = mock_vars(); - let output = get_schema_from_response_data(data, graph, invalid_variant); + let data: graph_fetch_query::ResponseData = serde_json::from_value(json_response).unwrap(); + let graph_ref = mock_graph_ref(); + let output = get_schema_from_response_data(data, graph_ref); assert!(output.is_err()); } @@ -106,14 +110,17 @@ mod tests { "variants": [], }, }); - let data: fetch_schema_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let (graph, invalid_variant) = mock_vars(); - let output = get_schema_from_response_data(data, graph, invalid_variant); + let data: graph_fetch_query::ResponseData = serde_json::from_value(json_response).unwrap(); + let graph_ref = mock_graph_ref(); + let output = get_schema_from_response_data(data, graph_ref); assert!(output.is_err()); } - fn mock_vars() -> (String, String) { - ("mygraph".to_string(), "current".to_string()) + fn mock_graph_ref() -> GraphRef { + GraphRef { + name: "mygraph".to_string(), + variant: "current".to_string(), + } } } diff --git a/crates/rover-client/src/operations/graph/fetch/types.rs b/crates/rover-client/src/operations/graph/fetch/types.rs new file mode 100644 index 000000000..bbfe7fc7b --- /dev/null +++ b/crates/rover-client/src/operations/graph/fetch/types.rs @@ -0,0 +1,18 @@ +use crate::operations::graph::fetch::runner::graph_fetch_query; +use crate::shared::GraphRef; + +type QueryVariables = graph_fetch_query::Variables; + +#[derive(Debug, Clone, PartialEq)] +pub struct GraphFetchInput { + pub graph_ref: GraphRef, +} + +impl From for QueryVariables { + fn from(input: GraphFetchInput) -> Self { + Self { + graph_id: input.graph_ref.name, + variant: Some(input.graph_ref.variant), + } + } +} diff --git a/crates/rover-client/src/operations/graph/introspect/runner.rs b/crates/rover-client/src/operations/graph/introspect/runner.rs index c9516c10d..2734beff5 100644 --- a/crates/rover-client/src/operations/graph/introspect/runner.rs +++ b/crates/rover-client/src/operations/graph/introspect/runner.rs @@ -8,7 +8,7 @@ use std::convert::TryFrom; #[derive(GraphQLQuery)] #[graphql( query_path = "src/operations/graph/introspect/introspect_query.graphql", - schema_path = "src/operations//graph/introspect/introspect_schema.graphql", + schema_path = "src/operations/graph/introspect/introspect_schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] diff --git a/crates/rover-client/src/operations/graph/publish/mod.rs b/crates/rover-client/src/operations/graph/publish/mod.rs new file mode 100644 index 000000000..4d4a2c184 --- /dev/null +++ b/crates/rover-client/src/operations/graph/publish/mod.rs @@ -0,0 +1,5 @@ +mod runner; +mod types; + +pub use runner::run; +pub use types::{GraphPublishInput, GraphPublishResponse}; diff --git a/crates/rover-client/src/operations/graph/publish.graphql b/crates/rover-client/src/operations/graph/publish/publish_mutation.graphql similarity index 70% rename from crates/rover-client/src/operations/graph/publish.graphql rename to crates/rover-client/src/operations/graph/publish/publish_mutation.graphql index b260df1b0..8535b5689 100644 --- a/crates/rover-client/src/operations/graph/publish.graphql +++ b/crates/rover-client/src/operations/graph/publish/publish_mutation.graphql @@ -1,14 +1,14 @@ -mutation PublishSchemaMutation( +mutation GraphPublishMutation( + $graph_id: ID! $variant: String! - $graphId: ID! - $schemaDocument: String - $gitContext: GitContextInput! + $proposed_schema: String! + $git_context: GitContextInput! ) { - service(id: $graphId) { + service(id: $graph_id) { uploadSchema( tag: $variant - schemaDocument: $schemaDocument - gitContext: $gitContext + schemaDocument: $proposed_schema + gitContext: $git_context ) { code message diff --git a/crates/rover-client/src/operations/graph/publish.rs b/crates/rover-client/src/operations/graph/publish/runner.rs similarity index 75% rename from crates/rover-client/src/operations/graph/publish.rs rename to crates/rover-client/src/operations/graph/publish/runner.rs index d9b8b9787..c46032d20 100644 --- a/crates/rover-client/src/operations/graph/publish.rs +++ b/crates/rover-client/src/operations/graph/publish/runner.rs @@ -1,4 +1,6 @@ use crate::blocking::StudioClient; +use crate::operations::graph::publish::{GraphPublishInput, GraphPublishResponse}; +use crate::shared::GraphRef; use crate::RoverClientError; use graphql_client::*; @@ -6,40 +8,36 @@ use graphql_client::*; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/operations/graph/publish.graphql", + query_path = "src/operations/graph/publish/publish_mutation.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. stash_schema_query -pub struct PublishSchemaMutation; - -#[derive(Debug, PartialEq)] -pub struct PublishResponse { - pub schema_hash: String, - pub change_summary: String, -} +/// Snake case of this name is the mod name. i.e. graph_publish_mutation +pub(crate) struct GraphPublishMutation; /// Returns a message from apollo studio about the status of the update, and /// a sha256 hash of the schema to be used with `schema publish` pub fn run( - variables: publish_schema_mutation::Variables, + input: GraphPublishInput, client: &StudioClient, -) -> Result { - let graph = variables.graph_id.clone(); - let data = client.post::(variables)?; - let publish_response = get_publish_response_from_data(data, graph)?; +) -> Result { + let graph_ref = input.graph_ref.clone(); + let data = client.post::(input.into())?; + let publish_response = get_publish_response_from_data(data, graph_ref)?; build_response(publish_response) } fn get_publish_response_from_data( - data: publish_schema_mutation::ResponseData, - graph: String, -) -> Result { + data: graph_publish_mutation::ResponseData, + graph_ref: GraphRef, +) -> Result { // then, from the response data, get .service?.upload_schema? - let service_data = data.service.ok_or(RoverClientError::NoService { graph })?; + let service_data = data + .service + .ok_or(RoverClientError::GraphNotFound { graph_ref })?; service_data .upload_schema @@ -49,8 +47,8 @@ fn get_publish_response_from_data( } fn build_response( - publish_response: publish_schema_mutation::PublishSchemaMutationServiceUploadSchema, -) -> Result { + publish_response: graph_publish_mutation::GraphPublishMutationServiceUploadSchema, +) -> Result { if !publish_response.success { let msg = format!( "Schema upload failed with error: {}", @@ -82,14 +80,13 @@ fn build_response( build_change_summary(publish_response.tag.unwrap().diff_to_previous) }; - Ok(PublishResponse { + Ok(GraphPublishResponse { schema_hash: hash, change_summary, }) } -type ChangeDiff = - publish_schema_mutation::PublishSchemaMutationServiceUploadSchemaTagDiffToPrevious; +type ChangeDiff = graph_publish_mutation::GraphPublishMutationServiceUploadSchemaTagDiffToPrevious; /// builds a string-representation of the diff between two schemas /// e.g. ` [Fields: +2 -1 â–³0, Types: +4 -0 â–³7]` or `[No Changes]` @@ -131,25 +128,25 @@ mod tests { } } }); - let data: publish_schema_mutation::ResponseData = + let data: graph_publish_mutation::ResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_publish_response_from_data(data, "mygraph".to_string()); + let output = get_publish_response_from_data(data, mock_graph_ref()); assert!(output.is_ok()); assert_eq!( output.unwrap(), - publish_schema_mutation::PublishSchemaMutationServiceUploadSchema { + graph_publish_mutation::GraphPublishMutationServiceUploadSchema { code: "IT_WERK".to_string(), message: "it really do be published".to_string(), success: true, tag: Some( - publish_schema_mutation::PublishSchemaMutationServiceUploadSchemaTag { + graph_publish_mutation::GraphPublishMutationServiceUploadSchemaTag { variant: - publish_schema_mutation::PublishSchemaMutationServiceUploadSchemaTagVariant { + graph_publish_mutation::GraphPublishMutationServiceUploadSchemaTagVariant { name: "current".to_string() }, schema: - publish_schema_mutation::PublishSchemaMutationServiceUploadSchemaTagSchema { + graph_publish_mutation::GraphPublishMutationServiceUploadSchemaTagSchema { hash: "123456".to_string() }, diff_to_previous: None, @@ -162,9 +159,9 @@ mod tests { #[test] fn get_publish_response_from_data_errs_with_no_service() { let json_response = json!({ "service": null }); - let data: publish_schema_mutation::ResponseData = + let data: graph_publish_mutation::ResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_publish_response_from_data(data, "mygraph".to_string()); + let output = get_publish_response_from_data(data, mock_graph_ref()); assert!(output.is_err()); } @@ -176,9 +173,9 @@ mod tests { "uploadSchema": null } }); - let data: publish_schema_mutation::ResponseData = + let data: graph_publish_mutation::ResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_publish_response_from_data(data, "mygraph".to_string()); + let output = get_publish_response_from_data(data, mock_graph_ref()); assert!(output.is_err()); } @@ -194,14 +191,14 @@ mod tests { "schema": { "hash": "123456" } } }); - let update_response: publish_schema_mutation::PublishSchemaMutationServiceUploadSchema = + let update_response: graph_publish_mutation::GraphPublishMutationServiceUploadSchema = serde_json::from_value(json_response).unwrap(); let output = build_response(update_response); assert!(output.is_ok()); assert_eq!( output.unwrap(), - PublishResponse { + GraphPublishResponse { schema_hash: "123456".to_string(), change_summary: "[No Changes]".to_string(), } @@ -216,7 +213,7 @@ mod tests { "success": false, "tag": null }); - let update_response: publish_schema_mutation::PublishSchemaMutationServiceUploadSchema = + let update_response: graph_publish_mutation::GraphPublishMutationServiceUploadSchema = serde_json::from_value(json_response).unwrap(); let output = build_response(update_response); @@ -231,7 +228,7 @@ mod tests { "success": true, "tag": null }); - let update_response: publish_schema_mutation::PublishSchemaMutationServiceUploadSchema = + let update_response: graph_publish_mutation::GraphPublishMutationServiceUploadSchema = serde_json::from_value(json_response).unwrap(); let output = build_response(update_response); @@ -263,4 +260,11 @@ mod tests { fn build_change_summary_works_with_no_changes() { assert_eq!(build_change_summary(None), "[No Changes]".to_string()) } + + fn mock_graph_ref() -> GraphRef { + GraphRef { + name: "mygraph".to_string(), + variant: "current".to_string(), + } + } } diff --git a/crates/rover-client/src/operations/graph/publish/types.rs b/crates/rover-client/src/operations/graph/publish/types.rs new file mode 100644 index 000000000..8fee1a4cd --- /dev/null +++ b/crates/rover-client/src/operations/graph/publish/types.rs @@ -0,0 +1,40 @@ +use crate::operations::graph::publish::runner::graph_publish_mutation; +use crate::shared::{GitContext, GraphRef}; + +#[derive(Clone, Debug, PartialEq)] +pub struct GraphPublishInput { + pub graph_ref: GraphRef, + pub proposed_schema: String, + pub git_context: GitContext, +} + +type MutationVariables = graph_publish_mutation::Variables; +impl From for MutationVariables { + fn from(input: GraphPublishInput) -> Self { + Self { + graph_id: input.graph_ref.name, + variant: input.graph_ref.variant, + proposed_schema: input.proposed_schema, + git_context: input.git_context.into(), + } + } +} + +type GraphPublishContextInput = graph_publish_mutation::GitContextInput; +impl From for GraphPublishContextInput { + fn from(git_context: GitContext) -> GraphPublishContextInput { + GraphPublishContextInput { + branch: git_context.branch, + commit: git_context.commit, + committer: git_context.author, + remote_url: git_context.remote_url, + message: None, + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct GraphPublishResponse { + pub schema_hash: String, + pub change_summary: String, +} diff --git a/crates/rover-client/src/operations/subgraph/check/runner.rs b/crates/rover-client/src/operations/subgraph/check/runner.rs index fde0365bb..057534db4 100644 --- a/crates/rover-client/src/operations/subgraph/check/runner.rs +++ b/crates/rover-client/src/operations/subgraph/check/runner.rs @@ -1,7 +1,10 @@ use super::types::*; use crate::blocking::StudioClient; -use crate::operations::{config::is_federated, subgraph::check::types::MutationResponseData}; -use crate::shared::{CheckResponse, CompositionError, SchemaChange}; +use crate::operations::{ + config::is_federated::{self, IsFederatedInput}, + subgraph::check::types::MutationResponseData, +}; +use crate::shared::{CheckResponse, CompositionError, GraphRef, SchemaChange}; use crate::RoverClientError; use graphql_client::*; @@ -28,32 +31,31 @@ pub fn run( input: SubgraphCheckInput, client: &StudioClient, ) -> Result { - let graph = input.graph_ref.name.clone(); + let graph_ref = input.graph_ref.clone(); // This response is used to check whether or not the current graph is federated. let is_federated = is_federated::run( - is_federated::is_federated_graph::Variables { - graph_id: input.graph_ref.name.clone(), - graph_variant: input.graph_ref.variant.clone(), + IsFederatedInput { + graph_ref: graph_ref.clone(), }, &client, )?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { - graph, + graph_ref, can_operation_convert: false, }); } let variables = input.into(); let data = client.post::(variables)?; - get_check_response_from_data(data, graph) + get_check_response_from_data(data, graph_ref) } fn get_check_response_from_data( data: MutationResponseData, - graph_name: String, + graph_ref: GraphRef, ) -> Result { - let service = data.service.ok_or(RoverClientError::NoService { - graph: graph_name.clone(), + let service = data.service.ok_or(RoverClientError::GraphNotFound { + graph_ref: graph_ref.clone(), })?; // for some reason this is a `Vec>` @@ -110,7 +112,7 @@ fn get_check_response_from_data( }); } Err(RoverClientError::SubgraphCompositionErrors { - graph_name, + graph_ref, composition_errors, }) } diff --git a/crates/rover-client/src/operations/subgraph/check/types.rs b/crates/rover-client/src/operations/subgraph/check/types.rs index dd38b7f57..c8aa519ba 100644 --- a/crates/rover-client/src/operations/subgraph/check/types.rs +++ b/crates/rover-client/src/operations/subgraph/check/types.rs @@ -56,8 +56,23 @@ impl From for MutationVariables { config: MutationConfig { query_count_threshold: input.config.query_count_threshold, query_count_threshold_percentage: input.config.query_count_threshold_percentage, - from: input.config.validation_period_from, - to: input.config.validation_period_to, + from: Some( + input + .config + .validation_period + .clone() + .unwrap_or_default() + .from + .to_string(), + ), + to: Some( + input + .config + .validation_period + .unwrap_or_default() + .to + .to_string(), + ), // we don't support configuring these, but we can't leave them out excluded_clients: None, ignored_operations: None, diff --git a/crates/rover-client/src/operations/subgraph/delete/runner.rs b/crates/rover-client/src/operations/subgraph/delete/runner.rs index 4355bd228..a40e71d20 100644 --- a/crates/rover-client/src/operations/subgraph/delete/runner.rs +++ b/crates/rover-client/src/operations/subgraph/delete/runner.rs @@ -1,6 +1,6 @@ use crate::blocking::StudioClient; use crate::operations::subgraph::delete::types::*; -use crate::shared::CompositionError; +use crate::shared::{CompositionError, GraphRef}; use crate::RoverClientError; use graphql_client::*; @@ -25,19 +25,19 @@ pub fn run( input: SubgraphDeleteInput, client: &StudioClient, ) -> Result { - let graph = input.graph_ref.name.clone(); + let graph_ref = input.graph_ref.clone(); let response_data = client.post::(input.into())?; - let data = get_delete_data_from_response(response_data, graph)?; + let data = get_delete_data_from_response(response_data, graph_ref)?; Ok(build_response(data)) } fn get_delete_data_from_response( response_data: subgraph_delete_mutation::ResponseData, - graph: String, + graph_ref: GraphRef, ) -> Result { let service_data = response_data .service - .ok_or(RoverClientError::NoService { graph })?; + .ok_or(RoverClientError::GraphNotFound { graph_ref })?; Ok(service_data.remove_implementing_service_and_trigger_composition) } @@ -94,7 +94,7 @@ mod tests { }); let data: subgraph_delete_mutation::ResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_delete_data_from_response(data, "mygraph".to_string()); + let output = get_delete_data_from_response(data, mock_graph_ref()); assert!(output.is_ok()); @@ -167,4 +167,11 @@ mod tests { } ); } + + fn mock_graph_ref() -> GraphRef { + GraphRef { + name: "mygraph".to_string(), + variant: "current".to_string(), + } + } } diff --git a/crates/rover-client/src/operations/subgraph/fetch/mod.rs b/crates/rover-client/src/operations/subgraph/fetch/mod.rs index 6258a0615..65ea4d534 100644 --- a/crates/rover-client/src/operations/subgraph/fetch/mod.rs +++ b/crates/rover-client/src/operations/subgraph/fetch/mod.rs @@ -2,4 +2,4 @@ mod runner; mod types; pub use runner::run; -pub use types::{SubgraphFetchInput, SubgraphFetchResponse}; +pub use types::SubgraphFetchInput; diff --git a/crates/rover-client/src/operations/subgraph/fetch/runner.rs b/crates/rover-client/src/operations/subgraph/fetch/runner.rs index 29423c600..dd0a1e2ac 100644 --- a/crates/rover-client/src/operations/subgraph/fetch/runner.rs +++ b/crates/rover-client/src/operations/subgraph/fetch/runner.rs @@ -1,6 +1,8 @@ use super::types::*; use crate::blocking::StudioClient; +use crate::shared::{FetchResponse, GraphRef, Sdl, SdlType}; use crate::RoverClientError; + use graphql_client::*; #[derive(GraphQLQuery)] @@ -21,7 +23,7 @@ pub(crate) struct SubgraphFetchQuery; pub fn run( input: SubgraphFetchInput, client: &StudioClient, -) -> Result { +) -> Result { let input_clone = input.clone(); let response_data = client.post::(input.into())?; get_sdl_from_response_data(input_clone, response_data) @@ -30,25 +32,33 @@ pub fn run( fn get_sdl_from_response_data( input: SubgraphFetchInput, response_data: SubgraphFetchResponseData, -) -> Result { - let service_list = get_services_from_response_data(&input.graph_ref.name, response_data)?; - let sdl = get_sdl_for_service(&input.subgraph, service_list)?; - Ok(SubgraphFetchResponse { sdl }) +) -> Result { + let graph_ref = input.graph_ref.clone(); + let service_list = get_services_from_response_data(graph_ref, response_data)?; + let sdl_contents = get_sdl_for_service(&input.subgraph, service_list)?; + Ok(FetchResponse { + sdl: Sdl { + contents: sdl_contents, + r#type: SdlType::Subgraph, + }, + }) } fn get_services_from_response_data( - graph_id: &str, + graph_ref: GraphRef, response_data: SubgraphFetchResponseData, ) -> Result { - let service_data = response_data.service.ok_or(RoverClientError::NoService { - graph: graph_id.to_string(), - })?; + let service_data = response_data + .service + .ok_or(RoverClientError::GraphNotFound { + graph_ref: graph_ref.clone(), + })?; // get list of services let services = match service_data.implementing_services { Some(services) => Ok(services), None => Err(RoverClientError::ExpectedFederatedGraph { - graph: graph_id.to_string(), + graph_ref: graph_ref.clone(), can_operation_convert: false, }), }?; @@ -57,7 +67,7 @@ fn get_services_from_response_data( Services::FederatedImplementingServices(services) => Ok(services.services), Services::NonFederatedImplementingService => { Err(RoverClientError::ExpectedFederatedGraph { - graph: graph_id.to_string(), + graph_ref, can_operation_convert: false, }) } @@ -114,7 +124,7 @@ mod tests { } }); let data: SubgraphFetchResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_services_from_response_data("mygraph", data); + let output = get_services_from_response_data(mock_graph_ref(), data); let expected_json = json!([ { @@ -144,7 +154,7 @@ mod tests { } }); let data: SubgraphFetchResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_services_from_response_data("mygraph", data); + let output = get_services_from_response_data(mock_graph_ref(), data); assert!(output.is_err()); } @@ -193,4 +203,11 @@ mod tests { let output = get_sdl_for_service("harambe-was-an-inside-job", service_list); assert!(output.is_err()); } + + fn mock_graph_ref() -> GraphRef { + GraphRef { + name: "mygraph".to_string(), + variant: "current".to_string(), + } + } } diff --git a/crates/rover-client/src/operations/subgraph/fetch/types.rs b/crates/rover-client/src/operations/subgraph/fetch/types.rs index 304805e57..68f0f1a75 100644 --- a/crates/rover-client/src/operations/subgraph/fetch/types.rs +++ b/crates/rover-client/src/operations/subgraph/fetch/types.rs @@ -21,8 +21,3 @@ impl From for QueryVariables { } } } - -#[derive(Debug, Clone, PartialEq)] -pub struct SubgraphFetchResponse { - pub sdl: String, -} diff --git a/crates/rover-client/src/operations/subgraph/introspect/runner.rs b/crates/rover-client/src/operations/subgraph/introspect/runner.rs index 1a6fe7f13..9009c2942 100644 --- a/crates/rover-client/src/operations/subgraph/introspect/runner.rs +++ b/crates/rover-client/src/operations/subgraph/introspect/runner.rs @@ -7,7 +7,7 @@ use graphql_client::*; #[derive(GraphQLQuery)] #[graphql( query_path = "src/operations/subgraph/introspect/introspect_query.graphql", - schema_path = "src/operations//subgraph/introspect/introspect_schema.graphql", + schema_path = "src/operations/subgraph/introspect/introspect_schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] diff --git a/crates/rover-client/src/operations/subgraph/list/runner.rs b/crates/rover-client/src/operations/subgraph/list/runner.rs index 1c1faed5e..b5562d463 100644 --- a/crates/rover-client/src/operations/subgraph/list/runner.rs +++ b/crates/rover-client/src/operations/subgraph/list/runner.rs @@ -1,5 +1,6 @@ use crate::blocking::StudioClient; use crate::operations::subgraph::list::types::*; +use crate::shared::GraphRef; use crate::RoverClientError; use graphql_client::*; @@ -24,24 +25,26 @@ pub fn run( input: SubgraphListInput, client: &StudioClient, ) -> Result { - let graph = input.graph_ref.name.clone(); + let graph_ref = input.graph_ref.clone(); let response_data = client.post::(input.into())?; let root_url = response_data.frontend_url_root.clone(); - let subgraphs = get_subgraphs_from_response_data(response_data, graph.clone())?; + let subgraphs = get_subgraphs_from_response_data(response_data, graph_ref.clone())?; Ok(SubgraphListResponse { subgraphs: format_subgraphs(&subgraphs), root_url, - graph_name: graph, + graph_ref, }) } fn get_subgraphs_from_response_data( response_data: QueryResponseData, - graph: String, + graph_ref: GraphRef, ) -> Result, RoverClientError> { - let service_data = response_data.service.ok_or(RoverClientError::NoService { - graph: graph.clone(), - })?; + let service_data = response_data + .service + .ok_or(RoverClientError::GraphNotFound { + graph_ref: graph_ref.clone(), + })?; // get list of services let services = match service_data.implementing_services { @@ -60,7 +63,7 @@ fn get_subgraphs_from_response_data( QueryGraphType::FederatedImplementingServices(services) => Ok(services.services), QueryGraphType::NonFederatedImplementingService => { Err(RoverClientError::ExpectedFederatedGraph { - graph, + graph_ref, can_operation_convert: false, }) } @@ -115,7 +118,7 @@ mod tests { } }); let data: QueryResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_subgraphs_from_response_data(data, "mygraph".to_string()); + let output = get_subgraphs_from_response_data(data, mock_graph_ref()); let expected_json = json!([ { @@ -145,7 +148,7 @@ mod tests { } }); let data: QueryResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_subgraphs_from_response_data(data, "mygraph".to_string()); + let output = get_subgraphs_from_response_data(data, mock_graph_ref()); assert!(output.is_err()); } @@ -174,4 +177,11 @@ mod tests { assert_eq!(formatted[0].name, "accounts".to_string()); assert_eq!(formatted[2].name, "shipping".to_string()); } + + fn mock_graph_ref() -> GraphRef { + GraphRef { + name: "mygraph".to_string(), + variant: "current".to_string(), + } + } } diff --git a/crates/rover-client/src/operations/subgraph/list/types.rs b/crates/rover-client/src/operations/subgraph/list/types.rs index 00b9da8b6..af260a33d 100644 --- a/crates/rover-client/src/operations/subgraph/list/types.rs +++ b/crates/rover-client/src/operations/subgraph/list/types.rs @@ -26,7 +26,7 @@ impl From for QueryVariables { pub struct SubgraphListResponse { pub subgraphs: Vec, pub root_url: String, - pub graph_name: String, + pub graph_ref: GraphRef, } #[derive(Clone, PartialEq, Debug)] diff --git a/crates/rover-client/src/operations/subgraph/publish/runner.rs b/crates/rover-client/src/operations/subgraph/publish/runner.rs index e1ed36987..d7886911e 100644 --- a/crates/rover-client/src/operations/subgraph/publish/runner.rs +++ b/crates/rover-client/src/operations/subgraph/publish/runner.rs @@ -1,7 +1,7 @@ use super::types::*; use crate::blocking::StudioClient; -use crate::operations::config::is_federated; -use crate::shared::CompositionError; +use crate::operations::config::is_federated::{self, IsFederatedInput}; +use crate::shared::{CompositionError, GraphRef}; use crate::RoverClientError; use graphql_client::*; @@ -23,37 +23,38 @@ pub fn run( input: SubgraphPublishInput, client: &StudioClient, ) -> Result { + let graph_ref = input.graph_ref.clone(); let variables: MutationVariables = input.clone().into(); - let graph = input.graph_ref.name.clone(); // We don't want to implicitly convert non-federated graph to supergraphs. // Error here if no --convert flag is passed _and_ the current context // is non-federated. Add a suggestion to require a --convert flag. if !input.convert_to_federated_graph { let is_federated = is_federated::run( - is_federated::is_federated_graph::Variables { - graph_id: input.graph_ref.name.clone(), - graph_variant: input.graph_ref.variant, + IsFederatedInput { + graph_ref: graph_ref.clone(), }, &client, )?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { - graph, + graph_ref, can_operation_convert: true, }); } } let data = client.post::(variables)?; - let publish_response = get_publish_response_from_data(data, graph)?; + let publish_response = get_publish_response_from_data(data, graph_ref)?; Ok(build_response(publish_response)) } fn get_publish_response_from_data( data: ResponseData, - graph: String, + graph_ref: GraphRef, ) -> Result { - let service_data = data.service.ok_or(RoverClientError::NoService { graph })?; + let service_data = data + .service + .ok_or(RoverClientError::GraphNotFound { graph_ref })?; Ok(service_data.upsert_implementing_service_and_trigger_composition) } diff --git a/crates/rover-client/src/operations/supergraph/fetch.graphql b/crates/rover-client/src/operations/supergraph/fetch/fetch_query.graphql similarity index 78% rename from crates/rover-client/src/operations/supergraph/fetch.graphql rename to crates/rover-client/src/operations/supergraph/fetch/fetch_query.graphql index 5436b091a..1da899e89 100644 --- a/crates/rover-client/src/operations/supergraph/fetch.graphql +++ b/crates/rover-client/src/operations/supergraph/fetch/fetch_query.graphql @@ -1,4 +1,4 @@ -query FetchSupergraphQuery($graphId: ID!, $variant: String!) { +query SupergraphFetchQuery($graph_id: ID!, $variant: String!) { frontendUrlRoot service(id: $graphId) { variants { @@ -13,7 +13,8 @@ query FetchSupergraphQuery($graphId: ID!, $variant: String!) { mostRecentCompositionPublish(graphVariant: $variant) { errors { message + code } } } -} \ No newline at end of file +} diff --git a/crates/rover-client/src/operations/supergraph/fetch/mod.rs b/crates/rover-client/src/operations/supergraph/fetch/mod.rs new file mode 100644 index 000000000..d97562469 --- /dev/null +++ b/crates/rover-client/src/operations/supergraph/fetch/mod.rs @@ -0,0 +1,5 @@ +mod runner; +mod types; + +pub use runner::run; +pub use types::SupergraphFetchInput; diff --git a/crates/rover-client/src/operations/supergraph/fetch.rs b/crates/rover-client/src/operations/supergraph/fetch/runner.rs similarity index 68% rename from crates/rover-client/src/operations/supergraph/fetch.rs rename to crates/rover-client/src/operations/supergraph/fetch/runner.rs index 5e7a05b5d..e0fc3d980 100644 --- a/crates/rover-client/src/operations/supergraph/fetch.rs +++ b/crates/rover-client/src/operations/supergraph/fetch/runner.rs @@ -1,5 +1,8 @@ use crate::blocking::StudioClient; +use crate::operations::supergraph::fetch::SupergraphFetchInput; +use crate::shared::{CompositionError, FetchResponse, GraphRef, Sdl, SdlType}; use crate::RoverClientError; + use graphql_client::*; // I'm not sure where this should live long-term @@ -10,41 +13,46 @@ type GraphQLDocument = String; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/operations/supergraph/fetch.graphql", + query_path = "src/operations/supergraph/fetch/fetch_query.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. fetch_supergraph_query -pub struct FetchSupergraphQuery; +/// Snake case of this name is the mod name. i.e. supergraph_fetch_query +pub(crate) struct SupergraphFetchQuery; /// The main function to be used from this module. This function fetches a /// core schema from apollo studio pub fn run( - variables: fetch_supergraph_query::Variables, + input: SupergraphFetchInput, client: &StudioClient, -) -> Result { - let graph = variables.graph_id.clone(); - let variant = variables.variant.clone(); - let response_data = client.post::(variables)?; - get_supergraph_sdl_from_response_data(response_data, graph, variant) +) -> Result { + let graph_ref = input.graph_ref.clone(); + let response_data = client.post::(input.into())?; + get_supergraph_sdl_from_response_data(response_data, graph_ref) } fn get_supergraph_sdl_from_response_data( - response_data: fetch_supergraph_query::ResponseData, - graph: String, - variant: String, -) -> Result { - let service_data = response_data.service.ok_or(RoverClientError::NoService { - graph: graph.clone(), - })?; + response_data: supergraph_fetch_query::ResponseData, + graph_ref: GraphRef, +) -> Result { + let service_data = response_data + .service + .ok_or(RoverClientError::GraphNotFound { + graph_ref: graph_ref.clone(), + })?; if let Some(schema_tag) = service_data.schema_tag { if let Some(composition_result) = schema_tag.composition_result { - if let Some(supergraph_sdl) = composition_result.supergraph_sdl { - Ok(supergraph_sdl) + if let Some(sdl_contents) = composition_result.supergraph_sdl { + Ok(FetchResponse { + sdl: Sdl { + contents: sdl_contents, + r#type: SdlType::Supergraph, + }, + }) } else { Err(RoverClientError::MalformedResponse { null_field: "supergraphSdl".to_string(), @@ -52,7 +60,7 @@ fn get_supergraph_sdl_from_response_data( } } else { Err(RoverClientError::ExpectedFederatedGraph { - graph, + graph_ref, can_operation_convert: false, }) } @@ -62,10 +70,13 @@ fn get_supergraph_sdl_from_response_data( let composition_errors = most_recent_composition_publish .errors .into_iter() - .map(|error| error.message) + .map(|error| CompositionError { + message: error.message, + code: error.code, + }) .collect(); Err(RoverClientError::NoCompositionPublishes { - graph, + graph_ref, composition_errors, }) } else { @@ -75,16 +86,15 @@ fn get_supergraph_sdl_from_response_data( valid_variants.push(variant.name) } - if !valid_variants.contains(&variant) { + if !valid_variants.contains(&graph_ref.variant) { Err(RoverClientError::NoSchemaForVariant { - graph, - invalid_variant: variant, + graph_ref, valid_variants, frontend_url_root: response_data.frontend_url_root, }) } else { Err(RoverClientError::ExpectedFederatedGraph { - graph, + graph_ref, can_operation_convert: false, }) } @@ -112,58 +122,74 @@ mod tests { } }, }); - let data: fetch_supergraph_query::ResponseData = + let data: supergraph_fetch_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let (graph, invalid_variant) = mock_vars(); - let output = get_supergraph_sdl_from_response_data(data, graph, invalid_variant); + let graph_ref = mock_graph_ref(); + let output = get_supergraph_sdl_from_response_data(data, graph_ref); assert!(output.is_ok()); - assert_eq!(output.unwrap(), "type Query { hello: String }".to_string()); + assert_eq!( + output.unwrap(), + FetchResponse { + sdl: Sdl { + contents: "type Query { hello: String }".to_string(), + r#type: SdlType::Supergraph + } + } + ); } #[test] fn get_schema_from_response_data_errs_on_no_service() { let json_response = json!({ "service": null, "frontendUrlRoot": "https://studio.apollographql.com" }); - let data: fetch_supergraph_query::ResponseData = + let data: supergraph_fetch_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let (graph, invalid_variant) = mock_vars(); - let output = get_supergraph_sdl_from_response_data(data, graph.clone(), invalid_variant); - let expected_error = RoverClientError::NoService { graph }.to_string(); + let graph_ref = mock_graph_ref(); + let output = get_supergraph_sdl_from_response_data(data, graph_ref.clone()); + let expected_error = RoverClientError::GraphNotFound { graph_ref }.to_string(); let actual_error = output.unwrap_err().to_string(); assert_eq!(actual_error, expected_error); } #[test] fn get_schema_from_response_data_errs_on_no_schema_tag() { - let (graph, variant) = mock_vars(); let composition_errors = vec![ - "Unknown type \"Unicorn\".".to_string(), - "Type Query must define one or more fields.".to_string(), + CompositionError { + message: "Unknown type \"Unicorn\".".to_string(), + code: Some("UNKNOWN_TYPE".to_string()), + }, + CompositionError { + message: "Type Query must define one or more fields.".to_string(), + code: None, + }, ]; let composition_errors_json = json!([ { - "message": composition_errors[0] + "message": composition_errors[0].message, + "code": composition_errors[0].code }, { - "message": composition_errors[1] + "message": composition_errors[1].message, + "code": composition_errors[1].code } ]); + let graph_ref = mock_graph_ref(); let json_response = json!({ "frontendUrlRoot": "https://studio.apollographql.com/", "service": { "schemaTag": null, - "variants": [{"name": variant}], + "variants": [{"name": &graph_ref.variant}], "mostRecentCompositionPublish": { - "errors": composition_errors_json + "errors": composition_errors_json, } }, }); - let data: fetch_supergraph_query::ResponseData = + let data: supergraph_fetch_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_supergraph_sdl_from_response_data(data, graph.clone(), variant); + let output = get_supergraph_sdl_from_response_data(data, graph_ref.clone()); let expected_error = RoverClientError::NoCompositionPublishes { - graph, + graph_ref, composition_errors, } .to_string(); @@ -173,7 +199,6 @@ mod tests { #[test] fn get_schema_from_response_data_errs_on_invalid_variant() { - let (graph, variant) = mock_vars(); let valid_variant = "cccuuurrreeennnttt".to_string(); let frontend_url_root = "https://studio.apollographql.com".to_string(); let json_response = json!({ @@ -184,12 +209,12 @@ mod tests { "mostRecentCompositionPublish": null }, }); - let data: fetch_supergraph_query::ResponseData = + let data: supergraph_fetch_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_supergraph_sdl_from_response_data(data, graph.clone(), variant.clone()); + let graph_ref = mock_graph_ref(); + let output = get_supergraph_sdl_from_response_data(data, graph_ref.clone()); let expected_error = RoverClientError::NoSchemaForVariant { - graph, - invalid_variant: variant, + graph_ref, valid_variants: vec![valid_variant], frontend_url_root, } @@ -200,7 +225,6 @@ mod tests { #[test] fn get_schema_from_response_data_errs_on_no_composition_result() { - let (graph, variant) = mock_vars(); let valid_variant = "current".to_string(); let frontend_url_root = "https://studio.apollographql.com".to_string(); let json_response = json!({ @@ -213,11 +237,12 @@ mod tests { "mostRecentCompositionResult": null }, }); - let data: fetch_supergraph_query::ResponseData = + let data: supergraph_fetch_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_supergraph_sdl_from_response_data(data, graph.clone(), variant.clone()); + let graph_ref = mock_graph_ref(); + let output = get_supergraph_sdl_from_response_data(data, graph_ref.clone()); let expected_error = RoverClientError::ExpectedFederatedGraph { - graph, + graph_ref, can_operation_convert: false, } .to_string(); @@ -227,7 +252,6 @@ mod tests { #[test] fn get_schema_from_response_data_errs_on_no_supergraph_sdl() { - let (graph, variant) = mock_vars(); let valid_variant = "current".to_string(); let frontend_url_root = "https://studio.apollographql.com".to_string(); let json_response = json!({ @@ -243,9 +267,10 @@ mod tests { "mostRecentCompositionPublish": null }, }); - let data: fetch_supergraph_query::ResponseData = + let data: supergraph_fetch_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_supergraph_sdl_from_response_data(data, graph.clone(), variant.clone()); + let graph_ref = mock_graph_ref(); + let output = get_supergraph_sdl_from_response_data(data, graph_ref.clone()); let expected_error = RoverClientError::MalformedResponse { null_field: "supergraphSdl".to_string(), } @@ -254,7 +279,10 @@ mod tests { assert_eq!(actual_error, expected_error); } - fn mock_vars() -> (String, String) { - ("mygraph".to_string(), "current".to_string()) + fn mock_graph_ref() -> GraphRef { + GraphRef { + name: "mygraph".to_string(), + variant: "current".to_string(), + } } } diff --git a/crates/rover-client/src/operations/supergraph/fetch/types.rs b/crates/rover-client/src/operations/supergraph/fetch/types.rs new file mode 100644 index 000000000..1bcfa8193 --- /dev/null +++ b/crates/rover-client/src/operations/supergraph/fetch/types.rs @@ -0,0 +1,18 @@ +use crate::operations::supergraph::fetch::runner::supergraph_fetch_query; +use crate::shared::GraphRef; + +type QueryVariables = supergraph_fetch_query::Variables; + +#[derive(Debug, Clone, PartialEq)] +pub struct SupergraphFetchInput { + pub graph_ref: GraphRef, +} + +impl From for QueryVariables { + fn from(input: SupergraphFetchInput) -> Self { + Self { + graph_id: input.graph_ref.name, + variant: input.graph_ref.variant, + } + } +} diff --git a/crates/rover-client/src/shared/check_response.rs b/crates/rover-client/src/shared/check_response.rs index 48dba4159..f2675835d 100644 --- a/crates/rover-client/src/shared/check_response.rs +++ b/crates/rover-client/src/shared/check_response.rs @@ -1,8 +1,11 @@ use std::cmp::Ordering; use std::fmt; +use std::str::FromStr; use crate::RoverClientError; +use serde::Serialize; + /// CheckResponse is the return type of the /// `graph` and `subgraph` check operations #[derive(Debug, Clone, PartialEq)] @@ -99,6 +102,37 @@ pub struct SchemaChange { pub struct CheckConfig { pub query_count_threshold: Option, pub query_count_threshold_percentage: Option, - pub validation_period_from: Option, - pub validation_period_to: Option, + pub validation_period: Option, +} + +#[derive(Debug, PartialEq, Eq, Serialize, Default, Clone)] +pub struct ValidationPeriod { + pub from: i64, + pub to: i64, +} + +// Validation period is parsed as human readable time. +// such as "10m 50s" +impl FromStr for ValidationPeriod { + type Err = RoverClientError; + fn from_str(period: &str) -> Result { + // attempt to parse strings like + // 15h 10m 2s into number of seconds + if period.contains("ns") || period.contains("us") || period.contains("ms") { + return Err(RoverClientError::ValidationPeriodTooGranular); + }; + let duration = humantime::parse_duration(period)?; + + let from = duration.as_secs() as i64; + let from = -from; + + let to = 0; + + Ok(ValidationPeriod { + // search "from" a negative time window + from: -from, + // search "to" now (-0) seconds + to: -to, + }) + } } diff --git a/crates/rover-client/src/shared/fetch_response.rs b/crates/rover-client/src/shared/fetch_response.rs new file mode 100644 index 000000000..5f4ab4381 --- /dev/null +++ b/crates/rover-client/src/shared/fetch_response.rs @@ -0,0 +1,17 @@ +#[derive(Debug, Clone, PartialEq)] +pub struct FetchResponse { + pub sdl: Sdl, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Sdl { + pub contents: String, + pub r#type: SdlType, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum SdlType { + Graph, + Subgraph, + Supergraph, +} diff --git a/crates/rover-client/src/shared/git_context.rs b/crates/rover-client/src/shared/git_context.rs index 37fad6ba3..6d065d800 100644 --- a/crates/rover-client/src/shared/git_context.rs +++ b/crates/rover-client/src/shared/git_context.rs @@ -1,5 +1,3 @@ -use crate::operations::graph; - use std::env; use git2::{Reference, Repository}; @@ -137,19 +135,6 @@ impl GitContext { } } -type GraphPublishContextInput = graph::publish::publish_schema_mutation::GitContextInput; -impl From for GraphPublishContextInput { - fn from(git_context: GitContext) -> GraphPublishContextInput { - GraphPublishContextInput { - branch: git_context.branch, - commit: git_context.commit, - committer: git_context.author, - remote_url: git_context.remote_url, - message: None, - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/rover-client/src/shared/mod.rs b/crates/rover-client/src/shared/mod.rs index 9a2f6f445..31e0f3e97 100644 --- a/crates/rover-client/src/shared/mod.rs +++ b/crates/rover-client/src/shared/mod.rs @@ -1,9 +1,13 @@ mod check_response; mod composition_error; +mod fetch_response; mod git_context; mod graph_ref; -pub use check_response::{ChangeSeverity, CheckConfig, CheckResponse, SchemaChange}; +pub use check_response::{ + ChangeSeverity, CheckConfig, CheckResponse, SchemaChange, ValidationPeriod, +}; pub use composition_error::CompositionError; +pub use fetch_response::{FetchResponse, Sdl, SdlType}; pub use git_context::GitContext; pub use graph_ref::GraphRef; diff --git a/src/command/graph/check.rs b/src/command/graph/check.rs index 2792be9a6..97035155b 100644 --- a/src/command/graph/check.rs +++ b/src/command/graph/check.rs @@ -2,14 +2,14 @@ use serde::Serialize; use structopt::StructOpt; use rover_client::operations::graph::check::{self, GraphCheckInput}; -use rover_client::shared::{CheckConfig, GitContext, GraphRef}; +use rover_client::shared::{CheckConfig, GitContext, GraphRef, ValidationPeriod}; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; use crate::utils::loaders::load_schema_from_flag; use crate::utils::parsers::{ parse_query_count_threshold, parse_query_percentage_threshold, parse_schema_source, - parse_validation_period, SchemaSource, ValidationPeriod, + SchemaSource, }; use crate::Result; @@ -44,7 +44,7 @@ pub struct Check { query_percentage_threshold: Option, /// Size of the time window with which to validate schema against (i.e "24h" or "1w 2d 5h") - #[structopt(long, parse(try_from_str = parse_validation_period))] + #[structopt(long)] validation_period: Option, } @@ -70,8 +70,7 @@ impl Check { config: CheckConfig { query_count_threshold: self.query_count_threshold, query_count_threshold_percentage: self.query_percentage_threshold, - validation_period_from: self.validation_period.clone().unwrap_or_default().from, - validation_period_to: self.validation_period.clone().unwrap_or_default().to, + validation_period: self.validation_period.clone(), }, }, &client, diff --git a/src/command/graph/fetch.rs b/src/command/graph/fetch.rs index 173aa4683..2e01fc0f8 100644 --- a/src/command/graph/fetch.rs +++ b/src/command/graph/fetch.rs @@ -2,7 +2,7 @@ use ansi_term::Colour::{Cyan, Yellow}; use serde::Serialize; use structopt::StructOpt; -use rover_client::operations::graph::fetch; +use rover_client::operations::graph::fetch::{self, GraphFetchInput}; use rover_client::shared::GraphRef; use crate::command::RoverStdout; @@ -33,15 +33,13 @@ impl Fetch { Yellow.normal().paint(&self.profile_name) ); - let sdl = fetch::run( - fetch::fetch_schema_query::Variables { - graph_id: self.graph.name.clone(), - hash: None, - variant: Some(self.graph.variant.clone()), + let fetch_response = fetch::run( + GraphFetchInput { + graph_ref: self.graph.clone(), }, &client, )?; - Ok(RoverStdout::Sdl(sdl)) + Ok(RoverStdout::FetchResponse(fetch_response)) } } diff --git a/src/command/graph/publish.rs b/src/command/graph/publish.rs index df7d3514d..d005b993c 100644 --- a/src/command/graph/publish.rs +++ b/src/command/graph/publish.rs @@ -2,7 +2,7 @@ use ansi_term::Colour::{Cyan, Yellow}; use serde::Serialize; use structopt::StructOpt; -use rover_client::operations::graph::publish; +use rover_client::operations::graph::publish::{self, GraphPublishInput, GraphPublishResponse}; use rover_client::shared::{GitContext, GraphRef}; use crate::command::RoverStdout; @@ -45,16 +45,15 @@ impl Publish { Yellow.normal().paint(&self.profile_name) ); - let schema_document = load_schema_from_flag(&self.schema, std::io::stdin())?; + let proposed_schema = load_schema_from_flag(&self.schema, std::io::stdin())?; - tracing::debug!("Publishing \n{}", &schema_document); + tracing::debug!("Publishing \n{}", &proposed_schema); let publish_response = publish::run( - publish::publish_schema_mutation::Variables { - graph_id: self.graph.name.clone(), - variant: self.graph.variant.clone(), - schema_document: Some(schema_document), - git_context: git_context.into(), + GraphPublishInput { + graph_ref: self.graph.clone(), + proposed_schema, + git_context, }, &client, )?; @@ -65,7 +64,7 @@ impl Publish { } /// handle all output logging from operation -fn handle_response(graph: &GraphRef, response: publish::PublishResponse) -> String { +fn handle_response(graph: &GraphRef, response: GraphPublishResponse) -> String { eprintln!( "{}#{} Published successfully {}", graph, response.schema_hash, response.change_summary @@ -76,7 +75,7 @@ fn handle_response(graph: &GraphRef, response: publish::PublishResponse) -> Stri #[cfg(test)] mod tests { - use super::{handle_response, publish, GraphRef}; + use super::*; #[test] fn handle_response_doesnt_err() { @@ -87,7 +86,7 @@ mod tests { }; let actual_hash = handle_response( &graph, - publish::PublishResponse { + GraphPublishResponse { schema_hash: expected_hash.clone(), change_summary: "".to_string(), }, diff --git a/src/command/output.rs b/src/command/output.rs index 685876718..141495f8e 100644 --- a/src/command/output.rs +++ b/src/command/output.rs @@ -7,7 +7,7 @@ use ansi_term::{Colour::Yellow, Style}; use atty::Stream; use crossterm::style::Attribute::Underlined; use rover_client::operations::subgraph::list::SubgraphListResponse; -use rover_client::shared::CheckResponse; +use rover_client::shared::{CheckResponse, FetchResponse, SdlType}; use termimad::MadSkin; /// RoverStdout defines all of the different types of data that are printed @@ -21,8 +21,7 @@ use termimad::MadSkin; #[derive(Clone, PartialEq, Debug)] pub enum RoverStdout { DocsList(HashMap<&'static str, &'static str>), - SupergraphSdl(String), - Sdl(String), + FetchResponse(FetchResponse), CoreSchema(String), SchemaHash(String), SubgraphList(SubgraphListResponse), @@ -52,13 +51,12 @@ impl RoverStdout { } println!("{}", table); } - RoverStdout::SupergraphSdl(sdl) => { - print_descriptor("Supergraph SDL"); - print_content(&sdl); - } - RoverStdout::Sdl(sdl) => { - print_descriptor("SDL"); - print_content(&sdl); + RoverStdout::FetchResponse(fetch_response) => { + match fetch_response.sdl.r#type { + SdlType::Graph | SdlType::Subgraph => print_descriptor("SDL"), + SdlType::Supergraph => print_descriptor("Supergraph SDL"), + } + print_content(&fetch_response.sdl.contents); } RoverStdout::CoreSchema(csdl) => { print_descriptor("CoreSchema"); @@ -97,7 +95,7 @@ impl RoverStdout { println!("{}", table); println!( "View full details at {}/graph/{}/service-list", - details.root_url, details.graph_name + details.root_url, details.graph_ref.name ); } RoverStdout::CheckResponse(check_response) => { @@ -168,8 +166,7 @@ pub(crate) fn print_check_response(check_response: &CheckResponse) { 0 => "There were no changes detected in the composed schema.".to_string(), _ => format!( "Compared {} schema changes against {} operations", - check_response.changes.len(), - check_response.number_of_checked_operations + num_changes, check_response.number_of_checked_operations ), }; diff --git a/src/command/subgraph/check.rs b/src/command/subgraph/check.rs index dc8c2fabf..67201bee4 100644 --- a/src/command/subgraph/check.rs +++ b/src/command/subgraph/check.rs @@ -2,14 +2,14 @@ use serde::Serialize; use structopt::StructOpt; use rover_client::operations::subgraph::check::{self, SubgraphCheckInput}; -use rover_client::shared::{CheckConfig, GitContext, GraphRef}; +use rover_client::shared::{CheckConfig, GitContext, GraphRef, ValidationPeriod}; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; use crate::utils::loaders::load_schema_from_flag; use crate::utils::parsers::{ parse_query_count_threshold, parse_query_percentage_threshold, parse_schema_source, - parse_validation_period, SchemaSource, ValidationPeriod, + SchemaSource, }; use crate::Result; @@ -49,7 +49,7 @@ pub struct Check { query_percentage_threshold: Option, /// Size of the time window with which to validate schema against (i.e "24h" or "1w 2d 5h") - #[structopt(long, parse(try_from_str = parse_validation_period))] + #[structopt(long)] validation_period: Option, } @@ -77,8 +77,7 @@ impl Check { config: CheckConfig { query_count_threshold: self.query_count_threshold, query_count_threshold_percentage: self.query_percentage_threshold, - validation_period_from: self.validation_period.clone().unwrap_or_default().from, - validation_period_to: self.validation_period.clone().unwrap_or_default().to, + validation_period: self.validation_period.clone(), }, }, &client, diff --git a/src/command/subgraph/fetch.rs b/src/command/subgraph/fetch.rs index 7ddf46857..ba7f35bc7 100644 --- a/src/command/subgraph/fetch.rs +++ b/src/command/subgraph/fetch.rs @@ -39,7 +39,7 @@ impl Fetch { Yellow.normal().paint(&self.profile_name) ); - let result = fetch::run( + let fetch_response = fetch::run( SubgraphFetchInput { graph_ref: self.graph.clone(), subgraph: self.subgraph.clone(), @@ -47,6 +47,6 @@ impl Fetch { &client, )?; - Ok(RoverStdout::Sdl(result.sdl)) + Ok(RoverStdout::FetchResponse(fetch_response)) } } diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index bd2e3609f..63086383c 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -150,7 +150,8 @@ pub(crate) fn get_subgraph_definitions( // and use that when no routing_url is provided. let url = &subgraph_data.routing_url.clone().unwrap_or_default(); - let subgraph_definition = SubgraphDefinition::new(subgraph_name, url, &result.sdl); + let subgraph_definition = + SubgraphDefinition::new(subgraph_name, url, &result.sdl.contents); subgraphs.push(subgraph_definition); } } diff --git a/src/command/supergraph/fetch.rs b/src/command/supergraph/fetch.rs index 5c4487edc..878b92bd5 100644 --- a/src/command/supergraph/fetch.rs +++ b/src/command/supergraph/fetch.rs @@ -1,7 +1,7 @@ use crate::utils::client::StudioClientConfig; use crate::{command::RoverStdout, Result}; -use rover_client::operations::supergraph::fetch; +use rover_client::operations::supergraph::fetch::{self, SupergraphFetchInput}; use rover_client::shared::GraphRef; use ansi_term::Colour::{Cyan, Yellow}; @@ -32,14 +32,13 @@ impl Fetch { Yellow.normal().paint(&self.profile_name) ); - let supergraph_sdl = fetch::run( - fetch::fetch_supergraph_query::Variables { - graph_id: self.graph.name.clone(), - variant: self.graph.variant.clone(), + let fetch_response = fetch::run( + SupergraphFetchInput { + graph_ref: self.graph.clone(), }, &client, )?; - Ok(RoverStdout::SupergraphSdl(supergraph_sdl)) + Ok(RoverStdout::FetchResponse(fetch_response)) } } diff --git a/src/error/metadata/mod.rs b/src/error/metadata/mod.rs index b2f33d9d4..b542f1f89 100644 --- a/src/error/metadata/mod.rs +++ b/src/error/metadata/mod.rs @@ -59,7 +59,7 @@ impl From<&mut anyhow::Error> for Metadata { (Some(Suggestion::SubmitIssue), Some(Code::E006)) } RoverClientError::SubgraphCompositionErrors { - graph_name, + graph_ref, composition_errors, } => { for composition_error in composition_errors { @@ -68,7 +68,7 @@ impl From<&mut anyhow::Error> for Metadata { } ( Some(Suggestion::FixSubgraphSchema { - graph_name: graph_name.clone(), + graph_ref: graph_ref.clone(), }), Some(Code::E029), ) @@ -86,7 +86,7 @@ impl From<&mut anyhow::Error> for Metadata { (Some(Suggestion::UseFederatedGraph), Some(Code::E007)) } RoverClientError::ExpectedFederatedGraph { - graph: _, + graph_ref: _, can_operation_convert, } => { if *can_operation_convert { @@ -96,14 +96,12 @@ impl From<&mut anyhow::Error> for Metadata { } } RoverClientError::NoSchemaForVariant { - graph, - invalid_variant, + graph_ref, valid_variants, frontend_url_root, } => ( Some(Suggestion::ProvideValidVariant { - graph_name: graph.clone(), - invalid_variant: invalid_variant.clone(), + graph_ref: graph_ref.clone(), valid_variants: valid_variants.clone(), frontend_url_root: frontend_url_root.clone(), }), @@ -116,12 +114,12 @@ impl From<&mut anyhow::Error> for Metadata { Some(Suggestion::ProvideValidSubgraph(valid_subgraphs.clone())), Some(Code::E009), ), - RoverClientError::NoService { graph: _ } => { + RoverClientError::GraphNotFound { .. } => { (Some(Suggestion::CheckGraphNameAndAuth), Some(Code::E010)) } - RoverClientError::GraphQl { msg: _ } => (None, None), - RoverClientError::IntrospectionError { msg: _ } => (None, Some(Code::E011)), - RoverClientError::ClientError { msg: _ } => (None, Some(Code::E012)), + RoverClientError::GraphQl { .. } => (None, None), + RoverClientError::IntrospectionError { .. } => (None, Some(Code::E011)), + RoverClientError::ClientError { .. } => (None, Some(Code::E012)), RoverClientError::InvalidKey => (Some(Suggestion::CheckKey), Some(Code::E013)), RoverClientError::MalformedKey => (Some(Suggestion::ProperKey), Some(Code::E014)), RoverClientError::UnparseableReleaseVersion { source: _ } => { @@ -129,7 +127,7 @@ impl From<&mut anyhow::Error> for Metadata { } RoverClientError::BadReleaseUrl => (Some(Suggestion::SubmitIssue), None), RoverClientError::NoCompositionPublishes { - graph: _, + graph_ref: _, composition_errors, } => { for composition_error in composition_errors { @@ -137,13 +135,17 @@ impl From<&mut anyhow::Error> for Metadata { } (Some(Suggestion::RunComposition), Some(Code::E027)) } - RoverClientError::AdhocError { msg: _ } => (None, None), + RoverClientError::AdhocError { .. } => (None, None), RoverClientError::CouldNotConnect { .. } => { (Some(Suggestion::CheckServerConnection), Some(Code::E028)) } RoverClientError::InvalidGraphRef { .. } => { unreachable!("Graph ref parse errors should be caught via structopt") } + RoverClientError::InvalidValidationPeriodDuration(_) + | RoverClientError::ValidationPeriodTooGranular => { + unreachable!("Validation period parse errors should be caught via structopt") + } }; return Metadata { suggestion, diff --git a/src/error/metadata/suggestion.rs b/src/error/metadata/suggestion.rs index a1d25f949..64cc73186 100644 --- a/src/error/metadata/suggestion.rs +++ b/src/error/metadata/suggestion.rs @@ -2,6 +2,7 @@ use std::cmp::Ordering; use std::fmt::{self, Display}; use ansi_term::Colour::{Cyan, Yellow}; +use rover_client::shared::GraphRef; use crate::utils::env::RoverEnvKey; @@ -18,8 +19,7 @@ pub enum Suggestion { CheckGraphNameAndAuth, ProvideValidSubgraph(Vec), ProvideValidVariant { - graph_name: String, - invalid_variant: String, + graph_ref: GraphRef, valid_variants: Vec, frontend_url_root: String, }, @@ -33,10 +33,10 @@ pub enum Suggestion { ConvertGraphToSubgraph, CheckGnuVersion, FixSubgraphSchema { - graph_name: String, + graph_ref: GraphRef, }, FixOperationsInSchema { - graph_name: String, + graph_ref: GraphRef, }, } @@ -85,15 +85,15 @@ impl Display for Suggestion { valid_subgraphs.join(", ") ) } - Suggestion::ProvideValidVariant { graph_name, invalid_variant, valid_variants, frontend_url_root} => { - if let Some(maybe_variant) = did_you_mean(invalid_variant, valid_variants).pop() { - format!("Did you mean \"{}@{}\"?", graph_name, maybe_variant) + Suggestion::ProvideValidVariant { graph_ref, valid_variants, frontend_url_root} => { + if let Some(maybe_variant) = did_you_mean(&graph_ref.variant, valid_variants).pop() { + format!("Did you mean \"{}@{}\"?", graph_ref.name, maybe_variant) } else { let num_valid_variants = valid_variants.len(); match num_valid_variants { - 0 => unreachable!(&format!("Graph \"{}\" exists but has no variants.", graph_name)), - 1 => format!("The only existing variant for graph \"{}\" is \"{}\".", graph_name, valid_variants[0]), - 2 => format!("The existing variants for graph \"{}\" are \"{}\" and \"{}\".", graph_name, valid_variants[0], valid_variants[1]), + 0 => unreachable!(&format!("Graph \"{}\" exists but has no variants.", graph_ref.name)), + 1 => format!("The only existing variant for graph \"{}\" is \"{}\".", graph_ref.name, valid_variants[0]), + 2 => format!("The existing variants for graph \"{}\" are \"{}\" and \"{}\".", graph_ref.name, valid_variants[0], valid_variants[1]), 3 ..= 10 => { let mut valid_variants_msg = "".to_string(); for (i, variant) in valid_variants.iter().enumerate() { @@ -105,11 +105,11 @@ impl Display for Suggestion { valid_variants_msg.push_str(", "); } } - format!("The existing variants for graph \"{}\" are {}.", graph_name, &valid_variants_msg) + format!("The existing variants for graph \"{}\" are {}.", &graph_ref.name, &valid_variants_msg) } _ => { - let graph_url = format!("{}/graph/{}/settings", &frontend_url_root, &graph_name); - format!("You can view the variants for graph \"{}\" by visiting {}", graph_name, Cyan.normal().paint(&graph_url)) + let graph_url = format!("{}/graph/{}/settings", &frontend_url_root, &graph_ref.name); + format!("You can view the variants for graph \"{}\" by visiting {}", graph_ref.name, Cyan.normal().paint(&graph_url)) } } } @@ -135,8 +135,8 @@ impl Display for Suggestion { Suggestion::CheckServerConnection => "Make sure the endpoint is accepting connections and is spelled correctly".to_string(), Suggestion::ConvertGraphToSubgraph => "If you are sure you want to convert a non-federated graph to a subgraph, you can re-run the same command with a `--convert` flag.".to_string(), Suggestion::CheckGnuVersion => "This is likely an issue with your current version of `glibc`. Try running `ldd --version`, and if the version >= 2.18, we suggest installing the Rover binary built for `x86_64-unknown-linux-gnu`".to_string(), - Suggestion::FixSubgraphSchema { graph_name } => format!("The changes in the schema you proposed are incompatible with graph {}. See {} for more information on resolving composition errors.", Yellow.normal().paint(graph_name), Cyan.normal().paint("https://www.apollographql.com/docs/federation/errors/")), - Suggestion::FixOperationsInSchema { graph_name } => format!("The changes in the schema you proposed are incompatible with graph {}. See {} for more information on resolving operation check errors.", Yellow.normal().paint(graph_name), Cyan.normal().paint("https://www.apollographql.com/docs/studio/schema-checks/")) + Suggestion::FixSubgraphSchema { graph_ref } => format!("The changes in the schema you proposed are incompatible with graph {}. See {} for more information on resolving composition errors.", Yellow.normal().paint(graph_ref.to_string()), Cyan.normal().paint("https://www.apollographql.com/docs/federation/errors/")), + Suggestion::FixOperationsInSchema { graph_ref } => format!("The changes in the schema you proposed are incompatible with graph {}. See {} for more information on resolving operation check errors.", Yellow.normal().paint(graph_ref.to_string()), Cyan.normal().paint("https://www.apollographql.com/docs/studio/schema-checks/")) }; write!(formatter, "{}", &suggestion) } diff --git a/src/utils/parsers.rs b/src/utils/parsers.rs index 9ede9dba8..492e484be 100644 --- a/src/utils/parsers.rs +++ b/src/utils/parsers.rs @@ -1,7 +1,4 @@ use camino::Utf8PathBuf; -use serde::Serialize; - -use std::convert::TryInto; use crate::{error::RoverError, Result}; @@ -24,44 +21,6 @@ pub fn parse_schema_source(loc: &str) -> Result { } } -#[derive(Debug, Serialize, Default, Clone)] -pub struct ValidationPeriod { - // these timestamps could be represented as i64, but the API expects - // Option - pub from: Option, - pub to: Option, -} - -// Validation period is parsed as human readable time. -// such as "10m 50s" -pub fn parse_validation_period(period: &str) -> Result { - // attempt to parse strings like - // 15h 10m 2s into number of seconds - if period.contains("ns") || period.contains("us") || period.contains("ms") { - return Err(RoverError::parse_error( - "You can only specify a duration as granular as seconds.", - )); - }; - let duration = humantime::parse_duration(period).map_err(RoverError::parse_error)?; - let window: i64 = duration - .as_secs() - .try_into() - .map_err(RoverError::parse_error)?; - - if window > 0 { - Ok(ValidationPeriod { - // search "from" a negative time window - from: Some(format!("{}", -window)), - // search "to" now (-0) seconds - to: Some("-0".to_string()), - }) - } else { - Err(RoverError::parse_error( - "The number of seconds must be a positive integer.", - )) - } -} - pub fn parse_query_count_threshold(threshold: &str) -> Result { let threshold = threshold.parse::()?; if threshold < 1 { From 20fd54f03aa818376c57c50dce7584517a6194af Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Thu, 15 Jul 2021 12:46:05 -0500 Subject: [PATCH 13/17] chore: do not re-export queries --- ARCHITECTURE.md | 16 +++++----------- .../src/operations/config/who_am_i/runner.rs | 2 +- .../src/operations/subgraph/list/runner.rs | 2 +- .../src/operations/subgraph/publish/runner.rs | 2 +- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index d81252fce..599f97fa4 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -195,7 +195,7 @@ To add these to our new `graph hello` command, we can copy and paste the field f pub struct Hello { /// @ of graph in Apollo Studio to publish to. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] + #[structopt(name = "GRAPH_REF"))] #[serde(skip_serializing)] graph: GraphRef @@ -206,12 +206,6 @@ pub struct Hello { } ``` -We'll have to also add some import statements at the top of our file to support parsing this new argument: - -```rust -use crate::utils::parsers::{parse_graph_ref, GraphRef}; -``` - Now if we run the command again, it will complain if we don't provide a graph ref: ```console @@ -279,11 +273,11 @@ You can see in the `src/operations/graph` directory a number of `.rs` files pair For our basic `graph hello` command, we're going to make a request to Apollo Studio that inquires about the existence of a particular graph, and nothing else. For this, we can use the `Query.service` field. -Create a `hello.graphql` file in `crates/rover-client/src/operations/graph` and paste the following into it: +Create a `hello_query.graphql` file in `crates/rover-client/src/operations/graph` and paste the following into it: ```graphql -query GraphHello($graphId: ID!) { - service(id: $graphId) { +query GraphHello($graph_id: ID!) { + service(id: $graph_id) { deletedAt } } @@ -315,7 +309,7 @@ use crate::RoverClientError; use graphql_client::*; ``` -Then, we'll create a new struct that will have auto-generated types for the `hello.graphql` file that we created earlier: +Then, we'll create a new struct that will have auto-generated types for the `hello_query.graphql` file that we created earlier: ```rust #[derive(GraphQLQuery)] diff --git a/crates/rover-client/src/operations/config/who_am_i/runner.rs b/crates/rover-client/src/operations/config/who_am_i/runner.rs index d63bf176c..0974cd49d 100644 --- a/crates/rover-client/src/operations/config/who_am_i/runner.rs +++ b/crates/rover-client/src/operations/config/who_am_i/runner.rs @@ -21,7 +21,7 @@ use graphql_client::*; /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. /// Snake case of this name is the mod name. i.e. config_who_am_i_query -pub struct ConfigWhoAmIQuery; +pub(crate) struct ConfigWhoAmIQuery; /// Get info from the registry about an API key, i.e. the name/id of the /// user/graph and what kind of key it is (GRAPH/USER/Other) diff --git a/crates/rover-client/src/operations/subgraph/list/runner.rs b/crates/rover-client/src/operations/subgraph/list/runner.rs index b5562d463..1a5a2c3f6 100644 --- a/crates/rover-client/src/operations/subgraph/list/runner.rs +++ b/crates/rover-client/src/operations/subgraph/list/runner.rs @@ -18,7 +18,7 @@ type Timestamp = String; /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. /// Snake case of this name is the mod name. i.e. subgraph_list_query -pub struct SubgraphListQuery; +pub(crate) struct SubgraphListQuery; /// Fetches list of subgraphs for a given graph, returns name & url of each pub fn run( diff --git a/crates/rover-client/src/operations/subgraph/publish/runner.rs b/crates/rover-client/src/operations/subgraph/publish/runner.rs index d7886911e..fbd1c3aa2 100644 --- a/crates/rover-client/src/operations/subgraph/publish/runner.rs +++ b/crates/rover-client/src/operations/subgraph/publish/runner.rs @@ -17,7 +17,7 @@ use graphql_client::*; /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. /// Snake case of this name is the mod name. i.e. subgraph_publish_mutation -pub struct SubgraphPublishMutation; +pub(crate) struct SubgraphPublishMutation; pub fn run( input: SubgraphPublishInput, From a16f4f71cc54d03fdae6d5e68dd2c6c46d8a7b0d Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Thu, 15 Jul 2021 12:53:37 -0500 Subject: [PATCH 14/17] chore: finish wiring OperationCheck error --- crates/rover-client/src/error.rs | 5 ++++- .../src/operations/graph/check/runner.rs | 8 ++++---- .../src/operations/subgraph/check/runner.rs | 2 +- crates/rover-client/src/shared/check_response.rs | 7 ++++++- docs/source/errors.md | 4 ++++ src/error/metadata/mod.rs | 13 ++++++++----- 6 files changed, 27 insertions(+), 12 deletions(-) diff --git a/crates/rover-client/src/error.rs b/crates/rover-client/src/error.rs index 2ea70a76f..7e9a1fb49 100644 --- a/crates/rover-client/src/error.rs +++ b/crates/rover-client/src/error.rs @@ -143,7 +143,10 @@ pub enum RoverClientError { // we nest the CheckResponse here because we want to print the entire response even // if there were failures #[error("{}", check_response_error_msg(.check_response))] - OperationCheckFailure { check_response: CheckResponse }, + OperationCheckFailure { + graph_ref: GraphRef, + check_response: CheckResponse, + }, /// This error occurs when a user has a malformed Graph Ref #[error("Graph IDs must be in the format or @, where can only contain letters, numbers, or the characters `-` or `_`, and must be 64 characters or less. must be 64 characters or less.")] diff --git a/crates/rover-client/src/operations/graph/check/runner.rs b/crates/rover-client/src/operations/graph/check/runner.rs index d2315b61f..83e0873a0 100644 --- a/crates/rover-client/src/operations/graph/check/runner.rs +++ b/crates/rover-client/src/operations/graph/check/runner.rs @@ -38,9 +38,9 @@ fn get_check_response_from_data( data: MutationResponseData, graph_ref: GraphRef, ) -> Result { - let service = data - .service - .ok_or(RoverClientError::GraphNotFound { graph_ref })?; + let service = data.service.ok_or(RoverClientError::GraphNotFound { + graph_ref: graph_ref.clone(), + })?; let target_url = service.check_schema.target_url; let diff_to_previous = service.check_schema.diff_to_previous; @@ -65,5 +65,5 @@ fn get_check_response_from_data( num_failures, }; - check_response.check_for_failures() + check_response.check_for_failures(graph_ref) } diff --git a/crates/rover-client/src/operations/subgraph/check/runner.rs b/crates/rover-client/src/operations/subgraph/check/runner.rs index 057534db4..0df7f1fa7 100644 --- a/crates/rover-client/src/operations/subgraph/check/runner.rs +++ b/crates/rover-client/src/operations/subgraph/check/runner.rs @@ -100,7 +100,7 @@ fn get_check_response_from_data( changes, change_severity, }; - check_response.check_for_failures() + check_response.check_for_failures(graph_ref) } else { let num_failures = query_composition_errors.len(); diff --git a/crates/rover-client/src/shared/check_response.rs b/crates/rover-client/src/shared/check_response.rs index f2675835d..135bda99c 100644 --- a/crates/rover-client/src/shared/check_response.rs +++ b/crates/rover-client/src/shared/check_response.rs @@ -2,6 +2,7 @@ use std::cmp::Ordering; use std::fmt; use std::str::FromStr; +use crate::shared::GraphRef; use crate::RoverClientError; use serde::Serialize; @@ -40,10 +41,14 @@ impl CheckResponse { } } - pub fn check_for_failures(&self) -> Result { + pub fn check_for_failures( + &self, + graph_ref: GraphRef, + ) -> Result { match &self.num_failures.cmp(&0) { Ordering::Equal => Ok(self.clone()), Ordering::Greater => Err(RoverClientError::OperationCheckFailure { + graph_ref, check_response: self.clone(), }), Ordering::Less => unreachable!("Somehow encountered a negative number of failures."), diff --git a/docs/source/errors.md b/docs/source/errors.md index b5a98a192..cc2a8f55f 100644 --- a/docs/source/errors.md +++ b/docs/source/errors.md @@ -242,3 +242,7 @@ There are many reasons why you may run into composition errors. This error shoul Some composition errors are part of normal workflows. For instance, you may need to publish a subgraph that does not compose if you are trying to [migrate an entity or field](https://www.apollographql.com/docs/federation/entities/#migrating-entities-and-fields-advanced). +### E030 + +This error occurs when an operation check fails. This means that you proposed a schema that would break operations in use by existing clients. You can configure this behavior in the Checks -> Configuration view in [Apollo Studio](https://studio.apollographql.com/), and you can read more about client checks [here](https://www.apollographql.com/docs/studio/schema-checks/). + diff --git a/src/error/metadata/mod.rs b/src/error/metadata/mod.rs index b542f1f89..8969d419d 100644 --- a/src/error/metadata/mod.rs +++ b/src/error/metadata/mod.rs @@ -73,13 +73,16 @@ impl From<&mut anyhow::Error> for Metadata { Some(Code::E029), ) } - RoverClientError::OperationCheckFailure { check_response } => { + RoverClientError::OperationCheckFailure { + graph_ref, + check_response, + } => { print_check_response(check_response); ( - Some(Suggestion::Adhoc( - "TODO: make a new error code and markdown file linking to client checks".to_string(), - )), - None, + Some(Suggestion::FixOperationsInSchema { + graph_ref: graph_ref.clone(), + }), + Some(Code::E030), ) } RoverClientError::SubgraphIntrospectionNotAvailable => { From 3988a5b86405517be35e26728f22b1106421d4a2 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Mon, 19 Jul 2021 11:12:23 -0500 Subject: [PATCH 15/17] chore: adds graphql linter (#677) --- crates/rover-client/.eslintrc.json | 60 + crates/rover-client/package-lock.json | 5041 +++++++++++++++++ crates/rover-client/package.json | 14 + .../is_federated/is_federated_query.graphql | 2 +- .../graph/check/check_mutation.graphql | 6 +- .../introspect/introspect_schema.graphql | 22 +- .../subgraph/delete/delete_mutation.graphql | 8 +- .../introspect/introspect_query.graphql | 3 +- .../introspect/introspect_schema.graphql | 5 + .../subgraph/list/list_query.graphql | 4 +- .../subgraph/publish/publish_mutation.graphql | 4 +- .../supergraph/fetch/fetch_query.graphql | 2 +- xtask/src/commands/lint.rs | 4 +- xtask/src/tools/npm.rs | 63 +- 14 files changed, 5199 insertions(+), 39 deletions(-) create mode 100644 crates/rover-client/.eslintrc.json create mode 100644 crates/rover-client/package-lock.json create mode 100644 crates/rover-client/package.json diff --git a/crates/rover-client/.eslintrc.json b/crates/rover-client/.eslintrc.json new file mode 100644 index 000000000..fc09e7e9a --- /dev/null +++ b/crates/rover-client/.eslintrc.json @@ -0,0 +1,60 @@ +{ + "root": true, + "overrides": [ + { + "files": ["*.graphql"], + "parser": "@graphql-eslint/eslint-plugin", + "plugins": ["@graphql-eslint"], + "parserOptions": { + "schema": "./.schema/schema.graphql", + "operations": ["./src/operations/**/*.graphql"] + }, + "rules": { + "@graphql-eslint/avoid-duplicate-fields": 2, + "@graphql-eslint/avoid-operation-name-prefix": 2, + "@graphql-eslint/avoid-typename-prefix": 2, + "@graphql-eslint/description-style": 2, + "@graphql-eslint/executable-definitions": 2, + "@graphql-eslint/fields-on-correct-type": 2, + "@graphql-eslint/fragments-on-composite-type": 2, + "@graphql-eslint/input-name": 2, + "@graphql-eslint/known-argument-names": 2, + "@graphql-eslint/known-directives": 2, + "@graphql-eslint/known-fragment-names": 2, + "@graphql-eslint/known-type-names": 2, + "@graphql-eslint/lone-anonymous-operation": 2, + "@graphql-eslint/no-anonymous-operations": 2, + "@graphql-eslint/no-case-insensitive-enum-values-duplicates": 2, + "@graphql-eslint/no-deprecated": 2, + "@graphql-eslint/no-fragment-cycles": 2, + "@graphql-eslint/no-hashtag-description": 2, + "@graphql-eslint/no-undefined-variables": 2, + "@graphql-eslint/no-unused-fields": 2, + "@graphql-eslint/no-unused-fragments": 2, + "@graphql-eslint/no-unused-variables": 2, + "@graphql-eslint/one-field-subscriptions": 2, + "@graphql-eslint/overlapping-fields-can-be-merged": 2, + "@graphql-eslint/possible-fragment-spread": 2, + "@graphql-eslint/possible-type-extension": 2, + "@graphql-eslint/provided-required-arguments": 2, + "@graphql-eslint/require-deprecation-reason": 2, + "@graphql-eslint/scalar-leafs": 2, + "@graphql-eslint/strict-id-in-types": 2, + "@graphql-eslint/unique-argument-names": 2, + "@graphql-eslint/unique-directive-names": 2, + "@graphql-eslint/unique-directive-names-per-location": 2, + "@graphql-eslint/unique-enum-value-names": 2, + "@graphql-eslint/unique-field-definition-names": 2, + "@graphql-eslint/unique-fragment-name": 2, + "@graphql-eslint/unique-input-field-names": 2, + "@graphql-eslint/unique-operation-name": 2, + "@graphql-eslint/unique-operation-types": 2, + "@graphql-eslint/unique-type-names": 2, + "@graphql-eslint/unique-variable-names": 2, + "@graphql-eslint/value-literals-of-correct-type": 2, + "@graphql-eslint/variables-are-input-types": 2, + "@graphql-eslint/variables-in-allowed-position": 2 + } + } + ] +} diff --git a/crates/rover-client/package-lock.json b/crates/rover-client/package-lock.json new file mode 100644 index 000000000..45898cc82 --- /dev/null +++ b/crates/rover-client/package-lock.json @@ -0,0 +1,5041 @@ +{ + "name": "rover-client", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "version": "0.0.0", + "devDependencies": { + "@graphql-eslint/eslint-plugin": "^1.1.3", + "eslint": "^7.30.0", + "graphql": "^15.5.1" + } + }, + "node_modules/@ardatan/aggregate-error": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@ardatan/aggregate-error/-/aggregate-error-0.0.6.tgz", + "integrity": "sha512-vyrkEHG1jrukmzTPtyWB4NLPauUw5bQeg4uhn8f+1SSynmrOcyvlb1GKQjjgoBzElLdfXCRYX8UnBlhklOHYRQ==", + "dev": true, + "dependencies": { + "tslib": "~2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@ardatan/aggregate-error/node_modules/tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", + "dev": true + }, + "node_modules/@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.14.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "dev": true, + "dependencies": { + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name/node_modules/@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-get-function-arity/node_modules/@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration/node_modules/@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.12.16", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.16.tgz", + "integrity": "sha512-c/+u9cqV6F0+4Hpq01jnJO+GLp2DdT63ppz9Xa+6cHaajM9VFzK/iDXiKK65YtpeVwu+ctfS6iqlMqRgQRzeCw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template/node_modules/@babel/parser": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz", + "integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template/node_modules/@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.13.tgz", + "integrity": "sha512-3Zb4w7eE/OslI0fTp8c7b286/cQps3+vdLW3UcwC8VSJC6GbKn55aeVVu2QJNuCDoeKyptLOFrPq8WqZZBodyA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.12.13", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "node_modules/@babel/types": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.13.tgz", + "integrity": "sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "node_modules/@endemolshinegroup/cosmiconfig-typescript-loader": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-3.0.2.tgz", + "integrity": "sha512-QRVtqJuS1mcT56oHpVegkKBlgtWjXw/gHNWO3eL9oyB5Sc7HBoc2OLG/nYpVfT/Jejvo3NUrD0Udk7XgoyDKkA==", + "dev": true, + "dependencies": { + "lodash.get": "^4", + "make-error": "^1", + "ts-node": "^9", + "tslib": "^2" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "cosmiconfig": ">=6" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz", + "integrity": "sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", + "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@graphql-eslint/eslint-plugin": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@graphql-eslint/eslint-plugin/-/eslint-plugin-1.1.3.tgz", + "integrity": "sha512-PLbNLFkfXOFI62qN644OiPk5iuX3YUpTs6KNCHGxyO9ZgHPxnU2XpOIW6rlQ8pqgJdOlEVLUn6u+7aGKOonGKA==", + "dev": true, + "dependencies": { + "@graphql-tools/code-file-loader": "~6.3.0", + "@graphql-tools/graphql-file-loader": "~6.2.0", + "@graphql-tools/graphql-tag-pluck": "~6.5.0", + "@graphql-tools/import": "^6.3.1", + "@graphql-tools/json-file-loader": "~6.2.6", + "@graphql-tools/load": "~6.2.0", + "@graphql-tools/url-loader": "~6.10.0", + "@graphql-tools/utils": "~7.10.0", + "graphql-config": "^3.2.0", + "graphql-depth-limit": "1.1.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + } + }, + "node_modules/@graphql-tools/batch-execute": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-7.1.2.tgz", + "integrity": "sha512-IuR2SB2MnC2ztA/XeTMTfWcA0Wy7ZH5u+nDkDNLAdX+AaSyDnsQS35sCmHqG0VOGTl7rzoyBWLCKGwSJplgtwg==", + "dev": true, + "dependencies": { + "@graphql-tools/utils": "^7.7.0", + "dataloader": "2.0.0", + "tslib": "~2.2.0", + "value-or-promise": "1.0.6" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0" + } + }, + "node_modules/@graphql-tools/batch-execute/node_modules/tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + }, + "node_modules/@graphql-tools/code-file-loader": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-6.3.1.tgz", + "integrity": "sha512-ZJimcm2ig+avgsEOWWVvAaxZrXXhiiSZyYYOJi0hk9wh5BxZcLUNKkTp6EFnZE/jmGUwuos3pIjUD3Hwi3Bwhg==", + "dev": true, + "dependencies": { + "@graphql-tools/graphql-tag-pluck": "^6.5.1", + "@graphql-tools/utils": "^7.0.0", + "tslib": "~2.1.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0" + } + }, + "node_modules/@graphql-tools/delegate": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-7.1.5.tgz", + "integrity": "sha512-bQu+hDd37e+FZ0CQGEEczmRSfQRnnXeUxI/0miDV+NV/zCbEdIJj5tYFNrKT03W6wgdqx8U06d8L23LxvGri/g==", + "dev": true, + "dependencies": { + "@ardatan/aggregate-error": "0.0.6", + "@graphql-tools/batch-execute": "^7.1.2", + "@graphql-tools/schema": "^7.1.5", + "@graphql-tools/utils": "^7.7.1", + "dataloader": "2.0.0", + "tslib": "~2.2.0", + "value-or-promise": "1.0.6" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0" + } + }, + "node_modules/@graphql-tools/delegate/node_modules/tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + }, + "node_modules/@graphql-tools/graphql-file-loader": { + "version": "6.2.7", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-6.2.7.tgz", + "integrity": "sha512-5k2SNz0W87tDcymhEMZMkd6/vs6QawDyjQXWtqkuLTBF3vxjxPD1I4dwHoxgWPIjjANhXybvulD7E+St/7s9TQ==", + "dev": true, + "dependencies": { + "@graphql-tools/import": "^6.2.6", + "@graphql-tools/utils": "^7.0.0", + "tslib": "~2.1.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0" + } + }, + "node_modules/@graphql-tools/graphql-tag-pluck": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-6.5.1.tgz", + "integrity": "sha512-7qkm82iFmcpb8M6/yRgzjShtW6Qu2OlCSZp8uatA3J0eMl87TxyJoUmL3M3UMMOSundAK8GmoyNVFUrueueV5Q==", + "dev": true, + "dependencies": { + "@babel/parser": "7.12.16", + "@babel/traverse": "7.12.13", + "@babel/types": "7.12.13", + "@graphql-tools/utils": "^7.0.0", + "tslib": "~2.1.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0" + } + }, + "node_modules/@graphql-tools/import": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/import/-/import-6.3.1.tgz", + "integrity": "sha512-1szR19JI6WPibjYurMLdadHKZoG9C//8I/FZ0Dt4vJSbrMdVNp8WFxg4QnZrDeMG4MzZc90etsyF5ofKjcC+jw==", + "dev": true, + "dependencies": { + "resolve-from": "5.0.0", + "tslib": "~2.2.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0" + } + }, + "node_modules/@graphql-tools/import/node_modules/tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + }, + "node_modules/@graphql-tools/json-file-loader": { + "version": "6.2.6", + "resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-6.2.6.tgz", + "integrity": "sha512-CnfwBSY5926zyb6fkDBHnlTblHnHI4hoBALFYXnrg0Ev4yWU8B04DZl/pBRUc459VNgO2x8/mxGIZj2hPJG1EA==", + "dev": true, + "dependencies": { + "@graphql-tools/utils": "^7.0.0", + "tslib": "~2.0.1" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0" + } + }, + "node_modules/@graphql-tools/json-file-loader/node_modules/tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", + "dev": true + }, + "node_modules/@graphql-tools/load": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-6.2.8.tgz", + "integrity": "sha512-JpbyXOXd8fJXdBh2ta0Q4w8ia6uK5FHzrTNmcvYBvflFuWly2LDTk2abbSl81zKkzswQMEd2UIYghXELRg8eTA==", + "dev": true, + "dependencies": { + "@graphql-tools/merge": "^6.2.12", + "@graphql-tools/utils": "^7.5.0", + "globby": "11.0.3", + "import-from": "3.0.0", + "is-glob": "4.0.1", + "p-limit": "3.1.0", + "tslib": "~2.2.0", + "unixify": "1.0.0", + "valid-url": "1.0.9" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0" + } + }, + "node_modules/@graphql-tools/load/node_modules/tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + }, + "node_modules/@graphql-tools/merge": { + "version": "6.2.14", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.2.14.tgz", + "integrity": "sha512-RWT4Td0ROJai2eR66NHejgf8UwnXJqZxXgDWDI+7hua5vNA2OW8Mf9K1Wav1ZkjWnuRp4ztNtkZGie5ISw55ow==", + "dev": true, + "dependencies": { + "@graphql-tools/schema": "^7.0.0", + "@graphql-tools/utils": "^7.7.0", + "tslib": "~2.2.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0" + } + }, + "node_modules/@graphql-tools/merge/node_modules/tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + }, + "node_modules/@graphql-tools/schema": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-7.1.5.tgz", + "integrity": "sha512-uyn3HSNSckf4mvQSq0Q07CPaVZMNFCYEVxroApOaw802m9DcZPgf9XVPy/gda5GWj9AhbijfRYVTZQgHnJ4CXA==", + "dev": true, + "dependencies": { + "@graphql-tools/utils": "^7.1.2", + "tslib": "~2.2.0", + "value-or-promise": "1.0.6" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0" + } + }, + "node_modules/@graphql-tools/schema/node_modules/tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + }, + "node_modules/@graphql-tools/url-loader": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-6.10.1.tgz", + "integrity": "sha512-DSDrbhQIv7fheQ60pfDpGD256ixUQIR6Hhf9Z5bRjVkXOCvO5XrkwoWLiU7iHL81GB1r0Ba31bf+sl+D4nyyfw==", + "dev": true, + "dependencies": { + "@graphql-tools/delegate": "^7.0.1", + "@graphql-tools/utils": "^7.9.0", + "@graphql-tools/wrap": "^7.0.4", + "@microsoft/fetch-event-source": "2.0.1", + "@types/websocket": "1.0.2", + "abort-controller": "3.0.0", + "cross-fetch": "3.1.4", + "extract-files": "9.0.0", + "form-data": "4.0.0", + "graphql-ws": "^4.4.1", + "is-promise": "4.0.0", + "isomorphic-ws": "4.0.1", + "lodash": "4.17.21", + "meros": "1.1.4", + "subscriptions-transport-ws": "^0.9.18", + "sync-fetch": "0.3.0", + "tslib": "~2.2.0", + "valid-url": "1.0.9", + "ws": "7.4.5" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0" + } + }, + "node_modules/@graphql-tools/url-loader/node_modules/tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + }, + "node_modules/@graphql-tools/utils": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-7.10.0.tgz", + "integrity": "sha512-d334r6bo9mxdSqZW6zWboEnnOOFRrAPVQJ7LkU8/6grglrbcu6WhwCLzHb90E94JI3TD3ricC3YGbUqIi9Xg0w==", + "dev": true, + "dependencies": { + "@ardatan/aggregate-error": "0.0.6", + "camel-case": "4.1.2", + "tslib": "~2.2.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0" + } + }, + "node_modules/@graphql-tools/utils/node_modules/tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + }, + "node_modules/@graphql-tools/wrap": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-7.0.8.tgz", + "integrity": "sha512-1NDUymworsOlb53Qfh7fonDi2STvqCtbeE68ntKY9K/Ju/be2ZNxrFSbrBHwnxWcN9PjISNnLcAyJ1L5tCUyhg==", + "dev": true, + "dependencies": { + "@graphql-tools/delegate": "^7.1.5", + "@graphql-tools/schema": "^7.1.5", + "@graphql-tools/utils": "^7.8.1", + "tslib": "~2.2.0", + "value-or-promise": "1.0.6" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0" + } + }, + "node_modules/@graphql-tools/wrap/node_modules/tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", + "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "dev": true + }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", + "dev": true + }, + "node_modules/@microsoft/fetch-event-source": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz", + "integrity": "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/node": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.3.2.tgz", + "integrity": "sha512-jJs9ErFLP403I+hMLGnqDRWT0RYKSvArxuBVh2veudHV7ifEC1WAmjJADacZ7mRbA2nWgHtn8xyECMAot0SkAw==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "node_modules/@types/websocket": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.2.tgz", + "integrity": "sha512-B5m9aq7cbbD/5/jThEr33nUY8WEfVi6A2YKCTOvw5Ldy7mtsOkqRvGjnzy6g7iMMDsgu7xREuCzqATLDLQVKcQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "node_modules/backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig-toml-loader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-toml-loader/-/cosmiconfig-toml-loader-1.0.0.tgz", + "integrity": "sha512-H/2gurFWVi7xXvCyvsWRLCMekl4tITJcX0QEsDMpzxtuxDyM59xLatYNg4s/k9AA/HdtCYfj2su8mgA0GSDLDA==", + "dev": true, + "dependencies": { + "@iarna/toml": "^2.2.5" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-fetch": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz", + "integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==", + "dev": true, + "dependencies": { + "node-fetch": "2.6.1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/dataloader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.0.0.tgz", + "integrity": "sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "7.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.30.0.tgz", + "integrity": "sha512-VLqz80i3as3NdloY44BQSJpFw534L9Oh+6zJOUaViV4JPd+DaHwutqP7tcpkW3YiXbK6s05RZl7yl7cQn+lijg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.2", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", + "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", + "dev": true + }, + "node_modules/extract-files": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/extract-files/-/extract-files-9.0.0.tgz", + "integrity": "sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==", + "dev": true, + "engines": { + "node": "^10.17.0 || ^12.0.0 || >= 13.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/jaydenseric" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/fastq": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz", + "integrity": "sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.1.tgz", + "integrity": "sha512-OMQjaErSFHmHqZe+PSidH5n8j3O0F2DdnVh8JB4j4eUQ2k6KvB0qGfrKIhapvez5JerBbmWkaLYUYWISaESoXg==", + "dev": true + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", + "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphql": { + "version": "15.5.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.5.1.tgz", + "integrity": "sha512-FeTRX67T3LoE3LWAxxOlW2K3Bz+rMYAC18rRguK4wgXaTZMiJwSUwDmPFo3UadAKbzirKIg5Qy+sNJXbpPRnQw==", + "dev": true, + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/graphql-config": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-3.3.0.tgz", + "integrity": "sha512-mSQIsPMssr7QrgqhnjI+CyVH6oQgCrgS6irHsTvwf7RFDRnR2k9kqpQOQgVoOytBSn0DOYryS0w0SAg9xor/Jw==", + "dev": true, + "dependencies": { + "@endemolshinegroup/cosmiconfig-typescript-loader": "3.0.2", + "@graphql-tools/graphql-file-loader": "^6.0.0", + "@graphql-tools/json-file-loader": "^6.0.0", + "@graphql-tools/load": "^6.0.0", + "@graphql-tools/merge": "^6.0.0", + "@graphql-tools/url-loader": "^6.0.0", + "@graphql-tools/utils": "^7.0.0", + "cosmiconfig": "7.0.0", + "cosmiconfig-toml-loader": "1.0.0", + "minimatch": "3.0.4", + "string-env-interpolation": "1.0.1" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + } + }, + "node_modules/graphql-depth-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/graphql-depth-limit/-/graphql-depth-limit-1.1.0.tgz", + "integrity": "sha512-+3B2BaG8qQ8E18kzk9yiSdAa75i/hnnOwgSeAxVJctGQPvmeiLtqKOYF6HETCyRjiF7Xfsyal0HbLlxCQkgkrw==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "graphql": "*" + } + }, + "node_modules/graphql-ws": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.9.0.tgz", + "integrity": "sha512-sHkK9+lUm20/BGawNEWNtVAeJzhZeBg21VmvmLoT5NdGVeZWv5PdIhkcayQIAgjSyyQ17WMKmbDijIPG2On+Ag==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "graphql": ">=0.11 <=15" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", + "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "dev": true, + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/iterall": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", + "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/meros": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/meros/-/meros-1.1.4.tgz", + "integrity": "sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@types/node": ">=12" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", + "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", + "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", + "dev": true, + "dependencies": { + "mime-db": "1.48.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/string-env-interpolation": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string-env-interpolation/-/string-env-interpolation-1.0.1.tgz", + "integrity": "sha512-78lwMoCcn0nNu8LszbP1UA7g55OeE4v7rCeWnM5B453rnNr4aq+5it3FEYtZrSEiMvHZOZ9Jlqb0OD0M2VInqg==", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/subscriptions-transport-ws": { + "version": "0.9.19", + "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz", + "integrity": "sha512-dxdemxFFB0ppCLg10FTtRqH/31FNRL1y1BQv8209MK5I4CwALb7iihQg+7p65lFcIl8MHatINWBLOqpgU4Kyyw==", + "dev": true, + "dependencies": { + "backo2": "^1.0.2", + "eventemitter3": "^3.1.0", + "iterall": "^1.2.1", + "symbol-observable": "^1.0.4", + "ws": "^5.2.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependencies": { + "graphql": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sync-fetch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/sync-fetch/-/sync-fetch-0.3.0.tgz", + "integrity": "sha512-dJp4qg+x4JwSEW1HibAuMi0IIrBI3wuQr2GimmqB7OXR50wmwzfdusG+p39R9w3R6aFtZ2mzvxvWKQ3Bd/vx3g==", + "dev": true, + "dependencies": { + "buffer": "^5.7.0", + "node-fetch": "^2.6.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/table": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.1.tgz", + "integrity": "sha512-42VLtQUOLefAvKFAQIxIZDaThq6om/PrfP0CYk3/vn+y4BMNkKnbli8ON2QCiHov4KkzOSJ/xSoBJdayiiYvVQ==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-node": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "dev": true, + "dependencies": { + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "typescript": ">=2.7" + } + }, + "node_modules/tslib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unixify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unixify/-/unixify-1.0.0.tgz", + "integrity": "sha1-OmQcjC/7zk2mg6XHDwOkYpQMIJA=", + "dev": true, + "dependencies": { + "normalize-path": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/valid-url": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", + "integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA=", + "dev": true + }, + "node_modules/value-or-promise": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.6.tgz", + "integrity": "sha512-9r0wQsWD8z/BxPOvnwbPf05ZvFngXyouE9EKB+5GbYix+BYnAwrIChCUyFIinfbf2FL/U71z+CPpbnmTdxrwBg==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/ws": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", + "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@ardatan/aggregate-error": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@ardatan/aggregate-error/-/aggregate-error-0.0.6.tgz", + "integrity": "sha512-vyrkEHG1jrukmzTPtyWB4NLPauUw5bQeg4uhn8f+1SSynmrOcyvlb1GKQjjgoBzElLdfXCRYX8UnBlhklOHYRQ==", + "dev": true, + "requires": { + "tslib": "~2.0.1" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", + "dev": true + } + } + }, + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-function-name": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "dependencies": { + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-get-function-arity": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + }, + "dependencies": { + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + }, + "dependencies": { + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.12.16", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.16.tgz", + "integrity": "sha512-c/+u9cqV6F0+4Hpq01jnJO+GLp2DdT63ppz9Xa+6cHaajM9VFzK/iDXiKK65YtpeVwu+ctfS6iqlMqRgQRzeCw==", + "dev": true + }, + "@babel/template": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "dependencies": { + "@babel/parser": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz", + "integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==", + "dev": true + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/traverse": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.13.tgz", + "integrity": "sha512-3Zb4w7eE/OslI0fTp8c7b286/cQps3+vdLW3UcwC8VSJC6GbKn55aeVVu2QJNuCDoeKyptLOFrPq8WqZZBodyA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.12.13", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.13.tgz", + "integrity": "sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "@endemolshinegroup/cosmiconfig-typescript-loader": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-3.0.2.tgz", + "integrity": "sha512-QRVtqJuS1mcT56oHpVegkKBlgtWjXw/gHNWO3eL9oyB5Sc7HBoc2OLG/nYpVfT/Jejvo3NUrD0Udk7XgoyDKkA==", + "dev": true, + "requires": { + "lodash.get": "^4", + "make-error": "^1", + "ts-node": "^9", + "tslib": "^2" + } + }, + "@eslint/eslintrc": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz", + "integrity": "sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "globals": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", + "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + } + } + }, + "@graphql-eslint/eslint-plugin": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@graphql-eslint/eslint-plugin/-/eslint-plugin-1.1.3.tgz", + "integrity": "sha512-PLbNLFkfXOFI62qN644OiPk5iuX3YUpTs6KNCHGxyO9ZgHPxnU2XpOIW6rlQ8pqgJdOlEVLUn6u+7aGKOonGKA==", + "dev": true, + "requires": { + "@graphql-tools/code-file-loader": "~6.3.0", + "@graphql-tools/graphql-file-loader": "~6.2.0", + "@graphql-tools/graphql-tag-pluck": "~6.5.0", + "@graphql-tools/import": "^6.3.1", + "@graphql-tools/json-file-loader": "~6.2.6", + "@graphql-tools/load": "~6.2.0", + "@graphql-tools/url-loader": "~6.10.0", + "@graphql-tools/utils": "~7.10.0", + "graphql-config": "^3.2.0", + "graphql-depth-limit": "1.1.0" + } + }, + "@graphql-tools/batch-execute": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-7.1.2.tgz", + "integrity": "sha512-IuR2SB2MnC2ztA/XeTMTfWcA0Wy7ZH5u+nDkDNLAdX+AaSyDnsQS35sCmHqG0VOGTl7rzoyBWLCKGwSJplgtwg==", + "dev": true, + "requires": { + "@graphql-tools/utils": "^7.7.0", + "dataloader": "2.0.0", + "tslib": "~2.2.0", + "value-or-promise": "1.0.6" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + } + } + }, + "@graphql-tools/code-file-loader": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-6.3.1.tgz", + "integrity": "sha512-ZJimcm2ig+avgsEOWWVvAaxZrXXhiiSZyYYOJi0hk9wh5BxZcLUNKkTp6EFnZE/jmGUwuos3pIjUD3Hwi3Bwhg==", + "dev": true, + "requires": { + "@graphql-tools/graphql-tag-pluck": "^6.5.1", + "@graphql-tools/utils": "^7.0.0", + "tslib": "~2.1.0" + } + }, + "@graphql-tools/delegate": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-7.1.5.tgz", + "integrity": "sha512-bQu+hDd37e+FZ0CQGEEczmRSfQRnnXeUxI/0miDV+NV/zCbEdIJj5tYFNrKT03W6wgdqx8U06d8L23LxvGri/g==", + "dev": true, + "requires": { + "@ardatan/aggregate-error": "0.0.6", + "@graphql-tools/batch-execute": "^7.1.2", + "@graphql-tools/schema": "^7.1.5", + "@graphql-tools/utils": "^7.7.1", + "dataloader": "2.0.0", + "tslib": "~2.2.0", + "value-or-promise": "1.0.6" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + } + } + }, + "@graphql-tools/graphql-file-loader": { + "version": "6.2.7", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-6.2.7.tgz", + "integrity": "sha512-5k2SNz0W87tDcymhEMZMkd6/vs6QawDyjQXWtqkuLTBF3vxjxPD1I4dwHoxgWPIjjANhXybvulD7E+St/7s9TQ==", + "dev": true, + "requires": { + "@graphql-tools/import": "^6.2.6", + "@graphql-tools/utils": "^7.0.0", + "tslib": "~2.1.0" + } + }, + "@graphql-tools/graphql-tag-pluck": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-6.5.1.tgz", + "integrity": "sha512-7qkm82iFmcpb8M6/yRgzjShtW6Qu2OlCSZp8uatA3J0eMl87TxyJoUmL3M3UMMOSundAK8GmoyNVFUrueueV5Q==", + "dev": true, + "requires": { + "@babel/parser": "7.12.16", + "@babel/traverse": "7.12.13", + "@babel/types": "7.12.13", + "@graphql-tools/utils": "^7.0.0", + "tslib": "~2.1.0" + } + }, + "@graphql-tools/import": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/import/-/import-6.3.1.tgz", + "integrity": "sha512-1szR19JI6WPibjYurMLdadHKZoG9C//8I/FZ0Dt4vJSbrMdVNp8WFxg4QnZrDeMG4MzZc90etsyF5ofKjcC+jw==", + "dev": true, + "requires": { + "resolve-from": "5.0.0", + "tslib": "~2.2.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + } + } + }, + "@graphql-tools/json-file-loader": { + "version": "6.2.6", + "resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-6.2.6.tgz", + "integrity": "sha512-CnfwBSY5926zyb6fkDBHnlTblHnHI4hoBALFYXnrg0Ev4yWU8B04DZl/pBRUc459VNgO2x8/mxGIZj2hPJG1EA==", + "dev": true, + "requires": { + "@graphql-tools/utils": "^7.0.0", + "tslib": "~2.0.1" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", + "dev": true + } + } + }, + "@graphql-tools/load": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-6.2.8.tgz", + "integrity": "sha512-JpbyXOXd8fJXdBh2ta0Q4w8ia6uK5FHzrTNmcvYBvflFuWly2LDTk2abbSl81zKkzswQMEd2UIYghXELRg8eTA==", + "dev": true, + "requires": { + "@graphql-tools/merge": "^6.2.12", + "@graphql-tools/utils": "^7.5.0", + "globby": "11.0.3", + "import-from": "3.0.0", + "is-glob": "4.0.1", + "p-limit": "3.1.0", + "tslib": "~2.2.0", + "unixify": "1.0.0", + "valid-url": "1.0.9" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + } + } + }, + "@graphql-tools/merge": { + "version": "6.2.14", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.2.14.tgz", + "integrity": "sha512-RWT4Td0ROJai2eR66NHejgf8UwnXJqZxXgDWDI+7hua5vNA2OW8Mf9K1Wav1ZkjWnuRp4ztNtkZGie5ISw55ow==", + "dev": true, + "requires": { + "@graphql-tools/schema": "^7.0.0", + "@graphql-tools/utils": "^7.7.0", + "tslib": "~2.2.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + } + } + }, + "@graphql-tools/schema": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-7.1.5.tgz", + "integrity": "sha512-uyn3HSNSckf4mvQSq0Q07CPaVZMNFCYEVxroApOaw802m9DcZPgf9XVPy/gda5GWj9AhbijfRYVTZQgHnJ4CXA==", + "dev": true, + "requires": { + "@graphql-tools/utils": "^7.1.2", + "tslib": "~2.2.0", + "value-or-promise": "1.0.6" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + } + } + }, + "@graphql-tools/url-loader": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-6.10.1.tgz", + "integrity": "sha512-DSDrbhQIv7fheQ60pfDpGD256ixUQIR6Hhf9Z5bRjVkXOCvO5XrkwoWLiU7iHL81GB1r0Ba31bf+sl+D4nyyfw==", + "dev": true, + "requires": { + "@graphql-tools/delegate": "^7.0.1", + "@graphql-tools/utils": "^7.9.0", + "@graphql-tools/wrap": "^7.0.4", + "@microsoft/fetch-event-source": "2.0.1", + "@types/websocket": "1.0.2", + "abort-controller": "3.0.0", + "cross-fetch": "3.1.4", + "extract-files": "9.0.0", + "form-data": "4.0.0", + "graphql-ws": "^4.4.1", + "is-promise": "4.0.0", + "isomorphic-ws": "4.0.1", + "lodash": "4.17.21", + "meros": "1.1.4", + "subscriptions-transport-ws": "^0.9.18", + "sync-fetch": "0.3.0", + "tslib": "~2.2.0", + "valid-url": "1.0.9", + "ws": "7.4.5" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + } + } + }, + "@graphql-tools/utils": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-7.10.0.tgz", + "integrity": "sha512-d334r6bo9mxdSqZW6zWboEnnOOFRrAPVQJ7LkU8/6grglrbcu6WhwCLzHb90E94JI3TD3ricC3YGbUqIi9Xg0w==", + "dev": true, + "requires": { + "@ardatan/aggregate-error": "0.0.6", + "camel-case": "4.1.2", + "tslib": "~2.2.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + } + } + }, + "@graphql-tools/wrap": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-7.0.8.tgz", + "integrity": "sha512-1NDUymworsOlb53Qfh7fonDi2STvqCtbeE68ntKY9K/Ju/be2ZNxrFSbrBHwnxWcN9PjISNnLcAyJ1L5tCUyhg==", + "dev": true, + "requires": { + "@graphql-tools/delegate": "^7.1.5", + "@graphql-tools/schema": "^7.1.5", + "@graphql-tools/utils": "^7.8.1", + "tslib": "~2.2.0", + "value-or-promise": "1.0.6" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + } + } + }, + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", + "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "dev": true + }, + "@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", + "dev": true + }, + "@microsoft/fetch-event-source": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz", + "integrity": "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@types/node": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.3.2.tgz", + "integrity": "sha512-jJs9ErFLP403I+hMLGnqDRWT0RYKSvArxuBVh2veudHV7ifEC1WAmjJADacZ7mRbA2nWgHtn8xyECMAot0SkAw==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/websocket": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.2.tgz", + "integrity": "sha512-B5m9aq7cbbD/5/jThEr33nUY8WEfVi6A2YKCTOvw5Ldy7mtsOkqRvGjnzy6g7iMMDsgu7xREuCzqATLDLQVKcQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "requires": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "cosmiconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "cosmiconfig-toml-loader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-toml-loader/-/cosmiconfig-toml-loader-1.0.0.tgz", + "integrity": "sha512-H/2gurFWVi7xXvCyvsWRLCMekl4tITJcX0QEsDMpzxtuxDyM59xLatYNg4s/k9AA/HdtCYfj2su8mgA0GSDLDA==", + "dev": true, + "requires": { + "@iarna/toml": "^2.2.5" + } + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "cross-fetch": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz", + "integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==", + "dev": true, + "requires": { + "node-fetch": "2.6.1" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "dataloader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.0.0.tgz", + "integrity": "sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ==", + "dev": true + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "7.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.30.0.tgz", + "integrity": "sha512-VLqz80i3as3NdloY44BQSJpFw534L9Oh+6zJOUaViV4JPd+DaHwutqP7tcpkW3YiXbK6s05RZl7yl7cQn+lijg==", + "dev": true, + "requires": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.2", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "globals": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", + "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true + }, + "eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", + "dev": true + }, + "extract-files": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/extract-files/-/extract-files-9.0.0.tgz", + "integrity": "sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fastq": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz", + "integrity": "sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.1.tgz", + "integrity": "sha512-OMQjaErSFHmHqZe+PSidH5n8j3O0F2DdnVh8JB4j4eUQ2k6KvB0qGfrKIhapvez5JerBbmWkaLYUYWISaESoXg==", + "dev": true + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globby": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", + "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, + "graphql": { + "version": "15.5.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.5.1.tgz", + "integrity": "sha512-FeTRX67T3LoE3LWAxxOlW2K3Bz+rMYAC18rRguK4wgXaTZMiJwSUwDmPFo3UadAKbzirKIg5Qy+sNJXbpPRnQw==", + "dev": true + }, + "graphql-config": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-3.3.0.tgz", + "integrity": "sha512-mSQIsPMssr7QrgqhnjI+CyVH6oQgCrgS6irHsTvwf7RFDRnR2k9kqpQOQgVoOytBSn0DOYryS0w0SAg9xor/Jw==", + "dev": true, + "requires": { + "@endemolshinegroup/cosmiconfig-typescript-loader": "3.0.2", + "@graphql-tools/graphql-file-loader": "^6.0.0", + "@graphql-tools/json-file-loader": "^6.0.0", + "@graphql-tools/load": "^6.0.0", + "@graphql-tools/merge": "^6.0.0", + "@graphql-tools/url-loader": "^6.0.0", + "@graphql-tools/utils": "^7.0.0", + "cosmiconfig": "7.0.0", + "cosmiconfig-toml-loader": "1.0.0", + "minimatch": "3.0.4", + "string-env-interpolation": "1.0.1" + } + }, + "graphql-depth-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/graphql-depth-limit/-/graphql-depth-limit-1.1.0.tgz", + "integrity": "sha512-+3B2BaG8qQ8E18kzk9yiSdAa75i/hnnOwgSeAxVJctGQPvmeiLtqKOYF6HETCyRjiF7Xfsyal0HbLlxCQkgkrw==", + "dev": true, + "requires": { + "arrify": "^1.0.1" + } + }, + "graphql-ws": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.9.0.tgz", + "integrity": "sha512-sHkK9+lUm20/BGawNEWNtVAeJzhZeBg21VmvmLoT5NdGVeZWv5PdIhkcayQIAgjSyyQ17WMKmbDijIPG2On+Ag==", + "dev": true, + "requires": {} + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "import-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", + "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "dev": true, + "requires": {} + }, + "iterall": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", + "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, + "lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "requires": { + "tslib": "^2.0.3" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "meros": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/meros/-/meros-1.1.4.tgz", + "integrity": "sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ==", + "dev": true, + "requires": {} + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "mime-db": { + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", + "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==", + "dev": true + }, + "mime-types": { + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", + "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", + "dev": true, + "requires": { + "mime-db": "1.48.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "requires": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-env-interpolation": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string-env-interpolation/-/string-env-interpolation-1.0.1.tgz", + "integrity": "sha512-78lwMoCcn0nNu8LszbP1UA7g55OeE4v7rCeWnM5B453rnNr4aq+5it3FEYtZrSEiMvHZOZ9Jlqb0OD0M2VInqg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "subscriptions-transport-ws": { + "version": "0.9.19", + "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz", + "integrity": "sha512-dxdemxFFB0ppCLg10FTtRqH/31FNRL1y1BQv8209MK5I4CwALb7iihQg+7p65lFcIl8MHatINWBLOqpgU4Kyyw==", + "dev": true, + "requires": { + "backo2": "^1.0.2", + "eventemitter3": "^3.1.0", + "iterall": "^1.2.1", + "symbol-observable": "^1.0.4", + "ws": "^5.2.0 || ^6.0.0 || ^7.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true + }, + "sync-fetch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/sync-fetch/-/sync-fetch-0.3.0.tgz", + "integrity": "sha512-dJp4qg+x4JwSEW1HibAuMi0IIrBI3wuQr2GimmqB7OXR50wmwzfdusG+p39R9w3R6aFtZ2mzvxvWKQ3Bd/vx3g==", + "dev": true, + "requires": { + "buffer": "^5.7.0", + "node-fetch": "^2.6.1" + } + }, + "table": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.1.tgz", + "integrity": "sha512-42VLtQUOLefAvKFAQIxIZDaThq6om/PrfP0CYk3/vn+y4BMNkKnbli8ON2QCiHov4KkzOSJ/xSoBJdayiiYvVQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-node": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, + "tslib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", + "dev": true + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "typescript": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "dev": true, + "peer": true + }, + "unixify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unixify/-/unixify-1.0.0.tgz", + "integrity": "sha1-OmQcjC/7zk2mg6XHDwOkYpQMIJA=", + "dev": true, + "requires": { + "normalize-path": "^2.1.1" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "valid-url": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", + "integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA=", + "dev": true + }, + "value-or-promise": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.6.tgz", + "integrity": "sha512-9r0wQsWD8z/BxPOvnwbPf05ZvFngXyouE9EKB+5GbYix+BYnAwrIChCUyFIinfbf2FL/U71z+CPpbnmTdxrwBg==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", + "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", + "dev": true, + "requires": {} + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/crates/rover-client/package.json b/crates/rover-client/package.json new file mode 100644 index 000000000..18a0f9a59 --- /dev/null +++ b/crates/rover-client/package.json @@ -0,0 +1,14 @@ +{ + "name": "rover-client", + "version": "0.0.0", + "description": "This package is solely used for linting GraphQL schemas", + "scripts": { + "lint": "eslint ." + }, + "author": "opensource@apollographql.com", + "devDependencies": { + "@graphql-eslint/eslint-plugin": "^1.1.3", + "eslint": "^7.30.0", + "graphql": "^15.5.1" + } +} diff --git a/crates/rover-client/src/operations/config/is_federated/is_federated_query.graphql b/crates/rover-client/src/operations/config/is_federated/is_federated_query.graphql index 9f8d216c5..4007e1af7 100644 --- a/crates/rover-client/src/operations/config/is_federated/is_federated_query.graphql +++ b/crates/rover-client/src/operations/config/is_federated/is_federated_query.graphql @@ -1,5 +1,5 @@ query IsFederatedGraph($graph_id: ID!, $variant: String!) { - service(id: $graphId) { + service(id: $graph_id) { implementingServices(graphVariant: $variant) { __typename } diff --git a/crates/rover-client/src/operations/graph/check/check_mutation.graphql b/crates/rover-client/src/operations/graph/check/check_mutation.graphql index e936d2993..513688e1b 100644 --- a/crates/rover-client/src/operations/graph/check/check_mutation.graphql +++ b/crates/rover-client/src/operations/graph/check/check_mutation.graphql @@ -5,11 +5,11 @@ mutation GraphCheckMutation( $git_context: GitContextInput! $config: HistoricQueryParameters! ) { - service(id: $graphId) { + service(id: $graph_id) { checkSchema( - proposedSchemaDocument: $schema + proposedSchemaDocument: $proposed_schema baseSchemaTag: $variant - gitContext: $gitContext + gitContext: $git_context historicParameters: $config ) { targetUrl diff --git a/crates/rover-client/src/operations/graph/introspect/introspect_schema.graphql b/crates/rover-client/src/operations/graph/introspect/introspect_schema.graphql index 805ad4486..c9030af48 100644 --- a/crates/rover-client/src/operations/graph/introspect/introspect_schema.graphql +++ b/crates/rover-client/src/operations/graph/introspect/introspect_schema.graphql @@ -1,11 +1,14 @@ +# eslint-disable-next-line schema { query: Query } +# eslint-disable-next-line type Query { __schema: __Schema } +# eslint-disable-next-line type __Schema { types: [__Type!]! queryType: __Type! @@ -14,30 +17,32 @@ type __Schema { directives: [__Directive!]! } +# eslint-disable-next-line type __Type { kind: __TypeKind! name: String description: String - # OBJECT and INTERFACE only + "OBJECT and INTERFACE only" fields(includeDeprecated: Boolean = false): [__Field!] - # OBJECT only + "OBJECT only" interfaces: [__Type!] - # INTERFACE and UNION only + "INTERFACE and UNION only" possibleTypes: [__Type!] - # ENUM only + "ENUM only" enumValues(includeDeprecated: Boolean = false): [__EnumValue!] - # INPUT_OBJECT only + "INPUT_OBJECT only" inputFields: [__InputValue!] - # NON_NULL and LIST only + "NON_NULL and LIST only" ofType: __Type } +# eslint-disable-next-line type __Field { name: String! description: String @@ -47,6 +52,7 @@ type __Field { deprecationReason: String } +# eslint-disable-next-line type __InputValue { name: String! description: String @@ -54,6 +60,7 @@ type __InputValue { defaultValue: String } +# eslint-disable-next-line type __EnumValue { name: String! description: String @@ -61,6 +68,7 @@ type __EnumValue { deprecationReason: String } +# eslint-disable-next-line enum __TypeKind { SCALAR OBJECT @@ -72,6 +80,7 @@ enum __TypeKind { NON_NULL } +# eslint-disable-next-line type __Directive { name: String! description: String @@ -79,6 +88,7 @@ type __Directive { args: [__InputValue!]! } +# eslint-disable-next-line enum __DirectiveLocation { QUERY MUTATION diff --git a/crates/rover-client/src/operations/subgraph/delete/delete_mutation.graphql b/crates/rover-client/src/operations/subgraph/delete/delete_mutation.graphql index 40d22c3d5..c5a15a429 100644 --- a/crates/rover-client/src/operations/subgraph/delete/delete_mutation.graphql +++ b/crates/rover-client/src/operations/subgraph/delete/delete_mutation.graphql @@ -1,14 +1,14 @@ mutation SubgraphDeleteMutation( - $graphId: ID! + $graph_id: ID! $variant: String! $subgraph: String! - $dryRun: Boolean! + $dry_run: Boolean! ) { - service(id: $graphId) { + service(id: $graph_id) { removeImplementingServiceAndTriggerComposition( graphVariant: $variant name: $subgraph - dryRun: $dryRun + dryRun: $dry_run ) { errors { message diff --git a/crates/rover-client/src/operations/subgraph/introspect/introspect_query.graphql b/crates/rover-client/src/operations/subgraph/introspect/introspect_query.graphql index 4d6269dfe..a5023b264 100644 --- a/crates/rover-client/src/operations/subgraph/introspect/introspect_query.graphql +++ b/crates/rover-client/src/operations/subgraph/introspect/introspect_query.graphql @@ -1,4 +1,5 @@ -query SubgraphIntrospectQuery{ +query SubgraphIntrospectQuery { + # eslint-disable-next-line _service { sdl } diff --git a/crates/rover-client/src/operations/subgraph/introspect/introspect_schema.graphql b/crates/rover-client/src/operations/subgraph/introspect/introspect_schema.graphql index 52fd06ab0..9b16951ea 100644 --- a/crates/rover-client/src/operations/subgraph/introspect/introspect_schema.graphql +++ b/crates/rover-client/src/operations/subgraph/introspect/introspect_schema.graphql @@ -1,11 +1,16 @@ +# eslint-disable-next-line schema { query: Query } +# eslint-disable-next-line type Query { + # eslint-disable-next-line _service: _Service } +# eslint-disable-next-line type _Service { + # eslint-disable-next-line sdl: String! } diff --git a/crates/rover-client/src/operations/subgraph/list/list_query.graphql b/crates/rover-client/src/operations/subgraph/list/list_query.graphql index 69ec2f182..06a50be9a 100644 --- a/crates/rover-client/src/operations/subgraph/list/list_query.graphql +++ b/crates/rover-client/src/operations/subgraph/list/list_query.graphql @@ -1,6 +1,6 @@ -query SubgraphListQuery($graphId: ID!, $variant: String!) { +query SubgraphListQuery($graph_id: ID!, $variant: String!) { frontendUrlRoot - service(id: $graphId) { + service(id: $graph_id) { implementingServices(graphVariant: $variant) { __typename ... on FederatedImplementingServices { diff --git a/crates/rover-client/src/operations/subgraph/publish/publish_mutation.graphql b/crates/rover-client/src/operations/subgraph/publish/publish_mutation.graphql index c64f45468..06fade4d7 100644 --- a/crates/rover-client/src/operations/subgraph/publish/publish_mutation.graphql +++ b/crates/rover-client/src/operations/subgraph/publish/publish_mutation.graphql @@ -1,5 +1,5 @@ mutation SubgraphPublishMutation( - $graphId: ID! + $graph_id: ID! $variant: String! $subgraph: String! $url: String @@ -7,7 +7,7 @@ mutation SubgraphPublishMutation( $schema: PartialSchemaInput! $gitContext: GitContextInput! ) { - service(id: $graphId) { + service(id: $graph_id) { upsertImplementingServiceAndTriggerComposition( name: $subgraph url: $url diff --git a/crates/rover-client/src/operations/supergraph/fetch/fetch_query.graphql b/crates/rover-client/src/operations/supergraph/fetch/fetch_query.graphql index 1da899e89..0e1aebc68 100644 --- a/crates/rover-client/src/operations/supergraph/fetch/fetch_query.graphql +++ b/crates/rover-client/src/operations/supergraph/fetch/fetch_query.graphql @@ -1,6 +1,6 @@ query SupergraphFetchQuery($graph_id: ID!, $variant: String!) { frontendUrlRoot - service(id: $graphId) { + service(id: $graph_id) { variants { name } diff --git a/xtask/src/commands/lint.rs b/xtask/src/commands/lint.rs index a82506b29..08adea188 100644 --- a/xtask/src/commands/lint.rs +++ b/xtask/src/commands/lint.rs @@ -1,7 +1,7 @@ use anyhow::Result; use structopt::StructOpt; -use crate::tools::CargoRunner; +use crate::tools::{CargoRunner, NpmRunner}; #[derive(Debug, StructOpt)] pub struct Lint {} @@ -10,6 +10,8 @@ impl Lint { pub fn run(&self, verbose: bool) -> Result<()> { let cargo_runner = CargoRunner::new(verbose)?; cargo_runner.lint()?; + let npm_runner = NpmRunner::new(verbose)?; + npm_runner.lint()?; Ok(()) } } diff --git a/xtask/src/tools/npm.rs b/xtask/src/tools/npm.rs index a5a3bb239..2d57b0ff3 100644 --- a/xtask/src/tools/npm.rs +++ b/xtask/src/tools/npm.rs @@ -10,25 +10,37 @@ use crate::{ pub(crate) struct NpmRunner { runner: Runner, - npm_package_directory: Utf8PathBuf, + npm_installer_package_directory: Utf8PathBuf, + npm_lint_directory: Utf8PathBuf, } impl NpmRunner { pub(crate) fn new(verbose: bool) -> Result { let runner = Runner::new("npm", verbose)?; - let npm_package_directory = utils::project_root()?.join("installers").join("npm"); - - if npm_package_directory.exists() { - Ok(Self { - runner, - npm_package_directory, - }) - } else { - Err(anyhow!( + let project_root = utils::project_root()?; + + let npm_lint_directory = project_root.join("crates").join("rover-client"); + let npm_installer_package_directory = project_root.join("installers").join("npm"); + + if !npm_installer_package_directory.exists() { + return Err(anyhow!( "Rover's npm installer package does not seem to be located here:\n{}", - &npm_package_directory - )) + &npm_installer_package_directory + )); + } + + if !npm_lint_directory.exists() { + return Err(anyhow!( + "Rover's GraphQL linter package does not seem to be located here:\n{}", + &npm_lint_directory + )); } + + Ok(Self { + runner, + npm_installer_package_directory, + npm_lint_directory, + }) } /// prepares our npm installer package for release @@ -51,32 +63,47 @@ impl NpmRunner { Ok(()) } + pub(crate) fn lint(&self) -> Result<()> { + self.npm_exec(&["install"], &self.npm_lint_directory)?; + self.npm_exec(&["run", "lint"], &self.npm_lint_directory)?; + Ok(()) + } + fn update_dependency_tree(&self) -> Result<()> { - self.npm_exec(&["update"])?; + self.npm_exec(&["update"], &self.npm_installer_package_directory)?; Ok(()) } fn install_dependencies(&self) -> Result<()> { // we --ignore-scripts so that we do not attempt to download and unpack a // released rover tarball - self.npm_exec(&["install", "--ignore-scripts"])?; + self.npm_exec( + &["install", "--ignore-scripts"], + &self.npm_installer_package_directory, + )?; Ok(()) } fn update_version(&self) -> Result<()> { - self.npm_exec(&["version", &PKG_VERSION, "--allow-same-version"])?; + self.npm_exec( + &["version", &PKG_VERSION, "--allow-same-version"], + &self.npm_installer_package_directory, + )?; Ok(()) } fn publish_dry_run(&self) -> Result<()> { - let command_output = self.npm_exec(&["publish", "--dry-run"])?; + let command_output = self.npm_exec( + &["publish", "--dry-run"], + &self.npm_installer_package_directory, + )?; assert_publish_includes(&command_output) .with_context(|| "There were problems with the output of 'npm publish --dry-run'.") } - fn npm_exec(&self, args: &[&str]) -> Result { - self.runner.exec(args, &self.npm_package_directory, None) + fn npm_exec(&self, args: &[&str], directory: &Utf8PathBuf) -> Result { + self.runner.exec(args, &directory, None) } } From 9bc37ecb3ea619c78698d65b0fd83715f5acef43 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Thu, 22 Jul 2021 12:26:49 -0500 Subject: [PATCH 16/17] fix: graph_ref -> graphref --- src/command/supergraph/compose/do_compose.rs | 2 +- src/command/supergraph/config.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index 63086383c..b51d949c8 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -129,7 +129,7 @@ pub(crate) fn get_subgraph_definitions( subgraphs.push(subgraph_definition); } SchemaSource::Subgraph { - graph_ref, + graphref: graph_ref, subgraph, } => { // given a graph_ref and subgraph, run subgraph fetch to diff --git a/src/command/supergraph/config.rs b/src/command/supergraph/config.rs index c091adb55..dbbd70623 100644 --- a/src/command/supergraph/config.rs +++ b/src/command/supergraph/config.rs @@ -27,7 +27,7 @@ pub(crate) struct Subgraph { pub(crate) enum SchemaSource { File { file: Utf8PathBuf }, SubgraphIntrospection { subgraph_url: Url }, - Subgraph { graph_ref: String, subgraph: String }, + Subgraph { graphref: String, subgraph: String }, } #[cfg(feature = "composition-js")] From 5138bcda327fde8a647896572729e0afdcf7582b Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Mon, 26 Jul 2021 14:49:16 -0500 Subject: [PATCH 17/17] feat: structured output (#676) --- ARCHITECTURE.md | 46 +- Cargo.lock | 13 + Cargo.toml | 1 + crates/rover-client/Cargo.toml | 3 +- crates/rover-client/src/error.rs | 49 +- .../src/operations/graph/check/runner.rs | 24 +- .../src/operations/graph/publish/mod.rs | 4 +- .../src/operations/graph/publish/runner.rs | 86 +- .../src/operations/graph/publish/types.rs | 118 +- .../src/operations/subgraph/check/runner.rs | 44 +- .../src/operations/subgraph/delete/runner.rs | 43 +- .../src/operations/subgraph/delete/types.rs | 14 +- .../src/operations/subgraph/list/mod.rs | 2 +- .../src/operations/subgraph/list/runner.rs | 7 +- .../src/operations/subgraph/list/types.rs | 19 +- .../subgraph/publish/publish_mutation.graphql | 4 +- .../src/operations/subgraph/publish/runner.rs | 70 +- .../src/operations/subgraph/publish/types.rs | 16 +- .../src/operations/supergraph/fetch/runner.rs | 47 +- .../rover-client/src/shared/build_errors.rs | 174 +++ .../rover-client/src/shared/check_response.rs | 99 +- .../src/shared/composition_error.rs | 18 - .../rover-client/src/shared/fetch_response.rs | 10 +- crates/rover-client/src/shared/graph_ref.rs | 3 +- crates/rover-client/src/shared/mod.rs | 4 +- crates/sdl-encoder/src/schema_def.rs | 2 +- docs/source/errors.md | 4 +- src/bin/rover.rs | 65 +- src/cli.rs | 161 ++- src/command/config/auth.rs | 6 +- src/command/config/clear.rs | 6 +- src/command/config/delete.rs | 6 +- src/command/config/list.rs | 6 +- src/command/config/mod.rs | 4 +- src/command/config/whoami.rs | 6 +- src/command/docs/list.rs | 6 +- src/command/docs/mod.rs | 4 +- src/command/docs/open.rs | 6 +- src/command/docs/shortlinks.rs | 6 +- src/command/explain.rs | 6 +- src/command/graph/check.rs | 6 +- src/command/graph/fetch.rs | 6 +- src/command/graph/introspect.rs | 6 +- src/command/graph/mod.rs | 4 +- src/command/graph/publish.rs | 44 +- src/command/info.rs | 6 +- src/command/install/mod.rs | 6 +- src/command/mod.rs | 2 +- src/command/output.rs | 1020 ++++++++++++++++- src/command/subgraph/check.rs | 6 +- src/command/subgraph/delete.rs | 129 +-- src/command/subgraph/fetch.rs | 6 +- src/command/subgraph/introspect.rs | 6 +- src/command/subgraph/list.rs | 6 +- src/command/subgraph/mod.rs | 6 +- src/command/subgraph/publish.rs | 95 +- src/command/supergraph/compose/do_compose.rs | 53 +- src/command/supergraph/compose/no_compose.rs | 4 +- src/command/supergraph/fetch.rs | 6 +- src/command/supergraph/mod.rs | 4 +- src/command/update/check.rs | 6 +- src/command/update/mod.rs | 4 +- src/error/metadata/codes/E029.md | 6 +- src/error/metadata/mod.rs | 60 +- src/error/metadata/suggestion.rs | 11 +- src/error/mod.rs | 74 +- src/utils/stringify.rs | 18 +- src/utils/table.rs | 11 +- 68 files changed, 2014 insertions(+), 809 deletions(-) create mode 100644 crates/rover-client/src/shared/build_errors.rs delete mode 100644 crates/rover-client/src/shared/composition_error.rs diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 599f97fa4..1b117879a 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -116,8 +116,8 @@ A minimal command in Rover would be laid out exactly like this: pub struct MyNewCommand { } impl MyNewCommand { - pub fn run(&self) -> Result { - Ok(RoverStdout::None) + pub fn run(&self) -> Result { + Ok(RoverOutput::None) } } ``` @@ -128,16 +128,16 @@ For our `graph hello` command, we'll add a new `hello.rs` file under `src/comman use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::Result; #[derive(Debug, Serialize, StructOpt)] pub struct Hello { } impl Hello { - pub fn run(&self) -> Result { + pub fn run(&self) -> Result { eprintln!("Hello, world!"); - Ok(RoverStdout::None) + Ok(RoverOutput::None) } } ``` @@ -348,7 +348,7 @@ Before we go any further, lets make sure everything is set up properly. We're go It should look something like this (you should make sure you are following the style of other commands when creating new ones): ```rust -pub fn run(&self, client_config: StudioClientConfig) -> Result { +pub fn run(&self, client_config: StudioClientConfig) -> Result { let client = client_config.get_client(&self.profile_name)?; let graph_ref = self.graph.to_string(); eprintln!( @@ -362,7 +362,10 @@ pub fn run(&self, client_config: StudioClientConfig) -> Result { }, &client, )?; - Ok(RoverStdout::PlainText(deleted_at)) + println!("{:?}", deleted_at); + + // TODO: Add a new output type! + Ok(RoverOutput::None) } ``` @@ -399,17 +402,32 @@ Unfortunately this is not the cleanest API and doesn't match the pattern set by You'll want to define all of the types scoped to this command in `types.rs`, and re-export them from the top level `hello` module, and nothing else. -##### `RoverStdout` +##### `RoverOutput` -Now that you can actually execute the `hello::run` query and return its result, you should create a new variant of `RoverStdout` in `src/command/output.rs` that is not `PlainText`. Your new variant should print the descriptor using the `print_descriptor` function, and print the raw content using `print_content`. +Now that you can actually execute the `hello::run` query and return its result, you should create a new variant of `RoverOutput` in `src/command/output.rs` that is not `None`. Your new variant should print the descriptor using the `print_descriptor` function, and print the raw content using `print_content`. -To do so, change the line `Ok(RoverStdout::PlainText(deleted_at))` to `Ok(RoverStdout::DeletedAt(deleted_at))`, add a new `DeletedAt(String)` variant to `RoverStdout`, and then match on it in `pub fn print(&self)`: +To do so, change the line `Ok(RoverOutput::None)` to `Ok(RoverOutput::DeletedAt(deleted_at))`, add a new `DeletedAt(String)` variant to `RoverOutput`, and then match on it in `pub fn print(&self)` and `pub fn get_json(&self)`: ```rust -... -RoverStdout::DeletedAt(timestamp) => { - print_descriptor("Deleted At"); - print_content(×tamp); +pub fn print(&self) { + match self { + ... + RoverOutput::DeletedAt(timestamp) => { + print_descriptor("Deleted At"); + print_content(×tamp); + } + ... + } +} + +pub fn get_json(&self) -> Value { + match self { + ... + RoverOutput::DeletedAt(timestamp) => { + json!({ "deleted_at": timestamp.to_string() }) + } + ... + } } ``` diff --git a/Cargo.lock b/Cargo.lock index 8360b5340..8d5d179ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,6 +83,16 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" +[[package]] +name = "assert-json-diff" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f1c3703dd33532d7f0ca049168930e9099ecac238e23cf932f3a69c42f06da" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "assert_cmd" version = "1.0.7" @@ -321,6 +331,7 @@ dependencies = [ "libc", "num-integer", "num-traits", + "serde", "time", "winapi", ] @@ -1915,6 +1926,7 @@ version = "0.1.8" dependencies = [ "ansi_term 0.12.1", "anyhow", + "assert-json-diff", "assert_cmd", "assert_fs", "atty", @@ -1970,6 +1982,7 @@ dependencies = [ "indoc", "online", "pretty_assertions", + "prettytable-rs", "regex", "reqwest", "sdl-encoder", diff --git a/Cargo.toml b/Cargo.toml index 01dcb44c3..3bdb7ad04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ url = { version = "2.2.2", features = ["serde"] } [dev-dependencies] assert_cmd = "1.0.7" assert_fs = "1.0.3" +assert-json-diff = "2.0.1" predicates = "2.0.0" reqwest = { version = "0.11.4", default-features = false, features = ["blocking", "native-tls-vendored"] } serial_test = "0.5.0" diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index 99d56f702..e1c5315de 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -12,12 +12,13 @@ houston = {path = "../houston"} # crates.io deps camino = "1" -chrono = "0.4" +chrono = { version = "0.4", features = ["serde"] } git-url-parse = "0.3.1" git2 = { version = "0.13.20", default-features = false, features = ["vendored-openssl"] } graphql_client = "0.9" http = "0.2" humantime = "2.1.0" +prettytable-rs = "0.8.0" reqwest = { version = "0.11", default-features = false, features = ["blocking", "brotli", "gzip", "json", "native-tls-vendored"] } regex = "1" sdl-encoder = {path = "../sdl-encoder"} diff --git a/crates/rover-client/src/error.rs b/crates/rover-client/src/error.rs index 7e9a1fb49..47f2568c8 100644 --- a/crates/rover-client/src/error.rs +++ b/crates/rover-client/src/error.rs @@ -1,7 +1,7 @@ use reqwest::Url; use thiserror::Error; -use crate::shared::{CheckResponse, CompositionError, GraphRef}; +use crate::shared::{BuildErrors, CheckResponse, GraphRef}; /// RoverClientError represents all possible failures that can occur during a client request. #[derive(Error, Debug)] @@ -96,19 +96,21 @@ pub enum RoverClientError { GraphNotFound { graph_ref: GraphRef }, /// if someone attempts to get a core schema from a supergraph that has - /// no composition results we return this error. - #[error( - "No supergraph SDL exists for \"{graph_ref}\" because its subgraphs failed to compose." - )] - NoCompositionPublishes { + /// no successful build in the API, we return this error. + #[error("No supergraph SDL exists for \"{graph_ref}\" because its subgraphs failed to build.")] + NoSupergraphBuilds { graph_ref: GraphRef, - composition_errors: Vec, + source: BuildErrors, }, - #[error("{}", subgraph_composition_error_msg(.composition_errors))] - SubgraphCompositionErrors { + #[error("Encountered {} while trying to build a supergraph.", .source.length_string())] + BuildErrors { source: BuildErrors }, + + #[error("Encountered {} while trying to build subgraph \"{subgraph}\" into supergraph \"{graph_ref}\".", .source.length_string())] + SubgraphBuildErrors { + subgraph: String, graph_ref: GraphRef, - composition_errors: Vec, + source: BuildErrors, }, /// This error occurs when the Studio API returns no implementing services for a graph @@ -142,7 +144,7 @@ pub enum RoverClientError { /// While checking the proposed schema, we encountered changes that would break existing operations // we nest the CheckResponse here because we want to print the entire response even // if there were failures - #[error("{}", check_response_error_msg(.check_response))] + #[error("{}", operation_check_error_msg(.check_response))] OperationCheckFailure { graph_ref: GraphRef, check_response: CheckResponse, @@ -174,29 +176,14 @@ pub enum RoverClientError { SubgraphIntrospectionNotAvailable, } -fn subgraph_composition_error_msg(composition_errors: &[CompositionError]) -> String { - let num_failures = composition_errors.len(); - if num_failures == 0 { - unreachable!("No composition errors were encountered while composing the supergraph."); - } - let mut msg = String::new(); - msg.push_str(&match num_failures { - 1 => "Encountered 1 composition error while composing the supergraph.".to_string(), - _ => format!( - "Encountered {} composition errors while composing the supergraph.", - num_failures - ), - }); - msg -} - -fn check_response_error_msg(check_response: &CheckResponse) -> String { - let plural = match check_response.num_failures { +fn operation_check_error_msg(check_response: &CheckResponse) -> String { + let failure_count = check_response.get_failure_count(); + let plural = match failure_count { 1 => "", _ => "s", }; format!( - "This operation has encountered {} change{} that would break existing clients.", - check_response.num_failures, plural + "This operation check has encountered {} schema change{} that would break operations from existing client traffic.", + failure_count, plural ) } diff --git a/crates/rover-client/src/operations/graph/check/runner.rs b/crates/rover-client/src/operations/graph/check/runner.rs index 83e0873a0..152ab808e 100644 --- a/crates/rover-client/src/operations/graph/check/runner.rs +++ b/crates/rover-client/src/operations/graph/check/runner.rs @@ -1,7 +1,5 @@ use crate::blocking::StudioClient; -use crate::operations::graph::check::types::{ - GraphCheckInput, MutationChangeSeverity, MutationResponseData, -}; +use crate::operations::graph::check::types::{GraphCheckInput, MutationResponseData}; use crate::shared::{CheckResponse, GraphRef}; use crate::RoverClientError; @@ -45,25 +43,19 @@ fn get_check_response_from_data( let diff_to_previous = service.check_schema.diff_to_previous; - let number_of_checked_operations = diff_to_previous.number_of_checked_operations.unwrap_or(0); + let operation_check_count = diff_to_previous.number_of_checked_operations.unwrap_or(0) as u64; - let change_severity = diff_to_previous.severity.into(); + let result = diff_to_previous.severity.into(); let mut changes = Vec::with_capacity(diff_to_previous.changes.len()); - let mut num_failures = 0; for change in diff_to_previous.changes { - if let MutationChangeSeverity::FAILURE = change.severity { - num_failures += 1; - } changes.push(change.into()); } - let check_response = CheckResponse { + CheckResponse::try_new( target_url, - number_of_checked_operations, + operation_check_count, changes, - change_severity, - num_failures, - }; - - check_response.check_for_failures(graph_ref) + result, + graph_ref, + ) } diff --git a/crates/rover-client/src/operations/graph/publish/mod.rs b/crates/rover-client/src/operations/graph/publish/mod.rs index 4d4a2c184..e4fe3abb2 100644 --- a/crates/rover-client/src/operations/graph/publish/mod.rs +++ b/crates/rover-client/src/operations/graph/publish/mod.rs @@ -2,4 +2,6 @@ mod runner; mod types; pub use runner::run; -pub use types::{GraphPublishInput, GraphPublishResponse}; +pub use types::{ + ChangeSummary, FieldChanges, GraphPublishInput, GraphPublishResponse, TypeChanges, +}; diff --git a/crates/rover-client/src/operations/graph/publish/runner.rs b/crates/rover-client/src/operations/graph/publish/runner.rs index c46032d20..4ff0fbaec 100644 --- a/crates/rover-client/src/operations/graph/publish/runner.rs +++ b/crates/rover-client/src/operations/graph/publish/runner.rs @@ -1,4 +1,5 @@ use crate::blocking::StudioClient; +use crate::operations::graph::publish::types::{ChangeSummary, FieldChanges, TypeChanges}; use crate::operations::graph::publish::{GraphPublishInput, GraphPublishResponse}; use crate::shared::GraphRef; use crate::RoverClientError; @@ -75,39 +76,66 @@ fn build_response( // which very well may have changes. For this, we'll just look at the code // first and handle the response as if there was `None` for the diff let change_summary = if publish_response.code == "NO_CHANGES" { - build_change_summary(None) + ChangeSummary::none() } else { - build_change_summary(publish_response.tag.unwrap().diff_to_previous) + let diff = publish_response + .tag + .ok_or_else(|| RoverClientError::MalformedResponse { + null_field: "service.upload_schema.tag".to_string(), + })? + .diff_to_previous; + + if let Some(diff) = diff { + diff.into() + } else { + ChangeSummary::none() + } }; Ok(GraphPublishResponse { - schema_hash: hash, + api_schema_hash: hash, change_summary, }) } -type ChangeDiff = graph_publish_mutation::GraphPublishMutationServiceUploadSchemaTagDiffToPrevious; +type QueryChangeDiff = + graph_publish_mutation::GraphPublishMutationServiceUploadSchemaTagDiffToPrevious; -/// builds a string-representation of the diff between two schemas -/// e.g. ` [Fields: +2 -1 â–³0, Types: +4 -0 â–³7]` or `[No Changes]` -fn build_change_summary(diff: Option) -> String { - match diff { - None => "[No Changes]".to_string(), - Some(diff) => { - let changes = diff.change_summary; - let fields = format!( - "Fields: +{} -{} â–³ {}", - changes.field.additions, changes.field.removals, changes.field.edits - ); - let types = format!( - "Types: +{} -{} â–³ {}", - changes.type_.additions, changes.type_.removals, changes.type_.edits - ); - format!("[{}, {}]", fields, types) +impl From for ChangeSummary { + fn from(input: QueryChangeDiff) -> Self { + Self { + field_changes: input.change_summary.field.into(), + type_changes: input.change_summary.type_.into(), } } } +type QueryFieldChanges = + graph_publish_mutation::GraphPublishMutationServiceUploadSchemaTagDiffToPreviousChangeSummaryField; + +impl From for FieldChanges { + fn from(input: QueryFieldChanges) -> Self { + Self::with_diff( + input.additions as u64, + input.removals as u64, + input.edits as u64, + ) + } +} + +type QueryTypeChanges = + graph_publish_mutation::GraphPublishMutationServiceUploadSchemaTagDiffToPreviousChangeSummaryType; + +impl From for TypeChanges { + fn from(input: QueryTypeChanges) -> Self { + Self::with_diff( + input.additions as u64, + input.removals as u64, + input.edits as u64, + ) + } +} + #[cfg(test)] mod tests { use super::*; @@ -199,8 +227,8 @@ mod tests { assert_eq!( output.unwrap(), GraphPublishResponse { - schema_hash: "123456".to_string(), - change_summary: "[No Changes]".to_string(), + api_schema_hash: "123456".to_string(), + change_summary: ChangeSummary::none(), } ); } @@ -251,14 +279,20 @@ mod tests { } } }); - let diff_to_previous: ChangeDiff = serde_json::from_value(json_diff).unwrap(); - let output = build_change_summary(Some(diff_to_previous)); - assert_eq!(output, "[Fields: +3 -1 â–³ 0, Types: +4 -0 â–³ 2]".to_string()) + let diff_to_previous: QueryChangeDiff = serde_json::from_value(json_diff).unwrap(); + let output: ChangeSummary = diff_to_previous.into(); + assert_eq!( + output.to_string(), + "[Fields: +3 -1 â–³ 0, Types: +4 -0 â–³ 2]".to_string() + ) } #[test] fn build_change_summary_works_with_no_changes() { - assert_eq!(build_change_summary(None), "[No Changes]".to_string()) + assert_eq!( + ChangeSummary::none().to_string(), + "[No Changes]".to_string() + ) } fn mock_graph_ref() -> GraphRef { diff --git a/crates/rover-client/src/operations/graph/publish/types.rs b/crates/rover-client/src/operations/graph/publish/types.rs index 8fee1a4cd..b51b0808b 100644 --- a/crates/rover-client/src/operations/graph/publish/types.rs +++ b/crates/rover-client/src/operations/graph/publish/types.rs @@ -1,6 +1,10 @@ use crate::operations::graph::publish::runner::graph_publish_mutation; use crate::shared::{GitContext, GraphRef}; +use serde::Serialize; + +use std::fmt; + #[derive(Clone, Debug, PartialEq)] pub struct GraphPublishInput { pub graph_ref: GraphRef, @@ -33,8 +37,116 @@ impl From for GraphPublishContextInput { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Serialize, Debug, PartialEq)] pub struct GraphPublishResponse { - pub schema_hash: String, - pub change_summary: String, + pub api_schema_hash: String, + #[serde(flatten)] + pub change_summary: ChangeSummary, +} + +#[derive(Clone, Serialize, Debug, PartialEq)] +pub struct ChangeSummary { + pub field_changes: FieldChanges, + pub type_changes: TypeChanges, +} + +impl ChangeSummary { + pub(crate) fn none() -> ChangeSummary { + ChangeSummary { + field_changes: FieldChanges::none(), + type_changes: TypeChanges::none(), + } + } + + pub(crate) fn is_none(&self) -> bool { + self.field_changes.is_none() && self.type_changes.is_none() + } +} + +impl fmt::Display for ChangeSummary { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.is_none() { + write!(f, "[No Changes]") + } else { + write!(f, "[{}, {}]", &self.field_changes, &self.type_changes) + } + } +} + +#[derive(Clone, Serialize, Debug, PartialEq)] +pub struct FieldChanges { + pub additions: u64, + pub removals: u64, + pub edits: u64, +} + +impl FieldChanges { + pub(crate) fn none() -> FieldChanges { + FieldChanges { + additions: 0, + removals: 0, + edits: 0, + } + } + + pub(crate) fn with_diff(additions: u64, removals: u64, edits: u64) -> FieldChanges { + FieldChanges { + additions, + removals, + edits, + } + } + + pub(crate) fn is_none(&self) -> bool { + self.additions == 0 && self.removals == 0 && self.edits == 0 + } +} + +impl fmt::Display for FieldChanges { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Fields: +{} -{} â–³ {}", + &self.additions, &self.removals, &self.edits + ) + } +} + +#[derive(Clone, Serialize, Debug, PartialEq)] +pub struct TypeChanges { + pub additions: u64, + pub removals: u64, + pub edits: u64, +} + +impl TypeChanges { + pub(crate) fn none() -> TypeChanges { + TypeChanges { + additions: 0, + removals: 0, + edits: 0, + } + } + + pub(crate) fn with_diff(additions: u64, removals: u64, edits: u64) -> TypeChanges { + TypeChanges { + additions, + removals, + edits, + } + } + + pub(crate) fn is_none(&self) -> bool { + self.additions == 0 && self.removals == 0 && self.edits == 0 + } +} + +impl fmt::Display for TypeChanges { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Types: +{} -{} â–³ {}", + &self.additions, &self.removals, &self.edits + ) + } } diff --git a/crates/rover-client/src/operations/subgraph/check/runner.rs b/crates/rover-client/src/operations/subgraph/check/runner.rs index 0df7f1fa7..4b34dbba2 100644 --- a/crates/rover-client/src/operations/subgraph/check/runner.rs +++ b/crates/rover-client/src/operations/subgraph/check/runner.rs @@ -4,7 +4,7 @@ use crate::operations::{ config::is_federated::{self, IsFederatedInput}, subgraph::check::types::MutationResponseData, }; -use crate::shared::{CheckResponse, CompositionError, GraphRef, SchemaChange}; +use crate::shared::{BuildError, CheckResponse, GraphRef, SchemaChange}; use crate::RoverClientError; use graphql_client::*; @@ -32,6 +32,7 @@ pub fn run( client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); + let subgraph = input.subgraph.clone(); // This response is used to check whether or not the current graph is federated. let is_federated = is_federated::run( IsFederatedInput { @@ -47,12 +48,13 @@ pub fn run( } let variables = input.into(); let data = client.post::(variables)?; - get_check_response_from_data(data, graph_ref) + get_check_response_from_data(data, graph_ref, subgraph) } fn get_check_response_from_data( data: MutationResponseData, graph_ref: GraphRef, + subgraph: String, ) -> Result { let service = data.service.ok_or(RoverClientError::GraphNotFound { graph_ref: graph_ref.clone(), @@ -75,17 +77,13 @@ fn get_check_response_from_data( let diff_to_previous = check_schema_result.diff_to_previous; - let number_of_checked_operations = - diff_to_previous.number_of_checked_operations.unwrap_or(0); + let operation_check_count = + diff_to_previous.number_of_checked_operations.unwrap_or(0) as u64; - let change_severity = diff_to_previous.severity.into(); + let result = diff_to_previous.severity.into(); let mut changes = Vec::with_capacity(diff_to_previous.changes.len()); - let mut num_failures = 0; for change in diff_to_previous.changes { - if let MutationChangeSeverity::FAILURE = change.severity { - num_failures += 1; - } changes.push(SchemaChange { code: change.code, severity: change.severity.into(), @@ -93,27 +91,27 @@ fn get_check_response_from_data( }); } - let check_response = CheckResponse { - num_failures, - target_url: check_schema_result.target_url, - number_of_checked_operations, + CheckResponse::try_new( + check_schema_result.target_url, + operation_check_count, changes, - change_severity, - }; - check_response.check_for_failures(graph_ref) + result, + graph_ref, + ) } else { let num_failures = query_composition_errors.len(); - let mut composition_errors = Vec::with_capacity(num_failures); + let mut build_errors = Vec::with_capacity(num_failures); for query_composition_error in query_composition_errors { - composition_errors.push(CompositionError { - message: query_composition_error.message, - code: query_composition_error.code, - }); + build_errors.push(BuildError::composition_error( + query_composition_error.message, + query_composition_error.code, + )); } - Err(RoverClientError::SubgraphCompositionErrors { + Err(RoverClientError::SubgraphBuildErrors { + subgraph, graph_ref, - composition_errors, + source: build_errors.into(), }) } } diff --git a/crates/rover-client/src/operations/subgraph/delete/runner.rs b/crates/rover-client/src/operations/subgraph/delete/runner.rs index a40e71d20..9acfd9ed4 100644 --- a/crates/rover-client/src/operations/subgraph/delete/runner.rs +++ b/crates/rover-client/src/operations/subgraph/delete/runner.rs @@ -1,6 +1,6 @@ use crate::blocking::StudioClient; use crate::operations::subgraph::delete::types::*; -use crate::shared::{CompositionError, GraphRef}; +use crate::shared::{BuildError, BuildErrors, GraphRef}; use crate::RoverClientError; use graphql_client::*; @@ -43,27 +43,19 @@ fn get_delete_data_from_response( } fn build_response(response: MutationComposition) -> SubgraphDeleteResponse { - let composition_errors: Vec = response + let build_errors: BuildErrors = response .errors .iter() .filter_map(|error| { - error.as_ref().map(|e| CompositionError { - message: e.message.clone(), - code: e.code.clone(), - }) + error + .as_ref() + .map(|e| BuildError::composition_error(e.message.clone(), e.code.clone())) }) .collect(); - // if there are no errors, just return None - let composition_errors = if !composition_errors.is_empty() { - Some(composition_errors) - } else { - None - }; - SubgraphDeleteResponse { - updated_gateway: response.updated_gateway, - composition_errors, + supergraph_was_updated: response.updated_gateway, + build_errors, } } @@ -136,17 +128,12 @@ mod tests { assert_eq!( parsed, SubgraphDeleteResponse { - composition_errors: Some(vec![ - CompositionError { - message: "wow".to_string(), - code: None - }, - CompositionError { - message: "boo".to_string(), - code: Some("BOO".to_string()) - } - ]), - updated_gateway: false, + build_errors: vec![ + BuildError::composition_error("wow".to_string(), None), + BuildError::composition_error("boo".to_string(), Some("BOO".to_string())) + ] + .into(), + supergraph_was_updated: false, } ); } @@ -162,8 +149,8 @@ mod tests { assert_eq!( parsed, SubgraphDeleteResponse { - composition_errors: None, - updated_gateway: true, + build_errors: BuildErrors::new(), + supergraph_was_updated: true, } ); } diff --git a/crates/rover-client/src/operations/subgraph/delete/types.rs b/crates/rover-client/src/operations/subgraph/delete/types.rs index 0bb7b67d2..86052896e 100644 --- a/crates/rover-client/src/operations/subgraph/delete/types.rs +++ b/crates/rover-client/src/operations/subgraph/delete/types.rs @@ -1,11 +1,13 @@ use crate::{ operations::subgraph::delete::runner::subgraph_delete_mutation, - shared::{CompositionError, GraphRef}, + shared::{BuildErrors, GraphRef}, }; pub(crate) type MutationComposition = subgraph_delete_mutation::SubgraphDeleteMutationServiceRemoveImplementingServiceAndTriggerComposition; pub(crate) type MutationVariables = subgraph_delete_mutation::Variables; +use serde::Serialize; + #[cfg(test)] pub(crate) type MutationCompositionErrors = subgraph_delete_mutation::SubgraphDeleteMutationServiceRemoveImplementingServiceAndTriggerCompositionErrors; @@ -19,11 +21,13 @@ pub struct SubgraphDeleteInput { /// this struct contains all the info needed to print the result of the delete. /// `updated_gateway` is true when composition succeeds and the gateway config /// is updated for the gateway to consume. `composition_errors` is just a list -/// of strings for when there are composition errors as a result of the delete. -#[derive(Debug, PartialEq)] +/// of strings for when there are build errors as a result of the delete. +#[derive(Debug, Clone, Serialize, PartialEq)] pub struct SubgraphDeleteResponse { - pub updated_gateway: bool, - pub composition_errors: Option>, + pub supergraph_was_updated: bool, + + #[serde(skip_serializing)] + pub build_errors: BuildErrors, } impl From for MutationVariables { diff --git a/crates/rover-client/src/operations/subgraph/list/mod.rs b/crates/rover-client/src/operations/subgraph/list/mod.rs index 912e0a83c..dd493dd4c 100644 --- a/crates/rover-client/src/operations/subgraph/list/mod.rs +++ b/crates/rover-client/src/operations/subgraph/list/mod.rs @@ -2,4 +2,4 @@ mod runner; mod types; pub use runner::run; -pub use types::{SubgraphListInput, SubgraphListResponse}; +pub use types::{SubgraphInfo, SubgraphListInput, SubgraphListResponse, SubgraphUpdatedAt}; diff --git a/crates/rover-client/src/operations/subgraph/list/runner.rs b/crates/rover-client/src/operations/subgraph/list/runner.rs index 1a5a2c3f6..2b9912783 100644 --- a/crates/rover-client/src/operations/subgraph/list/runner.rs +++ b/crates/rover-client/src/operations/subgraph/list/runner.rs @@ -78,14 +78,17 @@ fn format_subgraphs(subgraphs: &[QuerySubgraphInfo]) -> Vec { .map(|subgraph| SubgraphInfo { name: subgraph.name.clone(), url: subgraph.url.clone(), - updated_at: subgraph.updated_at.clone().parse().ok(), + updated_at: SubgraphUpdatedAt { + local: subgraph.updated_at.clone().parse().ok(), + utc: subgraph.updated_at.clone().parse().ok(), + }, }) .collect(); // sort and reverse, so newer items come first. We use _unstable here, since // we don't care which order equal items come in the list (it's unlikely that // we'll even have equal items after all) - subgraphs.sort_unstable_by(|a, b| a.updated_at.cmp(&b.updated_at).reverse()); + subgraphs.sort_unstable_by(|a, b| a.updated_at.utc.cmp(&b.updated_at.utc).reverse()); subgraphs } diff --git a/crates/rover-client/src/operations/subgraph/list/types.rs b/crates/rover-client/src/operations/subgraph/list/types.rs index af260a33d..8e18dd9f2 100644 --- a/crates/rover-client/src/operations/subgraph/list/types.rs +++ b/crates/rover-client/src/operations/subgraph/list/types.rs @@ -6,7 +6,8 @@ pub(crate) type QueryGraphType = subgraph_list_query::SubgraphListQueryServiceIm type QueryVariables = subgraph_list_query::Variables; -use chrono::{DateTime, Local}; +use chrono::{DateTime, Local, Utc}; +use serde::Serialize; #[derive(Clone, PartialEq, Debug)] pub struct SubgraphListInput { @@ -22,16 +23,26 @@ impl From for QueryVariables { } } -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, Serialize, PartialEq, Debug)] pub struct SubgraphListResponse { pub subgraphs: Vec, + + #[serde(skip_serializing)] pub root_url: String, + + #[serde(skip_serializing)] pub graph_ref: GraphRef, } -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, Serialize, PartialEq, Debug)] pub struct SubgraphInfo { pub name: String, pub url: Option, // optional, and may not be a real url - pub updated_at: Option>, + pub updated_at: SubgraphUpdatedAt, +} + +#[derive(Clone, Serialize, PartialEq, Debug)] +pub struct SubgraphUpdatedAt { + pub local: Option>, + pub utc: Option>, } diff --git a/crates/rover-client/src/operations/subgraph/publish/publish_mutation.graphql b/crates/rover-client/src/operations/subgraph/publish/publish_mutation.graphql index 06fade4d7..0777dadd5 100644 --- a/crates/rover-client/src/operations/subgraph/publish/publish_mutation.graphql +++ b/crates/rover-client/src/operations/subgraph/publish/publish_mutation.graphql @@ -5,7 +5,7 @@ mutation SubgraphPublishMutation( $url: String $revision: String! $schema: PartialSchemaInput! - $gitContext: GitContextInput! + $git_context: GitContextInput! ) { service(id: $graph_id) { upsertImplementingServiceAndTriggerComposition( @@ -14,7 +14,7 @@ mutation SubgraphPublishMutation( revision: $revision activePartialSchema: $schema graphVariant: $variant - gitContext: $gitContext + gitContext: $git_context ) { compositionConfig { schemaHash diff --git a/crates/rover-client/src/operations/subgraph/publish/runner.rs b/crates/rover-client/src/operations/subgraph/publish/runner.rs index fbd1c3aa2..9f6df852b 100644 --- a/crates/rover-client/src/operations/subgraph/publish/runner.rs +++ b/crates/rover-client/src/operations/subgraph/publish/runner.rs @@ -1,7 +1,7 @@ use super::types::*; use crate::blocking::StudioClient; use crate::operations::config::is_federated::{self, IsFederatedInput}; -use crate::shared::{CompositionError, GraphRef}; +use crate::shared::{BuildError, BuildErrors, GraphRef}; use crate::RoverClientError; use graphql_client::*; @@ -60,32 +60,24 @@ fn get_publish_response_from_data( } fn build_response(publish_response: UpdateResponse) -> SubgraphPublishResponse { - let composition_errors: Vec = publish_response + let build_errors: BuildErrors = publish_response .errors .iter() .filter_map(|error| { - error.as_ref().map(|e| CompositionError { - message: e.message.clone(), - code: e.code.clone(), - }) + error + .as_ref() + .map(|e| BuildError::composition_error(e.message.clone(), e.code.clone())) }) .collect(); - // if there are no errors, just return None - let composition_errors = if !composition_errors.is_empty() { - Some(composition_errors) - } else { - None - }; - SubgraphPublishResponse { - schema_hash: match publish_response.composition_config { + api_schema_hash: match publish_response.composition_config { Some(config) => Some(config.schema_hash), None => None, }, - did_update_gateway: publish_response.did_update_gateway, + supergraph_was_updated: publish_response.did_update_gateway, subgraph_was_created: publish_response.service_was_created, - composition_errors, + build_errors, } } @@ -99,7 +91,7 @@ mod tests { "compositionConfig": { "schemaHash": "5gf564" }, "errors": [ { - "message": "[Accounts] User -> composition error", + "message": "[Accounts] User -> build error", "code": null }, null, // this is technically allowed in the types @@ -117,18 +109,19 @@ mod tests { assert_eq!( output, SubgraphPublishResponse { - schema_hash: Some("5gf564".to_string()), - composition_errors: Some(vec![ - CompositionError { - message: "[Accounts] User -> composition error".to_string(), - code: None - }, - CompositionError { - message: "[Products] Product -> another one".to_string(), - code: Some("ERROR".to_string()) - } - ]), - did_update_gateway: false, + api_schema_hash: Some("5gf564".to_string()), + build_errors: vec![ + BuildError::composition_error( + "[Accounts] User -> build error".to_string(), + None + ), + BuildError::composition_error( + "[Products] Product -> another one".to_string(), + Some("ERROR".to_string()) + ) + ] + .into(), + supergraph_was_updated: false, subgraph_was_created: true, } ); @@ -148,9 +141,9 @@ mod tests { assert_eq!( output, SubgraphPublishResponse { - schema_hash: Some("5gf564".to_string()), - composition_errors: None, - did_update_gateway: true, + api_schema_hash: Some("5gf564".to_string()), + build_errors: BuildErrors::new(), + supergraph_was_updated: true, subgraph_was_created: true, } ); @@ -175,12 +168,13 @@ mod tests { assert_eq!( output, SubgraphPublishResponse { - schema_hash: None, - composition_errors: Some(vec![CompositionError { - message: "[Accounts] -> Things went really wrong".to_string(), - code: None - }]), - did_update_gateway: false, + api_schema_hash: None, + build_errors: vec![BuildError::composition_error( + "[Accounts] -> Things went really wrong".to_string(), + None + )] + .into(), + supergraph_was_updated: false, subgraph_was_created: false, } ); diff --git a/crates/rover-client/src/operations/subgraph/publish/types.rs b/crates/rover-client/src/operations/subgraph/publish/types.rs index 6472a70e4..4dd7eb182 100644 --- a/crates/rover-client/src/operations/subgraph/publish/types.rs +++ b/crates/rover-client/src/operations/subgraph/publish/types.rs @@ -1,6 +1,6 @@ use super::runner::subgraph_publish_mutation; -use crate::shared::{CompositionError, GitContext, GraphRef}; +use crate::shared::{BuildErrors, GitContext, GraphRef}; pub(crate) type ResponseData = subgraph_publish_mutation::ResponseData; pub(crate) type MutationVariables = subgraph_publish_mutation::Variables; @@ -9,6 +9,8 @@ pub(crate) type UpdateResponse = subgraph_publish_mutation::SubgraphPublishMutat type SchemaInput = subgraph_publish_mutation::PartialSchemaInput; type GitContextInput = subgraph_publish_mutation::GitContextInput; +use serde::Serialize; + #[derive(Debug, Clone, PartialEq)] pub struct SubgraphPublishInput { pub graph_ref: GraphRef, @@ -19,12 +21,16 @@ pub struct SubgraphPublishInput { pub convert_to_federated_graph: bool, } -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, Serialize, PartialEq)] pub struct SubgraphPublishResponse { - pub schema_hash: Option, - pub did_update_gateway: bool, + pub api_schema_hash: Option, + + pub supergraph_was_updated: bool, + pub subgraph_was_created: bool, - pub composition_errors: Option>, + + #[serde(skip_serializing)] + pub build_errors: BuildErrors, } impl From for MutationVariables { diff --git a/crates/rover-client/src/operations/supergraph/fetch/runner.rs b/crates/rover-client/src/operations/supergraph/fetch/runner.rs index e0fc3d980..8374c6d51 100644 --- a/crates/rover-client/src/operations/supergraph/fetch/runner.rs +++ b/crates/rover-client/src/operations/supergraph/fetch/runner.rs @@ -1,6 +1,6 @@ use crate::blocking::StudioClient; use crate::operations::supergraph::fetch::SupergraphFetchInput; -use crate::shared::{CompositionError, FetchResponse, GraphRef, Sdl, SdlType}; +use crate::shared::{BuildError, BuildErrors, FetchResponse, GraphRef, Sdl, SdlType}; use crate::RoverClientError; use graphql_client::*; @@ -67,17 +67,14 @@ fn get_supergraph_sdl_from_response_data( } else if let Some(most_recent_composition_publish) = service_data.most_recent_composition_publish { - let composition_errors = most_recent_composition_publish + let build_errors: BuildErrors = most_recent_composition_publish .errors .into_iter() - .map(|error| CompositionError { - message: error.message, - code: error.code, - }) + .map(|error| BuildError::composition_error(error.message, error.code)) .collect(); - Err(RoverClientError::NoCompositionPublishes { + Err(RoverClientError::NoSupergraphBuilds { graph_ref, - composition_errors, + source: build_errors, }) } else { let mut valid_variants = Vec::new(); @@ -154,24 +151,24 @@ mod tests { #[test] fn get_schema_from_response_data_errs_on_no_schema_tag() { - let composition_errors = vec![ - CompositionError { - message: "Unknown type \"Unicorn\".".to_string(), - code: Some("UNKNOWN_TYPE".to_string()), - }, - CompositionError { - message: "Type Query must define one or more fields.".to_string(), - code: None, - }, + let build_errors = vec![ + BuildError::composition_error( + "Unknown type \"Unicorn\".".to_string(), + Some("UNKNOWN_TYPE".to_string()), + ), + BuildError::composition_error( + "Type Query must define one or more fields.".to_string(), + None, + ), ]; - let composition_errors_json = json!([ + let build_errors_json = json!([ { - "message": composition_errors[0].message, - "code": composition_errors[0].code + "message": build_errors[0].get_message(), + "code": build_errors[0].get_code() }, { - "message": composition_errors[1].message, - "code": composition_errors[1].code + "message": build_errors[1].get_message(), + "code": build_errors[1].get_code() } ]); let graph_ref = mock_graph_ref(); @@ -181,16 +178,16 @@ mod tests { "schemaTag": null, "variants": [{"name": &graph_ref.variant}], "mostRecentCompositionPublish": { - "errors": composition_errors_json, + "errors": build_errors_json, } }, }); let data: supergraph_fetch_query::ResponseData = serde_json::from_value(json_response).unwrap(); let output = get_supergraph_sdl_from_response_data(data, graph_ref.clone()); - let expected_error = RoverClientError::NoCompositionPublishes { + let expected_error = RoverClientError::NoSupergraphBuilds { graph_ref, - composition_errors, + source: build_errors.into(), } .to_string(); let actual_error = output.unwrap_err().to_string(); diff --git a/crates/rover-client/src/shared/build_errors.rs b/crates/rover-client/src/shared/build_errors.rs new file mode 100644 index 000000000..1e7e66c8c --- /dev/null +++ b/crates/rover-client/src/shared/build_errors.rs @@ -0,0 +1,174 @@ +use std::{ + error::Error, + fmt::{self, Display}, + iter::FromIterator, +}; + +use serde::{ser::SerializeSeq, Deserialize, Serialize, Serializer}; + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +pub struct BuildError { + message: String, + code: Option, + r#type: BuildErrorType, +} + +impl BuildError { + pub fn composition_error(message: String, code: Option) -> BuildError { + BuildError { + message, + code, + r#type: BuildErrorType::Composition, + } + } + + pub fn get_message(&self) -> String { + self.message.clone() + } + + pub fn get_code(&self) -> Option { + self.code.clone() + } +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum BuildErrorType { + Composition, +} + +impl Display for BuildError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(code) = &self.code { + write!(f, "{}: ", code)?; + } else { + write!(f, "UNKNOWN: ")?; + } + write!(f, "{}", &self.message) + } +} + +#[derive(Debug, Deserialize, Default, Clone, PartialEq)] +pub struct BuildErrors { + build_errors: Vec, +} + +impl Serialize for BuildErrors { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut sequence = serializer.serialize_seq(Some(self.build_errors.len()))?; + for build_error in &self.build_errors { + sequence.serialize_element(build_error)?; + } + sequence.end() + } +} + +impl BuildErrors { + pub fn new() -> Self { + BuildErrors { + build_errors: Vec::new(), + } + } + + pub fn len(&self) -> usize { + self.build_errors.len() + } + + pub fn length_string(&self) -> String { + let num_failures = self.build_errors.len(); + if num_failures == 0 { + unreachable!("No build errors were encountered while composing the supergraph."); + } + + match num_failures { + 1 => "1 build error".to_string(), + _ => format!("{} build errors", num_failures), + } + } + + pub fn push(&mut self, error: BuildError) { + self.build_errors.push(error); + } + + pub fn is_empty(&self) -> bool { + self.build_errors.is_empty() + } +} + +impl Display for BuildErrors { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for build_error in &self.build_errors { + writeln!(f, "{}", build_error)?; + } + Ok(()) + } +} + +impl From> for BuildErrors { + fn from(build_errors: Vec) -> Self { + BuildErrors { build_errors } + } +} + +impl FromIterator for BuildErrors { + fn from_iter>(iter: I) -> Self { + let mut c = BuildErrors::new(); + + for i in iter { + c.push(i); + } + + c + } +} + +impl Error for BuildError {} +impl Error for BuildErrors {} + +#[cfg(test)] +mod tests { + use super::{BuildError, BuildErrors}; + + use serde_json::{json, Value}; + + #[test] + fn it_can_serialize_empty_errors() { + let build_errors = BuildErrors::new(); + assert_eq!( + serde_json::to_string(&build_errors).expect("Could not serialize build errors"), + json!([]).to_string() + ); + } + + #[test] + fn it_can_serialize_some_build_errors() { + let build_errors: BuildErrors = vec![ + BuildError::composition_error("wow".to_string(), None), + BuildError::composition_error("boo".to_string(), Some("BOO".to_string())), + ] + .into(); + + let actual_value: Value = serde_json::from_str( + &serde_json::to_string(&build_errors) + .expect("Could not convert build errors to string"), + ) + .expect("Could not convert build error string to serde_json::Value"); + + let expected_value = json!([ + { + "code": null, + "message": "wow", + "type": "composition" + }, + { + "code": "BOO", + "message": "boo", + "type": "composition" + } + ]); + assert_eq!(actual_value, expected_value); + } +} diff --git a/crates/rover-client/src/shared/check_response.rs b/crates/rover-client/src/shared/check_response.rs index 135bda99c..2831ab25a 100644 --- a/crates/rover-client/src/shared/check_response.rs +++ b/crates/rover-client/src/shared/check_response.rs @@ -1,64 +1,107 @@ use std::cmp::Ordering; -use std::fmt; +use std::fmt::{self}; use std::str::FromStr; use crate::shared::GraphRef; use crate::RoverClientError; +use prettytable::format::consts::FORMAT_BOX_CHARS; use serde::Serialize; +use prettytable::{cell, row, Table}; +use serde_json::{json, Value}; + /// CheckResponse is the return type of the /// `graph` and `subgraph` check operations -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Serialize, Clone, PartialEq)] pub struct CheckResponse { - pub target_url: Option, - pub number_of_checked_operations: i64, - pub changes: Vec, - pub change_severity: ChangeSeverity, - pub num_failures: i64, + target_url: Option, + operation_check_count: u64, + changes: Vec, + #[serde(skip_serializing)] + result: ChangeSeverity, + failure_count: u64, } impl CheckResponse { - pub fn new( + pub fn try_new( target_url: Option, - number_of_checked_operations: i64, + operation_check_count: u64, changes: Vec, - change_severity: ChangeSeverity, - ) -> CheckResponse { - let mut num_failures = 0; + result: ChangeSeverity, + graph_ref: GraphRef, + ) -> Result { + let mut failure_count = 0; for change in &changes { if let ChangeSeverity::FAIL = change.severity { - num_failures += 1; + failure_count += 1; } } - CheckResponse { + let check_response = CheckResponse { target_url, - number_of_checked_operations, + operation_check_count, changes, - change_severity, - num_failures, - } - } + result, + failure_count, + }; - pub fn check_for_failures( - &self, - graph_ref: GraphRef, - ) -> Result { - match &self.num_failures.cmp(&0) { - Ordering::Equal => Ok(self.clone()), + match failure_count.cmp(&0) { + Ordering::Equal => Ok(check_response), Ordering::Greater => Err(RoverClientError::OperationCheckFailure { graph_ref, - check_response: self.clone(), + check_response, }), Ordering::Less => unreachable!("Somehow encountered a negative number of failures."), } } + + pub fn get_table(&self) -> String { + let num_changes = self.changes.len(); + + let mut msg = match num_changes { + 0 => "There were no changes detected in the composed schema.".to_string(), + _ => format!( + "Compared {} schema changes against {} operations", + num_changes, self.operation_check_count + ), + }; + + msg.push('\n'); + + if !self.changes.is_empty() { + let mut table = Table::new(); + + table.set_format(*FORMAT_BOX_CHARS); + + // bc => sets top row to be bold and center + table.add_row(row![bc => "Change", "Code", "Description"]); + for check in &self.changes { + table.add_row(row![check.severity, check.code, check.description]); + } + + msg.push_str(&table.to_string()); + } + + if let Some(url) = &self.target_url { + msg.push_str(&format!("View full details at {}", url)); + } + + msg + } + + pub fn get_failure_count(&self) -> u64 { + self.failure_count + } + + pub fn get_json(&self) -> Value { + json!(self) + } } /// ChangeSeverity indicates whether a proposed change /// in a GraphQL schema passed or failed the check -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Serialize, Clone, PartialEq)] pub enum ChangeSeverity { /// The proposed schema has passed the checks PASS, @@ -89,7 +132,7 @@ impl fmt::Display for ChangeSeverity { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Serialize, Clone, PartialEq)] pub struct SchemaChange { /// The code associated with a given change /// e.g. 'TYPE_REMOVED' diff --git a/crates/rover-client/src/shared/composition_error.rs b/crates/rover-client/src/shared/composition_error.rs deleted file mode 100644 index b45e51c5a..000000000 --- a/crates/rover-client/src/shared/composition_error.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::fmt::{self, Display}; - -#[derive(Debug, Clone, PartialEq)] -pub struct CompositionError { - pub message: String, - pub code: Option, -} - -impl Display for CompositionError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(code) = &self.code { - write!(f, "{}: ", code)?; - } else { - write!(f, "UNKNOWN: ")?; - } - write!(f, "{}", &self.message) - } -} diff --git a/crates/rover-client/src/shared/fetch_response.rs b/crates/rover-client/src/shared/fetch_response.rs index 5f4ab4381..b5ddc80bb 100644 --- a/crates/rover-client/src/shared/fetch_response.rs +++ b/crates/rover-client/src/shared/fetch_response.rs @@ -1,15 +1,19 @@ -#[derive(Debug, Clone, PartialEq)] +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] pub struct FetchResponse { pub sdl: Sdl, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Serialize, PartialEq)] pub struct Sdl { pub contents: String, + #[serde(skip_serializing)] pub r#type: SdlType, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(rename_all(serialize = "lowercase"))] pub enum SdlType { Graph, Subgraph, diff --git a/crates/rover-client/src/shared/graph_ref.rs b/crates/rover-client/src/shared/graph_ref.rs index 5cad87c3e..99d0c16f1 100644 --- a/crates/rover-client/src/shared/graph_ref.rs +++ b/crates/rover-client/src/shared/graph_ref.rs @@ -4,8 +4,9 @@ use std::str::FromStr; use crate::RoverClientError; use regex::Regex; +use serde::Serialize; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Serialize, Clone, PartialEq)] pub struct GraphRef { pub name: String, pub variant: String, diff --git a/crates/rover-client/src/shared/mod.rs b/crates/rover-client/src/shared/mod.rs index 31e0f3e97..e09603fb1 100644 --- a/crates/rover-client/src/shared/mod.rs +++ b/crates/rover-client/src/shared/mod.rs @@ -1,13 +1,13 @@ +mod build_errors; mod check_response; -mod composition_error; mod fetch_response; mod git_context; mod graph_ref; +pub use build_errors::{BuildError, BuildErrors}; pub use check_response::{ ChangeSeverity, CheckConfig, CheckResponse, SchemaChange, ValidationPeriod, }; -pub use composition_error::CompositionError; pub use fetch_response::{FetchResponse, Sdl, SdlType}; pub use git_context::GitContext; pub use graph_ref::GraphRef; diff --git a/crates/sdl-encoder/src/schema_def.rs b/crates/sdl-encoder/src/schema_def.rs index 19905380e..2d74b7c45 100644 --- a/crates/sdl-encoder/src/schema_def.rs +++ b/crates/sdl-encoder/src/schema_def.rs @@ -81,7 +81,7 @@ impl Default for SchemaDef { impl Display for SchemaDef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(description) = &self.description { - // We are determing on whether to have description formatted as + // We determine whether to have description formatted as // a multiline comment based on whether or not it already includes a // \n. match description.contains('\n') { diff --git a/docs/source/errors.md b/docs/source/errors.md index cc2a8f55f..0e258a69a 100644 --- a/docs/source/errors.md +++ b/docs/source/errors.md @@ -237,9 +237,9 @@ If you encountered this error while running introspection, you'll want to make s This error occurs when you propose a subgraph schema that could not be composed. -There are many reasons why you may run into composition errors. This error should include information about _why_ the proposed subgraph schema could not be composed. Error code references can be found [here](https://www.apollographql.com/docs/federation/errors/). +There are many reasons why you may run into build errors. This error should include information about _why_ the proposed subgraph schema could not be composed. Error code references can be found [here](https://www.apollographql.com/docs/federation/errors/). -Some composition errors are part of normal workflows. For instance, you may need to publish a subgraph that does not compose if you are trying to [migrate an entity or field](https://www.apollographql.com/docs/federation/entities/#migrating-entities-and-fields-advanced). +Some build errors are part of normal workflows. For instance, you may need to publish a subgraph that does not compose if you are trying to [migrate an entity or field](https://www.apollographql.com/docs/federation/entities/#migrating-entities-and-fields-advanced). ### E030 diff --git a/src/bin/rover.rs b/src/bin/rover.rs index cb663447c..25ce697d0 100644 --- a/src/bin/rover.rs +++ b/src/bin/rover.rs @@ -1,65 +1,16 @@ -use command::RoverStdout; use robot_panic::setup_panic; -use rover::*; -use sputnik::Session; +use rover::cli::Rover; use structopt::StructOpt; -use std::{process, thread}; - fn main() { setup_panic!(Metadata { - name: PKG_NAME.into(), - version: PKG_VERSION.into(), - authors: PKG_AUTHORS.into(), - homepage: PKG_HOMEPAGE.into(), - repository: PKG_REPOSITORY.into() + name: rover::PKG_NAME.into(), + version: rover::PKG_VERSION.into(), + authors: rover::PKG_AUTHORS.into(), + homepage: rover::PKG_HOMEPAGE.into(), + repository: rover::PKG_REPOSITORY.into() }); - if let Err(error) = run() { - tracing::debug!(?error); - eprint!("{}", error); - process::exit(1) - } else { - process::exit(0) - } -} - -fn run() -> Result<()> { - let app = cli::Rover::from_args(); - timber::init(app.log_level); - tracing::trace!(command_structure = ?app); - - // attempt to create a new `Session` to capture anonymous usage data - let output: RoverStdout = match Session::new(&app) { - // if successful, report the usage data in the background - Ok(session) => { - // kicks off the reporting on a background thread - let report_thread = thread::spawn(move || { - // log + ignore errors because it is not in the critical path - let _ = session.report().map_err(|telemetry_error| { - tracing::debug!(?telemetry_error); - telemetry_error - }); - }); - - // kicks off the app on the main thread - // don't return an error with ? quite yet - // since we still want to report the usage data - let app_result = app.run(); - - // makes sure the reporting finishes in the background - // before continuing. - // ignore errors because it is not in the critical path - let _ = report_thread.join(); - - // return result of app execution - // now that we have reported our usage data - app_result - } - - // otherwise just run the app without reporting - Err(_) => app.run(), - }?; - output.print(); - Ok(()) + let app = Rover::from_args(); + app.run(); } diff --git a/src/cli.rs b/src/cli.rs index 67224b47c..157548841 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,21 +1,25 @@ +use camino::Utf8PathBuf; use reqwest::blocking::Client; use serde::Serialize; use structopt::{clap::AppSettings, StructOpt}; -use crate::command::{self, RoverStdout}; +use crate::command::output::JsonOutput; +use crate::command::{self, RoverOutput}; use crate::utils::{ client::StudioClientConfig, env::{RoverEnv, RoverEnvKey}, - stringify::from_display, + stringify::option_from_display, version, }; -use crate::Result; +use crate::{anyhow, Result}; + use config::Config; use houston as config; use rover_client::shared::GitContext; +use sputnik::Session; use timber::{Level, LEVELS}; -use camino::Utf8PathBuf; +use std::{process, str::FromStr, thread}; #[derive(Debug, Serialize, StructOpt)] #[structopt( @@ -51,16 +55,20 @@ You can open the full documentation for Rover by running: ")] pub struct Rover { #[structopt(subcommand)] - pub command: Command, + command: Command, /// Specify Rover's log level #[structopt(long = "log", short = "l", global = true, possible_values = &LEVELS, case_insensitive = true)] - #[serde(serialize_with = "from_display")] - pub log_level: Option, + #[serde(serialize_with = "option_from_display")] + log_level: Option, + + /// Use json output + #[structopt(long = "output", default_value = "plain", possible_values = &["json", "plain"], case_insensitive = true, global = true)] + output_type: OutputType, #[structopt(skip)] #[serde(skip_serializing)] - pub env_store: RoverEnv, + pub(crate) env_store: RoverEnv, #[structopt(skip)] #[serde(skip_serializing)] @@ -68,6 +76,96 @@ pub struct Rover { } impl Rover { + pub fn run(&self) -> ! { + timber::init(self.log_level); + tracing::trace!(command_structure = ?self); + + // attempt to create a new `Session` to capture anonymous usage data + let rover_output = match Session::new(self) { + // if successful, report the usage data in the background + Ok(session) => { + // kicks off the reporting on a background thread + let report_thread = thread::spawn(move || { + // log + ignore errors because it is not in the critical path + let _ = session.report().map_err(|telemetry_error| { + tracing::debug!(?telemetry_error); + telemetry_error + }); + }); + + // kicks off the app on the main thread + // don't return an error with ? quite yet + // since we still want to report the usage data + let app_result = self.execute_command(); + + // makes sure the reporting finishes in the background + // before continuing. + // ignore errors because it is not in the critical path + let _ = report_thread.join(); + + // return result of app execution + // now that we have reported our usage data + app_result + } + + // otherwise just run the app without reporting + Err(_) => self.execute_command(), + }; + + match rover_output { + Ok(output) => { + match self.output_type { + OutputType::Plain => output.print(), + OutputType::Json => println!("{}", JsonOutput::from(output)), + } + process::exit(0); + } + Err(error) => { + match self.output_type { + OutputType::Json => println!("{}", JsonOutput::from(error)), + OutputType::Plain => { + tracing::debug!(?error); + error.print(); + } + } + process::exit(1); + } + } + } + + pub fn execute_command(&self) -> Result { + // before running any commands, we check if rover is up to date + // this only happens once a day automatically + // we skip this check for the `rover update` commands, since they + // do their own checks + + if let Command::Update(_) = &self.command { /* skip check */ + } else { + let config = self.get_rover_config(); + if let Ok(config) = config { + let _ = version::check_for_update(config, false, self.get_reqwest_client()); + } + } + + match &self.command { + Command::Config(command) => command.run(self.get_client_config()?), + Command::Supergraph(command) => command.run(self.get_client_config()?), + Command::Docs(command) => command.run(), + Command::Graph(command) => { + command.run(self.get_client_config()?, self.get_git_context()?) + } + Command::Subgraph(command) => { + command.run(self.get_client_config()?, self.get_git_context()?) + } + Command::Update(command) => { + command.run(self.get_rover_config()?, self.get_reqwest_client()) + } + Command::Install(command) => command.run(self.get_install_override_path()?), + Command::Info(command) => command.run(), + Command::Explain(command) => command.run(), + } + } + pub(crate) fn get_rover_config(&self) -> Result { let override_home: Option = self .env_store @@ -146,37 +244,26 @@ pub enum Command { Explain(command::Explain), } -impl Rover { - pub fn run(&self) -> Result { - // before running any commands, we check if rover is up to date - // this only happens once a day automatically - // we skip this check for the `rover update` commands, since they - // do their own checks +#[derive(Debug, Serialize, Clone, PartialEq)] +pub enum OutputType { + Plain, + Json, +} - if let Command::Update(_) = &self.command { /* skip check */ - } else { - let config = self.get_rover_config(); - if let Ok(config) = config { - let _ = version::check_for_update(config, false, self.get_reqwest_client()); - } - } +impl FromStr for OutputType { + type Err = anyhow::Error; - match &self.command { - Command::Config(command) => command.run(self.get_client_config()?), - Command::Supergraph(command) => command.run(self.get_client_config()?), - Command::Docs(command) => command.run(), - Command::Graph(command) => { - command.run(self.get_client_config()?, self.get_git_context()?) - } - Command::Subgraph(command) => { - command.run(self.get_client_config()?, self.get_git_context()?) - } - Command::Update(command) => { - command.run(self.get_rover_config()?, self.get_reqwest_client()) - } - Command::Install(command) => command.run(self.get_install_override_path()?), - Command::Info(command) => command.run(), - Command::Explain(command) => command.run(), + fn from_str(input: &str) -> std::result::Result { + match input { + "plain" => Ok(Self::Plain), + "json" => Ok(Self::Json), + _ => Err(anyhow!("Invalid output type.")), } } } + +impl Default for OutputType { + fn default() -> Self { + OutputType::Plain + } +} diff --git a/src/command/config/auth.rs b/src/command/config/auth.rs index ae15cb16f..90951bcec 100644 --- a/src/command/config/auth.rs +++ b/src/command/config/auth.rs @@ -5,7 +5,7 @@ use structopt::StructOpt; use config::Profile; use houston as config; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::{anyhow, Result}; #[derive(Debug, Serialize, StructOpt)] @@ -26,13 +26,13 @@ pub struct Auth { } impl Auth { - pub fn run(&self, config: config::Config) -> Result { + pub fn run(&self, config: config::Config) -> Result { let api_key = api_key_prompt()?; Profile::set_api_key(&self.profile_name, &config, &api_key)?; Profile::get_credential(&self.profile_name, &config).map(|_| { eprintln!("Successfully saved API key."); })?; - Ok(RoverStdout::None) + Ok(RoverOutput::EmptySuccess) } } diff --git a/src/command/config/clear.rs b/src/command/config/clear.rs index a587885cb..5ebc9f214 100644 --- a/src/command/config/clear.rs +++ b/src/command/config/clear.rs @@ -1,7 +1,7 @@ use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::Result; use houston as config; @@ -13,9 +13,9 @@ use houston as config; pub struct Clear {} impl Clear { - pub fn run(&self, config: config::Config) -> Result { + pub fn run(&self, config: config::Config) -> Result { config.clear()?; eprintln!("Successfully cleared all configuration."); - Ok(RoverStdout::None) + Ok(RoverOutput::EmptySuccess) } } diff --git a/src/command/config/delete.rs b/src/command/config/delete.rs index 9c9664b4f..e282ae0bb 100644 --- a/src/command/config/delete.rs +++ b/src/command/config/delete.rs @@ -3,7 +3,7 @@ use structopt::StructOpt; use houston as config; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::Result; #[derive(Debug, Serialize, StructOpt)] @@ -20,9 +20,9 @@ pub struct Delete { } impl Delete { - pub fn run(&self, config: config::Config) -> Result { + pub fn run(&self, config: config::Config) -> Result { config::Profile::delete(&self.name, &config)?; eprintln!("Successfully deleted profile \"{}\"", &self.name); - Ok(RoverStdout::None) + Ok(RoverOutput::EmptySuccess) } } diff --git a/src/command/config/list.rs b/src/command/config/list.rs index 907bc0f9c..9bd56b892 100644 --- a/src/command/config/list.rs +++ b/src/command/config/list.rs @@ -4,15 +4,15 @@ use structopt::StructOpt; use crate::Result; use houston as config; -use crate::command::RoverStdout; +use crate::command::RoverOutput; #[derive(Serialize, Debug, StructOpt)] /// List all configuration profiles pub struct List {} impl List { - pub fn run(&self, config: config::Config) -> Result { + pub fn run(&self, config: config::Config) -> Result { let profiles = config::Profile::list(&config)?; - Ok(RoverStdout::Profiles(profiles)) + Ok(RoverOutput::Profiles(profiles)) } } diff --git a/src/command/config/mod.rs b/src/command/config/mod.rs index a9eed151a..71a1b87aa 100644 --- a/src/command/config/mod.rs +++ b/src/command/config/mod.rs @@ -7,7 +7,7 @@ mod whoami; use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::utils::client::StudioClientConfig; use crate::Result; @@ -36,7 +36,7 @@ pub enum Command { } impl Config { - pub fn run(&self, client_config: StudioClientConfig) -> Result { + pub fn run(&self, client_config: StudioClientConfig) -> Result { match &self.command { Command::Auth(command) => command.run(client_config.config), Command::List(command) => command.run(client_config.config), diff --git a/src/command/config/whoami.rs b/src/command/config/whoami.rs index 7398f9ceb..c0d7f7d35 100644 --- a/src/command/config/whoami.rs +++ b/src/command/config/whoami.rs @@ -6,7 +6,7 @@ use structopt::StructOpt; use houston::CredentialOrigin; use crate::anyhow; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::utils::client::StudioClientConfig; use crate::utils::env::RoverEnvKey; use crate::Result; @@ -22,7 +22,7 @@ pub struct WhoAmI { } impl WhoAmI { - pub fn run(&self, client_config: StudioClientConfig) -> Result { + pub fn run(&self, client_config: StudioClientConfig) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; eprintln!("Checking identity of your API key against the registry."); @@ -80,6 +80,6 @@ impl WhoAmI { eprintln!("{}", message); - Ok(RoverStdout::None) + Ok(RoverOutput::EmptySuccess) } } diff --git a/src/command/docs/list.rs b/src/command/docs/list.rs index 6a1190f2b..3f951f57a 100644 --- a/src/command/docs/list.rs +++ b/src/command/docs/list.rs @@ -1,4 +1,4 @@ -use crate::{command::RoverStdout, Result}; +use crate::{command::RoverOutput, Result}; use super::shortlinks; @@ -9,8 +9,8 @@ use structopt::StructOpt; pub struct List {} impl List { - pub fn run(&self) -> Result { - Ok(RoverStdout::DocsList( + pub fn run(&self) -> Result { + Ok(RoverOutput::DocsList( shortlinks::get_shortlinks_with_description(), )) } diff --git a/src/command/docs/mod.rs b/src/command/docs/mod.rs index 0950c0eb4..ce3c0f6c8 100644 --- a/src/command/docs/mod.rs +++ b/src/command/docs/mod.rs @@ -5,7 +5,7 @@ pub mod shortlinks; use serde::Serialize; use structopt::StructOpt; -use crate::{command::RoverStdout, Result}; +use crate::{command::RoverOutput, Result}; #[derive(Debug, Serialize, StructOpt)] pub struct Docs { @@ -23,7 +23,7 @@ pub enum Command { } impl Docs { - pub fn run(&self) -> Result { + pub fn run(&self) -> Result { match &self.command { Command::List(command) => command.run(), Command::Open(command) => command.run(), diff --git a/src/command/docs/open.rs b/src/command/docs/open.rs index 7e43ff6db..8f39136d3 100644 --- a/src/command/docs/open.rs +++ b/src/command/docs/open.rs @@ -1,4 +1,4 @@ -use crate::{anyhow, command::RoverStdout, Result}; +use crate::{anyhow, command::RoverOutput, Result}; use super::shortlinks; @@ -15,7 +15,7 @@ pub struct Open { } impl Open { - pub fn run(&self) -> Result { + pub fn run(&self) -> Result { let url = shortlinks::get_url_from_slug(&self.slug); let yellow_browser_var = format!("{}", Yellow.normal().paint("$BROWSER")); let cyan_url = format!("{}", Cyan.normal().paint(&url)); @@ -40,6 +40,6 @@ impl Open { Ok(()) }?; - Ok(RoverStdout::None) + Ok(RoverOutput::EmptySuccess) } } diff --git a/src/command/docs/shortlinks.rs b/src/command/docs/shortlinks.rs index 18833e758..6471824c0 100644 --- a/src/command/docs/shortlinks.rs +++ b/src/command/docs/shortlinks.rs @@ -1,9 +1,9 @@ pub const URL_BASE: &str = "https://go.apollo.dev/r"; -use std::collections::HashMap; +use std::collections::BTreeMap; -pub fn get_shortlinks_with_description() -> HashMap<&'static str, &'static str> { - let mut links = HashMap::new(); +pub fn get_shortlinks_with_description() -> BTreeMap<&'static str, &'static str> { + let mut links = BTreeMap::new(); links.insert("docs", "Rover's Documentation Homepage"); links.insert("api-keys", "Understanding Apollo's API Keys"); links.insert("contributing", "Contributing to Rover"); diff --git a/src/command/explain.rs b/src/command/explain.rs index 4c2d3a5ad..4de0510d7 100644 --- a/src/command/explain.rs +++ b/src/command/explain.rs @@ -1,4 +1,4 @@ -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::error::metadata::code::Code; use crate::Result; use serde::Serialize; @@ -12,8 +12,8 @@ pub struct Explain { } impl Explain { - pub fn run(&self) -> Result { + pub fn run(&self) -> Result { let explanation = &self.code.explain(); - Ok(RoverStdout::Markdown(explanation.clone())) + Ok(RoverOutput::ErrorExplanation(explanation.clone())) } } diff --git a/src/command/graph/check.rs b/src/command/graph/check.rs index 97035155b..d18851021 100644 --- a/src/command/graph/check.rs +++ b/src/command/graph/check.rs @@ -4,7 +4,7 @@ use structopt::StructOpt; use rover_client::operations::graph::check::{self, GraphCheckInput}; use rover_client::shared::{CheckConfig, GitContext, GraphRef, ValidationPeriod}; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::utils::client::StudioClientConfig; use crate::utils::loaders::load_schema_from_flag; use crate::utils::parsers::{ @@ -53,7 +53,7 @@ impl Check { &self, client_config: StudioClientConfig, git_context: GitContext, - ) -> Result { + ) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; let proposed_schema = load_schema_from_flag(&self.schema, std::io::stdin())?; @@ -76,6 +76,6 @@ impl Check { &client, )?; - Ok(RoverStdout::CheckResponse(res)) + Ok(RoverOutput::CheckResponse(res)) } } diff --git a/src/command/graph/fetch.rs b/src/command/graph/fetch.rs index 2e01fc0f8..478811b2d 100644 --- a/src/command/graph/fetch.rs +++ b/src/command/graph/fetch.rs @@ -5,7 +5,7 @@ use structopt::StructOpt; use rover_client::operations::graph::fetch::{self, GraphFetchInput}; use rover_client::shared::GraphRef; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::utils::client::StudioClientConfig; use crate::Result; @@ -24,7 +24,7 @@ pub struct Fetch { } impl Fetch { - pub fn run(&self, client_config: StudioClientConfig) -> Result { + pub fn run(&self, client_config: StudioClientConfig) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; let graph_ref = self.graph.to_string(); eprintln!( @@ -40,6 +40,6 @@ impl Fetch { &client, )?; - Ok(RoverStdout::FetchResponse(fetch_response)) + Ok(RoverOutput::FetchResponse(fetch_response)) } } diff --git a/src/command/graph/introspect.rs b/src/command/graph/introspect.rs index 991f09e5d..da66e047b 100644 --- a/src/command/graph/introspect.rs +++ b/src/command/graph/introspect.rs @@ -10,7 +10,7 @@ use rover_client::{ operations::graph::introspect::{self, GraphIntrospectInput}, }; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::utils::parsers::parse_header; #[derive(Debug, Serialize, StructOpt)] @@ -31,7 +31,7 @@ pub struct Introspect { } impl Introspect { - pub fn run(&self, client: Client) -> Result { + pub fn run(&self, client: Client) -> Result { let client = GraphQLClient::new(&self.endpoint.to_string(), client)?; // add the flag headers to a hashmap to pass along to rover-client @@ -44,7 +44,7 @@ impl Introspect { let introspection_response = introspect::run(GraphIntrospectInput { headers }, &client)?; - Ok(RoverStdout::Introspection( + Ok(RoverOutput::Introspection( introspection_response.schema_sdl, )) } diff --git a/src/command/graph/mod.rs b/src/command/graph/mod.rs index 410690612..0603fb4e5 100644 --- a/src/command/graph/mod.rs +++ b/src/command/graph/mod.rs @@ -6,7 +6,7 @@ mod publish; use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::utils::client::StudioClientConfig; use crate::Result; @@ -39,7 +39,7 @@ impl Graph { &self, client_config: StudioClientConfig, git_context: GitContext, - ) -> Result { + ) -> Result { match &self.command { Command::Check(command) => command.run(client_config, git_context), Command::Fetch(command) => command.run(client_config), diff --git a/src/command/graph/publish.rs b/src/command/graph/publish.rs index d005b993c..c6208e5df 100644 --- a/src/command/graph/publish.rs +++ b/src/command/graph/publish.rs @@ -2,10 +2,10 @@ use ansi_term::Colour::{Cyan, Yellow}; use serde::Serialize; use structopt::StructOpt; -use rover_client::operations::graph::publish::{self, GraphPublishInput, GraphPublishResponse}; +use rover_client::operations::graph::publish::{self, GraphPublishInput}; use rover_client::shared::{GitContext, GraphRef}; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::utils::client::StudioClientConfig; use crate::utils::loaders::load_schema_from_flag; use crate::utils::parsers::{parse_schema_source, SchemaSource}; @@ -36,7 +36,7 @@ impl Publish { &self, client_config: StudioClientConfig, git_context: GitContext, - ) -> Result { + ) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; let graph_ref = self.graph.to_string(); eprintln!( @@ -58,39 +58,9 @@ impl Publish { &client, )?; - let hash = handle_response(&self.graph, publish_response); - Ok(RoverStdout::SchemaHash(hash)) - } -} - -/// handle all output logging from operation -fn handle_response(graph: &GraphRef, response: GraphPublishResponse) -> String { - eprintln!( - "{}#{} Published successfully {}", - graph, response.schema_hash, response.change_summary - ); - - response.schema_hash -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn handle_response_doesnt_err() { - let expected_hash = "123456".to_string(); - let graph = GraphRef { - name: "harambe".to_string(), - variant: "inside-job".to_string(), - }; - let actual_hash = handle_response( - &graph, - GraphPublishResponse { - schema_hash: expected_hash.clone(), - change_summary: "".to_string(), - }, - ); - assert_eq!(actual_hash, expected_hash); + Ok(RoverOutput::GraphPublishResponse { + graph_ref: self.graph.clone(), + publish_response, + }) } } diff --git a/src/command/info.rs b/src/command/info.rs index 55756b6e0..0f719690c 100644 --- a/src/command/info.rs +++ b/src/command/info.rs @@ -1,4 +1,4 @@ -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::Result; use crate::PKG_VERSION; use serde::Serialize; @@ -9,7 +9,7 @@ use structopt::StructOpt; pub struct Info {} impl Info { - pub fn run(&self) -> Result { + pub fn run(&self) -> Result { let os = os_info::get(); // something like "/usr/bin/zsh" or "Unknown" @@ -28,6 +28,6 @@ impl Info { PKG_VERSION, location, os, shell ); - Ok(RoverStdout::None) + Ok(RoverOutput::EmptySuccess) } } diff --git a/src/command/install/mod.rs b/src/command/install/mod.rs index ca2f34be3..293a4b941 100644 --- a/src/command/install/mod.rs +++ b/src/command/install/mod.rs @@ -5,7 +5,7 @@ use structopt::StructOpt; use binstall::Installer; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::PKG_NAME; use crate::{anyhow, Context, Result}; use crate::{command::docs::shortlinks, utils::env::RoverEnvKey}; @@ -20,7 +20,7 @@ pub struct Install { } impl Install { - pub fn run(&self, override_install_path: Option) -> Result { + pub fn run(&self, override_install_path: Option) -> Result { let binary_name = PKG_NAME.to_string(); if let Ok(executable_location) = env::current_exe() { let executable_location = Utf8PathBuf::try_from(executable_location)?; @@ -68,7 +68,7 @@ impl Install { } else { eprintln!("{} was not installed. To override the existing installation, you can pass the `--force` flag to the installer.", &binary_name); } - Ok(RoverStdout::None) + Ok(RoverOutput::EmptySuccess) } else { Err(anyhow!("Failed to get the current executable's path.").into()) } diff --git a/src/command/mod.rs b/src/command/mod.rs index 7d0b6c04b..7cf6e8bd3 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -16,7 +16,7 @@ pub use explain::Explain; pub use graph::Graph; pub use info::Info; pub use install::Install; -pub use output::RoverStdout; +pub use output::RoverOutput; pub use subgraph::Subgraph; pub use supergraph::Supergraph; pub use update::Update; diff --git a/src/command/output.rs b/src/command/output.rs index 141495f8e..146139540 100644 --- a/src/command/output.rs +++ b/src/command/output.rs @@ -1,43 +1,65 @@ -use std::fmt::Debug; -use std::{collections::HashMap, fmt::Display}; +use std::collections::BTreeMap; +use std::fmt::{self, Debug, Display}; +use crate::error::RoverError; use crate::utils::table::{self, cell, row}; -use ansi_term::{Colour::Yellow, Style}; +use ansi_term::{ + Colour::{Cyan, Red, Yellow}, + Style, +}; use atty::Stream; use crossterm::style::Attribute::Underlined; +use rover_client::operations::graph::publish::GraphPublishResponse; +use rover_client::operations::subgraph::delete::SubgraphDeleteResponse; use rover_client::operations::subgraph::list::SubgraphListResponse; -use rover_client::shared::{CheckResponse, FetchResponse, SdlType}; +use rover_client::operations::subgraph::publish::SubgraphPublishResponse; +use rover_client::shared::{CheckResponse, FetchResponse, GraphRef, SdlType}; +use rover_client::RoverClientError; +use serde::Serialize; +use serde_json::{json, Value}; use termimad::MadSkin; -/// RoverStdout defines all of the different types of data that are printed -/// to `stdout`. Every one of Rover's commands should return `anyhow::Result` +/// RoverOutput defines all of the different types of data that are printed +/// to `stdout`. Every one of Rover's commands should return `anyhow::Result` /// If the command needs to output some type of data, it should be structured -/// in this enum, and its print logic should be handled in `RoverStdout::print` +/// in this enum, and its print logic should be handled in `RoverOutput::print` /// /// Not all commands will output machine readable information, and those should -/// return `Ok(RoverStdout::None)`. If a new command is added and it needs to +/// return `Ok(RoverOutput::EmptySuccess)`. If a new command is added and it needs to /// return something that is not described well in this enum, it should be added. #[derive(Clone, PartialEq, Debug)] -pub enum RoverStdout { - DocsList(HashMap<&'static str, &'static str>), +pub enum RoverOutput { + DocsList(BTreeMap<&'static str, &'static str>), FetchResponse(FetchResponse), CoreSchema(String), - SchemaHash(String), SubgraphList(SubgraphListResponse), CheckResponse(CheckResponse), - VariantList(Vec), + GraphPublishResponse { + graph_ref: GraphRef, + publish_response: GraphPublishResponse, + }, + SubgraphPublishResponse { + graph_ref: GraphRef, + subgraph: String, + publish_response: SubgraphPublishResponse, + }, + SubgraphDeleteResponse { + graph_ref: GraphRef, + subgraph: String, + dry_run: bool, + delete_response: SubgraphDeleteResponse, + }, Profiles(Vec), Introspection(String), - Markdown(String), - PlainText(String), - None, + ErrorExplanation(String), + EmptySuccess, } -impl RoverStdout { +impl RoverOutput { pub fn print(&self) { match self { - RoverStdout::DocsList(shortlinks) => { + RoverOutput::DocsList(shortlinks) => { eprintln!( "You can open any of these documentation pages by running {}.\n", Yellow.normal().paint("`rover docs open `") @@ -51,22 +73,108 @@ impl RoverStdout { } println!("{}", table); } - RoverStdout::FetchResponse(fetch_response) => { + RoverOutput::FetchResponse(fetch_response) => { match fetch_response.sdl.r#type { SdlType::Graph | SdlType::Subgraph => print_descriptor("SDL"), SdlType::Supergraph => print_descriptor("Supergraph SDL"), } print_content(&fetch_response.sdl.contents); } - RoverStdout::CoreSchema(csdl) => { + RoverOutput::GraphPublishResponse { + graph_ref, + publish_response, + } => { + eprintln!( + "{}#{} Published successfully {}", + graph_ref, publish_response.api_schema_hash, publish_response.change_summary + ); + print_one_line_descriptor("Schema Hash"); + print_content(&publish_response.api_schema_hash); + } + RoverOutput::SubgraphPublishResponse { + graph_ref, + subgraph, + publish_response, + } => { + if publish_response.subgraph_was_created { + eprintln!( + "A new subgraph called '{}' for the '{}' graph was created", + subgraph, graph_ref + ); + } else { + eprintln!( + "The '{}' subgraph for the '{}' graph was updated", + subgraph, graph_ref + ); + } + + if publish_response.supergraph_was_updated { + eprintln!("The gateway for the '{}' graph was updated with a new schema, composed from the updated '{}' subgraph", graph_ref, subgraph); + } else { + eprintln!( + "The gateway for the '{}' graph was NOT updated with a new schema", + graph_ref + ); + } + + if !publish_response.build_errors.is_empty() { + let warn_prefix = Red.normal().paint("WARN:"); + eprintln!("{} The following build errors occurred:", warn_prefix,); + eprintln!("{}", &publish_response.build_errors); + } + } + RoverOutput::SubgraphDeleteResponse { + graph_ref, + subgraph, + dry_run, + delete_response, + } => { + let warn_prefix = Red.normal().paint("WARN:"); + if *dry_run { + if !delete_response.build_errors.is_empty() { + eprintln!( + "{} Deleting the {} subgraph from {} would result in the following build errors:", + warn_prefix, + Cyan.normal().paint(subgraph), + Cyan.normal().paint(graph_ref.to_string()), + ); + + eprintln!("{}", &delete_response.build_errors); + eprintln!("{} This is only a prediction. If the graph changes before confirming, these errors could change.", warn_prefix); + } else { + eprintln!("{} At the time of checking, there would be no build errors resulting from the deletion of this subgraph.", warn_prefix); + eprintln!("{} This is only a prediction. If the graph changes before confirming, there could be build errors.", warn_prefix) + } + } else { + if delete_response.supergraph_was_updated { + eprintln!( + "The {} subgraph was removed from {}. Remaining subgraphs were composed.", + Cyan.normal().paint(subgraph), + Cyan.normal().paint(graph_ref.to_string()), + ) + } else { + eprintln!( + "{} The gateway for {} was not updated. See errors below.", + warn_prefix, + Cyan.normal().paint(graph_ref.to_string()) + ) + } + + if !delete_response.build_errors.is_empty() { + eprintln!( + "{} There were build errors as a result of deleting the subgraph:", + warn_prefix, + ); + + eprintln!("{}", &delete_response.build_errors); + } + } + } + RoverOutput::CoreSchema(csdl) => { print_descriptor("CoreSchema"); print_content(&csdl); } - RoverStdout::SchemaHash(hash) => { - print_one_line_descriptor("Schema Hash"); - print_content(&hash); - } - RoverStdout::SubgraphList(details) => { + RoverOutput::SubgraphList(details) => { let mut table = table::get_table(); // bc => sets top row to be bold and center @@ -83,7 +191,7 @@ impl RoverStdout { } else { url }; - let formatted_updated_at: String = if let Some(dt) = subgraph.updated_at { + let formatted_updated_at: String = if let Some(dt) = subgraph.updated_at.local { dt.format("%Y-%m-%d %H:%M:%S %Z").to_string() } else { "N/A".to_string() @@ -98,16 +206,11 @@ impl RoverStdout { details.root_url, details.graph_ref.name ); } - RoverStdout::CheckResponse(check_response) => { - print_check_response(check_response); - } - RoverStdout::VariantList(variants) => { - print_descriptor("Variants"); - for variant in variants { - println!("{}", variant); - } + RoverOutput::CheckResponse(check_response) => { + print_descriptor("Check Result"); + print_content(check_response.get_table()); } - RoverStdout::Profiles(profiles) => { + RoverOutput::Profiles(profiles) => { if profiles.is_empty() { eprintln!("No profiles found."); } else { @@ -118,23 +221,101 @@ impl RoverStdout { println!("{}", profile); } } - RoverStdout::Introspection(introspection_response) => { + RoverOutput::Introspection(introspection_response) => { print_descriptor("Introspection Response"); print_content(&introspection_response); } - RoverStdout::Markdown(markdown_string) => { + RoverOutput::ErrorExplanation(explanation) => { // underline bolded md let mut skin = MadSkin::default(); skin.bold.add_attr(Underlined); - println!("{}", skin.inline(&markdown_string)); + println!("{}", skin.inline(&explanation)); + } + RoverOutput::EmptySuccess => (), + } + } + + pub(crate) fn get_internal_data_json(&self) -> Value { + match self { + RoverOutput::DocsList(shortlinks) => { + let mut shortlink_vec = Vec::with_capacity(shortlinks.len()); + for (shortlink_slug, shortlink_description) in shortlinks { + shortlink_vec.push( + json!({"slug": shortlink_slug, "description": shortlink_description }), + ); + } + json!({ "shortlinks": shortlink_vec }) } - RoverStdout::PlainText(text) => { - println!("{}", text); + RoverOutput::FetchResponse(fetch_response) => json!(fetch_response), + RoverOutput::CoreSchema(csdl) => json!({ "core_schema": csdl }), + RoverOutput::GraphPublishResponse { + graph_ref: _, + publish_response, + } => json!(publish_response), + RoverOutput::SubgraphPublishResponse { + graph_ref: _, + subgraph: _, + publish_response, + } => json!(publish_response), + RoverOutput::SubgraphDeleteResponse { + graph_ref: _, + subgraph: _, + dry_run: _, + delete_response, + } => { + json!(delete_response) } - RoverStdout::None => (), + RoverOutput::SubgraphList(list_response) => json!(list_response), + RoverOutput::CheckResponse(check_response) => check_response.get_json(), + RoverOutput::Profiles(profiles) => json!({ "profiles": profiles }), + RoverOutput::Introspection(introspection_response) => { + json!({ "introspection_response": introspection_response }) + } + RoverOutput::ErrorExplanation(explanation_markdown) => { + json!({ "explanation_markdown": explanation_markdown }) + } + RoverOutput::EmptySuccess => json!(null), } } + + pub(crate) fn get_internal_error_json(&self) -> Value { + let rover_error = match self { + RoverOutput::SubgraphPublishResponse { + graph_ref, + subgraph, + publish_response, + } => { + if !publish_response.build_errors.is_empty() { + Some(RoverError::from(RoverClientError::SubgraphBuildErrors { + subgraph: subgraph.clone(), + graph_ref: graph_ref.clone(), + source: publish_response.build_errors.clone(), + })) + } else { + None + } + } + RoverOutput::SubgraphDeleteResponse { + graph_ref, + subgraph, + dry_run: _, + delete_response, + } => { + if !delete_response.build_errors.is_empty() { + Some(RoverError::from(RoverClientError::SubgraphBuildErrors { + subgraph: subgraph.clone(), + graph_ref: graph_ref.clone(), + source: delete_response.build_errors.clone(), + })) + } else { + None + } + } + _ => None, + }; + json!(rover_error) + } } fn print_descriptor(descriptor: impl Display) { @@ -159,32 +340,755 @@ fn print_content(content: impl Display) { } } -pub(crate) fn print_check_response(check_response: &CheckResponse) { - let num_changes = check_response.changes.len(); +#[derive(Debug, Clone, Serialize)] +pub(crate) struct JsonOutput { + json_version: JsonVersion, + data: JsonData, + error: Value, +} + +impl JsonOutput { + pub(crate) fn success(data: Value, error: Value) -> JsonOutput { + JsonOutput { + json_version: JsonVersion::OneBeta, + data: JsonData::success(data), + error, + } + } + + pub(crate) fn failure(data: Value, error: Value) -> JsonOutput { + JsonOutput { + json_version: JsonVersion::OneBeta, + data: JsonData::failure(data), + error, + } + } +} + +impl fmt::Display for JsonOutput { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", json!(self)) + } +} + +impl From for JsonOutput { + fn from(error: RoverError) -> Self { + let data = error.get_internal_data_json(); + let error = error.get_internal_error_json(); + JsonOutput::failure(data, error) + } +} + +impl From for JsonOutput { + fn from(output: RoverOutput) -> Self { + let data = output.get_internal_data_json(); + let error = output.get_internal_error_json(); + JsonOutput::success(data, error) + } +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct JsonData { + #[serde(flatten)] + inner: Value, + success: bool, +} + +impl JsonData { + pub(crate) fn success(inner: Value) -> JsonData { + JsonData { + inner, + success: true, + } + } + + pub(crate) fn failure(inner: Value) -> JsonData { + JsonData { + inner, + success: false, + } + } +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) enum JsonVersion { + #[serde(rename = "1.beta")] + OneBeta, +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; - let msg = match num_changes { - 0 => "There were no changes detected in the composed schema.".to_string(), - _ => format!( - "Compared {} schema changes against {} operations", - num_changes, check_response.number_of_checked_operations - ), + use assert_json_diff::assert_json_eq; + use chrono::{DateTime, Local, Utc}; + use rover_client::{ + operations::{ + graph::publish::{ChangeSummary, FieldChanges, TypeChanges}, + subgraph::{ + delete::SubgraphDeleteResponse, + list::{SubgraphInfo, SubgraphUpdatedAt}, + }, + }, + shared::{BuildError, BuildErrors, ChangeSeverity, SchemaChange, Sdl}, }; - eprintln!("{}", &msg); + use crate::anyhow; + + use super::*; + + #[test] + fn docs_list_json() { + let mut mock_shortlinks = BTreeMap::new(); + mock_shortlinks.insert("slug_one", "description_one"); + mock_shortlinks.insert("slug_two", "description_two"); + let actual_json: JsonOutput = RoverOutput::DocsList(mock_shortlinks).into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "shortlinks": [ + { + "slug": "slug_one", + "description": "description_one" + }, + { + "slug": "slug_two", + "description": "description_two" + } + ], + "success": true + }, + "error": null + }); + assert_json_eq!(expected_json, actual_json); + } + + #[test] + fn fetch_response_json() { + let mock_fetch_response = FetchResponse { + sdl: Sdl { + contents: "sdl contents".to_string(), + r#type: SdlType::Subgraph, + }, + }; + let actual_json: JsonOutput = RoverOutput::FetchResponse(mock_fetch_response).into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "sdl": { + "contents": "sdl contents", + }, + "success": true + }, + "error": null + }); + assert_json_eq!(expected_json, actual_json); + } + + #[test] + fn core_schema_json() { + let mock_core_schema = "core schema contents".to_string(); + let actual_json: JsonOutput = RoverOutput::CoreSchema(mock_core_schema).into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "core_schema": "core schema contents", + "success": true + }, + "error": null + }); + assert_json_eq!(expected_json, actual_json); + } + + #[test] + fn subgraph_list_json() { + let now_utc: DateTime = Utc::now(); + let now_local: DateTime = now_utc.into(); + let mock_subgraph_list_response = SubgraphListResponse { + subgraphs: vec![ + SubgraphInfo { + name: "subgraph one".to_string(), + url: Some("http://localhost:4001".to_string()), + updated_at: SubgraphUpdatedAt { + local: Some(now_local), + utc: Some(now_utc), + }, + }, + SubgraphInfo { + name: "subgraph two".to_string(), + url: None, + updated_at: SubgraphUpdatedAt { + local: None, + utc: None, + }, + }, + ], + root_url: "https://studio.apollographql.com/".to_string(), + graph_ref: GraphRef { + name: "graph".to_string(), + variant: "current".to_string(), + }, + }; + let actual_json: JsonOutput = RoverOutput::SubgraphList(mock_subgraph_list_response).into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "subgraphs": [ + { + "name": "subgraph one", + "url": "http://localhost:4001", + "updated_at": { + "local": now_local, + "utc": now_utc + } + }, + { + "name": "subgraph two", + "url": null, + "updated_at": { + "local": null, + "utc": null + } + } + ], + "success": true + }, + "error": null + }); + assert_json_eq!(expected_json, actual_json); + } + + #[test] + fn subgraph_delete_success_json() { + let mock_subgraph_delete = SubgraphDeleteResponse { + supergraph_was_updated: true, + build_errors: BuildErrors::new(), + }; + let actual_json: JsonOutput = RoverOutput::SubgraphDeleteResponse { + delete_response: mock_subgraph_delete, + subgraph: "subgraph".to_string(), + dry_run: false, + graph_ref: GraphRef { + name: "name".to_string(), + variant: "current".to_string(), + }, + } + .into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "supergraph_was_updated": true, + "success": true, + }, + "error": null + }); + assert_json_eq!(expected_json, actual_json); + } + + #[test] + fn subgraph_delete_build_errors_json() { + let mock_subgraph_delete = SubgraphDeleteResponse { + supergraph_was_updated: false, + build_errors: vec![ + BuildError::composition_error( + "[Accounts] -> Things went really wrong".to_string(), + Some("AN_ERROR_CODE".to_string()), + ), + BuildError::composition_error( + "[Films] -> Something else also went wrong".to_string(), + None, + ), + ] + .into(), + }; + let actual_json: JsonOutput = RoverOutput::SubgraphDeleteResponse { + delete_response: mock_subgraph_delete, + subgraph: "subgraph".to_string(), + dry_run: true, + graph_ref: GraphRef { + name: "name".to_string(), + variant: "current".to_string(), + }, + } + .into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "supergraph_was_updated": false, + "success": true, + }, + "error": { + "message": "Encountered 2 build errors while trying to build subgraph \"subgraph\" into supergraph \"name@current\".", + "code": "E029", + "details": { + "build_errors": [ + { + "message": "[Accounts] -> Things went really wrong", + "code": "AN_ERROR_CODE", + "type": "composition" + }, + { + "message": "[Films] -> Something else also went wrong", + "code": null, + "type": "composition" + } + ], + } + } + }); + assert_json_eq!(expected_json, actual_json); + } + + #[test] + fn supergraph_fetch_no_successful_publishes_json() { + let graph_ref = GraphRef { + name: "name".to_string(), + variant: "current".to_string(), + }; + let source = BuildErrors::from(vec![ + BuildError::composition_error( + "[Accounts] -> Things went really wrong".to_string(), + Some("AN_ERROR_CODE".to_string()), + ), + BuildError::composition_error( + "[Films] -> Something else also went wrong".to_string(), + None, + ), + ]); + let actual_json: JsonOutput = + RoverError::new(RoverClientError::NoSupergraphBuilds { graph_ref, source }).into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "success": false + }, + "error": { + "message": "No supergraph SDL exists for \"name@current\" because its subgraphs failed to build.", + "details": { + "build_errors": [ + { + "message": "[Accounts] -> Things went really wrong", + "code": "AN_ERROR_CODE", + "type": "composition", + }, + { + "message": "[Films] -> Something else also went wrong", + "code": null, + "type": "composition" + } + ] + }, + "code": "E027" + } + }); + assert_json_eq!(actual_json, expected_json); + } + + #[test] + fn check_success_response_json() { + let graph_ref = GraphRef { + name: "name".to_string(), + variant: "current".to_string(), + }; + let mock_check_response = CheckResponse::try_new( + Some("https://studio.apollographql.com/graph/my-graph/composition/big-hash?variant=current".to_string()), + 10, + vec![ + SchemaChange { + code: "SOMETHING_HAPPENED".to_string(), + description: "beeg yoshi".to_string(), + severity: ChangeSeverity::PASS, + }, + SchemaChange { + code: "WOW".to_string(), + description: "that was so cool".to_string(), + severity: ChangeSeverity::PASS, + } + ], + ChangeSeverity::PASS, + graph_ref, + ); + if let Ok(mock_check_response) = mock_check_response { + let actual_json: JsonOutput = RoverOutput::CheckResponse(mock_check_response).into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "target_url": "https://studio.apollographql.com/graph/my-graph/composition/big-hash?variant=current", + "operation_check_count": 10, + "changes": [ + { + "code": "SOMETHING_HAPPENED", + "description": "beeg yoshi", + "severity": "PASS" + }, + { + "code": "WOW", + "description": "that was so cool", + "severity": "PASS" + }, + ], + "failure_count": 0, + "success": true, + }, + "error": null + }); + assert_json_eq!(expected_json, actual_json); + } else { + panic!("The shape of this response should return a CheckResponse") + } + } + + #[test] + fn check_failure_response_json() { + let graph_ref = GraphRef { + name: "name".to_string(), + variant: "current".to_string(), + }; + let check_response = CheckResponse::try_new( + Some("https://studio.apollographql.com/graph/my-graph/composition/big-hash?variant=current".to_string()), + 10, + vec![ + SchemaChange { + code: "SOMETHING_HAPPENED".to_string(), + description: "beeg yoshi".to_string(), + severity: ChangeSeverity::FAIL, + }, + SchemaChange { + code: "WOW".to_string(), + description: "that was so cool".to_string(), + severity: ChangeSeverity::FAIL, + } + ], + ChangeSeverity::FAIL, graph_ref); + + if let Err(operation_check_failure) = check_response { + let actual_json: JsonOutput = RoverError::new(operation_check_failure).into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "target_url": "https://studio.apollographql.com/graph/my-graph/composition/big-hash?variant=current", + "operation_check_count": 10, + "changes": [ + { + "code": "SOMETHING_HAPPENED", + "description": "beeg yoshi", + "severity": "FAIL" + }, + { + "code": "WOW", + "description": "that was so cool", + "severity": "FAIL" + }, + ], + "failure_count": 2, + "success": false, + }, + "error": { + "message": "This operation check has encountered 2 schema changes that would break operations from existing client traffic.", + "code": "E030", + } + }); + assert_json_eq!(expected_json, actual_json); + } else { + panic!("The shape of this response should return a RoverClientError") + } + } + + #[test] + fn graph_publish_response_json() { + let mock_publish_response = GraphPublishResponse { + api_schema_hash: "123456".to_string(), + change_summary: ChangeSummary { + field_changes: FieldChanges { + additions: 2, + removals: 1, + edits: 0, + }, + type_changes: TypeChanges { + additions: 4, + removals: 0, + edits: 7, + }, + }, + }; + let actual_json: JsonOutput = RoverOutput::GraphPublishResponse { + graph_ref: GraphRef { + name: "graph".to_string(), + variant: "variant".to_string(), + }, + publish_response: mock_publish_response, + } + .into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "api_schema_hash": "123456", + "field_changes": { + "additions": 2, + "removals": 1, + "edits": 0 + }, + "type_changes": { + "additions": 4, + "removals": 0, + "edits": 7 + }, + "success": true + }, + "error": null + }); + assert_json_eq!(expected_json, actual_json); + } + + #[test] + fn subgraph_publish_success_response_json() { + let mock_publish_response = SubgraphPublishResponse { + api_schema_hash: Some("123456".to_string()), + build_errors: BuildErrors::new(), + supergraph_was_updated: true, + subgraph_was_created: true, + }; + let actual_json: JsonOutput = RoverOutput::SubgraphPublishResponse { + graph_ref: GraphRef { + name: "graph".to_string(), + variant: "variant".to_string(), + }, + subgraph: "subgraph".to_string(), + publish_response: mock_publish_response, + } + .into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "api_schema_hash": "123456", + "supergraph_was_updated": true, + "subgraph_was_created": true, + "success": true + }, + "error": null + }); + assert_json_eq!(expected_json, actual_json); + } - if !check_response.changes.is_empty() { - let mut table = table::get_table(); + #[test] + fn subgraph_publish_failure_response_json() { + let mock_publish_response = SubgraphPublishResponse { + api_schema_hash: None, - // bc => sets top row to be bold and center - table.add_row(row![bc => "Change", "Code", "Description"]); - for check in &check_response.changes { - table.add_row(row![check.severity, check.code, check.description]); + build_errors: vec![ + BuildError::composition_error( + "[Accounts] -> Things went really wrong".to_string(), + Some("AN_ERROR_CODE".to_string()), + ), + BuildError::composition_error( + "[Films] -> Something else also went wrong".to_string(), + None, + ), + ] + .into(), + supergraph_was_updated: false, + subgraph_was_created: false, + }; + let actual_json: JsonOutput = RoverOutput::SubgraphPublishResponse { + graph_ref: GraphRef { + name: "name".to_string(), + variant: "current".to_string(), + }, + subgraph: "subgraph".to_string(), + publish_response: mock_publish_response, } + .into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "api_schema_hash": null, + "subgraph_was_created": false, + "supergraph_was_updated": false, + "success": true + }, + "error": { + "message": "Encountered 2 build errors while trying to build subgraph \"subgraph\" into supergraph \"name@current\".", + "code": "E029", + "details": { + "build_errors": [ + { + "message": "[Accounts] -> Things went really wrong", + "code": "AN_ERROR_CODE", + "type": "composition", + }, + { + "message": "[Films] -> Something else also went wrong", + "code": null, + "type": "composition" + } + ] + } + } + }); + assert_json_eq!(expected_json, actual_json); + } - print_content(&table); + #[test] + fn profiles_json() { + let mock_profiles = vec!["default".to_string(), "staging".to_string()]; + let actual_json: JsonOutput = RoverOutput::Profiles(mock_profiles).into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "profiles": [ + "default", + "staging" + ], + "success": true + }, + "error": null + }); + assert_json_eq!(expected_json, actual_json); } - if let Some(url) = &check_response.target_url { - eprintln!("View full details at {}", url); + #[test] + fn introspection_json() { + let actual_json: JsonOutput = RoverOutput::Introspection( + "i cant believe its not a real introspection response".to_string(), + ) + .into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "introspection_response": "i cant believe its not a real introspection response", + "success": true + }, + "error": null + }); + assert_json_eq!(expected_json, actual_json); + } + + #[test] + fn error_explanation_json() { + let actual_json: JsonOutput = RoverOutput::ErrorExplanation( + "this error occurs when stuff is real complicated... I wouldn't worry about it" + .to_string(), + ) + .into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "explanation_markdown": "this error occurs when stuff is real complicated... I wouldn't worry about it", + "success": true + }, + "error": null + } + + ); + assert_json_eq!(expected_json, actual_json); + } + + #[test] + fn empty_success_json() { + let actual_json: JsonOutput = RoverOutput::EmptySuccess.into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "success": true + }, + "error": null + }); + assert_json_eq!(expected_json, actual_json); + } + + #[test] + fn base_error_message_json() { + let actual_json: JsonOutput = RoverError::new(anyhow!("Some random error")).into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "success": false + }, + "error": { + "message": "Some random error", + "code": null + } + }); + assert_json_eq!(expected_json, actual_json); + } + + #[test] + fn coded_error_message_json() { + let actual_json: JsonOutput = RoverError::new(RoverClientError::NoSubgraphInGraph { + invalid_subgraph: "invalid_subgraph".to_string(), + valid_subgraphs: Vec::new(), + }) + .into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "success": false + }, + "error": { + "message": "Could not find subgraph \"invalid_subgraph\".", + "code": "E009" + } + }); + assert_json_eq!(expected_json, actual_json) + } + + #[test] + fn composition_error_message_json() { + let source = BuildErrors::from(vec![ + BuildError::composition_error( + "[Accounts] -> Things went really wrong".to_string(), + Some("AN_ERROR_CODE".to_string()), + ), + BuildError::composition_error( + "[Films] -> Something else also went wrong".to_string(), + None, + ), + ]); + let actual_json: JsonOutput = + RoverError::from(RoverClientError::BuildErrors { source }).into(); + let expected_json = json!( + { + "json_version": "1.beta", + "data": { + "success": false + }, + "error": { + "details": { + "build_errors": [ + { + "message": "[Accounts] -> Things went really wrong", + "code": "AN_ERROR_CODE", + "type": "composition" + }, + { + "message": "[Films] -> Something else also went wrong", + "code": null, + "type": "composition" + } + ], + }, + "message": "Encountered 2 build errors while trying to build a supergraph.", + "code": "E029" + } + }); + assert_json_eq!(expected_json, actual_json) } } diff --git a/src/command/subgraph/check.rs b/src/command/subgraph/check.rs index 67201bee4..696ae2b2f 100644 --- a/src/command/subgraph/check.rs +++ b/src/command/subgraph/check.rs @@ -4,7 +4,7 @@ use structopt::StructOpt; use rover_client::operations::subgraph::check::{self, SubgraphCheckInput}; use rover_client::shared::{CheckConfig, GitContext, GraphRef, ValidationPeriod}; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::utils::client::StudioClientConfig; use crate::utils::loaders::load_schema_from_flag; use crate::utils::parsers::{ @@ -58,7 +58,7 @@ impl Check { &self, client_config: StudioClientConfig, git_context: GitContext, - ) -> Result { + ) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; let proposed_schema = load_schema_from_flag(&self.schema, std::io::stdin())?; @@ -83,6 +83,6 @@ impl Check { &client, )?; - Ok(RoverStdout::CheckResponse(res)) + Ok(RoverOutput::CheckResponse(res)) } } diff --git a/src/command/subgraph/delete.rs b/src/command/subgraph/delete.rs index de53867cb..4e8c3ac9e 100644 --- a/src/command/subgraph/delete.rs +++ b/src/command/subgraph/delete.rs @@ -1,14 +1,12 @@ -use ansi_term::Colour::{Cyan, Red, Yellow}; +use ansi_term::Colour::{Cyan, Yellow}; use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::utils::client::StudioClientConfig; use crate::Result; -use rover_client::operations::subgraph::delete::{ - self, SubgraphDeleteInput, SubgraphDeleteResponse, -}; +use rover_client::operations::subgraph::delete::{self, SubgraphDeleteInput}; use rover_client::shared::GraphRef; #[derive(Debug, Serialize, StructOpt)] @@ -30,75 +28,68 @@ pub struct Delete { subgraph: String, /// Skips the step where the command asks for user confirmation before - /// deleting the subgraph. Also skips preview of composition errors that + /// deleting the subgraph. Also skips preview of build errors that /// might occur #[structopt(long)] confirm: bool, } impl Delete { - pub fn run(&self, client_config: StudioClientConfig) -> Result { + pub fn run(&self, client_config: StudioClientConfig) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; - let graph_ref = self.graph.to_string(); eprintln!( - "Checking for composition errors resulting from deleting subgraph {} from {} using credentials from the {} profile.", + "Checking for build errors resulting from deleting subgraph {} from {} using credentials from the {} profile.", Cyan.normal().paint(&self.subgraph), - Cyan.normal().paint(&graph_ref), + Cyan.normal().paint(self.graph.to_string()), Yellow.normal().paint(&self.profile_name) ); // this is probably the normal path -- preview a subgraph delete // and make the user confirm it manually. if !self.confirm { - // run delete with dryRun, so we can preview composition errors + let dry_run = true; + // run delete with dryRun, so we can preview build errors let delete_dry_run_response = delete::run( SubgraphDeleteInput { graph_ref: self.graph.clone(), subgraph: self.subgraph.clone(), - dry_run: true, + dry_run, }, &client, )?; - handle_dry_run_response(delete_dry_run_response, &self.subgraph, &graph_ref); + RoverOutput::SubgraphDeleteResponse { + graph_ref: self.graph.clone(), + subgraph: self.subgraph.clone(), + dry_run, + delete_response: delete_dry_run_response, + } + .print(); // I chose not to error here, since this is a perfectly valid path if !confirm_delete()? { eprintln!("Delete cancelled by user"); - return Ok(RoverStdout::None); + return Ok(RoverOutput::EmptySuccess); } } + let dry_run = false; + let delete_response = delete::run( SubgraphDeleteInput { graph_ref: self.graph.clone(), subgraph: self.subgraph.clone(), - dry_run: false, + dry_run, }, &client, )?; - handle_response(delete_response, &self.subgraph, &graph_ref); - Ok(RoverStdout::None) - } -} - -fn handle_dry_run_response(response: SubgraphDeleteResponse, subgraph: &str, graph_ref: &str) { - let warn_prefix = Red.normal().paint("WARN:"); - if let Some(errors) = response.composition_errors { - eprintln!( - "{} Deleting the {} subgraph from {} would result in the following composition errors:", - warn_prefix, - Cyan.normal().paint(subgraph), - Cyan.normal().paint(graph_ref), - ); - for error in errors { - eprintln!("{}", &error); - } - eprintln!("{} This is only a prediction. If the graph changes before confirming, these errors could change.", warn_prefix); - } else { - eprintln!("{} At the time of checking, there would be no composition errors resulting from the deletion of this subgraph.", warn_prefix); - eprintln!("{} This is only a prediction. If the graph changes before confirming, there could be composition errors.", warn_prefix) + Ok(RoverOutput::SubgraphDeleteResponse { + graph_ref: self.graph.clone(), + subgraph: self.subgraph.clone(), + dry_run, + delete_response, + }) } } @@ -112,69 +103,3 @@ fn confirm_delete() -> Result { Ok(false) } } - -fn handle_response(response: SubgraphDeleteResponse, subgraph: &str, graph_ref: &str) { - let warn_prefix = Red.normal().paint("WARN:"); - if response.updated_gateway { - eprintln!( - "The {} subgraph was removed from {}. Remaining subgraphs were composed.", - Cyan.normal().paint(subgraph), - Cyan.normal().paint(graph_ref), - ) - } else { - eprintln!( - "{} The gateway for {} was not updated. See errors below.", - warn_prefix, - Cyan.normal().paint(graph_ref) - ) - } - - if let Some(errors) = response.composition_errors { - eprintln!( - "{} There were composition errors as a result of deleting the subgraph:", - warn_prefix, - ); - - for error in errors { - eprintln!("{}", &error); - } - } -} - -#[cfg(test)] -mod tests { - use super::{handle_response, SubgraphDeleteResponse}; - use rover_client::shared::CompositionError; - - #[test] - fn handle_response_doesnt_error_with_all_successes() { - let response = SubgraphDeleteResponse { - composition_errors: None, - updated_gateway: true, - }; - - handle_response(response, "accounts", "my-graph@current"); - } - - #[test] - fn handle_response_doesnt_error_with_all_failures() { - let response = SubgraphDeleteResponse { - composition_errors: Some(vec![ - CompositionError { - message: "a bad thing happened".to_string(), - code: None, - }, - CompositionError { - message: "another bad thing".to_string(), - code: None, - }, - ]), - updated_gateway: false, - }; - - handle_response(response, "accounts", "my-graph@prod"); - } - - // TODO: test the actual output of the logs whenever we do design work - // for the commands :) -} diff --git a/src/command/subgraph/fetch.rs b/src/command/subgraph/fetch.rs index ba7f35bc7..92643e71b 100644 --- a/src/command/subgraph/fetch.rs +++ b/src/command/subgraph/fetch.rs @@ -5,7 +5,7 @@ use structopt::StructOpt; use rover_client::operations::subgraph::fetch::{self, SubgraphFetchInput}; use rover_client::shared::GraphRef; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::utils::client::StudioClientConfig; use crate::Result; @@ -29,7 +29,7 @@ pub struct Fetch { } impl Fetch { - pub fn run(&self, client_config: StudioClientConfig) -> Result { + pub fn run(&self, client_config: StudioClientConfig) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; let graph_ref = self.graph.to_string(); eprintln!( @@ -47,6 +47,6 @@ impl Fetch { &client, )?; - Ok(RoverStdout::FetchResponse(fetch_response)) + Ok(RoverOutput::FetchResponse(fetch_response)) } } diff --git a/src/command/subgraph/introspect.rs b/src/command/subgraph/introspect.rs index bdce9cdea..5b527495d 100644 --- a/src/command/subgraph/introspect.rs +++ b/src/command/subgraph/introspect.rs @@ -9,7 +9,7 @@ use rover_client::{ operations::subgraph::introspect::{self, SubgraphIntrospectInput}, }; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::utils::parsers::parse_header; use crate::Result; @@ -36,7 +36,7 @@ pub struct Introspect { } impl Introspect { - pub fn run(&self, client: Client) -> Result { + pub fn run(&self, client: Client) -> Result { let client = GraphQLClient::new(&self.endpoint.to_string(), client)?; // add the flag headers to a hashmap to pass along to rover-client @@ -49,6 +49,6 @@ impl Introspect { let introspection_response = introspect::run(SubgraphIntrospectInput { headers }, &client)?; - Ok(RoverStdout::Introspection(introspection_response.result)) + Ok(RoverOutput::Introspection(introspection_response.result)) } } diff --git a/src/command/subgraph/list.rs b/src/command/subgraph/list.rs index 4bcbc312c..c1794801e 100644 --- a/src/command/subgraph/list.rs +++ b/src/command/subgraph/list.rs @@ -5,7 +5,7 @@ use structopt::StructOpt; use rover_client::operations::subgraph::list::{self, SubgraphListInput}; use rover_client::shared::GraphRef; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::utils::client::StudioClientConfig; use crate::Result; @@ -24,7 +24,7 @@ pub struct List { } impl List { - pub fn run(&self, client_config: StudioClientConfig) -> Result { + pub fn run(&self, client_config: StudioClientConfig) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; eprintln!( @@ -40,6 +40,6 @@ impl List { &client, )?; - Ok(RoverStdout::SubgraphList(list_details)) + Ok(RoverOutput::SubgraphList(list_details)) } } diff --git a/src/command/subgraph/mod.rs b/src/command/subgraph/mod.rs index ee1ff3704..08ba2adb0 100644 --- a/src/command/subgraph/mod.rs +++ b/src/command/subgraph/mod.rs @@ -8,7 +8,7 @@ mod publish; use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::utils::client::StudioClientConfig; use crate::Result; @@ -22,7 +22,7 @@ pub struct Subgraph { #[derive(Debug, Serialize, StructOpt)] pub enum Command { - /// Check for composition errors and breaking changes caused by an updated subgraph schema + /// Check for build errors and breaking changes caused by an updated subgraph schema /// against the federated graph in the Apollo graph registry Check(check::Check), @@ -47,7 +47,7 @@ impl Subgraph { &self, client_config: StudioClientConfig, git_context: GitContext, - ) -> Result { + ) -> Result { match &self.command { Command::Publish(command) => command.run(client_config, git_context), Command::Introspect(command) => command.run(client_config.get_reqwest_client()), diff --git a/src/command/subgraph/publish.rs b/src/command/subgraph/publish.rs index 5ee695359..4d2f4490e 100644 --- a/src/command/subgraph/publish.rs +++ b/src/command/subgraph/publish.rs @@ -1,8 +1,8 @@ -use ansi_term::Colour::{Cyan, Red, Yellow}; +use ansi_term::Colour::{Cyan, Yellow}; use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::utils::{ client::StudioClientConfig, loaders::load_schema_from_flag, @@ -10,9 +10,7 @@ use crate::utils::{ }; use crate::Result; -use rover_client::operations::subgraph::publish::{ - self, SubgraphPublishInput, SubgraphPublishResponse, -}; +use rover_client::operations::subgraph::publish::{self, SubgraphPublishInput}; use rover_client::shared::{GitContext, GraphRef}; #[derive(Debug, Serialize, StructOpt)] @@ -56,12 +54,11 @@ impl Publish { &self, client_config: StudioClientConfig, git_context: GitContext, - ) -> Result { + ) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; - let graph_ref = format!("{}:{}", &self.graph.name, &self.graph.variant); eprintln!( "Publishing SDL to {} (subgraph: {}) using credentials from the {} profile.", - Cyan.normal().paint(&graph_ref), + Cyan.normal().paint(&self.graph.to_string()), Cyan.normal().paint(&self.subgraph), Yellow.normal().paint(&self.profile_name) ); @@ -82,82 +79,10 @@ impl Publish { &client, )?; - handle_publish_response(publish_response, &self.subgraph, &self.graph.name); - Ok(RoverStdout::None) - } -} - -fn handle_publish_response(response: SubgraphPublishResponse, subgraph: &str, graph: &str) { - if response.subgraph_was_created { - eprintln!( - "A new subgraph called '{}' for the '{}' graph was created", - subgraph, graph - ); - } else { - eprintln!( - "The '{}' subgraph for the '{}' graph was updated", - subgraph, graph - ); - } - - if response.did_update_gateway { - eprintln!("The gateway for the '{}' graph was updated with a new schema, composed from the updated '{}' subgraph", graph, subgraph); - } else { - eprintln!( - "The gateway for the '{}' graph was NOT updated with a new schema", - graph - ); + Ok(RoverOutput::SubgraphPublishResponse { + graph_ref: self.graph.clone(), + subgraph: self.subgraph.clone(), + publish_response, + }) } - - if let Some(errors) = response.composition_errors { - let warn_prefix = Red.normal().paint("WARN:"); - eprintln!("{} The following composition errors occurred:", warn_prefix,); - for error in errors { - eprintln!("{}", &error); - } - } -} - -#[cfg(test)] -mod tests { - use super::{handle_publish_response, SubgraphPublishResponse}; - use rover_client::shared::CompositionError; - - // this test is a bit weird, since we can't test the output. We just verify it - // doesn't error - #[test] - fn handle_response_doesnt_error_with_all_successes() { - let response = SubgraphPublishResponse { - schema_hash: Some("123456".to_string()), - did_update_gateway: true, - subgraph_was_created: true, - composition_errors: None, - }; - - handle_publish_response(response, "accounts", "my-graph"); - } - - #[test] - fn handle_response_doesnt_error_with_all_failures() { - let response = SubgraphPublishResponse { - schema_hash: None, - did_update_gateway: false, - subgraph_was_created: false, - composition_errors: Some(vec![ - CompositionError { - message: "a bad thing happened".to_string(), - code: None, - }, - CompositionError { - message: "another bad thing".to_string(), - code: None, - }, - ]), - }; - - handle_publish_response(response, "accounts", "my-graph"); - } - - // TODO: test the actual output of the logs whenever we do design work - // for the commands :) } diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index b51d949c8..8fd80b813 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -1,22 +1,19 @@ use crate::command::supergraph::config::{self, SchemaSource, SupergraphConfig}; use crate::utils::client::StudioClientConfig; -use crate::{anyhow, command::RoverStdout, error::RoverError, Result, Suggestion}; +use crate::{anyhow, command::RoverOutput, error::RoverError, Result, Suggestion}; -use ansi_term::Colour::Red; -use camino::Utf8PathBuf; +use rover_client::blocking::GraphQLClient; +use rover_client::operations::subgraph::fetch::{self, SubgraphFetchInput}; +use rover_client::operations::subgraph::introspect::{self, SubgraphIntrospectInput}; +use rover_client::shared::{BuildError, GraphRef}; +use rover_client::RoverClientError; -use rover_client::operations::subgraph::fetch::SubgraphFetchInput; -use rover_client::operations::subgraph::introspect::SubgraphIntrospectInput; -use rover_client::shared::GraphRef; -use rover_client::{ - blocking::GraphQLClient, - operations::subgraph::{fetch, introspect}, -}; +use camino::Utf8PathBuf; +use harmonizer::ServiceDefinition as SubgraphDefinition; use serde::Serialize; -use std::{collections::HashMap, fs, str::FromStr}; use structopt::StructOpt; -use harmonizer::ServiceDefinition as SubgraphDefinition; +use std::{collections::HashMap, fs, str::FromStr}; #[derive(Debug, Serialize, StructOpt)] pub struct Compose { @@ -32,7 +29,7 @@ pub struct Compose { } impl Compose { - pub fn run(&self, client_config: StudioClientConfig) -> Result { + pub fn run(&self, client_config: StudioClientConfig) -> Result { let supergraph_config = config::parse_supergraph_config(&self.config_path)?; let subgraph_definitions = get_subgraph_definitions( supergraph_config, @@ -42,24 +39,20 @@ impl Compose { )?; match harmonizer::harmonize(subgraph_definitions) { - Ok(core_schema) => Ok(RoverStdout::CoreSchema(core_schema)), - Err(composition_errors) => { - let num_failures = composition_errors.len(); - for composition_error in composition_errors { - eprintln!("{} {}", Red.bold().paint("error:"), &composition_error) - } - match num_failures { - 0 => unreachable!("Composition somehow failed with no composition errors."), - 1 => Err( - anyhow!("Encountered 1 composition error while composing the graph.") - .into(), - ), - _ => Err(anyhow!( - "Encountered {} composition errors while composing the graph.", - num_failures - ) - .into()), + Ok(core_schema) => Ok(RoverOutput::CoreSchema(core_schema)), + Err(harmonizer_composition_errors) => { + let mut build_errors = Vec::with_capacity(harmonizer_composition_errors.len()); + for harmonizer_composition_error in harmonizer_composition_errors { + if let Some(message) = &harmonizer_composition_error.message { + build_errors.push(BuildError::composition_error( + message.to_string(), + Some(harmonizer_composition_error.code().to_string()), + )); + } } + Err(RoverError::new(RoverClientError::BuildErrors { + source: build_errors.into(), + })) } } } diff --git a/src/command/supergraph/compose/no_compose.rs b/src/command/supergraph/compose/no_compose.rs index bc9c6904e..dea61423f 100644 --- a/src/command/supergraph/compose/no_compose.rs +++ b/src/command/supergraph/compose/no_compose.rs @@ -5,7 +5,7 @@ use structopt::StructOpt; use crate::utils::client::StudioClientConfig; use crate::{ anyhow, - command::RoverStdout, + command::RoverOutput, error::{RoverError, Suggestion}, Result, }; @@ -24,7 +24,7 @@ pub struct Compose { } impl Compose { - pub fn run(&self, _client_config: StudioClientConfig) -> Result { + pub fn run(&self, _client_config: StudioClientConfig) -> Result { let mut err = RoverError::new(anyhow!( "This version of Rover does not support this command." )); diff --git a/src/command/supergraph/fetch.rs b/src/command/supergraph/fetch.rs index 878b92bd5..793af1f87 100644 --- a/src/command/supergraph/fetch.rs +++ b/src/command/supergraph/fetch.rs @@ -1,5 +1,5 @@ use crate::utils::client::StudioClientConfig; -use crate::{command::RoverStdout, Result}; +use crate::{command::RoverOutput, Result}; use rover_client::operations::supergraph::fetch::{self, SupergraphFetchInput}; use rover_client::shared::GraphRef; @@ -23,7 +23,7 @@ pub struct Fetch { } impl Fetch { - pub fn run(&self, client_config: StudioClientConfig) -> Result { + pub fn run(&self, client_config: StudioClientConfig) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; let graph_ref = self.graph.to_string(); eprintln!( @@ -39,6 +39,6 @@ impl Fetch { &client, )?; - Ok(RoverStdout::FetchResponse(fetch_response)) + Ok(RoverOutput::FetchResponse(fetch_response)) } } diff --git a/src/command/supergraph/mod.rs b/src/command/supergraph/mod.rs index 8b575b5fa..51e2141a3 100644 --- a/src/command/supergraph/mod.rs +++ b/src/command/supergraph/mod.rs @@ -5,7 +5,7 @@ mod fetch; use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::utils::client::StudioClientConfig; use crate::Result; @@ -26,7 +26,7 @@ pub enum Command { } impl Supergraph { - pub fn run(&self, client_config: StudioClientConfig) -> Result { + pub fn run(&self, client_config: StudioClientConfig) -> Result { match &self.command { Command::Fetch(command) => command.run(client_config), Command::Compose(command) => command.run(client_config), diff --git a/src/command/update/check.rs b/src/command/update/check.rs index f4fe31ebd..3b73ab4ad 100644 --- a/src/command/update/check.rs +++ b/src/command/update/check.rs @@ -2,7 +2,7 @@ use reqwest::blocking::Client; use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::{utils::version, Result}; use houston as config; @@ -13,8 +13,8 @@ pub struct Check { } impl Check { - pub fn run(&self, config: config::Config, client: Client) -> Result { + pub fn run(&self, config: config::Config, client: Client) -> Result { version::check_for_update(config, true, client)?; - Ok(RoverStdout::None) + Ok(RoverOutput::EmptySuccess) } } diff --git a/src/command/update/mod.rs b/src/command/update/mod.rs index 9296ba172..8964ff90e 100644 --- a/src/command/update/mod.rs +++ b/src/command/update/mod.rs @@ -4,7 +4,7 @@ use reqwest::blocking::Client; use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverStdout; +use crate::command::RoverOutput; use crate::Result; use houston as config; @@ -22,7 +22,7 @@ pub enum Command { } impl Update { - pub fn run(&self, config: config::Config, client: Client) -> Result { + pub fn run(&self, config: config::Config, client: Client) -> Result { match &self.command { Command::Check(command) => command.run(config, client), } diff --git a/src/error/metadata/codes/E029.md b/src/error/metadata/codes/E029.md index 793b1022b..195333e5f 100644 --- a/src/error/metadata/codes/E029.md +++ b/src/error/metadata/codes/E029.md @@ -1,5 +1,5 @@ -This error occurs when you propose a subgraph schema that could not be composed. +This error occurs when you propose a subgraph schema that could not be built. -There are many reasons why you may run into composition errors. This error should include information about _why_ the proposed subgraph schema could not be composed. Error code references can be found [here](https://www.apollographql.com/docs/federation/errors/). +There are many reasons why you may run into build errors. This error should include information about _why_ the proposed subgraph schema could not be composed. Error code references can be found [here](https://www.apollographql.com/docs/federation/errors/). -Some composition errors are part of normal workflows. For instance, you may need to publish a subgraph that does not compose if you are trying to [migrate an entity or field](https://www.apollographql.com/docs/federation/entities/#migrating-entities-and-fields-advanced). +Some build errors are part of normal workflows. For instance, you may need to publish a subgraph that does not compose if you are trying to [migrate an entity or field](https://www.apollographql.com/docs/federation/entities/#migrating-entities-and-fields-advanced). diff --git a/src/error/metadata/mod.rs b/src/error/metadata/mod.rs index 8969d419d..39887b477 100644 --- a/src/error/metadata/mod.rs +++ b/src/error/metadata/mod.rs @@ -7,19 +7,23 @@ pub use suggestion::Suggestion; use houston::HoustonProblem; use rover_client::RoverClientError; -use crate::{command::output::print_check_response, utils::env::RoverEnvKey}; +use crate::utils::env::RoverEnvKey; use std::{env, fmt::Display}; -use ansi_term::Colour::Red; +use serde::Serialize; /// Metadata contains extra information about specific errors /// Currently this includes an optional error `Code` /// and an optional `Suggestion` -#[derive(Default, Debug)] +#[derive(Default, Serialize, Debug)] pub struct Metadata { + // skip serializing for now until we can appropriately strip color codes + #[serde(skip_serializing)] pub suggestion: Option, pub code: Option, + + #[serde(skip_serializing)] pub is_parse_error: bool, } @@ -58,33 +62,29 @@ impl From<&mut anyhow::Error> for Metadata { RoverClientError::InvalidSeverity => { (Some(Suggestion::SubmitIssue), Some(Code::E006)) } - RoverClientError::SubgraphCompositionErrors { + RoverClientError::SubgraphBuildErrors { graph_ref, - composition_errors, - } => { - for composition_error in composition_errors { - let error_descriptor = format!("{} ", Red.bold().paint("error:")); - eprintln!("{} {}", &error_descriptor, &composition_error); - } - ( - Some(Suggestion::FixSubgraphSchema { - graph_ref: graph_ref.clone(), - }), - Some(Code::E029), - ) + subgraph, + source: _, + } => ( + Some(Suggestion::FixSubgraphSchema { + graph_ref: graph_ref.clone(), + subgraph: subgraph.clone(), + }), + Some(Code::E029), + ), + RoverClientError::BuildErrors { .. } => { + (Some(Suggestion::FixCompositionErrors), Some(Code::E029)) } RoverClientError::OperationCheckFailure { graph_ref, - check_response, - } => { - print_check_response(check_response); - ( - Some(Suggestion::FixOperationsInSchema { - graph_ref: graph_ref.clone(), - }), - Some(Code::E030), - ) - } + check_response: _, + } => ( + Some(Suggestion::FixOperationsInSchema { + graph_ref: graph_ref.clone(), + }), + Some(Code::E030), + ), RoverClientError::SubgraphIntrospectionNotAvailable => { (Some(Suggestion::UseFederatedGraph), Some(Code::E007)) } @@ -129,13 +129,7 @@ impl From<&mut anyhow::Error> for Metadata { (Some(Suggestion::SubmitIssue), Some(Code::E015)) } RoverClientError::BadReleaseUrl => (Some(Suggestion::SubmitIssue), None), - RoverClientError::NoCompositionPublishes { - graph_ref: _, - composition_errors, - } => { - for composition_error in composition_errors { - eprintln!("{} {}", Red.bold().paint("error:"), composition_error); - } + RoverClientError::NoSupergraphBuilds { .. } => { (Some(Suggestion::RunComposition), Some(Code::E027)) } RoverClientError::AdhocError { .. } => (None, None), diff --git a/src/error/metadata/suggestion.rs b/src/error/metadata/suggestion.rs index 64cc73186..506166681 100644 --- a/src/error/metadata/suggestion.rs +++ b/src/error/metadata/suggestion.rs @@ -6,8 +6,10 @@ use rover_client::shared::GraphRef; use crate::utils::env::RoverEnvKey; +use serde::Serialize; + /// `Suggestion` contains possible suggestions for remedying specific errors. -#[derive(Debug)] +#[derive(Serialize, Debug)] pub enum Suggestion { SubmitIssue, SetConfigHome, @@ -34,7 +36,9 @@ pub enum Suggestion { CheckGnuVersion, FixSubgraphSchema { graph_ref: GraphRef, + subgraph: String, }, + FixCompositionErrors, FixOperationsInSchema { graph_ref: GraphRef, }, @@ -71,7 +75,7 @@ impl Display for Suggestion { ) } Suggestion::RunComposition => { - format!("Try resolving the composition errors in your subgraph(s), and publish them with the {} command.", Yellow.normal().paint("`rover subgraph publish`")) + format!("Try resolving the build errors in your subgraph(s), and publish them with the {} command.", Yellow.normal().paint("`rover subgraph publish`")) } Suggestion::UseFederatedGraph => { "Try running the command on a valid federated graph, or use the appropriate `rover graph` command instead of `rover subgraph`.".to_string() @@ -135,7 +139,8 @@ impl Display for Suggestion { Suggestion::CheckServerConnection => "Make sure the endpoint is accepting connections and is spelled correctly".to_string(), Suggestion::ConvertGraphToSubgraph => "If you are sure you want to convert a non-federated graph to a subgraph, you can re-run the same command with a `--convert` flag.".to_string(), Suggestion::CheckGnuVersion => "This is likely an issue with your current version of `glibc`. Try running `ldd --version`, and if the version >= 2.18, we suggest installing the Rover binary built for `x86_64-unknown-linux-gnu`".to_string(), - Suggestion::FixSubgraphSchema { graph_ref } => format!("The changes in the schema you proposed are incompatible with graph {}. See {} for more information on resolving composition errors.", Yellow.normal().paint(graph_ref.to_string()), Cyan.normal().paint("https://www.apollographql.com/docs/federation/errors/")), + Suggestion::FixSubgraphSchema { graph_ref, subgraph } => format!("The changes in the schema you proposed for subgraph {} are incompatible with supergraph {}. See {} for more information on resolving build errors.", Yellow.normal().paint(subgraph.to_string()), Yellow.normal().paint(graph_ref.to_string()), Cyan.normal().paint("https://www.apollographql.com/docs/federation/errors/")), + Suggestion::FixCompositionErrors => format!("The subgraph schemas you provided are incompatible with each other. See {} for more information on resolving build errors.", Cyan.normal().paint("https://www.apollographql.com/docs/federation/errors/")), Suggestion::FixOperationsInSchema { graph_ref } => format!("The changes in the schema you proposed are incompatible with graph {}. See {} for more information on resolving operation check errors.", Yellow.normal().paint(graph_ref.to_string()), Cyan.normal().paint("https://www.apollographql.com/docs/studio/schema-checks/")) }; write!(formatter, "{}", &suggestion) diff --git a/src/error/mod.rs b/src/error/mod.rs index 1da93d72b..dde8b47a1 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -6,21 +6,64 @@ pub(crate) use metadata::Metadata; pub type Result = std::result::Result; use ansi_term::Colour::{Cyan, Red}; +use rover_client::RoverClientError; +use serde::ser::SerializeStruct; +use serde::{Serialize, Serializer}; +use serde_json::{json, Value}; use std::borrow::BorrowMut; +use std::error::Error; use std::fmt::{self, Debug, Display}; pub use self::metadata::Suggestion; +use rover_client::shared::BuildErrors; + /// A specialized `Error` type for Rover that wraps `anyhow` /// and provides some extra `Metadata` for end users depending /// on the specific error they encountered. -#[derive(Debug)] +#[derive(Serialize, Debug)] pub struct RoverError { + #[serde(flatten, serialize_with = "serialize_anyhow")] error: anyhow::Error, + + #[serde(flatten)] metadata: Metadata, } +#[derive(Serialize, Debug)] +#[serde(rename_all = "snake_case")] +enum RoverDetails { + BuildErrors(BuildErrors), +} + +fn serialize_anyhow(error: &anyhow::Error, serializer: S) -> std::result::Result +where + S: Serializer, +{ + let top_level_struct = "error"; + let message_field_name = "message"; + let details_struct = "details"; + + if let Some(rover_client_error) = error.downcast_ref::() { + if let Some(rover_client_error_source) = rover_client_error.source() { + if let Some(build_errors) = rover_client_error_source.downcast_ref::() { + let mut top_level_data = serializer.serialize_struct(top_level_struct, 2)?; + top_level_data.serialize_field(message_field_name, &error.to_string())?; + top_level_data.serialize_field( + details_struct, + &RoverDetails::BuildErrors(build_errors.clone()), + )?; + return top_level_data.end(); + } + } + } + + let mut data = serializer.serialize_struct(top_level_struct, 1)?; + data.serialize_field(message_field_name, &error.to_string())?; + data.end() +} + impl RoverError { pub fn new(error: E) -> Self where @@ -49,6 +92,33 @@ impl RoverError { pub fn suggestion(&mut self) -> &Option { &self.metadata.suggestion } + + pub fn print(&self) { + if let Some(RoverClientError::OperationCheckFailure { + graph_ref: _, + check_response, + }) = self.error.downcast_ref::() + { + println!("{}", check_response.get_table()); + } + + eprintln!("{}", self); + } + + pub(crate) fn get_internal_data_json(&self) -> Value { + if let Some(RoverClientError::OperationCheckFailure { + graph_ref: _, + check_response, + }) = self.error.downcast_ref::() + { + return check_response.get_json(); + } + Value::Null + } + + pub(crate) fn get_internal_error_json(&self) -> Value { + json!(self) + } } impl Display for RoverError { @@ -64,7 +134,7 @@ impl Display for RoverError { "error:".to_string() }; let error_descriptor = Red.bold().paint(&error_descriptor_message); - writeln!(formatter, "{} {}", error_descriptor, &self.error)?; + writeln!(formatter, "{} {:?}", error_descriptor, &self.error)?; error_descriptor_message }; diff --git a/src/utils/stringify.rs b/src/utils/stringify.rs index 6bd57471e..3d45e594c 100644 --- a/src/utils/stringify.rs +++ b/src/utils/stringify.rs @@ -2,20 +2,30 @@ //! a struct with the `Display`/`FromStr` implementations //! if it does not implement `Serialize`/`Deserialize` //! code taken from this: https://github.com/serde-rs/serde/issues/1316 -//! and can be used by annotating a field with -//! #[serde(serialize_with = "from_display")] +//! and can be used by annotating a field with either +//! #[serde(serialize_with = "from_display")] or +//! #[serde(serialize_with = "option_from_display")] +//! depending on if the type you're serializing is nested in an Option use std::fmt::Display; use serde::Serializer; -pub fn from_display(value: &Option, serializer: S) -> Result +pub fn option_from_display(value: &Option, serializer: S) -> Result where T: Display, S: Serializer, { if let Some(value) = value { - serializer.collect_str(value) + from_display(value, serializer) } else { serializer.serialize_none() } } + +pub fn from_display(value: &T, serializer: S) -> Result +where + T: Display, + S: Serializer, +{ + serializer.collect_str(value) +} diff --git a/src/utils/table.rs b/src/utils/table.rs index c19ad7778..1b098c1ec 100644 --- a/src/utils/table.rs +++ b/src/utils/table.rs @@ -1,9 +1,16 @@ -use prettytable::{format::consts::FORMAT_BOX_CHARS, Table}; +use prettytable::{ + format::{consts::FORMAT_BOX_CHARS, TableFormat}, + Table, +}; pub use prettytable::{cell, row}; pub fn get_table() -> Table { let mut table = Table::new(); - table.set_format(*FORMAT_BOX_CHARS); + table.set_format(get_table_format()); table } + +pub fn get_table_format() -> TableFormat { + *FORMAT_BOX_CHARS +}