Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .changeset/busy-cloths-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
'hive-console-sdk-rs': minor
---

Breaking Changes to avoid future breaking changes;

Switch to [Builder](https://rust-unofficial.github.io/patterns/patterns/creational/builder.html) pattern for `SupergraphFetcher`, `PersistedDocumentsManager` and `UsageAgent` structs.

No more `try_new` or `try_new_async` or `try_new_sync` functions, instead use `SupergraphFetcherBuilder`, `PersistedDocumentsManagerBuilder` and `UsageAgentBuilder` structs to create instances.

Benefits;

- No need to provide all parameters at once when creating an instance even for default values.

Example;
```rust
// Before
let fetcher = SupergraphFetcher::try_new_async(
"SOME_ENDPOINT", // endpoint
"SOME_KEY",
"MyUserAgent/1.0".to_string(),
Duration::from_secs(5), // connect_timeout
Duration::from_secs(10), // request_timeout
false, // accept_invalid_certs
3, // retry_count
)?;

// After
// No need to provide all parameters at once, can use default values
let fetcher = SupergraphFetcherBuilder::new()
.endpoint("SOME_ENDPOINT".to_string())
.key("SOME_KEY".to_string())
.build_async()?;
```

- Easier to add new configuration options in the future without breaking existing code.

Example;

```rust
let fetcher = SupergraphFetcher::try_new_async(
"SOME_ENDPOINT", // endpoint
"SOME_KEY",
"MyUserAgent/1.0".to_string(),
Duration::from_secs(5), // connect_timeout
Duration::from_secs(10), // request_timeout
false, // accept_invalid_certs
3, // retry_count
circuit_breaker_config, // Breaking Change -> new parameter added
)?;

let fetcher = SupergraphFetcherBuilder::new()
.endpoint("SOME_ENDPOINT".to_string())
.key("SOME_KEY".to_string())
.build_async()?; // No breaking change, circuit_breaker_config can be added later if needed
```
20 changes: 20 additions & 0 deletions .changeset/light-walls-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'hive-console-sdk-rs': patch
---

Circuit Breaker Implementation and Multiple Endpoints Support

Implementation of Circuit Breakers in Hive Console Rust SDK, you can learn more [here](https://the-guild.dev/graphql/hive/product-updates/2025-12-04-cdn-mirror-and-circuit-breaker)

Breaking Changes:

Now `endpoint` configuration accepts multiple endpoints as an array for `SupergraphFetcherBuilder` and `PersistedDocumentsManager`.

```diff
SupergraphFetcherBuilder::default()
- .endpoint(endpoint)
+ .add_endpoint(endpoint1)
+ .add_endpoint(endpoint2)
```

This change requires updating the configuration structure to accommodate multiple endpoints.
17 changes: 17 additions & 0 deletions .changeset/violet-waves-happen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'hive-apollo-router-plugin': major
---

- Multiple endpoints support for `HiveRegistry` and `PersistedOperationsPlugin`

Breaking Changes:
- Now there is no `endpoint` field in the configuration, it has been replaced with `endpoints`, which is an array of strings. You are not affected if you use environment variables to set the endpoint.

```diff
HiveRegistry::new(
Some(
HiveRegistryConfig {
- endpoint: String::from("CDN_ENDPOINT"),
+ endpoints: vec![String::from("CDN_ENDPOINT1"), String::from("CDN_ENDPOINT2")],
)
)
19 changes: 17 additions & 2 deletions configs/cargo/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

78 changes: 62 additions & 16 deletions packages/libraries/router/src/persisted_documents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub static PERSISTED_DOCUMENT_HASH_KEY: &str = "hive::persisted_document_hash";
pub struct Config {
pub enabled: Option<bool>,
/// GraphQL Hive persisted documents CDN endpoint URL.
pub endpoint: Option<String>,
pub endpoint: Option<EndpointConfig>,
/// GraphQL Hive persisted documents CDN access token.
pub key: Option<String>,
/// Whether arbitrary documents should be allowed along-side persisted documents.
Expand All @@ -57,6 +57,25 @@ pub struct Config {
pub cache_size: Option<u64>,
}

#[derive(Clone, Debug, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum EndpointConfig {
Single(String),
Multiple(Vec<String>),
}

impl From<&str> for EndpointConfig {
fn from(value: &str) -> Self {
EndpointConfig::Single(value.into())
}
}

impl From<&[&str]> for EndpointConfig {
fn from(value: &[&str]) -> Self {
EndpointConfig::Multiple(value.iter().map(|s| s.to_string()).collect())
}
}

pub struct PersistedDocumentsPlugin {
persisted_documents_manager: Option<Arc<PersistedDocumentsManager>>,
allow_arbitrary_documents: bool,
Expand All @@ -72,11 +91,14 @@ impl PersistedDocumentsPlugin {
allow_arbitrary_documents,
});
}
let endpoint = match &config.endpoint {
Some(ep) => ep.clone(),
let endpoints = match &config.endpoint {
Some(ep) => match ep {
EndpointConfig::Single(url) => vec![url.clone()],
EndpointConfig::Multiple(urls) => urls.clone(),
},
None => {
if let Ok(ep) = env::var("HIVE_CDN_ENDPOINT") {
ep
vec![ep]
} else {
return Err(
"Endpoint for persisted documents CDN is not configured. Please set it via the plugin configuration or HIVE_CDN_ENDPOINT environment variable."
Expand All @@ -100,17 +122,41 @@ impl PersistedDocumentsPlugin {
}
};

let mut persisted_documents_manager = PersistedDocumentsManager::builder()
.key(key)
.user_agent(format!("hive-apollo-router/{}", PLUGIN_VERSION));

for endpoint in endpoints {
persisted_documents_manager = persisted_documents_manager.add_endpoint(endpoint);
}

if let Some(connect_timeout) = config.connect_timeout {
persisted_documents_manager =
persisted_documents_manager.connect_timeout(Duration::from_secs(connect_timeout));
}

if let Some(request_timeout) = config.request_timeout {
persisted_documents_manager =
persisted_documents_manager.request_timeout(Duration::from_secs(request_timeout));
}

if let Some(retry_count) = config.retry_count {
persisted_documents_manager = persisted_documents_manager.max_retries(retry_count);
}

if let Some(accept_invalid_certs) = config.accept_invalid_certs {
persisted_documents_manager =
persisted_documents_manager.accept_invalid_certs(accept_invalid_certs);
}

if let Some(cache_size) = config.cache_size {
persisted_documents_manager = persisted_documents_manager.cache_size(cache_size);
}

let persisted_documents_manager = persisted_documents_manager.build()?;

Ok(PersistedDocumentsPlugin {
persisted_documents_manager: Some(Arc::new(PersistedDocumentsManager::new(
key,
endpoint,
config.accept_invalid_certs.unwrap_or(false),
Duration::from_secs(config.connect_timeout.unwrap_or(5)),
Duration::from_secs(config.request_timeout.unwrap_or(15)),
config.retry_count.unwrap_or(3),
config.cache_size.unwrap_or(1000),
format!("hive-apollo-router/{}", PLUGIN_VERSION),
))),
persisted_documents_manager: Some(Arc::new(persisted_documents_manager)),
allow_arbitrary_documents,
})
}
Expand Down Expand Up @@ -344,8 +390,8 @@ mod hive_persisted_documents_tests {
Self { server }
}

fn endpoint(&self) -> String {
self.server.url("")
fn endpoint(&self) -> EndpointConfig {
EndpointConfig::Single(self.server.url(""))
}

/// Registers a valid artifact URL with an actual GraphQL document
Expand Down
45 changes: 24 additions & 21 deletions packages/libraries/router/src/registry.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use crate::consts::PLUGIN_VERSION;
use crate::registry_logger::Logger;
use anyhow::{anyhow, Result};
use hive_console_sdk::supergraph_fetcher::sync::SupergraphFetcherSyncState;
use hive_console_sdk::supergraph_fetcher::SupergraphFetcher;
use hive_console_sdk::supergraph_fetcher::SupergraphFetcherSyncState;
use sha2::Digest;
use sha2::Sha256;
use std::env;
use std::io::Write;
use std::thread;
use std::time::Duration;

#[derive(Debug)]
pub struct HiveRegistry {
Expand All @@ -18,7 +17,7 @@ pub struct HiveRegistry {
}

pub struct HiveRegistryConfig {
endpoint: Option<String>,
endpoints: Vec<String>,
key: Option<String>,
poll_interval: Option<u64>,
accept_invalid_certs: Option<bool>,
Expand All @@ -29,7 +28,7 @@ impl HiveRegistry {
#[allow(clippy::new_ret_no_self)]
pub fn new(user_config: Option<HiveRegistryConfig>) -> Result<()> {
let mut config = HiveRegistryConfig {
endpoint: None,
endpoints: vec![],
key: None,
poll_interval: None,
accept_invalid_certs: Some(true),
Expand All @@ -38,7 +37,7 @@ impl HiveRegistry {

// Pass values from user's config
if let Some(user_config) = user_config {
config.endpoint = user_config.endpoint;
config.endpoints = user_config.endpoints;
config.key = user_config.key;
config.poll_interval = user_config.poll_interval;
config.accept_invalid_certs = user_config.accept_invalid_certs;
Expand All @@ -47,9 +46,9 @@ impl HiveRegistry {

// Pass values from environment variables if they are not set in the user's config

if config.endpoint.is_none() {
if config.endpoints.is_empty() {
if let Ok(endpoint) = env::var("HIVE_CDN_ENDPOINT") {
config.endpoint = Some(endpoint);
config.endpoints.push(endpoint);
}
}

Expand Down Expand Up @@ -86,7 +85,7 @@ impl HiveRegistry {
}

// Resolve values
let endpoint = config.endpoint.unwrap_or_default();
let endpoint = config.endpoints;
let key = config.key.unwrap_or_default();
let poll_interval: u64 = config.poll_interval.unwrap_or(10);
let accept_invalid_certs = config.accept_invalid_certs.unwrap_or(false);
Expand Down Expand Up @@ -120,19 +119,23 @@ impl HiveRegistry {
.to_string_lossy()
.to_string(),
);
env::set_var("APOLLO_ROUTER_SUPERGRAPH_PATH", file_name.clone());
env::set_var("APOLLO_ROUTER_HOT_RELOAD", "true");

let fetcher = SupergraphFetcher::try_new_sync(
endpoint,
&key,
format!("hive-apollo-router/{}", PLUGIN_VERSION),
Duration::from_secs(5),
Duration::from_secs(60),
accept_invalid_certs,
3,
)
.map_err(|e| anyhow!("Failed to create SupergraphFetcher: {}", e))?;
unsafe {
env::set_var("APOLLO_ROUTER_SUPERGRAPH_PATH", file_name.clone());
env::set_var("APOLLO_ROUTER_HOT_RELOAD", "true");
}

let mut fetcher = SupergraphFetcher::builder()
.key(key)
.user_agent(format!("hive-apollo-router/{}", PLUGIN_VERSION))
.accept_invalid_certs(accept_invalid_certs);

for ep in endpoint {
fetcher = fetcher.add_endpoint(ep);
}

let fetcher = fetcher
.build_sync()
.map_err(|e| anyhow!("Failed to create SupergraphFetcher: {}", e))?;

let registry = HiveRegistry {
fetcher,
Expand Down
Loading
Loading