Skip to content

Commit cc6cd28

Browse files
authored
feat(hive-console-sdk): async SupergraphFetcher, retry_count in SupergraphFetcher & user_agent in PersistedDocumentsManager (#7246)
1 parent c26e2e7 commit cc6cd28

File tree

8 files changed

+353
-125
lines changed

8 files changed

+353
-125
lines changed

.changeset/lemon-hairs-sort.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
'hive-console-sdk-rs': minor
3+
---
4+
Breaking;
5+
- `SupergraphFetcher` now has two different modes: async and sync. You can choose between `SupergraphFetcherAsyncClient` and `SupergraphFetcherSyncClient` based on your needs. See the examples at the bottom.
6+
- `SupergraphFetcher` now has a new `retry_count` parameter to specify how many times to retry fetching the supergraph in case of failures.
7+
- `PersistedDocumentsManager` new needs `user_agent` parameter to be sent to Hive Console when fetching persisted queries.
8+
- `UsageAgent::new` is now `UsageAgent::try_new` and it returns a `Result` with `Arc`, so you can freely clone it across threads. This change was made to handle potential errors during the creation of the HTTP client. Make sure to handle the `Result` when creating a `UsageAgent`.
9+
10+
```rust
11+
// Sync Mode
12+
let fetcher = SupergraphFetcher::try_new_sync(/* params */)
13+
.map_err(|e| anyhow!("Failed to create SupergraphFetcher: {}", e))?;
14+
15+
// Use the fetcher to fetch the supergraph (Sync)
16+
let supergraph = fetcher
17+
.fetch_supergraph()
18+
.map_err(|e| anyhow!("Failed to fetch supergraph: {}", e))?;
19+
20+
// Async Mode
21+
22+
let fetcher = SupergraphFetcher::try_new_async(/* params */)
23+
.map_err(|e| anyhow!("Failed to create SupergraphFetcher: {}", e))?;
24+
25+
// Use the fetcher to fetch the supergraph (Async)
26+
let supergraph = fetcher
27+
.fetch_supergraph()
28+
.await
29+
.map_err(|e| anyhow!("Failed to fetch supergraph: {}", e))?;
30+
```

configs/cargo/Cargo.lock

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

packages/libraries/router/src/persisted_documents.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ use std::time::Duration;
2424
use tower::{BoxError, ServiceBuilder, ServiceExt};
2525
use tracing::{debug, info, warn};
2626

27+
use crate::consts::PLUGIN_VERSION;
28+
2729
pub static PERSISTED_DOCUMENT_HASH_KEY: &str = "hive::persisted_document_hash";
2830

2931
#[derive(Clone, Debug, Deserialize, JsonSchema, Default)]
@@ -107,6 +109,7 @@ impl PersistedDocumentsPlugin {
107109
Duration::from_secs(config.request_timeout.unwrap_or(15)),
108110
config.retry_count.unwrap_or(3),
109111
config.cache_size.unwrap_or(1000),
112+
format!("hive-apollo-router/{}", PLUGIN_VERSION),
110113
))),
111114
allow_arbitrary_documents,
112115
})

packages/libraries/router/src/registry.rs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::consts::PLUGIN_VERSION;
22
use crate::registry_logger::Logger;
33
use anyhow::{anyhow, Result};
44
use hive_console_sdk::supergraph_fetcher::SupergraphFetcher;
5+
use hive_console_sdk::supergraph_fetcher::SupergraphFetcherSyncState;
56
use sha2::Digest;
67
use sha2::Sha256;
78
use std::env;
@@ -12,7 +13,7 @@ use std::time::Duration;
1213
#[derive(Debug)]
1314
pub struct HiveRegistry {
1415
file_name: String,
15-
fetcher: SupergraphFetcher,
16+
fetcher: SupergraphFetcher<SupergraphFetcherSyncState>,
1617
pub logger: Logger,
1718
}
1819

@@ -122,16 +123,19 @@ impl HiveRegistry {
122123
env::set_var("APOLLO_ROUTER_SUPERGRAPH_PATH", file_name.clone());
123124
env::set_var("APOLLO_ROUTER_HOT_RELOAD", "true");
124125

125-
let mut registry = HiveRegistry {
126-
fetcher: SupergraphFetcher::try_new(
127-
endpoint,
128-
key,
129-
format!("hive-apollo-router/{}", PLUGIN_VERSION),
130-
Duration::from_secs(5),
131-
Duration::from_secs(60),
132-
accept_invalid_certs,
133-
)
134-
.map_err(|e| anyhow!("Failed to create SupergraphFetcher: {}", e))?,
126+
let fetcher = SupergraphFetcher::try_new_sync(
127+
endpoint,
128+
&key,
129+
format!("hive-apollo-router/{}", PLUGIN_VERSION),
130+
Duration::from_secs(5),
131+
Duration::from_secs(60),
132+
accept_invalid_certs,
133+
3,
134+
)
135+
.map_err(|e| anyhow!("Failed to create SupergraphFetcher: {}", e))?;
136+
137+
let registry = HiveRegistry {
138+
fetcher,
135139
file_name,
136140
logger,
137141
};
@@ -156,9 +160,12 @@ impl HiveRegistry {
156160
Ok(())
157161
}
158162

159-
fn initial_supergraph(&mut self) -> Result<(), String> {
163+
fn initial_supergraph(&self) -> Result<(), String> {
160164
let mut file = std::fs::File::create(self.file_name.clone()).map_err(|e| e.to_string())?;
161-
let resp = self.fetcher.fetch_supergraph()?;
165+
let resp = self
166+
.fetcher
167+
.fetch_supergraph()
168+
.map_err(|err| err.to_string())?;
162169

163170
match resp {
164171
Some(supergraph) => {
@@ -173,7 +180,7 @@ impl HiveRegistry {
173180
Ok(())
174181
}
175182

176-
fn poll(&mut self) {
183+
fn poll(&self) {
177184
match self.fetcher.fetch_supergraph() {
178185
Ok(new_supergraph) => {
179186
if let Some(new_supergraph) = new_supergraph {

packages/libraries/router/src/usage.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use core::ops::Drop;
88
use futures::StreamExt;
99
use graphql_parser::parse_schema;
1010
use graphql_parser::schema::Document;
11+
use hive_console_sdk::agent::UsageAgentExt;
1112
use hive_console_sdk::agent::{ExecutionReport, UsageAgent};
1213
use http::HeaderValue;
1314
use rand::Rng;
@@ -245,8 +246,8 @@ impl Plugin for UsagePlugin {
245246

246247
let agent = if enabled {
247248
let flush_interval = Duration::from_secs(flush_interval);
248-
let agent = Arc::new(UsageAgent::new(
249-
token.expect("token is set"),
249+
let agent = UsageAgent::try_new(
250+
&token.expect("token is set"),
250251
endpoint,
251252
target_id,
252253
buffer_size,
@@ -255,7 +256,8 @@ impl Plugin for UsagePlugin {
255256
accept_invalid_certs,
256257
flush_interval,
257258
format!("hive-apollo-router/{}", PLUGIN_VERSION),
258-
));
259+
)
260+
.map_err(Box::new)?;
259261
start_flush_interval(agent.clone());
260262
Some(agent)
261263
} else {

packages/libraries/sdk-rs/src/agent.rs

Lines changed: 59 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::graphql::OperationProcessor;
22
use graphql_parser::schema::Document;
3+
use reqwest::header::{HeaderMap, HeaderValue};
34
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
45
use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};
56
use serde::Serialize;
@@ -103,17 +104,13 @@ impl Buffer {
103104
Ok(reports)
104105
}
105106
}
106-
107-
#[derive(Clone)]
108107
pub struct UsageAgent {
109-
token: String,
110108
buffer_size: usize,
111109
endpoint: String,
112-
buffer: Arc<Buffer>,
113-
processor: Arc<OperationProcessor>,
110+
buffer: Buffer,
111+
processor: OperationProcessor,
114112
client: ClientWithMiddleware,
115113
flush_interval: Duration,
116-
user_agent: String,
117114
}
118115

119116
fn non_empty_string(value: Option<String>) -> Option<String> {
@@ -130,14 +127,18 @@ pub enum AgentError {
130127
Forbidden,
131128
#[error("unable to send report: rate limited")]
132129
RateLimited,
130+
#[error("invalid token provided: {0}")]
131+
InvalidToken(String),
132+
#[error("unable to instantiate the http client for reports sending: {0}")]
133+
HTTPClientCreationError(reqwest::Error),
133134
#[error("unable to send report: {0}")]
134135
Unknown(String),
135136
}
136137

137138
impl UsageAgent {
138139
#[allow(clippy::too_many_arguments)]
139-
pub fn new(
140-
token: String,
140+
pub fn try_new(
141+
token: &str,
141142
endpoint: String,
142143
target_id: Option<String>,
143144
buffer_size: usize,
@@ -146,22 +147,35 @@ impl UsageAgent {
146147
accept_invalid_certs: bool,
147148
flush_interval: Duration,
148149
user_agent: String,
149-
) -> Self {
150-
let processor = Arc::new(OperationProcessor::new());
151-
150+
) -> Result<Arc<Self>, AgentError> {
152151
let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3);
153152

153+
let mut default_headers = HeaderMap::new();
154+
155+
default_headers.insert("X-Usage-API-Version", HeaderValue::from_static("2"));
156+
157+
let mut authorization_header = HeaderValue::from_str(&format!("Bearer {}", token))
158+
.map_err(|_| AgentError::InvalidToken(token.to_string()))?;
159+
160+
authorization_header.set_sensitive(true);
161+
162+
default_headers.insert(reqwest::header::AUTHORIZATION, authorization_header);
163+
164+
default_headers.insert(
165+
reqwest::header::CONTENT_TYPE,
166+
HeaderValue::from_static("application/json"),
167+
);
168+
154169
let reqwest_agent = reqwest::Client::builder()
155170
.danger_accept_invalid_certs(accept_invalid_certs)
156171
.connect_timeout(connect_timeout)
157172
.timeout(request_timeout)
173+
.user_agent(user_agent)
158174
.build()
159-
.map_err(|err| err.to_string())
160-
.expect("Couldn't instantiate the http client for reports sending!");
175+
.map_err(AgentError::HTTPClientCreationError)?;
161176
let client = ClientBuilder::new(reqwest_agent)
162177
.with(RetryTransientMiddleware::new_with_policy(retry_policy))
163178
.build();
164-
let buffer = Arc::new(Buffer::new());
165179

166180
let mut endpoint = endpoint;
167181

@@ -171,16 +185,14 @@ impl UsageAgent {
171185
}
172186
}
173187

174-
UsageAgent {
175-
buffer,
176-
processor,
177-
endpoint,
178-
token,
188+
Ok(Arc::new(Self {
179189
buffer_size,
190+
endpoint,
191+
buffer: Buffer::new(),
192+
processor: OperationProcessor::new(),
180193
client,
181194
flush_interval,
182-
user_agent,
183-
}
195+
}))
184196
}
185197

186198
fn produce_report(&self, reports: Vec<ExecutionReport>) -> Result<Report, AgentError> {
@@ -256,28 +268,13 @@ impl UsageAgent {
256268
Ok(report)
257269
}
258270

259-
pub fn add_report(&self, execution_report: ExecutionReport) -> Result<(), AgentError> {
260-
let size = self.buffer.push(execution_report)?;
261-
262-
self.flush_if_full(size)?;
263-
264-
Ok(())
265-
}
266-
267271
pub async fn send_report(&self, report: Report) -> Result<(), AgentError> {
268272
let report_body =
269273
serde_json::to_vec(&report).map_err(|e| AgentError::Unknown(e.to_string()))?;
270274
// Based on https://the-guild.dev/graphql/hive/docs/specs/usage-reports#data-structure
271275
let resp = self
272276
.client
273277
.post(&self.endpoint)
274-
.header("X-Usage-API-Version", "2")
275-
.header(
276-
reqwest::header::AUTHORIZATION,
277-
format!("Bearer {}", self.token),
278-
)
279-
.header(reqwest::header::USER_AGENT, self.user_agent.to_string())
280-
.header(reqwest::header::CONTENT_TYPE, "application/json")
281278
.header(reqwest::header::CONTENT_LENGTH, report_body.len())
282279
.body(report_body)
283280
.send()
@@ -297,17 +294,6 @@ impl UsageAgent {
297294
}
298295
}
299296

300-
pub fn flush_if_full(&self, size: usize) -> Result<(), AgentError> {
301-
if size >= self.buffer_size {
302-
let cloned_self = self.clone();
303-
tokio::task::spawn(async move {
304-
cloned_self.flush().await;
305-
});
306-
}
307-
308-
Ok(())
309-
}
310-
311297
pub async fn flush(&self) {
312298
let execution_reports = match self.buffer.drain() {
313299
Ok(res) => res,
@@ -345,3 +331,29 @@ impl UsageAgent {
345331
}
346332
}
347333
}
334+
335+
pub trait UsageAgentExt {
336+
fn add_report(&self, execution_report: ExecutionReport) -> Result<(), AgentError>;
337+
fn flush_if_full(&self, size: usize) -> Result<(), AgentError>;
338+
}
339+
340+
impl UsageAgentExt for Arc<UsageAgent> {
341+
fn flush_if_full(&self, size: usize) -> Result<(), AgentError> {
342+
if size >= self.buffer_size {
343+
let cloned_self = self.clone();
344+
tokio::task::spawn(async move {
345+
cloned_self.flush().await;
346+
});
347+
}
348+
349+
Ok(())
350+
}
351+
352+
fn add_report(&self, execution_report: ExecutionReport) -> Result<(), AgentError> {
353+
let size = self.buffer.push(execution_report)?;
354+
355+
self.flush_if_full(size)?;
356+
357+
Ok(())
358+
}
359+
}

0 commit comments

Comments
 (0)