Skip to content

Commit

Permalink
feat: rover supergraph fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
EverlastingBugstopper committed Apr 28, 2021
1 parent 309a336 commit 0159bf2
Show file tree
Hide file tree
Showing 11 changed files with 232 additions and 2 deletions.
5 changes: 5 additions & 0 deletions crates/rover-client/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ pub enum RoverClientError {
#[error("Could not find graph with name \"{graph}\"")]
NoService { graph: String },

/// if someone attempts to get a core schema from a supergraph that has
/// no composition results we return this error.
#[error("There has been no successful composition for supergraph \"{graph}\".")]
NoCompositionPublishes { graph: String },

/// This error occurs when the Studio API returns no implementing services for a graph
/// This response shouldn't be possible!
#[error("The response from Apollo Studio was malformed. Response body contains `null` value for \"{null_field}\"")]
Expand Down
3 changes: 3 additions & 0 deletions crates/rover-client/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ pub mod graph;
/// all rover-client functionality for the "subgraph" commands in rover
pub mod subgraph;

/// all rover-client functionality for the "supergraph" commands in rover
pub mod supergraph;

/// all rover config-related functionality
pub mod config;
17 changes: 17 additions & 0 deletions crates/rover-client/src/query/supergraph/fetch.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
query FetchSupergraphQuery($variant: String!, $graphId: ID!) {
frontendUrlRoot
service(id: $graphId) {
schema(tag: $variant) {
hash
}
mostRecentCompositionPublish(graphVariant: $variant) {
supergraphSdl
errors {
message
}
}
variants {
name
}
}
}
139 changes: 139 additions & 0 deletions crates/rover-client/src/query/supergraph/fetch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use crate::blocking::StudioClient;
use crate::RoverClientError;
use graphql_client::*;

// I'm not sure where this should live long-term
/// this is because of the custom GraphQLDocument scalar in the schema
type GraphQLDocument = String;

#[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/supergraph/fetch.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. fetch_supergraph_query
pub struct FetchSupergraphQuery;

/// The main function to be used from this module. This function fetches a
/// core schema from apollo studio
pub fn run(
variables: fetch_supergraph_query::Variables,
client: &StudioClient,
) -> Result<String, RoverClientError> {
let graph = variables.graph_id.clone();
let invalid_variant = variables.variant.clone();
let response_data = client.post::<FetchSupergraphQuery>(variables)?;
get_supergraph_sdl_from_response_data(response_data, graph, invalid_variant)
}

fn get_supergraph_sdl_from_response_data(
response_data: fetch_supergraph_query::ResponseData,
graph: String,
invalid_variant: String,
) -> Result<String, RoverClientError> {
let service_data = match response_data.service {
Some(data) => Ok(data),
None => Err(RoverClientError::NoService {
graph: graph.clone(),
}),
}?;

let mut valid_variants = Vec::new();

for variant in service_data.variants {
valid_variants.push(variant.name)
}

if service_data.schema.is_none() {
return Err(RoverClientError::NoSchemaForVariant {
graph,
invalid_variant,
valid_variants,
frontend_url_root: response_data.frontend_url_root,
});
}

if let Some(composition_publish) = service_data.most_recent_composition_publish {
if let Some(supergraph_sdl) = composition_publish.supergraph_sdl {
Ok(supergraph_sdl)
} else {
let mut error_msg = "".to_string();
// TODO: make this less hacky and let folks know these are composition errors.
for error in composition_publish.errors {
error_msg.push_str(&error.message);
error_msg.push_str("\n")
}
Err(RoverClientError::AdhocError { msg: error_msg })
}
} else {
Err(RoverClientError::NoCompositionPublishes { graph })
}
}

#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn get_supergraph_sdl_from_response_data_works() {
let json_response = json!({
"frontendUrlRoot": "https://studio.apollographql.com",
"service": {
"schema": {
"hash": "123"
},
"mostRecentCompositionPublish": {
"supergraphSdl": "type Query { hello: String }",
"errors": []
},
"variants": []
}
});
let data: fetch_supergraph_query::ResponseData =
serde_json::from_value(json_response).unwrap();
let (graph, invalid_variant) = mock_vars();
let output = get_supergraph_sdl_from_response_data(data, graph, invalid_variant);

assert!(output.is_ok());
assert_eq!(output.unwrap(), "type Query { hello: String }".to_string());
}

#[test]
fn get_schema_from_response_data_errs_on_no_service() {
let json_response =
json!({ "service": null, "frontendUrlRoot": "https://studio.apollographql.com" });
let data: fetch_supergraph_query::ResponseData =
serde_json::from_value(json_response).unwrap();
let (graph, invalid_variant) = mock_vars();
let output = get_supergraph_sdl_from_response_data(data, graph, invalid_variant);

assert!(output.is_err());
}

#[test]
fn get_schema_from_response_data_errs_on_no_schema() {
let json_response = json!({
"frontendUrlRoot": "https://studio.apollographql.com/",
"service": {
"schema": null,
"variants": [],
},
});
let data: fetch_supergraph_query::ResponseData =
serde_json::from_value(json_response).unwrap();
let (graph, invalid_variant) = mock_vars();
let output = get_supergraph_sdl_from_response_data(data, graph, invalid_variant);

assert!(output.is_err());
}

fn mock_vars() -> (String, String) {
("mygraph".to_string(), "current".to_string())
}
}
2 changes: 2 additions & 0 deletions crates/rover-client/src/query/supergraph/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// "supergraph fetch"
pub mod fetch;
2 changes: 1 addition & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ impl Rover {
Command::Config(command) => {
command.run(self.get_rover_config()?, self.get_client_config()?)
}
Command::Supergraph(command) => command.run(),
Command::Supergraph(command) => command.run(self.get_client_config()?),
Command::Docs(command) => command.run(),
Command::Graph(command) => {
command.run(self.get_client_config()?, self.get_git_context()?)
Expand Down
5 changes: 5 additions & 0 deletions src/command/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::utils::table::{self, cell, row};
#[derive(Clone, PartialEq, Debug)]
pub enum RoverStdout {
DocsList(HashMap<&'static str, &'static str>),
SupergraphSdl(String),
Sdl(String),
CoreSchema(String),
SchemaHash(String),
Expand Down Expand Up @@ -45,6 +46,10 @@ impl RoverStdout {
}
println!("{}", table);
}
RoverStdout::SupergraphSdl(sdl) => {
print_descriptor("Supergraph SDL");
print_content(&sdl);
}
RoverStdout::Sdl(sdl) => {
print_descriptor("SDL");
print_content(&sdl);
Expand Down
45 changes: 45 additions & 0 deletions src/command/supergraph/fetch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use crate::utils::client::StudioClientConfig;
use crate::utils::parsers::{parse_graph_ref, GraphRef};
use crate::{command::RoverStdout, Result};

use rover_client::query::supergraph::fetch;

use ansi_term::Colour::{Cyan, Yellow};
use serde::Serialize;
use structopt::StructOpt;

#[derive(Debug, Serialize, StructOpt)]
pub struct Fetch {
/// <NAME>@<VARIANT> of graph in Apollo Studio to fetch from.
/// @<VARIANT> may be left off, defaulting to @current
#[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))]
#[serde(skip_serializing)]
graph: GraphRef,

/// Name of configuration profile to use
#[structopt(long = "profile", default_value = "default")]
#[serde(skip_serializing)]
profile_name: String,
}

impl Fetch {
pub fn run(&self, client_config: StudioClientConfig) -> Result<RoverStdout> {
let client = client_config.get_client(&self.profile_name)?;
let graph_ref = self.graph.to_string();
eprintln!(
"Fetching supergraph SDL from {} using credentials from the {} profile.",
Cyan.normal().paint(&graph_ref),
Yellow.normal().paint(&self.profile_name)
);

let supergraph_sdl = fetch::run(
fetch::fetch_supergraph_query::Variables {
graph_id: self.graph.name.clone(),
variant: self.graph.variant.clone(),
},
&client,
)?;

Ok(RoverStdout::SupergraphSdl(supergraph_sdl))
}
}
9 changes: 8 additions & 1 deletion src/command/supergraph/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
mod compose;
pub(crate) mod config;
mod fetch;

use serde::Serialize;
use structopt::StructOpt;

use crate::command::RoverStdout;
use crate::utils::client::StudioClientConfig;
use crate::Result;

#[derive(Debug, Serialize, StructOpt)]
Expand All @@ -17,12 +19,17 @@ pub struct Supergraph {
pub enum Command {
/// Locally compose a supergraph schema from a set of subgraph schemas
Compose(compose::Compose),

// TODO: fill in some more help info on this one depending on behavior we hash out
/// Fetch supergraph SDL
Fetch(fetch::Fetch),
}

impl Supergraph {
pub fn run(&self) -> Result<RoverStdout> {
pub fn run(&self, client_config: StudioClientConfig) -> Result<RoverStdout> {
match &self.command {
Command::Compose(command) => command.run(),
Command::Fetch(command) => command.run(client_config),
}
}
}
3 changes: 3 additions & 0 deletions src/error/metadata/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ impl From<&mut anyhow::Error> for Metadata {
| RoverClientError::SendRequest(_)
| RoverClientError::MalformedResponse { null_field: _ }
| RoverClientError::InvalidSeverity => (Some(Suggestion::SubmitIssue), None),
RoverClientError::NoCompositionPublishes { graph: _ } => {
(Some(Suggestion::RunComposition), None)
}
RoverClientError::ExpectedFederatedGraph { graph: _ } => {
(Some(Suggestion::UseFederatedGraph), None)
}
Expand Down
4 changes: 4 additions & 0 deletions src/error/metadata/suggestion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub enum Suggestion {
CreateConfig,
ListProfiles,
UseFederatedGraph,
RunComposition,
CheckGraphNameAndAuth,
ProvideValidSubgraph(Vec<String>),
ProvideValidVariant {
Expand Down Expand Up @@ -58,6 +59,9 @@ impl Display for Suggestion {
Yellow.normal().paint("`--profile`")
)
}
Suggestion::RunComposition => {
format!("Try pushing a subgraph with the {} command.", Yellow.normal().paint("`rover subgraph publish`"))
}
Suggestion::UseFederatedGraph => {
"Try running the command on a valid federated graph.".to_string()
}
Expand Down

0 comments on commit 0159bf2

Please sign in to comment.