Skip to content

Commit

Permalink
feat: structured output (#676)
Browse files Browse the repository at this point in the history
  • Loading branch information
EverlastingBugstopper authored Jul 26, 2021
1 parent 9bc37ec commit 5138bcd
Show file tree
Hide file tree
Showing 68 changed files with 2,014 additions and 809 deletions.
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

0 comments on commit 5138bcd

Please sign in to comment.