Skip to content

Commit

Permalink
feat(rover): subgraph checks
Browse files Browse the repository at this point in the history
  • Loading branch information
EverlastingBugstopper committed Dec 11, 2020
1 parent 9f22db6 commit 5dfe6d7
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 47 deletions.
1 change: 0 additions & 1 deletion crates/rover-client/src/query/graph/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ pub struct CheckResponse {
fn get_check_response_from_data(
data: check_schema_query::ResponseData,
) -> Result<CheckResponse, RoverClientError> {
tracing::debug!("{:#?}", &data);
let service = data.service.ok_or(RoverClientError::NoService)?;
let target_url = get_url(service.check_schema.target_url);

Expand Down
48 changes: 48 additions & 0 deletions crates/rover-client/src/query/subgraph/check.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
mutation CheckPartialSchemaQuery (
$graph_id: ID!
$variant: String!
$implementingServiceName: String!
$partialSchema: PartialSchemaInput!
) {
service(id: $graph_id) {
checkPartialSchema(
graphVariant: $variant
implementingServiceName: $implementingServiceName
partialSchema: $partialSchema
) {
compositionValidationResult {
compositionValidationDetails {
schemaHash
}
graphCompositionID
errors {
message
}
}
checkSchemaResult {
diffToPrevious {
severity
affectedClients {
__typename
}
affectedQueries {
__typename
}
numberOfCheckedOperations
changes {
severity
code
description
}
validationConfig {
from
to
queryCountThreshold
queryCountThresholdPercentage
}
}
targetUrl
}
}
}
}
78 changes: 78 additions & 0 deletions crates/rover-client/src/query/subgraph/check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use crate::blocking::StudioClient;
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",
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;

/// The main function to be used from this module.
/// This function takes a proposed schema and validates it against a pushed
/// schema.
pub fn run(
variables: check_partial_schema_query::Variables,
client: &StudioClient,
) -> Result<CheckResponse, RoverClientError> {
let data = client.post::<CheckPartialSchemaQuery>(variables)?;
get_check_response_from_data(data)
}

#[derive(Debug)]
pub struct CheckResponse {
pub target_url: Option<Url>,
pub number_of_checked_operations: i64,
pub change_severity: check_partial_schema_query::ChangeSeverity,
pub changes: Vec<check_partial_schema_query::CheckPartialSchemaQueryServiceCheckPartialSchemaCheckSchemaResultDiffToPreviousChanges>
}

fn get_check_response_from_data(
data: check_partial_schema_query::ResponseData,
) -> Result<CheckResponse, RoverClientError> {
let service = data.service.ok_or(RoverClientError::NoService)?;

// TODO: fix this error
let check_schema_result = service.check_partial_schema.check_schema_result.ok_or(RoverClientError::NoData)?;
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;

Ok(CheckResponse {
target_url,
number_of_checked_operations,
change_severity,
changes,
})
}

fn get_url(url: Option<String>) -> Option<Url> {
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,
}
}
3 changes: 3 additions & 0 deletions crates/rover-client/src/query/subgraph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ pub mod push;

/// "subgraph delete" command execution
pub mod delete;

/// "subgraph check" command execution
pub mod check;
53 changes: 36 additions & 17 deletions src/command/graph/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,42 @@ use std::path::PathBuf;
use anyhow::{Context, Result};
use serde::Serialize;
use structopt::StructOpt;
use prettytable::{cell, row, Table};

use rover_client::query::graph::check;

use crate::client::get_studio_client;
use crate::command::RoverStdout;
use crate::utils::parsers::{parse_graph_ref, GraphRef};

#[derive(Debug, Serialize, StructOpt)]
pub struct Check {
/// ID of graph in Apollo Studio to fetch from
#[structopt(name = "GRAPH_NAME")]
/// <NAME>@<VARIANT> of graph in Apollo Studio to validate.
/// @<VARIANT> may be left off, defaulting to @current
#[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))]
#[serde(skip_serializing)]
graph_name: String,

/// Name of graph variant in Apollo Studio to fetch from
#[structopt(long, default_value = "current")]
#[serde(skip_serializing)]
variant: String,
graph: GraphRef,

/// Name of configuration profile to use
#[structopt(long = "profile", default_value = "default")]
#[serde(skip_serializing)]
profile_name: String,

/// Path of .graphql/.gql schema file to push
#[structopt(long, short = "s")]
#[structopt(long = "schema", short = "s")]
#[serde(skip_serializing)]
schema: PathBuf,
schema_path: PathBuf,
}

impl Check {
pub fn run(&self) -> Result<RoverStdout> {
let client =
get_studio_client(&self.profile_name).context("Failed to get studio client")?;
let schema = std::fs::read_to_string(&self.schema)
.with_context(|| format!("Could not read file `{}`", &self.schema.display()))?;
let schema = std::fs::read_to_string(&self.schema_path).with_context(|| format!("Could not read file `{}`", &self.schema_path.display()))?;
let res = check::run(
check::check_schema_query::Variables {
graph_id: self.graph_name.clone(),
variant: Some(self.variant.clone()),
graph_id: self.graph.name.clone(),
variant: Some(self.graph.variant.clone()),
schema: Some(schema),
},
&client,
Expand All @@ -50,8 +47,8 @@ impl Check {

tracing::info!(
"Validated schema against metrics from variant {} on graph {}",
&self.variant,
&self.graph_name
&self.graph.variant,
&self.graph.name
);
tracing::info!(
"Compared {} schema changes against {} operations",
Expand All @@ -64,6 +61,28 @@ impl Check {
tracing::info!("{}", url.to_string());
}

Ok(RoverStdout::Checks(res.changes))
let mut table = Table::new();

let mut num_failures: u64 = 0;
table.add_row(row!["Change", "Code", "Description"]);
for check in res.changes {
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);

match num_failures {
0 => Ok(RoverStdout::None),
1 => Err(anyhow::anyhow!("Encountered 1 failure.")),
_ => Err(anyhow::anyhow!("Encountered {} failures.", num_failures))
}
}
}
19 changes: 0 additions & 19 deletions src/command/output.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use atty::{self, Stream};
use prettytable::{cell, row, Table};

use rover_client::query::graph::check::check_schema_query;

/// RoverStdout defines all of the different types of data that are printed
/// to `stdout`. Every one of Rover's commands should return `anyhow::Result<RoverStdout>`
Expand All @@ -15,7 +12,6 @@ use rover_client::query::graph::check::check_schema_query;
pub enum RoverStdout {
SDL(String),
SchemaHash(String),
Checks(Vec<check_schema_query::CheckSchemaQueryServiceCheckSchemaDiffToPreviousChanges>),
None,
}

Expand All @@ -40,21 +36,6 @@ impl RoverStdout {
}
println!("{}", &hash);
}
RoverStdout::Checks(checks) => {
let mut table = Table::new();

table.add_row(row!["Change", "Code", "Description"]);
for check in checks {
let change = match check.severity {
check_schema_query::ChangeSeverity::NOTICE => "PASS",
check_schema_query::ChangeSeverity::FAILURE => "FAIL",
_ => "UNKNOWN",
};
table.add_row(row![change, check.code, check.description]);
}

table.printstd();
}
RoverStdout::None => (),
}
}
Expand Down
82 changes: 72 additions & 10 deletions src/command/subgraph/check.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,98 @@
use std::path::PathBuf;

use anyhow::Result;
use anyhow::{Context, Result};
use serde::Serialize;
use structopt::StructOpt;
use prettytable::{cell, row, Table};

use rover_client::query::subgraph::check;

use crate::client::get_studio_client;
use crate::command::RoverStdout;
use crate::utils::parsers::{parse_graph_ref, GraphRef};

#[derive(Debug, Serialize, StructOpt)]
pub struct Check {
/// ID of graph in Apollo Studio to fetch from
#[structopt(name = "GRAPH_NAME")]
/// <NAME>@<VARIANT> of graph in Apollo Studio to validate.
/// @<VARIANT> may be left off, defaulting to @current
#[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))]
#[serde(skip_serializing)]
graph_name: String,
graph: GraphRef,

/// Name of graph variant in Apollo Studio to fetch from
#[structopt(long, default_value = "current")]
/// Name of the implementing service to validate
#[structopt(long = "service", required = true)]
#[serde(skip_serializing)]
variant: String,
service_name: String,

/// Name of configuration profile to use
#[structopt(long = "profile", default_value = "default")]
#[serde(skip_serializing)]
profile_name: String,

/// Path of .graphql/.gql schema file to push
#[structopt(long, short = "s")]
#[structopt(long = "schema", short = "s")]
#[serde(skip_serializing)]
schema: PathBuf,
schema_path: PathBuf,
}

impl Check {
pub fn run(&self) -> Result<RoverStdout> {
Ok(RoverStdout::None)
let client =
get_studio_client(&self.profile_name).context("Failed to get studio client")?;
let sdl = std::fs::read_to_string(&self.schema_path).with_context(|| format!("Could not read file `{}`", &self.schema_path.display()))?;
let partial_schema = check::check_partial_schema_query::PartialSchemaInput {
sdl: Some(sdl),
hash: None
};
let res = check::run(
check::check_partial_schema_query::Variables {
graph_id: self.graph.name.clone(),
variant: self.graph.variant.clone(),
partial_schema,
implementing_service_name: self.service_name.clone()
},
&client,
)
.context("Failed to validate schema")?;

tracing::info!(
"Validated schema against metrics from variant {} on subgraph {}",
&self.graph.variant,
&self.graph.name
);
tracing::info!(
"Compared {} schema changes against {} operations",
res.changes.len(),
res.number_of_checked_operations
);

if let Some(url) = res.target_url {
tracing::info!("View full details here");
tracing::info!("{}", url.to_string());
}

let mut table = Table::new();

let mut num_failures: u64 = 0;
table.add_row(row!["Change", "Code", "Description"]);
for check in res.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);

match num_failures {
0 => Ok(RoverStdout::None),
1 => Err(anyhow::anyhow!("Encountered 1 failure.")),
_ => Err(anyhow::anyhow!("Encountered {} failures.", num_failures))
}
}
}

0 comments on commit 5dfe6d7

Please sign in to comment.