Skip to content

Commit da9dcab

Browse files
feat(rover): subgraph checks
1 parent 8cd0e88 commit da9dcab

File tree

5 files changed

+233
-0
lines changed

5 files changed

+233
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
mutation CheckPartialSchemaQuery (
2+
$graph_id: ID!
3+
$variant: String!
4+
$implementingServiceName: String!
5+
$partialSchema: PartialSchemaInput!
6+
) {
7+
service(id: $graph_id) {
8+
checkPartialSchema(
9+
graphVariant: $variant
10+
implementingServiceName: $implementingServiceName
11+
partialSchema: $partialSchema
12+
) {
13+
compositionValidationResult {
14+
compositionValidationDetails {
15+
schemaHash
16+
}
17+
graphCompositionID
18+
errors {
19+
message
20+
}
21+
}
22+
checkSchemaResult {
23+
diffToPrevious {
24+
severity
25+
numberOfCheckedOperations
26+
changes {
27+
severity
28+
code
29+
description
30+
}
31+
}
32+
targetUrl
33+
}
34+
}
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use crate::blocking::StudioClient;
2+
use crate::RoverClientError;
3+
use graphql_client::*;
4+
5+
use reqwest::Url;
6+
7+
#[derive(GraphQLQuery)]
8+
// The paths are relative to the directory where your `Cargo.toml` is located.
9+
// Both json and the GraphQL schema language are supported as sources for the schema
10+
#[graphql(
11+
query_path = "src/query/subgraph/check.graphql",
12+
schema_path = ".schema/schema.graphql",
13+
response_derives = "PartialEq, Debug, Serialize, Deserialize",
14+
deprecated = "warn"
15+
)]
16+
/// This struct is used to generate the module containing `Variables` and
17+
/// `ResponseData` structs.
18+
/// Snake case of this name is the mod name. i.e. check_partial_schema_query
19+
pub struct CheckPartialSchemaQuery;
20+
21+
/// The main function to be used from this module.
22+
/// This function takes a proposed schema and validates it against a pushed
23+
/// schema.
24+
pub fn run(
25+
variables: check_partial_schema_query::Variables,
26+
client: &StudioClient,
27+
) -> Result<CheckResponse, RoverClientError> {
28+
let data = client.post::<CheckPartialSchemaQuery>(variables)?;
29+
get_check_response_from_data(data)
30+
}
31+
32+
#[derive(Debug)]
33+
pub struct CheckResponse {
34+
pub target_url: Option<Url>,
35+
pub number_of_checked_operations: i64,
36+
pub change_severity: check_partial_schema_query::ChangeSeverity,
37+
pub changes: Vec<check_partial_schema_query::CheckPartialSchemaQueryServiceCheckPartialSchemaCheckSchemaResultDiffToPreviousChanges>
38+
}
39+
40+
fn get_check_response_from_data(
41+
data: check_partial_schema_query::ResponseData,
42+
) -> Result<CheckResponse, RoverClientError> {
43+
let service = data.service.ok_or(RoverClientError::NoService)?;
44+
45+
// TODO: fix this error
46+
let check_schema_result = service
47+
.check_partial_schema
48+
.check_schema_result
49+
.ok_or(RoverClientError::NoData)?;
50+
let target_url = get_url(check_schema_result.target_url);
51+
52+
let diff_to_previous = check_schema_result.diff_to_previous;
53+
54+
let number_of_checked_operations = diff_to_previous.number_of_checked_operations.unwrap_or(0);
55+
56+
let change_severity = diff_to_previous.severity;
57+
let changes = diff_to_previous.changes;
58+
59+
Ok(CheckResponse {
60+
target_url,
61+
number_of_checked_operations,
62+
change_severity,
63+
changes,
64+
})
65+
}
66+
67+
fn get_url(url: Option<String>) -> Option<Url> {
68+
match url {
69+
Some(url) => {
70+
let url = Url::parse(&url);
71+
match url {
72+
Ok(url) => Some(url),
73+
// if the API returns an invalid URL, don't put it in the response
74+
Err(_) => None,
75+
}
76+
}
77+
None => None,
78+
}
79+
}

crates/rover-client/src/query/subgraph/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ pub mod push;
33

44
/// "subgraph delete" command execution
55
pub mod delete;
6+
7+
/// "subgraph check" command execution
8+
pub mod check;

src/command/subgraph/check.rs

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use std::path::PathBuf;
2+
3+
use anyhow::{Context, Result};
4+
use prettytable::{cell, row, Table};
5+
use serde::Serialize;
6+
use structopt::StructOpt;
7+
8+
use rover_client::query::subgraph::check;
9+
10+
use crate::client::get_studio_client;
11+
use crate::command::RoverStdout;
12+
use crate::utils::parsers::{parse_graph_ref, GraphRef};
13+
14+
#[derive(Debug, Serialize, StructOpt)]
15+
pub struct Check {
16+
/// <NAME>@<VARIANT> of graph in Apollo Studio to validate.
17+
/// @<VARIANT> may be left off, defaulting to @current
18+
#[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))]
19+
#[serde(skip_serializing)]
20+
graph: GraphRef,
21+
22+
/// Name of the implementing service to validate
23+
#[structopt(long = "service", required = true)]
24+
#[serde(skip_serializing)]
25+
service_name: String,
26+
27+
/// Name of configuration profile to use
28+
#[structopt(long = "profile", default_value = "default")]
29+
#[serde(skip_serializing)]
30+
profile_name: String,
31+
32+
/// Path of .graphql/.gql schema file to push
33+
#[structopt(long = "schema", short = "s")]
34+
#[serde(skip_serializing)]
35+
schema_path: PathBuf,
36+
}
37+
38+
impl Check {
39+
pub fn run(&self) -> Result<RoverStdout> {
40+
let client =
41+
get_studio_client(&self.profile_name).context("Failed to get studio client")?;
42+
let sdl = std::fs::read_to_string(&self.schema_path)
43+
.with_context(|| format!("Could not read file `{}`", &self.schema_path.display()))?;
44+
let partial_schema = check::check_partial_schema_query::PartialSchemaInput {
45+
sdl: Some(sdl),
46+
hash: None,
47+
};
48+
let res = check::run(
49+
check::check_partial_schema_query::Variables {
50+
graph_id: self.graph.name.clone(),
51+
variant: self.graph.variant.clone(),
52+
partial_schema,
53+
implementing_service_name: self.service_name.clone(),
54+
},
55+
&client,
56+
)
57+
.context("Failed to validate schema")?;
58+
59+
tracing::info!(
60+
"Validated schema against metrics from variant {} on subgraph {}",
61+
&self.graph.variant,
62+
&self.graph.name
63+
);
64+
tracing::info!(
65+
"Compared {} schema changes against {} operations",
66+
res.changes.len(),
67+
res.number_of_checked_operations
68+
);
69+
70+
if let Some(url) = res.target_url {
71+
tracing::info!("View full details here");
72+
tracing::info!("{}", url.to_string());
73+
}
74+
75+
let num_failures = print_changes(&res.changes);
76+
77+
match num_failures {
78+
0 => Ok(RoverStdout::None),
79+
1 => Err(anyhow::anyhow!("Encountered 1 failure.")),
80+
_ => Err(anyhow::anyhow!("Encountered {} failures.", num_failures)),
81+
}
82+
}
83+
}
84+
85+
fn print_changes(
86+
checks: &[check::check_partial_schema_query::CheckPartialSchemaQueryServiceCheckPartialSchemaCheckSchemaResultDiffToPreviousChanges],
87+
) -> u64 {
88+
let mut num_failures = 0;
89+
90+
if checks.is_empty() {
91+
let mut table = Table::new();
92+
table.add_row(row!["Change", "Code", "Description"]);
93+
for check in checks {
94+
let change = match check.severity {
95+
check::check_partial_schema_query::ChangeSeverity::NOTICE => "PASS",
96+
check::check_partial_schema_query::ChangeSeverity::FAILURE => {
97+
num_failures += 1;
98+
"FAIL"
99+
}
100+
_ => unreachable!("Unknown change severity"),
101+
};
102+
table.add_row(row![change, check.code, check.description]);
103+
}
104+
105+
eprintln!("{}", table);
106+
}
107+
108+
num_failures
109+
}

src/command/subgraph/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod check;
12
mod delete;
23
mod push;
34

@@ -17,15 +18,20 @@ pub struct Subgraph {
1718
pub enum Command {
1819
/// Push an implementing service schema from a local file
1920
Push(push::Push),
21+
2022
/// Delete an implementing service and trigger composition
2123
Delete(delete::Delete),
24+
25+
/// Validate changes to an implementing service
26+
Check(check::Check),
2227
}
2328

2429
impl Subgraph {
2530
pub fn run(&self) -> Result<RoverStdout> {
2631
match &self.command {
2732
Command::Push(command) => command.run(),
2833
Command::Delete(command) => command.run(),
34+
Command::Check(command) => command.run(),
2935
}
3036
}
3137
}

0 commit comments

Comments
 (0)