use std::sync::Arc;
use hive_console_sdk::expressions::{CompileExpression, ProgramHints};
use hive_router_config::persisted_documents::{
PersistedDocumentsConfig, PersistedDocumentsStorageConfig,
};
use hive_router_config::primitives::value_or_expression::ValueOrExpression;
use hive_router_internal::background_tasks::BackgroundTasksManager;
use hive_router_internal::expressions::{ToVrlValue, ValueOrProgram};
use hive_router_plan_executor::execution::client_request_details::ntex_header_map_to_vrl_value;
use ntex::web::HttpRequest;
use crate::pipeline::error::PipelineError;
use crate::pipeline::persisted_documents::extract::DocumentIdResolver;
use crate::pipeline::persisted_documents::resolve::storage::{
StorageManifestReloadTask, StorageResolver,
};
use crate::pipeline::persisted_documents::resolve::{
FileManifestReloadTask, FileManifestResolver, HiveCDNResolver, PersistedDocumentResolver,
PersistedDocumentResolverError,
};
use crate::storage::StorageManager;
pub mod extract;
pub mod resolve;
pub mod types;
pub struct PersistedDocumentsRuntime {
pub document_id_resolver: Arc<DocumentIdResolver>,
pub persisted_document_resolver: Option<Arc<dyn PersistedDocumentResolver>>,
pub(crate) require_id: ValueOrProgram<bool>,
}
impl PersistedDocumentsRuntime {
pub async fn init(
config: &PersistedDocumentsConfig,
graphql_endpoint: &str,
background_tasks_mgr: &mut BackgroundTasksManager,
storage_manager: &Arc<StorageManager>,
) -> Result<Self, PersistedDocumentResolverError> {
let document_id_resolver = Arc::new(
DocumentIdResolver::from_config(config, graphql_endpoint).map_err(|error| {
PersistedDocumentResolverError::Configuration(format!(
"failed to build persisted document extraction plan: {error}"
))
})?,
);
let require_id = match &config.require_id {
ValueOrExpression::Value(value) => ValueOrProgram::Value(*value),
ValueOrExpression::Expression { expression } => {
let program = expression.compile_expression(None).map_err(|err| {
PersistedDocumentResolverError::Configuration(format!(
"Failed to compile persisted document require_id expression: {err}"
))
})?;
let hints = ProgramHints::from_program(&program);
ValueOrProgram::Program(Box::new(program), hints)
}
};
let persisted_document_resolver = if config.enabled {
let storage = config
.storage
.as_ref()
.ok_or(PersistedDocumentResolverError::StorageNotConfigured)?;
match storage {
PersistedDocumentsStorageConfig::File { config } => {
let resolver =
Arc::new(FileManifestResolver::from_storage_config(config).await?);
if resolver.has_watcher() {
background_tasks_mgr
.register_task(FileManifestReloadTask(resolver.clone()));
}
Some(resolver as Arc<dyn PersistedDocumentResolver>)
}
PersistedDocumentsStorageConfig::Hive { config } => {
let resolver = Arc::new(HiveCDNResolver::from_storage_config(config)?);
Some(resolver as Arc<dyn PersistedDocumentResolver>)
}
PersistedDocumentsStorageConfig::Storage { config } => {
match storage_manager.get_storage_runtime(&config.storage_id) {
Some(storage) => {
let resolver = Arc::new(
StorageResolver::from_storage_config(config, storage).await?,
);
if let Some(poll_interval) = &config.poll_interval {
background_tasks_mgr.register_task(StorageManifestReloadTask::new(
resolver.clone(),
*poll_interval,
));
}
Some(resolver as Arc<dyn PersistedDocumentResolver>)
}
None => {
return Err(PersistedDocumentResolverError::StorageNotFound(
config.storage_id.to_string(),
));
}
}
}
}
} else {
None
};
Ok(Self {
document_id_resolver,
persisted_document_resolver,
require_id,
})
}
pub fn supports_graphql_endpoint(&self, graphql_endpoint: &str) -> bool {
if !self.document_id_resolver.is_enabled() {
return true;
}
if !self.document_id_resolver.depends_on_graphql_path() {
return true;
}
let is_root_endpoint = graphql_endpoint.trim_end_matches('/').is_empty();
!is_root_endpoint
}
pub fn require_id(&self, request: &HttpRequest) -> Result<bool, PipelineError> {
self.require_id
.resolve_with_hints(|hints| {
hints.context_builder(|root| {
root.insert_object("request", |req| {
req.insert_lazy("method", || request.method().as_str().into())
.insert_lazy("headers", || {
ntex_header_map_to_vrl_value(request.headers())
})
.insert_lazy("url", || request.uri().to_vrl_value());
});
})
})
.map_err(PipelineError::PersistedDocumentIdExpressionEvaluationError)
}
}