use std::collections::{BTreeMap, HashMap};
use fraiseql_core::{
runtime::{CountSelection, EmbedSelection},
schema::RestConfig,
};
use super::params::{EmbeddedSpec, SelectEntry};
#[cfg(test)]
mod tests;
#[derive(Debug, Default, PartialEq, Eq)]
pub(super) struct SubSelect {
pub fields: Vec<String>,
pub embeds: Vec<EmbeddedSpec>,
pub counts: Vec<String>,
}
impl SubSelect {
pub(super) fn split(entries: &[SelectEntry]) -> Self {
let mut out = Self::default();
for entry in entries {
match entry {
SelectEntry::Field(name) => out.fields.push(name.clone()),
SelectEntry::Embedded(spec) => out.embeds.push(spec.clone()),
SelectEntry::Count(name) => out.counts.push(name.clone()),
}
}
out
}
}
#[must_use]
pub fn level_path(prefix: &str, relationship: &str) -> String {
if prefix.is_empty() {
relationship.to_string()
} else {
format!("{prefix}.{relationship}")
}
}
#[must_use]
pub fn embedded_level_paths(embeddings: &[EmbeddedSpec]) -> Vec<String> {
fn walk(prefix: &str, embeddings: &[EmbeddedSpec], out: &mut Vec<String>) {
for spec in embeddings {
let path = level_path(prefix, &spec.relationship);
walk(&path, &SubSelect::split(&spec.fields).embeds, out);
out.push(path);
}
}
let mut out = Vec::new();
walk("", embeddings, &mut out);
out
}
#[must_use]
pub fn default_embed_page(config: &RestConfig) -> u32 {
u32::try_from(config.default_embed_page_size.min(config.max_page_size)).unwrap_or(u32::MAX)
}
#[must_use]
pub fn count_output_key(relationship: &str) -> String {
format!("{relationship}_count")
}
#[must_use]
#[allow(clippy::implicit_hasher)] pub fn selections(
embeddings: &[EmbeddedSpec],
counts: &[String],
filters: &HashMap<String, serde_json::Value>,
pages: &BTreeMap<String, u32>,
default_page: u32,
) -> (Vec<EmbedSelection>, Vec<CountSelection>) {
selections_at("", embeddings, counts, filters, pages, default_page)
}
fn selections_at(
prefix: &str,
embeddings: &[EmbeddedSpec],
counts: &[String],
filters: &HashMap<String, serde_json::Value>,
pages: &BTreeMap<String, u32>,
default_page: u32,
) -> (Vec<EmbedSelection>, Vec<CountSelection>) {
let no_filters = HashMap::new();
let embeds = embeddings
.iter()
.map(|spec| {
let SubSelect {
fields,
embeds: nested,
counts: nested_counts,
} = SubSelect::split(&spec.fields);
let path = level_path(prefix, &spec.relationship);
let (embeds, counts) =
selections_at(&path, &nested, &nested_counts, &no_filters, pages, default_page);
EmbedSelection {
relationship: spec.relationship.clone(),
output_key: spec.rename.clone().unwrap_or_else(|| spec.relationship.clone()),
fields,
filter: filters.get(&spec.relationship).cloned(),
limit: Some(pages.get(&path).copied().unwrap_or(default_page)),
embeds,
counts,
}
})
.collect();
let counts = counts
.iter()
.map(|relationship| CountSelection {
relationship: relationship.clone(),
output_key: count_output_key(relationship),
filter: filters.get(relationship).cloned(),
})
.collect();
(embeds, counts)
}