Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: structured output #676

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ab64ba2
feat: structured output
EverlastingBugstopper Jul 15, 2021
61045a4
chore: fix main imports
EverlastingBugstopper Jul 15, 2021
80c6e43
chore: rename check response fields
EverlastingBugstopper Jul 16, 2021
b9c74bc
feat: structure composition errors (#678)
EverlastingBugstopper Jul 19, 2021
3f62257
feat: structure sub/graph publish (#679)
EverlastingBugstopper Jul 19, 2021
cc4dad2
chore: adds data.success bool to json output (#681)
EverlastingBugstopper Jul 19, 2021
92f9ce4
chore: test structured output
EverlastingBugstopper Jul 19, 2021
1e089b4
chore: structure subgraph delete
EverlastingBugstopper Jul 20, 2021
b998239
chore: rename schema_hash to api_schema_hash
EverlastingBugstopper Jul 21, 2021
5bf3550
chore: test serializer for composition errors
EverlastingBugstopper Jul 23, 2021
0b52c70
chore(json): move all composition errors to top level data
EverlastingBugstopper Jul 23, 2021
a2c5c12
chore(json): fix up operation check errors
EverlastingBugstopper Jul 23, 2021
4e66dba
chore(json): add from implementations to ChangeSummary
EverlastingBugstopper Jul 23, 2021
14e59ad
fixup: remove unnecessary match for operation checks
EverlastingBugstopper Jul 23, 2021
291c81a
fixup: add tests for operation check errors
EverlastingBugstopper Jul 23, 2021
5747172
chore(json): --json -> --output json
EverlastingBugstopper Jul 23, 2021
fdceb28
chore: address lints
EverlastingBugstopper Jul 26, 2021
ea99465
fixup: formatting
EverlastingBugstopper Jul 26, 2021
3dc6070
chore: error.composition_errors -> error.details.build_errors
EverlastingBugstopper Jul 26, 2021
4806738
chore: add top level version to json output
EverlastingBugstopper Jul 26, 2021
94b44cf
chore: finish cleaning up build error renaming
EverlastingBugstopper Jul 26, 2021
f38617d
chore: address clippy
EverlastingBugstopper Jul 26, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 32 additions & 14 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ A minimal command in Rover would be laid out exactly like this:
pub struct MyNewCommand { }

impl MyNewCommand {
pub fn run(&self) -> Result<RoverStdout> {
Ok(RoverStdout::None)
pub fn run(&self) -> Result<RoverOutput> {
Ok(RoverOutput::None)
}
}
```
Expand All @@ -128,16 +128,16 @@ For our `graph hello` command, we'll add a new `hello.rs` file under `src/comman
use serde::Serialize;
use structopt::StructOpt;

use crate::command::RoverStdout;
use crate::command::RoverOutput;
use crate::Result;

#[derive(Debug, Serialize, StructOpt)]
pub struct Hello { }

impl Hello {
pub fn run(&self) -> Result<RoverStdout> {
pub fn run(&self) -> Result<RoverOutput> {
eprintln!("Hello, world!");
Ok(RoverStdout::None)
Ok(RoverOutput::None)
}
}
```
Expand Down Expand Up @@ -348,7 +348,7 @@ Before we go any further, lets make sure everything is set up properly. We're go
It should look something like this (you should make sure you are following the style of other commands when creating new ones):

```rust
pub fn run(&self, client_config: StudioClientConfig) -> Result<RoverStdout> {
pub fn run(&self, client_config: StudioClientConfig) -> Result<RoverOutput> {
let client = client_config.get_client(&self.profile_name)?;
let graph_ref = self.graph.to_string();
eprintln!(
Expand All @@ -362,7 +362,10 @@ pub fn run(&self, client_config: StudioClientConfig) -> Result<RoverStdout> {
},
&client,
)?;
Ok(RoverStdout::PlainText(deleted_at))
println!("{:?}", deleted_at);

// TODO: Add a new output type!
Ok(RoverOutput::None)
}
```

Expand Down Expand Up @@ -399,17 +402,32 @@ Unfortunately this is not the cleanest API and doesn't match the pattern set by

You'll want to define all of the types scoped to this command in `types.rs`, and re-export them from the top level `hello` module, and nothing else.

##### `RoverStdout`
##### `RoverOutput`

Now that you can actually execute the `hello::run` query and return its result, you should create a new variant of `RoverStdout` in `src/command/output.rs` that is not `PlainText`. Your new variant should print the descriptor using the `print_descriptor` function, and print the raw content using `print_content`.
Now that you can actually execute the `hello::run` query and return its result, you should create a new variant of `RoverOutput` in `src/command/output.rs` that is not `None`. Your new variant should print the descriptor using the `print_descriptor` function, and print the raw content using `print_content`.

To do so, change the line `Ok(RoverStdout::PlainText(deleted_at))` to `Ok(RoverStdout::DeletedAt(deleted_at))`, add a new `DeletedAt(String)` variant to `RoverStdout`, and then match on it in `pub fn print(&self)`:
To do so, change the line `Ok(RoverOutput::None)` to `Ok(RoverOutput::DeletedAt(deleted_at))`, add a new `DeletedAt(String)` variant to `RoverOutput`, and then match on it in `pub fn print(&self)` and `pub fn get_json(&self)`:

```rust
...
RoverStdout::DeletedAt(timestamp) => {
print_descriptor("Deleted At");
print_content(&timestamp);
pub fn print(&self) {
match self {
...
RoverOutput::DeletedAt(timestamp) => {
print_descriptor("Deleted At");
print_content(&timestamp);
}
...
}
}

pub fn get_json(&self) -> Value {
match self {
...
RoverOutput::DeletedAt(timestamp) => {
json!({ "deleted_at": timestamp.to_string() })
}
...
}
}
```

Expand Down
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ url = { version = "2.2.2", features = ["serde"] }
[dev-dependencies]
assert_cmd = "1.0.7"
assert_fs = "1.0.3"
assert-json-diff = "2.0.1"
predicates = "2.0.0"
reqwest = { version = "0.11.4", default-features = false, features = ["blocking", "native-tls-vendored"] }
serial_test = "0.5.0"
Expand Down
3 changes: 2 additions & 1 deletion crates/rover-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ houston = {path = "../houston"}

# crates.io deps
camino = "1"
chrono = "0.4"
chrono = { version = "0.4", features = ["serde"] }
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"
prettytable-rs = "0.8.0"
reqwest = { version = "0.11", default-features = false, features = ["blocking", "brotli", "gzip", "json", "native-tls-vendored"] }
regex = "1"
sdl-encoder = {path = "../sdl-encoder"}
Expand Down
49 changes: 18 additions & 31 deletions crates/rover-client/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use reqwest::Url;
use thiserror::Error;

use crate::shared::{CheckResponse, CompositionError, GraphRef};
use crate::shared::{BuildErrors, CheckResponse, GraphRef};

/// RoverClientError represents all possible failures that can occur during a client request.
#[derive(Error, Debug)]
Expand Down Expand Up @@ -96,19 +96,21 @@ pub enum RoverClientError {
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_ref}\" because its subgraphs failed to compose."
)]
NoCompositionPublishes {
/// no successful build in the API, we return this error.
#[error("No supergraph SDL exists for \"{graph_ref}\" because its subgraphs failed to build.")]
NoSupergraphBuilds {
graph_ref: GraphRef,
composition_errors: Vec<CompositionError>,
source: BuildErrors,
},

#[error("{}", subgraph_composition_error_msg(.composition_errors))]
SubgraphCompositionErrors {
#[error("Encountered {} while trying to build a supergraph.", .source.length_string())]
BuildErrors { source: BuildErrors },

#[error("Encountered {} while trying to build subgraph \"{subgraph}\" into supergraph \"{graph_ref}\".", .source.length_string())]
SubgraphBuildErrors {
subgraph: String,
graph_ref: GraphRef,
composition_errors: Vec<CompositionError>,
source: BuildErrors,
},

/// This error occurs when the Studio API returns no implementing services for a graph
Expand Down Expand Up @@ -142,7 +144,7 @@ pub enum RoverClientError {
/// 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
#[error("{}", check_response_error_msg(.check_response))]
#[error("{}", operation_check_error_msg(.check_response))]
OperationCheckFailure {
graph_ref: GraphRef,
check_response: CheckResponse,
Expand Down Expand Up @@ -174,29 +176,14 @@ pub enum RoverClientError {
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
}

fn check_response_error_msg(check_response: &CheckResponse) -> String {
let plural = match check_response.num_failures {
fn operation_check_error_msg(check_response: &CheckResponse) -> String {
let failure_count = check_response.get_failure_count();
let plural = match failure_count {
1 => "",
_ => "s",
};
format!(
"This operation has encountered {} change{} that would break existing clients.",
check_response.num_failures, plural
"This operation check has encountered {} schema change{} that would break operations from existing client traffic.",
failure_count, plural
)
}
24 changes: 8 additions & 16 deletions crates/rover-client/src/operations/graph/check/runner.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use crate::blocking::StudioClient;
use crate::operations::graph::check::types::{
GraphCheckInput, MutationChangeSeverity, MutationResponseData,
};
use crate::operations::graph::check::types::{GraphCheckInput, MutationResponseData};
use crate::shared::{CheckResponse, GraphRef};
use crate::RoverClientError;

Expand Down Expand Up @@ -45,25 +43,19 @@ fn get_check_response_from_data(

let diff_to_previous = service.check_schema.diff_to_previous;

let number_of_checked_operations = diff_to_previous.number_of_checked_operations.unwrap_or(0);
let operation_check_count = diff_to_previous.number_of_checked_operations.unwrap_or(0) as u64;

let change_severity = diff_to_previous.severity.into();
let result = diff_to_previous.severity.into();
let mut changes = Vec::with_capacity(diff_to_previous.changes.len());
let mut num_failures = 0;
for change in diff_to_previous.changes {
if let MutationChangeSeverity::FAILURE = change.severity {
num_failures += 1;
}
changes.push(change.into());
}

let check_response = CheckResponse {
CheckResponse::try_new(
target_url,
number_of_checked_operations,
operation_check_count,
changes,
change_severity,
num_failures,
};

check_response.check_for_failures(graph_ref)
result,
graph_ref,
)
}
4 changes: 3 additions & 1 deletion crates/rover-client/src/operations/graph/publish/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ mod runner;
mod types;

pub use runner::run;
pub use types::{GraphPublishInput, GraphPublishResponse};
pub use types::{
ChangeSummary, FieldChanges, GraphPublishInput, GraphPublishResponse, TypeChanges,
};
Loading