Skip to content

Commit 2f6e4da

Browse files
authored
enhance(router): use Hive Console SDK for Supergraph Fetching (#571)
Use `hive-console-sdk` for fetching supergraph from Hive CDN Documentation changes graphql-hive/console#7283
1 parent 42cb729 commit 2f6e4da

File tree

9 files changed

+194
-119
lines changed

9 files changed

+194
-119
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
default: minor
3+
---
4+
5+
Use `hive-console-sdk` to load supergraph from Hive Console instead of custom implementation.
6+
7+
### Breaking Changes
8+
9+
The configuration for the `hive` supergraph source has been updated. The `timeout` field is now `request_timeout`, and new options `connect_timeout` and `accept_invalid_certs` have been added.
10+
11+
```yaml
12+
supergraph:
13+
source: hive
14+
endpoint: "https://cdn.graphql-hive.com/supergraph"
15+
key: "YOUR_CDN_KEY"
16+
# Old `timeout` is now `request_timeout`
17+
request_timeout: 30s
18+
# New options
19+
connect_timeout: 10s
20+
accept_invalid_certs: false

Cargo.lock

Lines changed: 45 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin/router/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ serde_json = { workspace = true }
4848

4949
mimalloc = { version = "0.1.48", features = ["v3"] }
5050
moka = { version = "0.12.10", features = ["future"] }
51+
hive-console-sdk = "0.2.0"
5152
ulid = "1.2.1"
5253
tokio-util = "0.7.16"
5354
cookie = "0.18.1"

bin/router/src/supergraph/base.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ pub enum LoadSupergraphError {
99
NetworkError(#[from] reqwest_middleware::Error),
1010
#[error("Failed to read supergraph from network: {0}")]
1111
NetworkResponseError(#[from] reqwest::Error),
12+
#[error("Failed to lock supergraph: {0}")]
13+
LockError(String),
14+
#[error("Failed to initialize the loader: {0}")]
15+
InitializationError(String),
16+
#[error("Invalid configuration: {0}")]
17+
InvalidConfiguration(String),
1218
}
1319

1420
#[derive(Debug)]

bin/router/src/supergraph/hive.rs

Lines changed: 49 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,52 @@
11
use async_trait::async_trait;
2-
use http::{
3-
header::{ETAG, IF_NONE_MATCH, USER_AGENT},
4-
HeaderValue, StatusCode,
2+
use hive_console_sdk::supergraph_fetcher::{
3+
SupergraphFetcher, SupergraphFetcherAsyncState, SupergraphFetcherError,
54
};
6-
use lazy_static::lazy_static;
7-
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
8-
use reqwest_retry::RetryTransientMiddleware;
9-
use retry_policies::policies::ExponentialBackoff;
105
use std::time::Duration;
11-
use tokio::sync::RwLock;
126
use tracing::{debug, error};
137

148
use crate::{
159
consts::ROUTER_VERSION,
1610
supergraph::base::{LoadSupergraphError, ReloadSupergraphResult, SupergraphLoader},
1711
};
1812

19-
lazy_static! {
20-
pub static ref USER_AGENT_VALUE: HeaderValue = {
21-
HeaderValue::from_str(&format!("hive-router/{}", ROUTER_VERSION))
22-
.expect("failed to construct user-agent")
23-
};
24-
}
25-
26-
static AUTH_HEADER_NAME: &str = "x-hive-cdn-key";
27-
2813
pub struct SupergraphHiveConsoleLoader {
29-
endpoint: String,
30-
key: String,
31-
http_client: ClientWithMiddleware,
14+
fetcher: SupergraphFetcher<SupergraphFetcherAsyncState>,
3215
poll_interval: Duration,
33-
timeout: Duration,
34-
last_etag: RwLock<Option<HeaderValue>>,
3516
}
3617

37-
#[async_trait]
38-
impl SupergraphLoader for SupergraphHiveConsoleLoader {
39-
async fn load(&self) -> Result<ReloadSupergraphResult, LoadSupergraphError> {
40-
debug!(
41-
"Fetching supergraph from Hive Console CDN: '{}'",
42-
self.endpoint,
43-
);
44-
45-
let mut req = self
46-
.http_client
47-
.get(&self.endpoint)
48-
.header(AUTH_HEADER_NAME, &self.key)
49-
.header(USER_AGENT, USER_AGENT_VALUE.clone())
50-
.timeout(self.timeout);
51-
52-
let mut etag_used = false;
53-
54-
match self.last_etag.try_read() {
55-
Ok(lock_guard) => {
56-
if let Some(etag) = lock_guard.as_ref() {
57-
req = req.header(IF_NONE_MATCH, etag);
58-
etag_used = true;
59-
}
18+
impl From<SupergraphFetcherError> for LoadSupergraphError {
19+
fn from(err: SupergraphFetcherError) -> Self {
20+
match err {
21+
SupergraphFetcherError::NetworkError(e) => LoadSupergraphError::NetworkError(e),
22+
SupergraphFetcherError::NetworkResponseError(e) => {
23+
LoadSupergraphError::NetworkResponseError(e)
6024
}
61-
Err(e) => {
62-
error!("Failed to read etag record: {:?}", e);
25+
SupergraphFetcherError::Lock(e) => LoadSupergraphError::LockError(e),
26+
SupergraphFetcherError::FetcherCreationError(e) => {
27+
LoadSupergraphError::InitializationError(e.to_string())
6328
}
64-
};
65-
66-
let response = req.send().await?.error_for_status()?;
67-
68-
if etag_used && response.status() == StatusCode::NOT_MODIFIED {
69-
Ok(ReloadSupergraphResult::Unchanged)
70-
} else {
71-
if let Some(new_etag) = response.headers().get(ETAG) {
72-
match self.last_etag.try_write() {
73-
Ok(mut v) => {
74-
debug!("saving etag record: {:?}", new_etag);
75-
*v = Some(new_etag.clone());
76-
}
77-
Err(e) => {
78-
error!("Failed to save etag record: {:?}", e);
79-
}
80-
}
29+
SupergraphFetcherError::InvalidKey(e) => {
30+
LoadSupergraphError::InvalidConfiguration(format!("Invalid CDN key: {}", e))
8131
}
32+
}
33+
}
34+
}
8235

83-
let content = response
84-
.text()
85-
.await
86-
.map_err(LoadSupergraphError::NetworkResponseError)?;
87-
88-
Ok(ReloadSupergraphResult::Changed { new_sdl: content })
36+
#[async_trait]
37+
impl SupergraphLoader for SupergraphHiveConsoleLoader {
38+
async fn load(&self) -> Result<ReloadSupergraphResult, LoadSupergraphError> {
39+
let fetcher_result = self.fetcher.fetch_supergraph().await;
40+
match fetcher_result {
41+
// If there was an error fetching the supergraph, propagate it
42+
Err(err) => {
43+
error!("Error fetching supergraph from Hive Console: {}", err);
44+
Err(LoadSupergraphError::from(err))
45+
}
46+
// If the supergraph has not changed, return Unchanged
47+
Ok(None) => Ok(ReloadSupergraphResult::Unchanged),
48+
// If there is a new supergraph SDL, return it
49+
Ok(Some(sdl)) => Ok(ReloadSupergraphResult::Changed { new_sdl: sdl }),
8950
}
9051
}
9152

@@ -95,31 +56,34 @@ impl SupergraphLoader for SupergraphHiveConsoleLoader {
9556
}
9657

9758
impl SupergraphHiveConsoleLoader {
98-
pub fn new(
59+
pub fn try_new(
9960
endpoint: String,
10061
key: &str,
10162
poll_interval: Duration,
102-
timeout: Duration,
103-
retry_policy: ExponentialBackoff,
63+
connect_timeout: Duration,
64+
request_timeout: Duration,
65+
accept_invalid_certs: bool,
66+
retry_count: u32,
10467
) -> Result<Box<Self>, LoadSupergraphError> {
10568
debug!(
106-
"Creating supergraph source from Hive Console CDN: '{}' (poll interval: {}ms, timeout: {}ms)",
69+
"Creating supergraph source from Hive Console CDN: '{}' (poll interval: {}ms, request_timeout: {}ms)",
10770
endpoint,
10871
poll_interval.as_millis(),
109-
timeout.as_millis()
72+
request_timeout.as_millis()
11073
);
111-
112-
let client = ClientBuilder::new(reqwest::Client::new())
113-
.with(RetryTransientMiddleware::new_with_policy(retry_policy))
114-
.build();
115-
116-
Ok(Box::new(Self {
74+
let fetcher = SupergraphFetcher::try_new_async(
11775
endpoint,
118-
key: key.to_string(),
119-
http_client: client,
76+
key,
77+
format!("hive-router/{}", ROUTER_VERSION),
78+
connect_timeout,
79+
request_timeout,
80+
accept_invalid_certs,
81+
retry_count,
82+
)?;
83+
84+
Ok(Box::new(SupergraphHiveConsoleLoader {
85+
fetcher,
12086
poll_interval,
121-
timeout,
122-
last_etag: RwLock::new(None),
12387
}))
12488
}
12589
}

bin/router/src/supergraph/mod.rs

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,19 @@ pub fn resolve_from_config(
2727
SupergraphSource::HiveConsole {
2828
endpoint,
2929
key,
30-
poll_interval,
30+
connect_timeout,
31+
request_timeout,
32+
accept_invalid_certs,
3133
retry_policy,
32-
timeout,
33-
} => {
34-
let patched_endpoint = match endpoint.ends_with("/supergraph") {
35-
true => endpoint.to_string(),
36-
false => format!("{}/supergraph", endpoint),
37-
};
38-
39-
Ok(SupergraphHiveConsoleLoader::new(
40-
patched_endpoint,
41-
key,
42-
*poll_interval,
43-
*timeout,
44-
retry_policy.into(),
45-
)?)
46-
}
34+
poll_interval,
35+
} => Ok(SupergraphHiveConsoleLoader::try_new(
36+
endpoint.clone(),
37+
key,
38+
*poll_interval,
39+
*connect_timeout,
40+
*request_timeout,
41+
*accept_invalid_certs,
42+
retry_policy.max_retries,
43+
)?),
4744
}
4845
}

0 commit comments

Comments
 (0)