Skip to content
Closed
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
1 change: 1 addition & 0 deletions bin/router/src/shared_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ impl RouterSharedState {

let subgraph_executor_map = SubgraphExecutorMap::from_http_endpoint_map(
supergraph_state.subgraph_endpoint_map,
router_config.override_subgraph_urls.clone(),
router_config.traffic_shaping.clone(),
)
.expect("Failed to create subgraph executor map");
Expand Down
60 changes: 60 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
|[**headers**](#headers)|`object`|Configuration for the headers.<br/>Default: `{}`<br/>||
|[**http**](#http)|`object`|Configuration for the HTTP server/listener.<br/>Default: `{"host":"0.0.0.0","port":4000}`<br/>||
|[**log**](#log)|`object`|The router logger configuration.<br/>Default: `{"filter":null,"format":"json","level":"info"}`<br/>||
|[**override\_subgraph\_urls**](#override_subgraph_urls)|`object`|Configuration for overriding subgraph URLs.<br/>Default: `{"subgraphs":{}}`<br/>||
|[**query\_planner**](#query_planner)|`object`|Query planning configuration.<br/>Default: `{"allow_expose":false,"timeout":"10s"}`<br/>||
|[**supergraph**](#supergraph)|`object`|Configuration for the Federation supergraph source. By default, the router will use a local file-based supergraph source (`./supergraph.graphql`).<br/>Default: `{"path":"supergraph.graphql","source":"file"}`<br/>||
|[**traffic\_shaping**](#traffic_shaping)|`object`|Configuration for the traffic-shaper executor. Use these configurations to control how requests are being executed to subgraphs.<br/>Default: `{"dedupe_enabled":true,"max_connections_per_host":100,"pool_idle_timeout_seconds":50}`<br/>||
Expand Down Expand Up @@ -57,6 +58,21 @@ log:
filter: null
format: json
level: info
override_subgraph_urls:
subgraphs:
accounts:
url: https://accounts.example.com/graphql
products:
expression: |2-

if .request.headers."x-region" == "us-east" {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a case sensitive matching that is occurring? Or is it case-insensitive?
Are there any other request attributes that can be used here? Looks like the entire request object is available for use.

Copy link

@starJammer starJammer Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, does the statement have to return a value here?
Or will the default automatically be reused if no value is returned in the expression?

For example, suppose my default url is http://hello.us.com.

Is this a valid configuration for the products subgraph?:

override_subgraph_urls:
  subgraphs:
    products:
      expression: |2-
                if .request.headers."x-region" == "eu-east" {
                    "https://hello.eu.eu/graphql"
                }

I want to point out TWO things here:

  1. Notice that the default is http (plain http) and the EU version of the URL is https (secure). Is this sort of change to the scheme supported? This was not originally supported in the Apollo router and but I submitted a PR for it to be supported here Feature/6897 - Allow scheme for subgraph to be inspected and updated apollographql/router#6906

Since Apollo didn't support it by default, I don't want to assume that it is supported here.

  1. Notice that I didn't include an else statement in the expression in the configuration above. I would expect that the default URL would be used in this case. Is this supported, or does the expression have to return a value, even the default value, or will the default value be used if no value is returned?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My Rust isn't good at all so I can't quite look at the code myself to answer the above questions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notice that I didn't include an else statement in the expression in the configuration above. I would expect that the default URL would be used in this case. Is this supported, or does the expression have to return a value, even the default value, or will the default value be used if no value is returned?

Yes the URL in the supergraph will be used if the expression doesn't return anything.

Notice that the default is http (plain http) and the EU version of the URL is https (secure). Is this sort of change to the scheme supported? This was not originally supported in the Apollo router and but I submitted a PR for it to be supported here

Scheme doesn't matter in our implementation currently. It is possible to switch in between http and https.

Copy link
Member Author

@ardatan ardatan Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a case sensitive matching that is occurring? Or is it case-insensitive?

Header names are always lower-cased actually. So they are case-insensitive.

Are there any other request attributes that can be used here? Looks like the entire request object is available for use.

It has details like URL(search params etc), headers, method and the original router operation. After the implementation of JWT, it will have the jwt payload there too. Once we have a proper documentation then we'll explain the available variables in the VRL Context!

pub struct ClientRequestDetails<'a> {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds great! This is perfect. Because my organization chose to NOT use Apollo enterprise, we're only using basic query features of the router so switching to this new router now should be super easy.

The only thing we'll want to wait for is the ability to tie it to the Hive Registry for the supergraph schema. I saw there is an issue open for that and that it's already on the timeline.

"https://products-us-east.example.com/graphql"
} else if .request.headers."x-region" == "eu-west" {
"https://products-eu-west.example.com/graphql"
} else {
"https://products.example.com/graphql"
}

query_planner:
allow_expose: false
timeout: 10s
Expand Down Expand Up @@ -1306,6 +1322,50 @@ level: info

```

<a name="override_subgraph_urls"></a>
## override\_subgraph\_urls: object

Configuration for overriding subgraph URLs.


**Properties**

|Name|Type|Description|Required|
|----|----|-----------|--------|
|[**subgraphs**](#override_subgraph_urlssubgraphs)|`object`|Keys are subgraph names as defined in the supergraph schema.<br/>Default: `{}`<br/>||

**Example**

```yaml
subgraphs:
accounts:
url: https://accounts.example.com/graphql
products:
expression: |2-

if .request.headers."x-region" == "us-east" {
"https://products-us-east.example.com/graphql"
} else if .request.headers."x-region" == "eu-west" {
"https://products-eu-west.example.com/graphql"
} else {
"https://products.example.com/graphql"
}


```

<a name="override_subgraph_urlssubgraphs"></a>
### override\_subgraph\_urls\.subgraphs: object

Keys are subgraph names as defined in the supergraph schema.


**Additional Properties**

|Name|Type|Description|Required|
|----|----|-----------|--------|
|**Additional Properties**||||

<a name="query_planner"></a>
## query\_planner: object

Expand Down
1 change: 1 addition & 0 deletions lib/executor/src/execution/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,7 @@ impl<'exec> Executor<'exec> {
dedupe: self.dedupe_subgraph_requests,
operation_name: node.operation_name.as_deref(),
variables: variable_refs,
client_request: self.client_request,
representations,
headers: headers_map,
},
Expand Down
5 changes: 5 additions & 0 deletions lib/executor/src/executors/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ use async_trait::async_trait;
use bytes::Bytes;
use http::HeaderMap;

use crate::execution::plan::ClientRequestDetails;
use std::any::Any;

#[async_trait]
pub trait SubgraphExecutor {
async fn execute<'a>(
Expand All @@ -16,6 +19,7 @@ pub trait SubgraphExecutor {
{
Arc::new(Box::new(self))
}
fn as_any(&self) -> &dyn Any;
}

pub type SubgraphExecutorType = dyn crate::executors::common::SubgraphExecutor + Send + Sync;
Expand All @@ -28,6 +32,7 @@ pub struct HttpExecutionRequest<'a> {
pub operation_name: Option<&'a str>,
// TODO: variables could be stringified before even executing the request
pub variables: Option<HashMap<&'a str, &'a sonic_rs::Value>>,
pub client_request: &'a ClientRequestDetails<'a>,
pub headers: HeaderMap,
pub representations: Option<Vec<u8>>,
}
Expand Down
2 changes: 2 additions & 0 deletions lib/executor/src/executors/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ pub enum SubgraphExecutorError {
RequestFailure(String, String),
#[error("Failed to serialize variable \"{0}\": {1}")]
VariablesSerializationFailure(String, String),
#[error("Failed to parse VRL expression: {0}")]
VrlCompileError(String),
}
Loading
Loading