use std::{future::Future, pin::Pin, sync::Arc};
use arc_swap::ArcSwap;
use fraiseql_core::{db::traits::DatabaseAdapter, runtime::Executor, security::SecurityContext};
use fraiseql_error::Result;
use fraiseql_functions::host::live::QueryExecutor;
use serde_json::Value;
pub const SOURCE_TENANT_VAR: &str = "__source_tenant";
pub struct SourceQueryExecutor<A: DatabaseAdapter> {
executor: Arc<ArcSwap<Executor<A>>>,
identity: SecurityContext,
}
impl<A: DatabaseAdapter> SourceQueryExecutor<A> {
#[must_use]
pub const fn new(executor: Arc<ArcSwap<Executor<A>>>, identity: SecurityContext) -> Self {
Self { executor, identity }
}
}
fn resolve_identity(base: &SecurityContext, tenant: Option<&str>) -> SecurityContext {
match (base.tenant_id.is_none(), tenant) {
(true, Some(tenant)) => base.clone().with_tenant(tenant),
_ => base.clone(),
}
}
fn split_tenant_override(variables: Option<&Value>) -> (Option<Value>, Option<String>) {
let Some(Value::Object(map)) = variables else {
return (variables.cloned(), None);
};
if !map.contains_key(SOURCE_TENANT_VAR) {
return (Some(Value::Object(map.clone())), None);
}
let mut map = map.clone();
let tenant = match map.remove(SOURCE_TENANT_VAR) {
Some(Value::String(tenant)) if !tenant.trim().is_empty() => Some(tenant),
_ => None,
};
(Some(Value::Object(map)), tenant)
}
impl<A: DatabaseAdapter + 'static> QueryExecutor for SourceQueryExecutor<A> {
fn execute_query(
&self,
query: &str,
variables: Option<&Value>,
) -> Pin<Box<dyn Future<Output = Result<Value>> + Send + '_>> {
let executor = self.executor.load_full();
let (variables, tenant) = split_tenant_override(variables);
let identity = resolve_identity(&self.identity, tenant.as_deref());
let query = query.to_owned();
Box::pin(async move {
executor.execute_with_security(&query, variables.as_ref(), &identity).await
})
}
}
#[cfg(test)]
mod tests;