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 15, 2020
1 parent e1e1e06 commit 9a27a30
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 0 deletions.
36 changes: 36 additions & 0 deletions crates/rover-client/src/query/subgraph/check.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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
numberOfCheckedOperations
changes {
severity
code
description
}
}
targetUrl
}
}
}
}
79 changes: 79 additions & 0 deletions crates/rover-client/src/query/subgraph/check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use crate::blocking::StudioClient;
use crate::RoverClientError;
use graphql_client::*;

use reqwest::Url;

#[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,
}
}
2 changes: 2 additions & 0 deletions crates/rover-client/src/query/subgraph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ pub mod push;
/// "subgraph delete" command execution
pub mod delete;

/// "subgraph check" command execution
pub mod check;
109 changes: 109 additions & 0 deletions src/command/subgraph/check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use std::path::PathBuf;

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

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 {
/// <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: GraphRef,

/// Name of the implementing service to validate
#[structopt(long = "service", required = true)]
#[serde(skip_serializing)]
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 = "schema", short = "s")]
#[serde(skip_serializing)]
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 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 num_failures = print_changes(&res.changes);

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

fn print_changes(
checks: &[check::check_partial_schema_query::CheckPartialSchemaQueryServiceCheckPartialSchemaCheckSchemaResultDiffToPreviousChanges],
) -> u64 {
let mut num_failures = 0;

if checks.is_empty() {
let mut table = Table::new();
table.add_row(row!["Change", "Code", "Description"]);
for check in checks {
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);
}

num_failures
}
6 changes: 6 additions & 0 deletions src/command/subgraph/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod check;
mod delete;
mod push;

Expand All @@ -17,15 +18,20 @@ pub struct Subgraph {
pub enum Command {
/// Push an implementing service schema from a local file
Push(push::Push),

/// Delete an implementing service and trigger composition
Delete(delete::Delete),

/// Validate changes to an implementing service
Check(check::Check),
}

impl Subgraph {
pub fn run(&self) -> Result<RoverStdout> {
match &self.command {
Command::Push(command) => command.run(),
Command::Delete(command) => command.run(),
Command::Check(command) => command.run(),
}
}
}

0 comments on commit 9a27a30

Please sign in to comment.