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
25 changes: 25 additions & 0 deletions .changeset/graphql_endpoint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
router: patch
config: patch
---

# `graphql_endpoint` Configuration and `.request.path_params` in VRL

- Adds support for configuring the GraphQL endpoint path via the `graphql_endpoint` configuration option.

So you can have dynamic path params that can be used with VRL expressions.

`path_params` are also added to `.request` context in VRL for more dynamic configurations.

```yaml
http:
graphql_endpoint: /graphql/{document_id}
persisted_documents:
enabled: true
spec:
expression: .request.path_params.document_id
```

[Learn more about the `graphql_endpoint` configuration option in the documentation.](https://the-guild.dev/graphql/hive/docs/router/configuration/graphql_endpoint)

[Learn more about the `.request.path_params` configuration option in the documentation.](https://the-guild.dev/graphql/hive/docs/router/configuration/expressions#request)
15 changes: 15 additions & 0 deletions .changeset/persisted_documents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
router: patch
config: patch
executor: patch
---

# Persisted Documents

- Supports Hive's `documentId` spec, Relay's `doc_id` spec and Apollo's `extensions` based spec as options
- - It is also possible to use your own method to extract document ids using VRL expressions
- Hive Console and File sources are supported
- A flag to enable/disable arbitrary operations
- - A VRL Expression can also be used to decide this dynamically using headers or any other request details

[Learn more about Persisted Documents in the documentation.](https://the-guild.dev/graphql/hive/docs/router/configuration/persisted_documents)
15 changes: 15 additions & 0 deletions .changeset/shared_utilities_to_handle_vrl_expressions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
default: minor
---

# Breaking

Removed `pool_idle_timeout_seconds` from `traffic_shaping`, instead use `pool_idle_timeout` with duration format.

```diff
traffic_shaping:
- pool_idle_timeout_seconds: 30
+ pool_idle_timeout: 30s
```

#540 by @ardatan
47 changes: 47 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions bin/router/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ tokio-util = "0.7.16"
cookie = "0.18.1"
regex-automata = "0.4.10"
arc-swap = "1.7.1"
hive-console-sdk = "0.1.0"
31 changes: 27 additions & 4 deletions bin/router/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod consts;
mod http_utils;
mod jwt;
mod logger;
mod persisted_documents;
mod pipeline;
mod schema_state;
mod shared_state;
Expand All @@ -19,6 +20,7 @@ use crate::{
},
jwt::JwtAuthRuntime,
logger::configure_logging,
persisted_documents::PersistedDocumentsLoader,
pipeline::graphql_request_handler,
};

Expand Down Expand Up @@ -88,7 +90,9 @@ pub async fn router_entrypoint() -> Result<(), Box<dyn std::error::Error>> {
web::App::new()
.state(shared_state.clone())
.state(schema_state.clone())
.configure(configure_ntex_app)
.configure(|service_config| {
configure_ntex_app(service_config, &shared_state.router_config);
})
.default_service(web::to(landing_page_handler))
})
.bind(addr)?
Expand All @@ -111,17 +115,36 @@ pub async fn configure_app_from_config(
false => None,
};

let persisted_docs = if router_config.persisted_documents.enabled {
Some(PersistedDocumentsLoader::try_new(
&router_config.persisted_documents,
)?)
} else {
None
};

let router_config_arc = Arc::new(router_config);
let schema_state =
SchemaState::new_from_config(bg_tasks_manager, router_config_arc.clone()).await?;
let schema_state_arc = Arc::new(schema_state);
let shared_state = Arc::new(RouterSharedState::new(router_config_arc, jwt_runtime)?);
let shared_state = Arc::new(RouterSharedState::new(
router_config_arc,
jwt_runtime,
persisted_docs,
)?);

Ok((shared_state, schema_state_arc))
}

pub fn configure_ntex_app(cfg: &mut web::ServiceConfig) {
cfg.route("/graphql", web::to(graphql_endpoint_handler))
pub fn configure_ntex_app(
service_config: &mut web::ServiceConfig,
router_config: &HiveRouterConfig,
) {
service_config
.route(
&router_config.http.graphql_endpoint,
web::to(graphql_endpoint_handler),
)
.route("/health", web::to(health_check_handler))
.route("/readiness", web::to(readiness_check_handler));
}
89 changes: 89 additions & 0 deletions bin/router/src/persisted_documents/expr_input_val.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use std::collections::BTreeMap;

use hive_router_plan_executor::execution::client_request_details::{
client_header_map_to_vrl_value, client_path_params_to_vrl_value, client_url_to_vrl_value,
JwtRequestDetails,
};
use ntex::web::HttpRequest;
use sonic_rs::{JsonContainerTrait, JsonValueTrait};
use vrl::core::Value as VrlValue;

use crate::pipeline::execution_request::ExecutionRequest;

pub fn get_expression_input_val(
execution_request: &ExecutionRequest,
req: &HttpRequest,
jwt_request_details: &JwtRequestDetails<'_>,
) -> VrlValue {
let headers_value = client_header_map_to_vrl_value(req.headers());
let url_value = client_url_to_vrl_value(req.uri());
let path_params_value = client_path_params_to_vrl_value(req.match_info());
let request_obj = VrlValue::Object(BTreeMap::from([
("method".into(), req.method().as_str().into()),
("headers".into(), headers_value),
("url".into(), url_value),
("path_params".into(), path_params_value),
("jwt".into(), jwt_request_details.into()),
(
"body".into(),
execution_request_to_vrl_value(execution_request),
),
]));

VrlValue::Object(BTreeMap::from([("request".into(), request_obj)]))
}

fn execution_request_to_vrl_value(execution_request: &ExecutionRequest) -> VrlValue {
let mut obj = BTreeMap::new();
if let Some(op_name) = &execution_request.operation_name {
obj.insert("operationName".into(), op_name.clone().into());
}
if let Some(query) = &execution_request.query {
obj.insert("query".into(), query.clone().into());
}
for (k, v) in &execution_request.extra_params {
obj.insert(k.clone().into(), from_sonic_value_to_vrl_value(v));
}
VrlValue::Object(obj)
}

fn from_sonic_value_to_vrl_value(value: &sonic_rs::Value) -> VrlValue {
match value.get_type() {
sonic_rs::JsonType::Null => VrlValue::Null,
sonic_rs::JsonType::Boolean => VrlValue::Boolean(value.as_bool().unwrap_or(false)),
sonic_rs::JsonType::Number => {
if let Some(n) = value.as_i64() {
VrlValue::Integer(n)
} else if let Some(n) = value.as_f64() {
VrlValue::from_f64_or_zero(n)
} else {
VrlValue::Null
}
}
sonic_rs::JsonType::String => {
if let Some(s) = value.as_str() {
s.into()
} else {
VrlValue::Null
}
}
sonic_rs::JsonType::Array => {
if let Some(array) = value.as_array() {
let vec = array.iter().map(from_sonic_value_to_vrl_value).collect();
VrlValue::Array(vec)
} else {
VrlValue::Null
}
}
sonic_rs::JsonType::Object => {
if let Some(obj) = value.as_object() {
obj.iter()
.map(|(k, v)| (k.into(), from_sonic_value_to_vrl_value(v)))
.collect::<BTreeMap<_, _>>()
.into()
} else {
VrlValue::Null
}
}
}
}
28 changes: 28 additions & 0 deletions bin/router/src/persisted_documents/fetcher/file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::{collections::HashMap, fs::read_to_string};

use hive_router_config::primitives::file_path::FilePath;

use crate::persisted_documents::PersistedDocumentsError;

pub struct FilePersistedDocumentsManager {
operations: HashMap<String, String>,
}

impl FilePersistedDocumentsManager {
pub fn try_new(file_path: &FilePath) -> Result<Self, PersistedDocumentsError> {
let content =
read_to_string(&file_path.absolute).map_err(PersistedDocumentsError::FileReadError)?;

let operations: HashMap<String, String> =
serde_json::from_str(&content).map_err(PersistedDocumentsError::ParseError)?;

Ok(Self { operations })
}

pub fn resolve_document(&self, document_id: &str) -> Result<String, PersistedDocumentsError> {
match self.operations.get(document_id) {
Some(document) => Ok(document.clone()),
None => Err(PersistedDocumentsError::NotFound(document_id.to_string())),
}
}
}
Loading