use std::collections::HashMap;
use serde_json::{Map, Value};
use crate::pipeline::{parse, tokenize, RelAst};
use crate::schema::Schema;
use super::cache::ensure_cache;
#[derive(Debug, Clone, PartialEq)]
pub struct RelDep {
pub name: String,
pub fields: Vec<String>,
}
impl RelDep {
pub fn to_value(&self) -> Value {
Value::Object(Map::from_iter([
("name".to_string(), Value::String(self.name.clone())),
(
"fields".to_string(),
Value::Array(self.fields.iter().cloned().map(Value::String).collect()),
),
]))
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Injected {
All,
Fields(Vec<String>),
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct InjectInfo {
pub relations: Vec<(String, Injected)>,
pub fields: Vec<String>,
}
impl InjectInfo {
pub fn is_empty(&self) -> bool {
self.relations.is_empty() && self.fields.is_empty()
}
pub fn to_value(&self) -> Value {
let mut m = Map::new();
for (name, inj) in &self.relations {
let v = match inj {
Injected::All => Value::String("__all__".to_string()),
Injected::Fields(f) => Value::Array(f.iter().cloned().map(Value::String).collect()),
};
m.insert(name.clone(), v);
}
let mut root = Map::new();
if let Some(rm) = Value::Object(m).as_object() {
if !rm.is_empty() {
root.insert("relations".to_string(), Value::Object(rm.clone()));
}
}
if !self.fields.is_empty() {
root.insert(
"fields".to_string(),
Value::Array(self.fields.iter().cloned().map(Value::String).collect()),
);
}
Value::Object(Map::from_iter([(
"inject".to_string(),
Value::Object(root),
)]))
}
pub fn from_value(v: &Value) -> InjectInfo {
let mut relations = Vec::new();
let mut fields = Vec::new();
if let Some(Value::Object(m)) = v.get("inject").or(Some(v)) {
if let Some(Value::Object(rm)) = m.get("relations") {
for (name, inj) in rm {
let parsed = match inj {
Value::String(s) if s == "__all__" => Injected::All,
Value::Array(arr) => Injected::Fields(
arr.iter()
.map(|f| f.as_str().unwrap_or_default().to_string())
.collect(),
),
_ => continue,
};
relations.push((name.clone(), parsed));
}
}
if let Some(Value::Array(fa)) = m.get("fields") {
fields = fa
.iter()
.filter_map(|f| f.as_str().map(String::from))
.collect();
}
}
InjectInfo { relations, fields }
}
}
fn dep_slot<'a>(deps: &'a mut Vec<RelDep>, name: &str) -> &'a mut RelDep {
let idx = match deps.iter().position(|d| d.name == name) {
Some(i) => i,
None => {
deps.push(RelDep {
name: name.to_string(),
fields: Vec::new(),
});
deps.len() - 1
}
};
&mut deps[idx]
}
pub fn collect_field_deps(fields: &mut Vec<String>, schema: &Schema) -> Vec<String> {
let cache = ensure_cache(schema);
let mut added = Vec::new();
for entry in &cache.async_fn_list {
for dep in &entry.depends {
let trimmed = dep.trim();
if trimmed.is_empty() || trimmed == "_id" {
continue;
}
if trimmed.contains('{') || schema.relations.contains_key(trimmed) {
continue;
}
if !fields.contains(&trimmed.to_string()) {
fields.push(trimmed.to_string());
added.push(trimmed.to_string());
}
}
}
added
}
pub fn collect_rel_deps(schema: &Schema) -> Result<Vec<RelDep>, String> {
let cache = ensure_cache(schema);
let mut deps: Vec<RelDep> = Vec::new();
for entry in &cache.async_fn_list {
for dep in &entry.depends {
let trimmed = dep.trim();
if trimmed.is_empty() || trimmed == "_id" {
continue;
}
if trimmed.contains('{') {
let parsed = parse(&tokenize(trimmed))?;
if !schema.relations.contains_key(&parsed.model) {
continue;
}
let slot = dep_slot(&mut deps, &parsed.model);
for f in &parsed.fields {
if f != "_id" && !slot.fields.contains(f) {
slot.fields.push(f.clone());
}
}
} else {
if !schema.relations.contains_key(trimmed) {
continue;
}
dep_slot(&mut deps, trimmed);
}
}
}
Ok(deps)
}
pub fn inject_into_ast(relations: &mut Vec<(String, RelAst)>, rel_deps: &[RelDep]) -> InjectInfo {
let mut info = InjectInfo::default();
for dep in rel_deps {
match relations.iter().position(|(n, _)| n == &dep.name) {
Some(idx) => {
let (_, rel_ast) = &mut relations[idx];
let mut added = Vec::new();
for f in &dep.fields {
if !rel_ast.fields.contains(f) {
rel_ast.fields.push(f.clone());
added.push(f.clone());
}
}
if !added.is_empty() {
info.relations
.push((dep.name.clone(), Injected::Fields(added)));
}
}
None => {
relations.push((
dep.name.clone(),
RelAst {
fields: dep.fields.clone(),
relations: Vec::new(),
params: HashMap::new(),
},
));
info.relations.push((dep.name.clone(), Injected::All));
}
}
}
info
}
pub fn merge_depends_into_ast(
relations: &mut Vec<(String, RelAst)>,
schema: &Schema,
) -> Result<InjectInfo, String> {
if ensure_cache(schema).async_fn_list.is_empty() {
return Ok(InjectInfo::default());
}
let rel_deps = collect_rel_deps(schema)?;
if rel_deps.is_empty() {
return Ok(InjectInfo::default());
}
Ok(inject_into_ast(relations, &rel_deps))
}
pub fn strip_dep_injected(items: &mut [Value], info: &InjectInfo) {
if info.is_empty() {
return;
}
for item in items.iter_mut() {
let Some(o) = item.as_object_mut() else {
continue;
};
if !info.fields.is_empty() {
for f in &info.fields {
o.remove(f);
}
}
for (rel_name, injected) in &info.relations {
match injected {
Injected::All => {
o.remove(rel_name);
}
Injected::Fields(fields) => match o.get_mut(rel_name) {
Some(Value::Array(arr)) => {
for sub in arr.iter_mut() {
if let Some(so) = sub.as_object_mut() {
for f in fields {
so.remove(f);
}
}
}
}
Some(Value::Object(so)) => {
for f in fields {
so.remove(f);
}
}
_ => {}
},
}
}
}
}