Skip to content

Commit

Permalink
wip: refactor subgraph check and add line numbers
Browse files Browse the repository at this point in the history
  • Loading branch information
EverlastingBugstopper committed May 20, 2021
1 parent c1b08d9 commit 7d7332d
Show file tree
Hide file tree
Showing 14 changed files with 277 additions and 209 deletions.
21 changes: 21 additions & 0 deletions crates/rover-client/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use reqwest::Url;
use thiserror::Error;

use crate::query::subgraph::check::types::CompositionError;

/// RoverClientError represents all possible failures that can occur during a client request.
#[derive(Error, Debug)]
pub enum RoverClientError {
Expand Down Expand Up @@ -103,6 +105,12 @@ pub enum RoverClientError {
graph: String,
composition_errors: Vec<String>,
},

#[error("{}", subgraph_composition_error_msg(.composition_errors))]
SubgraphCompositionErrors {
graph_name: String,
composition_errors: Vec<CompositionError>
},

/// This error occurs when the Studio API returns no implementing services for a graph
/// This response shouldn't be possible!
Expand Down Expand Up @@ -141,3 +149,16 @@ pub enum RoverClientError {
#[error("This endpoint doesn't support subgraph introspection via the Query._service field")]
SubgraphIntrospectionNotAvailable,
}

fn subgraph_composition_error_msg(composition_errors: &[CompositionError]) -> String {
let num_failures = composition_errors.len();
if num_failures == 0 {
unreachable!("No composition errors were encountered while composing the supergraph.");
}
let mut msg = String::new();
msg.push_str(&match num_failures {
1 => "Encountered 1 composition error while composing the supergraph.".to_string(),
_ => format!("Encountered {} composition errors while composing the supergraph.", num_failures)
});
msg
}
119 changes: 0 additions & 119 deletions crates/rover-client/src/query/subgraph/check.rs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mutation CheckPartialSchemaQuery(
mutation SubgraphCheckQuery(
$graph_id: ID!
$variant: String!
$implementingServiceName: String!
Expand All @@ -17,6 +17,11 @@
compositionValidationResult {
errors {
message
code
locations {
line
column
}
}
}
checkSchemaResult {
Expand Down
3 changes: 3 additions & 0 deletions crates/rover-client/src/query/subgraph/check/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod query_runner;
pub(crate) mod types;
pub use types::SubgraphCheckResponse;
121 changes: 121 additions & 0 deletions crates/rover-client/src/query/subgraph/check/query_runner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use graphql_client::*;
use crate::blocking::StudioClient;
use crate::query::config::is_federated;
use crate::RoverClientError;
use super::types::*;

#[derive(GraphQLQuery)]
// The paths are relative to the directory where your `Cargo.toml` is located.
// Both json and the GraphQL schema language are supported as sources for the schema
#[graphql(
query_path = "src/query/subgraph/check/check_query.graphql",
schema_path = ".schema/schema.graphql",
response_derives = "PartialEq, Debug, Serialize, Deserialize",
deprecated = "warn"
)]
/// This struct is used to generate the module containing `Variables` and
/// `ResponseData` structs.
/// Snake case of this name is the mod name. i.e. subgraph_check_query
pub struct SubgraphCheckQuery;


/// The main function to be used from this module.
/// This function takes a proposed schema and validates it against a published
/// schema.
pub fn run(
variables: subgraph_check_query::Variables,
client: &StudioClient,
) -> Result<SubgraphCheckResponse, RoverClientError> {
let graph = variables.graph_id.clone();
// This response is used to check whether or not the current graph is federated.
let is_federated = is_federated::run(
is_federated::is_federated_graph::Variables {
graph_id: variables.graph_id.clone(),
graph_variant: variables.variant.clone(),
},
&client,
)?;
if !is_federated {
return Err(RoverClientError::ExpectedFederatedGraph {
graph,
can_operation_convert: false,
});
}
let data = client.post::<SubgraphCheckQuery>(variables)?;
get_check_response_from_data(data, graph)
}

fn get_check_response_from_data(
data: subgraph_check_query::ResponseData,
graph_name: String,
) -> Result<SubgraphCheckResponse, RoverClientError> {
let service = data.service.ok_or(RoverClientError::NoService { graph: graph_name.clone() })?;

// for some reason this is a `Vec<Option<CompositionError>>`
// we convert this to just `Vec<CompositionError>` because the `None`
// errors would be useless.
let query_composition_errors: Vec<subgraph_check_query::SubgraphCheckQueryServiceCheckPartialSchemaCompositionValidationResultErrors> = service
.check_partial_schema
.composition_validation_result
.errors;

if query_composition_errors.is_empty() {
let check_schema_result = service.check_partial_schema.check_schema_result.ok_or(
RoverClientError::MalformedResponse {
null_field: "service.check_partial_schema.check_schema_result".to_string(),
},
)?;

let target_url = check_schema_result.target_url.map(|u| u.to_string());

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.into();

let mut changes = Vec::with_capacity(diff_to_previous.changes.len());
for change in diff_to_previous.changes {
changes.push(SchemaChange {
code: change.code,
severity: change.severity.into(),
description: change.description
});
}

let check_result = SubgraphCheckResponse {
target_url,
number_of_checked_operations,
changes,
change_severity
};

Ok(check_result)
} else {
let num_failures = query_composition_errors.len();

let mut composition_errors = Vec::with_capacity(num_failures);
for query_composition_error in query_composition_errors {
let location = if !query_composition_error.locations.is_empty() {
if let Some(first_location) = &query_composition_error.locations[0] {
Some(ErrorLocation {
line: first_location.line,
column: first_location.column
})
} else {
None
}
} else {
None
};

composition_errors.push(CompositionError {
message: query_composition_error.message,
location,
code: query_composition_error.code
});
}
Err(RoverClientError::SubgraphCompositionErrors { graph_name, composition_errors })
}
}
60 changes: 60 additions & 0 deletions crates/rover-client/src/query/subgraph/check/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use std::fmt;

use super::query_runner::subgraph_check_query;

pub(crate) type Timestamp = String;
type QueryChangeSeverity = subgraph_check_query::ChangeSeverity;

#[derive(Debug, Clone, PartialEq)]
pub struct SubgraphCheckResponse {
pub target_url: Option<String>,
pub number_of_checked_operations: i64,
pub changes: Vec<SchemaChange>,
pub change_severity: ChangeSeverity
}

#[derive(Debug, Clone, PartialEq)]
pub enum ChangeSeverity {
PASS,
FAIL,
}

impl fmt::Display for ChangeSeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = match self {
ChangeSeverity::PASS => "PASS",
ChangeSeverity::FAIL => "FAIL"
};
write!(f, "{}", msg)
}
}

impl From<QueryChangeSeverity> for ChangeSeverity {
fn from(severity: QueryChangeSeverity) -> Self {
match severity {
QueryChangeSeverity::NOTICE => ChangeSeverity::PASS,
QueryChangeSeverity::FAILURE => ChangeSeverity::FAIL,
_ => unreachable!("Unknown change severity")
}
}
}

#[derive(Debug, Clone, PartialEq)]
pub struct SchemaChange {
pub code: String,
pub description: String,
pub severity: ChangeSeverity
}

#[derive(Debug, Clone, PartialEq)]
pub struct CompositionError {
pub message: String,
pub location: Option<ErrorLocation>,
pub code: Option<String>
}

#[derive(Debug, Clone, PartialEq)]
pub struct ErrorLocation {
pub line: i64,
pub column: i64,
}
Loading

0 comments on commit 7d7332d

Please sign in to comment.