use std::collections::HashSet;
use serde_json::Value;
use crate::permission::{
evaluate, get_readable_computes, get_readable_fields, get_readable_relations, Context, Doc,
RoleRules,
};
use crate::pipeline::{flatten_object_fields_impl, RelAst};
use crate::schema::{Registry, Schema};
use super::super::cache::{ensure_cache, Cache};
use super::super::defaults::{is_object_field, raw_field_default};
use super::super::registry::FnRegistry;
fn collect_needed(fields: &[String], cache: &Cache) -> Vec<String> {
let mut needed: Vec<String> = fields.to_vec();
let mut seen: HashSet<String> = needed.iter().cloned().collect();
for entry in &cache.fn_list {
for dep in &entry.depends {
if seen.insert(dep.clone()) {
needed.push(dep.clone());
}
}
}
needed
}
fn collect_dot(fields: &[String]) -> Vec<(String, Vec<String>)> {
let mut dot_fields: Vec<(String, Vec<String>)> = Vec::new();
for f in fields {
let Some(idx) = f.find('.') else { continue };
let root = &f[..idx];
let sub = &f[idx + 1..];
match dot_fields.iter_mut().find(|(k, _)| k == root) {
Some((_, subs)) => subs.push(sub.to_string()),
None => dot_fields.push((root.to_string(), vec![sub.to_string()])),
}
}
dot_fields
}
fn fill_defaults(
doc: &mut Value,
cache: &Cache,
needed: &[String],
dot_fields: &[(String, Vec<String>)],
) {
let Some(o) = doc.as_object_mut() else { return };
let mut fill = |key: &str| {
let absent = o.get(key).map(|v| v.is_null()).unwrap_or(true);
if absent {
if let Some(d) = cache.field_defaults.get(key) {
o.insert(key.to_string(), d.clone());
}
}
};
for key in needed {
fill(key);
}
for (root, _) in dot_fields {
fill(root);
}
}
fn fill_dot_nested(doc: &mut Value, schema: &Schema, root: &str, sub_path: &str) {
let Some(field) = schema.fields.get(root) else {
return;
};
if !is_object_field(field) {
return;
}
let Some(sub_def) = field
.fields
.as_ref()
.and_then(|v| v.as_object())
.and_then(|m| m.get(sub_path))
.cloned()
else {
return;
};
let Some(root_obj) = doc.as_object_mut().and_then(|o| o.get_mut(root)) else {
return;
};
let Some(ro) = root_obj.as_object_mut() else {
return;
};
let absent = ro.get(sub_path).map(|v| v.is_null()).unwrap_or(true);
if absent {
if let Some(d) = raw_field_default(&sub_def) {
ro.insert(sub_path.to_string(), d);
}
}
}
fn fill_nested_objects(doc: &mut Value, schema: &Schema, needed: &[String]) {
for key in needed {
if let Some(idx) = key.find('.') {
fill_dot_nested(doc, schema, &key[..idx], &key[idx + 1..]);
}
}
}
pub fn run_computes(
doc: &mut Value,
cache: &Cache,
fn_registry: Option<&dyn FnRegistry>,
) -> Result<(), String> {
if cache.fn_list.is_empty() {
return Ok(());
}
for entry in &cache.fn_list {
let v = match fn_registry {
Some(r) => r.call_sync(&entry.fn_ref, doc)?,
None => return Err(format!("计算列 {} 未注册同步实现", entry.key)),
};
if let Some(o) = doc.as_object_mut() {
o.insert(entry.key.clone(), v);
}
}
for entry in &cache.fn_list {
let Some(o) = doc.as_object_mut() else {
continue;
};
let absent = o.get(&entry.key).map(|v| v.is_null()).unwrap_or(true);
if absent {
if let Some(d) = cache.compute_defaults.get(&entry.key) {
if !d.is_null() {
o.insert(entry.key.clone(), d.clone());
}
}
}
}
Ok(())
}
fn descend_relations(
doc: &mut Value,
relations: &mut [(String, RelAst)],
schema: &Schema,
ctx: Option<&Context>,
registry: &Registry,
fn_registry: Option<&dyn FnRegistry>,
) -> Result<(), String> {
let readable = if ctx.is_some() {
get_readable_relations(registry.role_rules(), schema, ctx)
} else {
None
};
for (rel_name, rel_ast) in relations.iter_mut() {
if let Some(set) = &readable {
if !set.contains(rel_name) {
continue;
}
}
let Some(rel_def) = schema.relations.get(&*rel_name) else {
continue;
};
let rel_schema = registry.get(&rel_def.model)?;
let is_one = rel_def.rel_type == "one";
let Some(o) = doc.as_object_mut() else {
continue;
};
let stored = o.get(rel_name).cloned();
let Some(mut rel_val) = stored.filter(|v| !v.is_null()) else {
o.insert(
rel_name.clone(),
if is_one {
Value::Null
} else {
Value::Array(Vec::new())
},
);
continue;
};
let keep_id = rel_ast.fields.iter().any(|f| f == "_id");
match &mut rel_val {
Value::Array(arr) => {
for item in arr.iter_mut() {
process_node(
item,
&mut rel_ast.fields,
&mut rel_ast.relations,
rel_schema,
ctx,
registry,
fn_registry,
)?;
if !keep_id {
if let Value::Object(so) = item {
so.remove("_id");
}
}
}
}
Value::Object(_) => {
process_node(
&mut rel_val,
&mut rel_ast.fields,
&mut rel_ast.relations,
rel_schema,
ctx,
registry,
fn_registry,
)?;
if !keep_id {
if let Some(so) = rel_val.as_object_mut() {
so.remove("_id");
}
}
}
_ => {}
}
o.insert(rel_name.clone(), rel_val);
}
Ok(())
}
fn compute_keep(
fields: &[String],
relations: &[(String, RelAst)],
dot_fields: &[(String, Vec<String>)],
) -> HashSet<String> {
let mut keep: HashSet<String> = fields.iter().cloned().collect();
for (root, _) in dot_fields {
keep.insert(root.clone());
}
for (rel_name, _) in relations {
keep.insert(rel_name.clone());
}
keep.insert("_id".to_string());
keep
}
fn apply_readable_prune(
fields: &[String],
relations: &[(String, RelAst)],
schema: &Schema,
ctx: Option<&Context>,
registry: &Registry,
keep: &mut HashSet<String>,
) {
let readable_fields = crate::rbac::overlay_readable_fields(
registry,
&schema.name,
ctx,
get_readable_fields(registry.role_rules(), schema, ctx),
);
let readable_computes = crate::rbac::overlay_readable_fields(
registry,
&schema.name,
ctx,
get_readable_computes(registry.role_rules(), schema, ctx),
);
let readable_relations = get_readable_relations(registry.role_rules(), schema, ctx);
for key in fields {
if key == "_id" {
continue;
}
let unreadable_field = schema.fields.contains_key(key)
&& readable_fields
.as_ref()
.map(|s| !s.contains(key))
.unwrap_or(false);
let unreadable_compute = schema.compute(key).is_some()
&& readable_computes
.as_ref()
.map(|s| !s.contains(key))
.unwrap_or(false);
if unreadable_field || unreadable_compute {
keep.remove(key);
}
}
if let Some(rels) = readable_relations {
for (rel_name, _) in relations {
if !rels.contains(rel_name) {
keep.remove(rel_name);
}
}
}
}
fn apply_owner_read_prune(
doc: &Value,
fields: &[String],
relations: &[(String, RelAst)],
rules: &RoleRules,
schema: &Schema,
ctx: Option<&Context>,
keep: &mut HashSet<String>,
) {
for key in fields {
if key == "_id" || !keep.contains(key) {
continue;
}
if let Some(field) = schema.fields.get(key) {
if let Some(rl) = &field.read {
if !evaluate(rules, ctx, Some(rl), Doc::Doc(doc)) {
keep.remove(key);
}
}
}
}
for (key, comp) in &schema.computes {
if !keep.contains(key) {
continue;
}
if let Some(rl) = &comp.read {
if !evaluate(rules, ctx, Some(rl), Doc::Doc(doc)) {
keep.remove(key);
}
}
}
for (rel_name, _) in relations {
if !keep.contains(rel_name) {
continue;
}
if let Some(rel) = schema.relations.get(rel_name) {
if let Some(rl) = &rel.read {
if !evaluate(rules, ctx, Some(rl), Doc::Doc(doc)) {
keep.remove(rel_name);
}
}
}
}
}
fn prune_dot_subfields(doc: &mut Value, dot_fields: &[(String, Vec<String>)]) {
for (root, subs) in dot_fields {
let Some(obj) = doc.as_object_mut().and_then(|o| o.get_mut(root)) else {
continue;
};
if let Some(m) = obj.as_object_mut() {
m.retain(|k, _| subs.contains(k));
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn process_node(
doc: &mut Value,
fields: &mut Vec<String>,
relations: &mut Vec<(String, RelAst)>,
schema: &Schema,
ctx: Option<&Context>,
registry: &Registry,
fn_registry: Option<&dyn FnRegistry>,
) -> Result<(), String> {
if doc.is_null() {
return Ok(());
}
let cache = ensure_cache(schema);
flatten_object_fields_impl(fields, relations, schema);
let needed = collect_needed(fields, &cache);
let dot_fields = collect_dot(fields);
fill_defaults(doc, &cache, &needed, &dot_fields);
fill_nested_objects(doc, schema, &needed);
run_computes(doc, &cache, fn_registry)?;
descend_relations(doc, relations, schema, ctx, registry, fn_registry)?;
let mut keep = compute_keep(fields, relations, &dot_fields);
if ctx.is_some() {
apply_readable_prune(fields, relations, schema, ctx, registry, &mut keep);
apply_owner_read_prune(
doc,
fields,
relations,
registry.role_rules(),
schema,
ctx,
&mut keep,
);
}
if let Some(o) = doc.as_object_mut() {
o.retain(|k, _| keep.contains(k));
}
prune_dot_subfields(doc, &dot_fields);
Ok(())
}