1use serde_json::{json, Map, Value};
15
16use crate::permission::{can_write_schema, evaluate, Context, Doc};
17use crate::rbac::{ensure_write, filter_writable_data_overlay, WriteAction};
18use crate::schema::Registry;
19
20use super::cmd::{cmd_find_one_and_update, cmd_insert_one};
21use super::mutate::{
22 build_upsert_conditions, build_upsert_update, needs_new_id, object_of, upsert_one_update,
23 IdCursor,
24};
25use super::write::build_insert_doc;
26use super::{ensure_context, step_id_placeholder, ERR_NO_WRITE};
27
28pub fn plan_mutation(
33 schema_name: &str,
34 registry: &Registry,
35 ctx: Option<&Context>,
36 data: &Value,
37 now: i64,
38 new_ids: &[String],
39) -> Result<Value, String> {
40 ensure_context(registry, ctx)?;
41 let mut out = PlanOut::default();
42 let mut ids = IdCursor::new(new_ids);
43 plan_mutation_node(schema_name, registry, ctx, data, now, &mut ids, &mut out)?;
44 Ok(json!({ "steps": out.steps, "degraded": out.degraded }))
45}
46
47#[derive(Default)]
49struct PlanOut {
50 steps: Vec<Value>,
51 degraded: Vec<Value>,
52}
53
54fn plan_mutation_node(
56 schema_name: &str,
57 registry: &Registry,
58 ctx: Option<&Context>,
59 data: &Value,
60 now: i64,
61 ids: &mut IdCursor,
62 out: &mut PlanOut,
63) -> Result<(), String> {
64 let schema = registry.get(schema_name)?;
65 if !can_write_schema(registry.role_rules(), schema, ctx) {
66 return Err(ERR_NO_WRITE.to_string());
67 }
68 ensure_write(registry, schema, ctx, WriteAction::Insert)?;
70 let Some(obj) = data.as_object() else {
71 return Err("mutation 数据必须是对象".to_string());
72 };
73
74 let mut field_data = Map::new();
76 let mut relation_data: Vec<(&String, &Value)> = Vec::new();
77 for (key, val) in obj {
78 if let Some(rel_def) = schema.relations.get(key) {
79 let allowed = match ctx {
81 None => true,
82 Some(c) => match &rel_def.read {
83 Some(rl) => evaluate(registry.role_rules(), Some(c), Some(rl), Doc::Missing),
84 None => true,
85 },
86 };
87 if allowed {
88 relation_data.push((key, val));
89 } else {
90 out.degraded.push(json!({
93 "code": "relationSkipped",
94 "layer": "mutation",
95 "message": format!(
96 "模型 {schema_name} 的关系 \"{key}\" 不可读(read 权限不通过),其写入数据已跳过"
97 ),
98 "hint": "授予该关系 read 权限;如需该情形显式报错,请在应用层先校验权限",
99 }));
100 }
101 } else {
102 field_data.insert(key.clone(), val.clone());
103 }
104 }
105
106 let filtered_field = match ctx {
107 Some(_) => filter_writable_data_overlay(registry, schema, ctx, &Value::Object(field_data)),
109 None => Value::Object(field_data),
110 };
111
112 let step_idx = out.steps.len();
114 let parent_ph = step_id_placeholder(step_idx);
115 let or_conditions = build_upsert_conditions(schema, &filtered_field);
116 let command = if or_conditions.is_empty() {
117 let new_id = if needs_new_id(schema, &filtered_field) {
119 ids.next()?
120 } else {
121 ""
122 };
123 let doc = build_insert_doc(registry, schema, ctx, &filtered_field, now, new_id)?;
124 cmd_insert_one(schema, &Value::Object(doc))
125 } else {
126 let new_id = if needs_new_id(schema, &filtered_field) {
128 ids.next()?
129 } else {
130 ""
131 };
132 let update_doc = build_upsert_update(schema, &filtered_field, new_id, now);
133 let fu_options = json!({ "upsert": true, "returnDocument": "after" });
134 cmd_find_one_and_update(
135 schema,
136 &json!({ "$or": or_conditions }),
137 &update_doc,
138 &fu_options,
139 )
140 };
141 out.steps
142 .push(json!({ "model": schema_name, "command": command }));
143
144 for (rel_name, rel_val) in relation_data {
146 if rel_val.is_null() {
147 continue;
148 }
149 let rel_def = schema
151 .relations
152 .get(rel_name)
153 .ok_or_else(|| format!("模型 {} 的数据含未定义关系字段 {}", schema_name, rel_name))?;
154 let rel_schema = registry.get(&rel_def.model)?;
155
156 if rel_def.rel_type == "one" {
157 if !can_write_schema(registry.role_rules(), rel_schema, ctx) {
161 return Err(ERR_NO_WRITE.to_string());
162 }
163 ensure_write(registry, rel_schema, ctx, WriteAction::Insert)?;
165 let mut child = object_of(rel_val);
166 child.insert(rel_def.foreign_field.clone(), json!(parent_ph));
167 let child_val = match ctx {
169 Some(_) => {
171 filter_writable_data_overlay(registry, rel_schema, ctx, &Value::Object(child))
172 }
173 None => Value::Object(child),
174 };
175 let new_id = if needs_new_id(rel_schema, &child_val) {
177 ids.next()?
178 } else {
179 ""
180 };
181 let update_doc = upsert_one_update(rel_schema, &child_val, new_id, now);
182 let filter = json!({ rel_def.foreign_field.clone(): parent_ph });
183 let fu_options = json!({ "upsert": true, "returnDocument": "after" });
184 let cmd = cmd_find_one_and_update(rel_schema, &filter, &update_doc, &fu_options);
185 out.steps
186 .push(json!({ "model": rel_def.model, "command": cmd }));
187 } else if rel_def.rel_type == "many" {
188 let arr: Vec<Value> = match rel_val {
189 Value::Array(a) => a.clone(),
190 _ => vec![rel_val.clone()],
191 };
192 for child in arr {
193 if child.is_null() {
194 continue;
195 }
196 let mut c = object_of(&child);
197 c.insert(rel_def.foreign_field.clone(), json!(parent_ph));
198 plan_mutation_node(
199 &rel_def.model,
200 registry,
201 ctx,
202 &Value::Object(c),
203 now,
204 ids,
205 out,
206 )?;
207 }
208 } else {
209 return Err(format!(
212 "关系 \"{rel_name}\" 的 rel_type \"{}\" 不被支持(仅支持 one / many;D2:绝不静默跳过)",
213 rel_def.rel_type
214 ));
215 }
216 }
217
218 Ok(())
219}