Skip to content

Commit cdd43c1

Browse files
chore: refactor rover-client in pursuit of structured output (#557)
* chore: refactor subgraph check This commit does a lot of heavy lifting for the pending rebase. 1) Creates new input and output types in rover-client for subgraph check 2) Moves GitContext out of rover::utils to rover-client::utils 3) Creates error code E029 for composition errors 4) Styles cloud-composition errors like harmonizer * chore: refactor subgraph fetch (#575) * chore: refactor subgraph publish (#630) * chore: refactor config whoami (#633) * chore: refactor subgraph delete (#639) * chore: refactor subgraph list (#640) * chore: refactor subgraph introspect (#641) * chore: refactor graph introspect (#643) * chore: refactor release update checker (#646) * chore: begin adding shared types and consolidate check operations (#652) * chore: move GraphRef to rover-client (#664) * chore: refactor the rest of rover-client (#675) * chore: do not re-export queries * chore: finish wiring OperationCheck error * chore: adds graphql linter (#677) * fix: graph_ref -> graphref * feat: structured output (#676)
1 parent 6308b18 commit cdd43c1

File tree

148 files changed

+9659
-2486
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

148 files changed

+9659
-2486
lines changed

ARCHITECTURE.md

+55-35
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ A minimal command in Rover would be laid out exactly like this:
116116
pub struct MyNewCommand { }
117117

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

131-
use crate::command::RoverStdout;
131+
use crate::command::RoverOutput;
132132
use crate::Result;
133133

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

137137
impl Hello {
138-
pub fn run(&self) -> Result<RoverStdout> {
138+
pub fn run(&self) -> Result<RoverOutput> {
139139
eprintln!("Hello, world!");
140-
Ok(RoverStdout::None)
140+
Ok(RoverOutput::None)
141141
}
142142
}
143143
```
@@ -195,7 +195,7 @@ To add these to our new `graph hello` command, we can copy and paste the field f
195195
pub struct Hello {
196196
/// <NAME>@<VARIANT> of graph in Apollo Studio to publish to.
197197
/// @<VARIANT> may be left off, defaulting to @current
198-
#[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))]
198+
#[structopt(name = "GRAPH_REF"))]
199199
#[serde(skip_serializing)]
200200
graph: GraphRef
201201

@@ -206,12 +206,6 @@ pub struct Hello {
206206
}
207207
```
208208

209-
We'll have to also add some import statements at the top of our file to support parsing this new argument:
210-
211-
```rust
212-
use crate::utils::parsers::{parse_graph_ref, GraphRef};
213-
```
214-
215209
Now if we run the command again, it will complain if we don't provide a graph ref:
216210

217211
```console
@@ -228,13 +222,13 @@ For more information try --help
228222

229223
##### Setting up a command to work with `rover-client`
230224

231-
Most of Rover's commands make requests to Apollo Studio's API. Rather than handling the request logic in the repository's main package, Rover is structured so that this logic lives in `crates/rover-client`. This is helpful for separation of concerns and testing.
225+
Most of Rover's commands make requests to Apollo Studio's API, or to another GraphQL API. Rather than handling the request logic in the repository's main package, Rover is structured so that this logic lives in `crates/rover-client`. This is helpful for separation of concerns and testing.
232226

233227
To access functionality from `rover-client` in our `rover graph hello` command, we'll need to pass down a client from the entry to our command in `src/command/graph/mod.rs`.
234228

235229
You can do this by changing the `Command::Hello(command) => command.run(),` line to `Command::Hello(command) => command.run(client_config),`.
236230

237-
Then you'll need to change `Hello::run` to accept a `client_config: StudioClientConfig` parameter in `src/command/graph/hello.rs`, and add a `use crate::utils::client::StudioClientConfig` import statement. Then, at the top of the run function, you can create a `StudioClient` by adding `let client = client_config.get_client(&self.profile_name)?;`. You can see examples of this in the other commands.
231+
Then you'll need to change `Hello::run` to accept a `client_config: StudioClientConfig` parameter in `src/command/graph/hello.rs`, and add a `use crate::utils::client::StudioClientConfig` import statement. Then, at the top of the run function, you can create a `StudioClient` by adding `let client = client_config.get_authenticated_client(&self.profile_name)?;`. You can see examples of this in the other commands.
238232

239233
##### Auto-generated help command
240234

@@ -271,19 +265,19 @@ Whenever you create a new command, make sure to add `#[serde(skip_serializing)]`
271265

272266
##### Adding a query to Apollo Studio
273267

274-
The only piece of the `rover-client` crate that we need to be concerned with for now is the `src/query` directory. This is where all the queries to Apollo Studio live. This directory is roughly organized by the command names as well, but there might be some queries in these directories that are used by multiple commands.
268+
The only piece of the `rover-client` crate that we need to be concerned with for now is the `src/operations` directory. This is where all the queries to Apollo Studio live. This directory is roughly organized by the command names as well, but there might be some queries in these directories that are used by multiple commands.
275269

276-
You can see in the `src/query/graph` directory a number of `.rs` files paired with `.graphql` files. The `.graphql` files are the files where the GraphQL operations live, and the matching `.rs` files contain the logic needed to execute those operations.
270+
You can see in the `src/operations/graph` directory a number of `.rs` files paired with `.graphql` files. The `.graphql` files are the files where the GraphQL operations live, and the matching `.rs` files contain the logic needed to execute those operations.
277271

278272
##### Writing a GraphQL operation
279273

280274
For our basic `graph hello` command, we're going to make a request to Apollo Studio that inquires about the existence of a particular graph, and nothing else. For this, we can use the `Query.service` field.
281275

282-
Create a `hello.graphql` file in `crates/rover-client/src/query/graph` and paste the following into it:
276+
Create a `hello_query.graphql` file in `crates/rover-client/src/operations/graph` and paste the following into it:
283277

284278
```graphql
285-
query GraphHello($graphId: ID!) {
286-
service(id: $graphId) {
279+
query GraphHello($graph_id: ID!) {
280+
service(id: $graph_id) {
287281
deletedAt
288282
}
289283
}
@@ -295,32 +289,34 @@ This basic GraphQL operation uses a graph's unique ID (which we get from the `Gr
295289

296290
This project uses [graphql-client](https://docs.rs/graphql_client/latest/graphql_client/) to generate types for each raw `.graphql` query that we write.
297291

298-
First, create an empty file at `crates/rover-client/src/query/graph/hello.rs`.
292+
First, create an empty directory at `crates/rover-client/src/operations/graph/hello`, and then in that directory, create a `mod.rs` file to initialize the module.
299293

300-
To start compiling this file, we need to export the module in `crates/rover-client/src/query/graph/mod.rs`:
294+
To start compiling this file, we need to export the module in `crates/rover-client/src/operations/graph/mod.rs`:
301295

302296
```rust
303297
...
304-
/// "Graph hello" command execution
298+
/// "graph hello" command execution
305299
pub mod hello;
306300
```
307301

308-
Back in `hello.rs`, we'll import the following types:
302+
Back in our `hello` module, we'll create a `runner.rs`, and add `mod runner` to our `mod.rs` file.
303+
304+
Then, in `runner.rs`, import the following types:
309305

310306
```rust
311307
use crate::blocking::StudioClient;
312308
use crate::RoverClientError;
313309
use graphql_client::*;
314310
```
315311

316-
Then, we'll create a new struct that will have auto-generated types for the `hello.graphql` file that we created earlier:
312+
Then, we'll create a new struct that will have auto-generated types for the `hello_query.graphql` file that we created earlier:
317313

318314
```rust
319315
#[derive(GraphQLQuery)]
320316
// The paths are relative to the directory where your `Cargo.toml` is located.
321317
// Both json and the GraphQL schema language are supported as sources for the schema
322318
#[graphql(
323-
query_path = "src/query/graph/hello.graphql",
319+
query_path = "src/operations/graph/hello/hello_query.graphql",
324320
schema_path = ".schema/schema.graphql",
325321
response_derives = "PartialEq, Debug, Serialize, Deserialize",
326322
deprecated = "warn"
@@ -352,7 +348,7 @@ Before we go any further, lets make sure everything is set up properly. We're go
352348
It should look something like this (you should make sure you are following the style of other commands when creating new ones):
353349

354350
```rust
355-
pub fn run(&self, client_config: StudioClientConfig) -> Result<RoverStdout> {
351+
pub fn run(&self, client_config: StudioClientConfig) -> Result<RoverOutput> {
356352
let client = client_config.get_client(&self.profile_name)?;
357353
let graph_ref = self.graph.to_string();
358354
eprintln!(
@@ -366,7 +362,10 @@ pub fn run(&self, client_config: StudioClientConfig) -> Result<RoverStdout> {
366362
},
367363
&client,
368364
)?;
369-
Ok(RoverStdout::PlainText(deleted_at))
365+
println!("{:?}", deleted_at);
366+
367+
// TODO: Add a new output type!
368+
Ok(RoverOutput::None)
370369
}
371370
```
372371

@@ -395,19 +394,40 @@ fn build_response(
395394
}
396395
```
397396

398-
This should get you to the point where you can run `rover graph hello <GRAPH_REF>` and see if and when the last graph was deleted. From here, you should be able to follow the examples of other commands to write out tests for the `build_response` function. This is left as an exercise for the reader.
397+
This should get you to the point where you can run `rover graph hello <GRAPH_REF>` and see if and when the last graph was deleted. From here, you should be able to follow the examples of other commands to write out tests for the `build_response` function.
398+
399+
##### Clean up the API
399400

400-
##### `RoverStdout`
401+
Unfortunately this is not the cleanest API and doesn't match the pattern set by the rest of the commands. Each `rover-client` operation has an input type and an output type, along with a `run` function that takes in a `reqwest::blocking::Client`.
401402

402-
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`.
403+
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.
403404

404-
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)`:
405+
##### `RoverOutput`
406+
407+
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`.
408+
409+
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)`:
405410

406411
```rust
407-
...
408-
RoverStdout::DeletedAt(timestamp) => {
409-
print_descriptor("Deleted At");
410-
print_content(&timestamp);
412+
pub fn print(&self) {
413+
match self {
414+
...
415+
RoverOutput::DeletedAt(timestamp) => {
416+
print_descriptor("Deleted At");
417+
print_content(&timestamp);
418+
}
419+
...
420+
}
421+
}
422+
423+
pub fn get_json(&self) -> Value {
424+
match self {
425+
...
426+
RoverOutput::DeletedAt(timestamp) => {
427+
json!({ "deleted_at": timestamp.to_string() })
428+
}
429+
...
430+
}
411431
}
412432
```
413433

Cargo.lock

+27-12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-4
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,10 @@ git-url-parse = "0.3.1"
5353
git2 = { version = "0.13.20", default-features = false, features = ["vendored-openssl"] }
5454
harmonizer = { version = "0.27.0", optional = true }
5555
heck = "0.3.3"
56-
humantime = "2.1.0"
5756
opener = "0.5.0"
5857
os_info = "3.0"
5958
prettytable-rs = "0.8.0"
60-
reqwest = {version = "0.11", default-features = false, features = ["blocking", "brotli", "gzip", "json", "native-tls-vendored"]}
61-
regex = "1"
62-
semver = "1"
59+
reqwest = {version = "0.11.4", default-features = false, features = ["blocking"] }
6360
serde = "1.0"
6461
serde_json = "1.0"
6562
serde_yaml = "0.8"
@@ -75,6 +72,7 @@ url = { version = "2.2.2", features = ["serde"] }
7572
[dev-dependencies]
7673
assert_cmd = "1.0.7"
7774
assert_fs = "1.0.3"
75+
assert-json-diff = "2.0.1"
7876
predicates = "2.0.0"
7977
reqwest = { version = "0.11.4", default-features = false, features = ["blocking", "native-tls-vendored"] }
8078
serial_test = "0.5.0"

0 commit comments

Comments
 (0)