Skip to content

Commit 9b86870

Browse files
chore: refactor config whoami (#633)
1 parent 9238a45 commit 9b86870

File tree

11 files changed

+134
-105
lines changed

11 files changed

+134
-105
lines changed

crates/rover-client/src/blocking/client.rs

+26-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
use crate::{headers, RoverClientError};
1+
use crate::RoverClientError;
22
use graphql_client::{Error as GraphQLError, GraphQLQuery, Response as GraphQLResponse};
33
use reqwest::{
44
blocking::{Client as ReqwestClient, Response},
5-
header::HeaderMap,
5+
header::{HeaderMap, HeaderName, HeaderValue},
66
Error as ReqwestError, StatusCode,
77
};
88

99
use std::collections::HashMap;
1010

11+
pub(crate) const JSON_CONTENT_TYPE: &str = "application/json";
12+
pub(crate) const CLIENT_NAME: &str = "rover-client";
13+
1114
/// Represents a generic GraphQL client for making http requests.
1215
pub struct GraphQLClient {
1316
client: ReqwestClient,
@@ -35,7 +38,7 @@ impl GraphQLClient {
3538
variables: Q::Variables,
3639
header_map: &HashMap<String, String>,
3740
) -> Result<Q::ResponseData, RoverClientError> {
38-
let header_map = headers::build(header_map)?;
41+
let header_map = build_headers(header_map)?;
3942
let response = self.execute::<Q>(variables, header_map)?;
4043
GraphQLClient::handle_response::<Q>(response)
4144
}
@@ -125,6 +128,26 @@ fn handle_graphql_body_errors(errors: Vec<GraphQLError>) -> Result<(), RoverClie
125128
}
126129
}
127130

131+
/// Function for building a [HeaderMap] for making http requests. Use for
132+
/// Generic requests to any graphql endpoint.
133+
///
134+
/// Takes a single argument, list of header key/value pairs
135+
fn build_headers(header_map: &HashMap<String, String>) -> Result<HeaderMap, RoverClientError> {
136+
let mut headers = HeaderMap::new();
137+
138+
// this should be consistent for any graphql requests
139+
let content_type = HeaderValue::from_str(JSON_CONTENT_TYPE)?;
140+
headers.append("Content-Type", content_type);
141+
142+
for (key, value) in header_map {
143+
let header_key = HeaderName::from_bytes(key.as_bytes())?;
144+
let header_value = HeaderValue::from_str(&value)?;
145+
headers.append(header_key, header_value);
146+
}
147+
148+
Ok(headers)
149+
}
150+
128151
#[cfg(test)]
129152
mod tests {
130153
use super::*;

crates/rover-client/src/blocking/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ mod studio_client;
33

44
pub use client::GraphQLClient;
55
pub use studio_client::StudioClient;
6+
7+
pub(crate) use client::{CLIENT_NAME, JSON_CONTENT_TYPE};

crates/rover-client/src/blocking/studio_client.rs

+45-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
use crate::{blocking::GraphQLClient, headers, RoverClientError};
2-
use houston::Credential;
1+
use crate::{
2+
blocking::{GraphQLClient, CLIENT_NAME, JSON_CONTENT_TYPE},
3+
RoverClientError,
4+
};
5+
6+
use houston::{Credential, CredentialOrigin};
37

48
use graphql_client::GraphQLQuery;
9+
use reqwest::header::{HeaderMap, HeaderValue};
510
use reqwest::Error as ReqwestError;
611

712
/// Represents a client for making GraphQL requests to Apollo Studio.
813
pub struct StudioClient {
9-
pub credential: Credential,
14+
credential: Credential,
1015
client: GraphQLClient,
1116
version: String,
1217
}
@@ -33,8 +38,44 @@ impl StudioClient {
3338
&self,
3439
variables: Q::Variables,
3540
) -> Result<Q::ResponseData, RoverClientError> {
36-
let header_map = headers::build_studio_headers(&self.credential.api_key, &self.version)?;
41+
let header_map = self.build_studio_headers()?;
3742
let response = self.client.execute::<Q>(variables, header_map)?;
3843
GraphQLClient::handle_response::<Q>(response)
3944
}
45+
46+
/// Function for building a [HeaderMap] for making http requests. Use for making
47+
/// requests to Apollo Studio. We're leaving this separate from `build` since we
48+
/// need to be able to mark the api_key as sensitive (at the bottom)
49+
///
50+
/// Takes an `api_key` and a `client_version`, and returns a [HeaderMap].
51+
pub fn build_studio_headers(&self) -> Result<HeaderMap, RoverClientError> {
52+
let mut headers = HeaderMap::new();
53+
54+
let content_type = HeaderValue::from_str(JSON_CONTENT_TYPE)?;
55+
headers.insert("Content-Type", content_type);
56+
57+
// The headers "apollographql-client-name" and "apollographql-client-version"
58+
// are used for client identification in Apollo Studio.
59+
60+
// This provides metrics in Studio that help keep track of what parts of the schema
61+
// Rover uses, which ensures future changes to the API do not break Rover users.
62+
// more info here:
63+
// https://www.apollographql.com/docs/studio/client-awareness/#using-apollo-server-and-apollo-client
64+
65+
let client_name = HeaderValue::from_str(CLIENT_NAME)?;
66+
headers.insert("apollographql-client-name", client_name);
67+
tracing::debug!(?self.version);
68+
let client_version = HeaderValue::from_str(&self.version)?;
69+
headers.insert("apollographql-client-version", client_version);
70+
71+
let mut api_key = HeaderValue::from_str(&self.credential.api_key)?;
72+
api_key.set_sensitive(true);
73+
headers.insert("x-api-key", api_key);
74+
75+
Ok(headers)
76+
}
77+
78+
pub fn get_credential_origin(&self) -> CredentialOrigin {
79+
self.credential.origin.clone()
80+
}
4081
}

crates/rover-client/src/headers.rs

-61
This file was deleted.

crates/rover-client/src/lib.rs

-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66
pub mod blocking;
77
mod error;
88

9-
/// Module related to constructing request headers.
10-
pub mod headers;
11-
129
/// Module related to building an SDL from an introspection response.
1310
pub mod introspection;
1411

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/// runner for rover config whoami
2-
pub mod whoami;
2+
pub mod who_am_i;
33

44
/// runner is_federated check
55
pub mod is_federated;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
mod types;
2+
3+
pub mod query_runner;
4+
pub use types::{Actor, ConfigWhoAmIInput, RegistryIdentity};

crates/rover-client/src/query/config/whoami.rs crates/rover-client/src/query/config/who_am_i/query_runner.rs

+19-27
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
use crate::blocking::StudioClient;
2+
use crate::query::config::who_am_i::{
3+
types::{QueryActorType, QueryResponseData, RegistryIdentity},
4+
Actor, ConfigWhoAmIInput,
5+
};
26
use crate::RoverClientError;
7+
38
use houston::CredentialOrigin;
49

510
use graphql_client::*;
@@ -8,43 +13,28 @@ use graphql_client::*;
813
// The paths are relative to the directory where your `Cargo.toml` is located.
914
// Both json and the GraphQL schema language are supported as sources for the schema
1015
#[graphql(
11-
query_path = "src/query/config/whoami.graphql",
16+
query_path = "src/query/config/who_am_i/who_am_i_query.graphql",
1217
schema_path = ".schema/schema.graphql",
1318
response_derives = "PartialEq, Debug, Serialize, Deserialize",
1419
deprecated = "warn"
1520
)]
1621
/// This struct is used to generate the module containing `Variables` and
1722
/// `ResponseData` structs.
18-
/// Snake case of this name is the mod name. i.e. who_am_i_query
19-
pub struct WhoAmIQuery;
20-
21-
#[derive(Debug, PartialEq)]
22-
pub struct RegistryIdentity {
23-
pub id: String,
24-
pub graph_title: Option<String>,
25-
pub key_actor_type: Actor,
26-
pub credential_origin: CredentialOrigin,
27-
}
28-
29-
#[derive(Debug, PartialEq)]
30-
pub enum Actor {
31-
GRAPH,
32-
USER,
33-
OTHER,
34-
}
23+
/// Snake case of this name is the mod name. i.e. config_who_am_i_query
24+
pub struct ConfigWhoAmIQuery;
3525

3626
/// Get info from the registry about an API key, i.e. the name/id of the
3727
/// user/graph and what kind of key it is (GRAPH/USER/Other)
3828
pub fn run(
39-
variables: who_am_i_query::Variables,
29+
input: ConfigWhoAmIInput,
4030
client: &StudioClient,
4131
) -> Result<RegistryIdentity, RoverClientError> {
42-
let response_data = client.post::<WhoAmIQuery>(variables)?;
43-
get_identity_from_response_data(response_data, client.credential.origin.clone())
32+
let response_data = client.post::<ConfigWhoAmIQuery>(input.into())?;
33+
get_identity_from_response_data(response_data, client.get_credential_origin())
4434
}
4535

4636
fn get_identity_from_response_data(
47-
response_data: who_am_i_query::ResponseData,
37+
response_data: QueryResponseData,
4838
credential_origin: CredentialOrigin,
4939
) -> Result<RegistryIdentity, RoverClientError> {
5040
if let Some(me) = response_data.me {
@@ -54,13 +44,13 @@ fn get_identity_from_response_data(
5444
// more here: https://studio-staging.apollographql.com/graph/engine/schema/reference/enums/ActorType?variant=prod
5545

5646
let key_actor_type = match me.as_actor.type_ {
57-
who_am_i_query::ActorType::GRAPH => Actor::GRAPH,
58-
who_am_i_query::ActorType::USER => Actor::USER,
47+
QueryActorType::GRAPH => Actor::GRAPH,
48+
QueryActorType::USER => Actor::USER,
5949
_ => Actor::OTHER,
6050
};
6151

6252
let graph_title = match me.on {
63-
who_am_i_query::WhoAmIQueryMeOn::Service(s) => Some(s.title),
53+
config_who_am_i_query::ConfigWhoAmIQueryMeOn::Service(s) => Some(s.title),
6454
_ => None,
6555
};
6656

@@ -91,7 +81,8 @@ mod tests {
9181
},
9282
}
9383
});
94-
let data: who_am_i_query::ResponseData = serde_json::from_value(json_response).unwrap();
84+
let data: config_who_am_i_query::ResponseData =
85+
serde_json::from_value(json_response).unwrap();
9586
let output = get_identity_from_response_data(data, CredentialOrigin::EnvVar);
9687

9788
let expected_identity = RegistryIdentity {
@@ -116,7 +107,8 @@ mod tests {
116107
},
117108
}
118109
});
119-
let data: who_am_i_query::ResponseData = serde_json::from_value(json_response).unwrap();
110+
let data: config_who_am_i_query::ResponseData =
111+
serde_json::from_value(json_response).unwrap();
120112
let output = get_identity_from_response_data(data, CredentialOrigin::EnvVar);
121113

122114
let expected_identity = RegistryIdentity {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use super::query_runner::config_who_am_i_query;
2+
3+
use houston::CredentialOrigin;
4+
5+
pub(crate) type QueryResponseData = config_who_am_i_query::ResponseData;
6+
pub(crate) type QueryVariables = config_who_am_i_query::Variables;
7+
pub(crate) type QueryActorType = config_who_am_i_query::ActorType;
8+
9+
#[derive(Debug, PartialEq)]
10+
pub struct RegistryIdentity {
11+
pub id: String,
12+
pub graph_title: Option<String>,
13+
pub key_actor_type: Actor,
14+
pub credential_origin: CredentialOrigin,
15+
}
16+
17+
#[derive(Debug, PartialEq)]
18+
pub enum Actor {
19+
GRAPH,
20+
USER,
21+
OTHER,
22+
}
23+
24+
#[derive(Debug, PartialEq)]
25+
pub struct ConfigWhoAmIInput {}
26+
27+
impl From<ConfigWhoAmIInput> for QueryVariables {
28+
fn from(_input: ConfigWhoAmIInput) -> Self {
29+
Self {}
30+
}
31+
}

crates/rover-client/src/query/config/whoami.graphql crates/rover-client/src/query/config/who_am_i/who_am_i_query.graphql

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
query WhoAmIQuery {
1+
query ConfigWhoAmIQuery {
22
me {
33
__typename
44
... on Service {

src/command/config/whoami.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use ansi_term::Colour::Green;
2+
use rover_client::query::config::who_am_i::{query_runner, Actor, ConfigWhoAmIInput};
23
use serde::Serialize;
34
use structopt::StructOpt;
45

56
use houston::CredentialOrigin;
6-
use rover_client::query::config::whoami;
77

88
use crate::anyhow;
99
use crate::command::RoverStdout;
@@ -26,7 +26,7 @@ impl WhoAmI {
2626
let client = client_config.get_client(&self.profile_name)?;
2727
eprintln!("Checking identity of your API key against the registry.");
2828

29-
let identity = whoami::run(whoami::who_am_i_query::Variables {}, &client)?;
29+
let identity = query_runner::run(ConfigWhoAmIInput {}, &client)?;
3030

3131
let mut message = format!(
3232
"{}: {:?}\n",
@@ -35,7 +35,7 @@ impl WhoAmI {
3535
);
3636

3737
match identity.key_actor_type {
38-
whoami::Actor::GRAPH => {
38+
Actor::GRAPH => {
3939
if let Some(graph_title) = identity.graph_title {
4040
message.push_str(&format!(
4141
"{}: {}\n",
@@ -50,7 +50,7 @@ impl WhoAmI {
5050
));
5151
Ok(())
5252
}
53-
whoami::Actor::USER => {
53+
Actor::USER => {
5454
message.push_str(&format!(
5555
"{}: {}\n",
5656
Green.normal().paint("User ID"),
@@ -63,7 +63,7 @@ impl WhoAmI {
6363
)),
6464
}?;
6565

66-
let origin = match client.credential.origin {
66+
let origin = match client.get_credential_origin() {
6767
CredentialOrigin::ConfigFile(path) => format!("--profile {}", &path),
6868
CredentialOrigin::EnvVar => format!("${}", &RoverEnvKey::Key),
6969
};

0 commit comments

Comments
 (0)