pub mod executor;
#[cfg(test)]
mod tests;
use std::{collections::HashMap, sync::Arc};
use executor::{EmbedCtx, count_related, embed_into_rows, embed_into_single};
use fraiseql_core::{
db::traits::DatabaseAdapter,
schema::{CompiledSchema, RestConfig},
security::SecurityContext,
};
use super::{
handler::RestError,
params::{EmbeddedSpec, SelectEntry},
};
pub struct EmbeddingRequest<'a, A: DatabaseAdapter> {
pub executor: &'a Arc<fraiseql_core::runtime::Executor<A>>,
pub schema: &'a CompiledSchema,
pub config: &'a RestConfig,
pub parent_type_name: &'a str,
pub security_context: Option<&'a SecurityContext>,
}
#[allow(clippy::implicit_hasher)] pub async fn execute_embeddings<A: DatabaseAdapter>(
req: &EmbeddingRequest<'_, A>,
parent_data: &mut serde_json::Value,
embeddings: &[EmbeddedSpec],
embedding_filters: &HashMap<String, serde_json::Value>,
) -> Result<(), RestError> {
if embeddings.is_empty() {
return Ok(());
}
let parent_type = req.schema.find_type(req.parent_type_name).ok_or_else(|| {
RestError::internal(format!("Parent type not found: {}", req.parent_type_name))
})?;
let ctx = EmbedCtx {
executor: req.executor,
schema: req.schema,
config: req.config,
security_context: req.security_context,
};
for spec in embeddings {
let rel = parent_type
.relationships
.iter()
.find(|r| r.name == spec.relationship)
.ok_or_else(|| {
RestError::bad_request(format!(
"Type '{}' has no relationship '{}'",
req.parent_type_name, spec.relationship
))
})?;
let embedded_filter = embedding_filters.get(&spec.relationship);
let output_name = spec.rename.as_deref().unwrap_or(&spec.relationship);
let sub_field_names: Vec<String> = spec
.fields
.iter()
.filter_map(|e| match e {
SelectEntry::Field(name) => Some(name.clone()),
_ => None,
})
.collect();
match parent_data {
serde_json::Value::Array(rows) => {
embed_into_rows(&ctx, rel, output_name, &sub_field_names, embedded_filter, rows)
.await?;
},
serde_json::Value::Object(_) => {
embed_into_single(
&ctx,
rel,
output_name,
&sub_field_names,
embedded_filter,
parent_data,
)
.await?;
},
_ => {
},
}
}
Ok(())
}
pub async fn execute_embedding_counts<A: DatabaseAdapter>(
req: &EmbeddingRequest<'_, A>,
parent_data: &mut serde_json::Value,
count_fields: &[String],
) -> Result<(), RestError> {
if count_fields.is_empty() {
return Ok(());
}
let parent_type = req.schema.find_type(req.parent_type_name).ok_or_else(|| {
RestError::internal(format!("Parent type not found: {}", req.parent_type_name))
})?;
for count_rel_name in count_fields {
let rel = parent_type
.relationships
.iter()
.find(|r| r.name == *count_rel_name)
.ok_or_else(|| {
RestError::bad_request(format!(
"Type '{}' has no relationship '{count_rel_name}'",
req.parent_type_name
))
})?;
let count_key = format!("{count_rel_name}_count");
match parent_data {
serde_json::Value::Array(rows) => {
for row in rows.iter_mut() {
let count =
count_related(req.executor, req.schema, rel, row, req.security_context)
.await?;
if let Some(obj) = row.as_object_mut() {
obj.insert(count_key.clone(), serde_json::json!(count));
}
}
},
serde_json::Value::Object(_) => {
let count =
count_related(req.executor, req.schema, rel, parent_data, req.security_context)
.await?;
if let Some(obj) = parent_data.as_object_mut() {
obj.insert(count_key, serde_json::json!(count));
}
},
_ => {},
}
}
Ok(())
}