From 5b5859110bbfd086d293f8ae108367d55d1394f1 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Tue, 13 Jul 2021 15:48:40 -0500 Subject: [PATCH 1/4] chore: refactor graph fetch --- .../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} | 34 +++++++++---------- .../src/operations/graph/fetch/types.rs | 18 ++++++++++ .../src/operations/subgraph/fetch/mod.rs | 2 +- .../src/operations/subgraph/fetch/runner.rs | 8 +++-- .../src/operations/subgraph/fetch/types.rs | 5 --- .../rover-client/src/shared/fetch_response.rs | 4 +++ crates/rover-client/src/shared/mod.rs | 2 ++ src/command/graph/fetch.rs | 12 +++---- src/command/output.rs | 8 ++--- src/command/subgraph/fetch.rs | 4 +-- 13 files changed, 74 insertions(+), 50 deletions(-) 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} (76%) create mode 100644 crates/rover-client/src/operations/graph/fetch/types.rs create mode 100644 crates/rover-client/src/shared/fetch_response.rs 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 76% rename from crates/rover-client/src/operations/graph/fetch.rs rename to crates/rover-client/src/operations/graph/fetch/runner.rs index 3ac6f2aba..53d661e9e 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; use crate::RoverClientError; + use graphql_client::*; // I'm not sure where this should live long-term @@ -10,34 +13,31 @@ 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 = input.graph_ref.name.clone(); + let invalid_variant = input.graph_ref.variant.clone(); + let response_data = client.post::(input.into())?; + let sdl = get_schema_from_response_data(response_data, graph, invalid_variant)?; + Ok(FetchResponse { sdl }) } fn get_schema_from_response_data( - response_data: fetch_schema_query::ResponseData, + response_data: graph_fetch_query::ResponseData, graph: String, invalid_variant: String, ) -> Result { @@ -78,7 +78,7 @@ mod tests { "variants": [] } }); - let data: fetch_schema_query::ResponseData = serde_json::from_value(json_response).unwrap(); + let data: graph_fetch_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); @@ -90,7 +90,7 @@ 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 data: graph_fetch_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); @@ -106,7 +106,7 @@ mod tests { "variants": [], }, }); - let data: fetch_schema_query::ResponseData = serde_json::from_value(json_response).unwrap(); + let data: graph_fetch_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); 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/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..152be900a 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; 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,10 +32,10 @@ pub fn run( fn get_sdl_from_response_data( input: SubgraphFetchInput, response_data: SubgraphFetchResponseData, -) -> Result { +) -> 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 }) + Ok(FetchResponse { sdl }) } fn get_services_from_response_data( 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/shared/fetch_response.rs b/crates/rover-client/src/shared/fetch_response.rs new file mode 100644 index 000000000..000a76104 --- /dev/null +++ b/crates/rover-client/src/shared/fetch_response.rs @@ -0,0 +1,4 @@ +#[derive(Debug, Clone, PartialEq)] +pub struct FetchResponse { + pub sdl: String, +} diff --git a/crates/rover-client/src/shared/mod.rs b/crates/rover-client/src/shared/mod.rs index 9a2f6f445..806fd3ce8 100644 --- a/crates/rover-client/src/shared/mod.rs +++ b/crates/rover-client/src/shared/mod.rs @@ -1,9 +1,11 @@ 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 composition_error::CompositionError; +pub use fetch_response::FetchResponse; pub use git_context::GitContext; pub use graph_ref::GraphRef; 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/output.rs b/src/command/output.rs index 685876718..6020f18a3 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}; use termimad::MadSkin; /// RoverStdout defines all of the different types of data that are printed @@ -22,7 +22,7 @@ use termimad::MadSkin; pub enum RoverStdout { DocsList(HashMap<&'static str, &'static str>), SupergraphSdl(String), - Sdl(String), + FetchResponse(FetchResponse), CoreSchema(String), SchemaHash(String), SubgraphList(SubgraphListResponse), @@ -56,9 +56,9 @@ impl RoverStdout { print_descriptor("Supergraph SDL"); print_content(&sdl); } - RoverStdout::Sdl(sdl) => { + RoverStdout::FetchResponse(fetch_response) => { print_descriptor("SDL"); - print_content(&sdl); + print_content(&fetch_response.sdl); } RoverStdout::CoreSchema(csdl) => { print_descriptor("CoreSchema"); 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)) } } From ff15bd11b10496a3dba5fc076ee25f0a7982cd3a Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Wed, 14 Jul 2021 14:04:01 -0500 Subject: [PATCH 2/4] chore: move ValidationPeriod to rover-client --- Cargo.lock | 2 +- Cargo.toml | 1 - crates/rover-client/Cargo.toml | 1 + crates/rover-client/src/error.rs | 8 ++++ .../src/operations/graph/check/types.rs | 11 ++++- .../src/operations/subgraph/check/types.rs | 19 ++++++++- .../rover-client/src/shared/check_response.rs | 38 ++++++++++++++++- crates/rover-client/src/shared/mod.rs | 4 +- src/command/graph/check.rs | 9 ++-- src/command/output.rs | 3 +- src/command/subgraph/check.rs | 9 ++-- src/error/metadata/mod.rs | 4 ++ src/utils/parsers.rs | 41 ------------------- 13 files changed, 88 insertions(+), 62 deletions(-) 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..973f6da74 100644 --- a/crates/rover-client/src/error.rs +++ b/crates/rover-client/src/error.rs @@ -132,6 +132,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/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/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/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/mod.rs b/crates/rover-client/src/shared/mod.rs index 806fd3ce8..705c6eec5 100644 --- a/crates/rover-client/src/shared/mod.rs +++ b/crates/rover-client/src/shared/mod.rs @@ -4,7 +4,9 @@ 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; pub use git_context::GitContext; 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/output.rs b/src/command/output.rs index 6020f18a3..4d7aff961 100644 --- a/src/command/output.rs +++ b/src/command/output.rs @@ -168,8 +168,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/error/metadata/mod.rs b/src/error/metadata/mod.rs index b2f33d9d4..51e4ccc26 100644 --- a/src/error/metadata/mod.rs +++ b/src/error/metadata/mod.rs @@ -144,6 +144,10 @@ impl From<&mut anyhow::Error> for Metadata { 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/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 9646222a1353fdabe384d772fe47921a47a00943 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Wed, 14 Jul 2021 14:04:18 -0500 Subject: [PATCH 3/4] chore: update supergraph fetch composition errors --- crates/rover-client/src/error.rs | 2 +- .../src/operations/supergraph/fetch.graphql | 1 + .../src/operations/supergraph/fetch.rs | 25 ++++++++++++++----- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/crates/rover-client/src/error.rs b/crates/rover-client/src/error.rs index 973f6da74..33efb29a4 100644 --- a/crates/rover-client/src/error.rs +++ b/crates/rover-client/src/error.rs @@ -103,7 +103,7 @@ pub enum RoverClientError { #[error("No supergraph SDL exists for \"{graph}\" because its subgraphs failed to compose.")] NoCompositionPublishes { graph: String, - composition_errors: Vec, + composition_errors: Vec, }, #[error("{}", subgraph_composition_error_msg(.composition_errors))] diff --git a/crates/rover-client/src/operations/supergraph/fetch.graphql b/crates/rover-client/src/operations/supergraph/fetch.graphql index 5436b091a..a5c08d19d 100644 --- a/crates/rover-client/src/operations/supergraph/fetch.graphql +++ b/crates/rover-client/src/operations/supergraph/fetch.graphql @@ -13,6 +13,7 @@ query FetchSupergraphQuery($graphId: ID!, $variant: String!) { mostRecentCompositionPublish(graphVariant: $variant) { errors { message + code } } } diff --git a/crates/rover-client/src/operations/supergraph/fetch.rs b/crates/rover-client/src/operations/supergraph/fetch.rs index 5e7a05b5d..0908982d1 100644 --- a/crates/rover-client/src/operations/supergraph/fetch.rs +++ b/crates/rover-client/src/operations/supergraph/fetch.rs @@ -1,5 +1,7 @@ use crate::blocking::StudioClient; +use crate::shared::CompositionError; use crate::RoverClientError; + use graphql_client::*; // I'm not sure where this should live long-term @@ -62,7 +64,10 @@ 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, @@ -138,15 +143,23 @@ mod tests { 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 json_response = json!({ @@ -155,7 +168,7 @@ mod tests { "schemaTag": null, "variants": [{"name": variant}], "mostRecentCompositionPublish": { - "errors": composition_errors_json + "errors": composition_errors_json, } }, }); From f05c0ba2a35790ee75808db0492d0079822cc52c Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Thu, 15 Jul 2021 12:23:38 -0500 Subject: [PATCH 4/4] chore: refactor the rest of rover-client --- crates/rover-client/src/error.rs | 27 ++-- .../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/fetch/runner.rs | 47 ++++--- .../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/delete/runner.rs | 19 ++- .../src/operations/subgraph/fetch/runner.rs | 39 ++++-- .../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} | 4 +- .../src/operations/supergraph/fetch/mod.rs | 5 + .../supergraph/{fetch.rs => fetch/runner.rs} | 119 ++++++++++-------- .../src/operations/supergraph/fetch/types.rs | 18 +++ .../rover-client/src/shared/fetch_response.rs | 15 ++- crates/rover-client/src/shared/git_context.rs | 15 --- crates/rover-client/src/shared/mod.rs | 2 +- src/command/graph/publish.rs | 21 ++-- src/command/output.rs | 16 ++- src/command/supergraph/compose/do_compose.rs | 3 +- src/command/supergraph/fetch.rs | 11 +- src/error/metadata/mod.rs | 24 ++-- src/error/metadata/suggestion.rs | 30 ++--- 33 files changed, 427 insertions(+), 277 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 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} (82%) create mode 100644 crates/rover-client/src/operations/supergraph/fetch/mod.rs rename crates/rover-client/src/operations/supergraph/{fetch.rs => fetch/runner.rs} (74%) create mode 100644 crates/rover-client/src/operations/supergraph/fetch/types.rs diff --git a/crates/rover-client/src/error.rs b/crates/rover-client/src/error.rs index 33efb29a4..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, + 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, }, 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/fetch/runner.rs b/crates/rover-client/src/operations/graph/fetch/runner.rs index 53d661e9e..fc72a3b53 100644 --- a/crates/rover-client/src/operations/graph/fetch/runner.rs +++ b/crates/rover-client/src/operations/graph/fetch/runner.rs @@ -1,6 +1,6 @@ use crate::blocking::StudioClient; use crate::operations::graph::fetch::GraphFetchInput; -use crate::shared::FetchResponse; +use crate::shared::{FetchResponse, GraphRef, Sdl, SdlType}; use crate::RoverClientError; use graphql_client::*; @@ -29,21 +29,26 @@ pub fn run( input: GraphFetchInput, client: &StudioClient, ) -> Result { - let graph = input.graph_ref.name.clone(); - let invalid_variant = input.graph_ref.variant.clone(); + let graph_ref = input.graph_ref.clone(); let response_data = client.post::(input.into())?; - let sdl = get_schema_from_response_data(response_data, graph, invalid_variant)?; - Ok(FetchResponse { sdl }) + 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: graph_fetch_query::ResponseData, - graph: String, - invalid_variant: String, + 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, }) @@ -79,8 +83,8 @@ mod tests { } }); let data: graph_fetch_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 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()); @@ -91,8 +95,8 @@ mod tests { let json_response = json!({ "service": null, "frontendUrlRoot": "https://studio.apollographql.com" }); let data: graph_fetch_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 graph_ref = mock_graph_ref(); + let output = get_schema_from_response_data(data, graph_ref); assert!(output.is_err()); } @@ -107,13 +111,16 @@ mod tests { }, }); let data: graph_fetch_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 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/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/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/runner.rs b/crates/rover-client/src/operations/subgraph/fetch/runner.rs index 152be900a..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,6 @@ use super::types::*; use crate::blocking::StudioClient; -use crate::shared::FetchResponse; +use crate::shared::{FetchResponse, GraphRef, Sdl, SdlType}; use crate::RoverClientError; use graphql_client::*; @@ -33,24 +33,32 @@ 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(FetchResponse { sdl }) + 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, }), }?; @@ -59,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, }) } @@ -116,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!([ { @@ -146,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()); } @@ -195,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/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 82% rename from crates/rover-client/src/operations/supergraph/fetch.graphql rename to crates/rover-client/src/operations/supergraph/fetch/fetch_query.graphql index a5c08d19d..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 { @@ -17,4 +17,4 @@ query FetchSupergraphQuery($graphId: ID!, $variant: String!) { } } } -} \ 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 74% rename from crates/rover-client/src/operations/supergraph/fetch.rs rename to crates/rover-client/src/operations/supergraph/fetch/runner.rs index 0908982d1..e0fc3d980 100644 --- a/crates/rover-client/src/operations/supergraph/fetch.rs +++ b/crates/rover-client/src/operations/supergraph/fetch/runner.rs @@ -1,5 +1,6 @@ use crate::blocking::StudioClient; -use crate::shared::CompositionError; +use crate::operations::supergraph::fetch::SupergraphFetchInput; +use crate::shared::{CompositionError, FetchResponse, GraphRef, Sdl, SdlType}; use crate::RoverClientError; use graphql_client::*; @@ -12,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(), @@ -54,7 +60,7 @@ fn get_supergraph_sdl_from_response_data( } } else { Err(RoverClientError::ExpectedFederatedGraph { - graph, + graph_ref, can_operation_convert: false, }) } @@ -70,7 +76,7 @@ fn get_supergraph_sdl_from_response_data( }) .collect(); Err(RoverClientError::NoCompositionPublishes { - graph, + graph_ref, composition_errors, }) } else { @@ -80,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, }) } @@ -117,31 +122,38 @@ 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![ CompositionError { message: "Unknown type \"Unicorn\".".to_string(), @@ -162,21 +174,22 @@ mod tests { "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, } }, }); - 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(); @@ -186,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!({ @@ -197,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, } @@ -213,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!({ @@ -226,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(); @@ -240,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!({ @@ -256,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(), } @@ -267,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/fetch_response.rs b/crates/rover-client/src/shared/fetch_response.rs index 000a76104..5f4ab4381 100644 --- a/crates/rover-client/src/shared/fetch_response.rs +++ b/crates/rover-client/src/shared/fetch_response.rs @@ -1,4 +1,17 @@ #[derive(Debug, Clone, PartialEq)] pub struct FetchResponse { - pub sdl: String, + 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 705c6eec5..31e0f3e97 100644 --- a/crates/rover-client/src/shared/mod.rs +++ b/crates/rover-client/src/shared/mod.rs @@ -8,6 +8,6 @@ pub use check_response::{ ChangeSeverity, CheckConfig, CheckResponse, SchemaChange, ValidationPeriod, }; pub use composition_error::CompositionError; -pub use fetch_response::FetchResponse; +pub use fetch_response::{FetchResponse, Sdl, SdlType}; pub use git_context::GitContext; pub use graph_ref::GraphRef; 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 4d7aff961..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, FetchResponse}; +use rover_client::shared::{CheckResponse, FetchResponse, SdlType}; use termimad::MadSkin; /// RoverStdout defines all of the different types of data that are printed @@ -21,7 +21,6 @@ use termimad::MadSkin; #[derive(Clone, PartialEq, Debug)] pub enum RoverStdout { DocsList(HashMap<&'static str, &'static str>), - SupergraphSdl(String), FetchResponse(FetchResponse), CoreSchema(String), SchemaHash(String), @@ -52,13 +51,12 @@ impl RoverStdout { } println!("{}", table); } - RoverStdout::SupergraphSdl(sdl) => { - print_descriptor("Supergraph SDL"); - print_content(&sdl); - } RoverStdout::FetchResponse(fetch_response) => { - print_descriptor("SDL"); - print_content(&fetch_response.sdl); + 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) => { 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 51e4ccc26..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,7 +135,7 @@ 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)) } 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) }