use serde_json::{json, Map, Value};
use crate::permission::{can_write_schema, evaluate, filter_writable_data, Context, Doc};
use crate::schema::Registry;
use super::cmd::{cmd_find_one_and_update, cmd_insert_one};
use super::mutate::{
build_upsert_conditions, build_upsert_update, needs_new_id, object_of, upsert_one_update,
IdCursor,
};
use super::write::build_insert_doc;
use super::{ensure_context, step_id_placeholder, ERR_NO_WRITE};
pub fn plan_mutation(
schema_name: &str,
registry: &Registry,
ctx: Option<&Context>,
data: &Value,
now: i64,
new_ids: &[String],
) -> Result<Value, String> {
ensure_context(registry, ctx)?;
let mut out = PlanOut::default();
let mut ids = IdCursor::new(new_ids);
plan_mutation_node(schema_name, registry, ctx, data, now, &mut ids, &mut out)?;
Ok(json!({ "steps": out.steps, "degraded": out.degraded }))
}
#[derive(Default)]
struct PlanOut {
steps: Vec<Value>,
degraded: Vec<Value>,
}
fn plan_mutation_node(
schema_name: &str,
registry: &Registry,
ctx: Option<&Context>,
data: &Value,
now: i64,
ids: &mut IdCursor,
out: &mut PlanOut,
) -> Result<(), String> {
let schema = registry.get(schema_name)?;
if !can_write_schema(schema, ctx) {
return Err(ERR_NO_WRITE.to_string());
}
let Some(obj) = data.as_object() else {
return Err("mutation 数据必须是对象".to_string());
};
let mut field_data = Map::new();
let mut relation_data: Vec<(&String, &Value)> = Vec::new();
for (key, val) in obj {
if let Some(rel_def) = schema.relations.get(key) {
let allowed = match ctx {
None => true,
Some(c) => match &rel_def.read {
Some(rl) => evaluate(Some(c), Some(rl), Doc::Missing),
None => true,
},
};
if allowed {
relation_data.push((key, val));
} else {
out.degraded.push(json!({
"code": "relationSkipped",
"layer": "mutation",
"message": format!(
"模型 {schema_name} 的关系 \"{key}\" 不可读(read 权限不通过),其写入数据已跳过"
),
"hint": "授予该关系 read 权限;如需该情形显式报错,请在应用层先校验权限",
}));
}
} else {
field_data.insert(key.clone(), val.clone());
}
}
let filtered_field = match ctx {
Some(_) => filter_writable_data(schema, ctx, &Value::Object(field_data)),
None => Value::Object(field_data),
};
let step_idx = out.steps.len();
let parent_ph = step_id_placeholder(step_idx);
let or_conditions = build_upsert_conditions(schema, &filtered_field);
let command = if or_conditions.is_empty() {
let new_id = if needs_new_id(schema, &filtered_field) {
ids.next()?
} else {
""
};
let doc = build_insert_doc(schema, ctx, &filtered_field, now, new_id)?;
cmd_insert_one(schema, &Value::Object(doc))
} else {
let new_id = if needs_new_id(schema, &filtered_field) {
ids.next()?
} else {
""
};
let update_doc = build_upsert_update(schema, &filtered_field, new_id, now);
let fu_options = json!({ "upsert": true, "returnDocument": "after" });
cmd_find_one_and_update(
schema,
&json!({ "$or": or_conditions }),
&update_doc,
&fu_options,
)
};
out.steps
.push(json!({ "model": schema_name, "command": command }));
for (rel_name, rel_val) in relation_data {
if rel_val.is_null() {
continue;
}
let rel_def = schema
.relations
.get(rel_name)
.ok_or_else(|| format!("模型 {} 的数据含未定义关系字段 {}", schema_name, rel_name))?;
let rel_schema = registry.get(&rel_def.model)?;
if rel_def.rel_type == "one" {
if !can_write_schema(rel_schema, ctx) {
return Err(ERR_NO_WRITE.to_string());
}
let mut child = object_of(rel_val);
child.insert(rel_def.foreign_field.clone(), json!(parent_ph));
let child_val = match ctx {
Some(_) => filter_writable_data(rel_schema, ctx, &Value::Object(child)),
None => Value::Object(child),
};
let new_id = if needs_new_id(rel_schema, &child_val) {
ids.next()?
} else {
""
};
let update_doc = upsert_one_update(rel_schema, &child_val, new_id, now);
let filter = json!({ rel_def.foreign_field.clone(): parent_ph });
let fu_options = json!({ "upsert": true, "returnDocument": "after" });
let cmd = cmd_find_one_and_update(rel_schema, &filter, &update_doc, &fu_options);
out.steps
.push(json!({ "model": rel_def.model, "command": cmd }));
} else if rel_def.rel_type == "many" {
let arr: Vec<Value> = match rel_val {
Value::Array(a) => a.clone(),
_ => vec![rel_val.clone()],
};
for child in arr {
if child.is_null() {
continue;
}
let mut c = object_of(&child);
c.insert(rel_def.foreign_field.clone(), json!(parent_ph));
plan_mutation_node(
&rel_def.model,
registry,
ctx,
&Value::Object(c),
now,
ids,
out,
)?;
}
} else {
return Err(format!(
"关系 \"{rel_name}\" 的 rel_type \"{}\" 不被支持(仅支持 one / many;D2:绝不静默跳过)",
rel_def.rel_type
));
}
}
Ok(())
}