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::write_links::attach_write_links;
27use super::{ensure_context, step_id_placeholder, ERR_NO_WRITE};
28
29pub fn plan_mutation(
34 schema_name: &str,
35 registry: &Registry,
36 ctx: Option<&Context>,
37 data: &Value,
38 now: i64,
39 new_ids: &[String],
40) -> Result<Value, String> {
41 ensure_context(registry, ctx)?;
42 let mut out = PlanOut::default();
43 let mut ids = IdCursor::new(new_ids);
44 plan_mutation_node(schema_name, registry, ctx, data, now, &mut ids, &mut out)?;
45 let plan = json!({ "steps": out.steps, "degraded": out.degraded });
46
47 let models: Vec<String> = plan
49 .get("steps")
50 .and_then(|s| s.as_array())
51 .map(|steps| {
52 let mut v: Vec<String> = Vec::new();
53 for s in steps {
54 if let Some(m) = s.get("model").and_then(|x| x.as_str()) {
55 if !v.iter().any(|x| x == m) {
56 v.push(m.to_string());
57 }
58 }
59 }
60 v
61 })
62 .unwrap_or_default();
63 let names: Vec<&str> = models.iter().map(String::as_str).collect();
64 attach_write_links(plan, registry, &names, registry.write_link_policy())
65}
66
67#[derive(Default)]
69struct PlanOut {
70 steps: Vec<Value>,
71 degraded: Vec<Value>,
72}
73
74fn plan_mutation_node(
76 schema_name: &str,
77 registry: &Registry,
78 ctx: Option<&Context>,
79 data: &Value,
80 now: i64,
81 ids: &mut IdCursor,
82 out: &mut PlanOut,
83) -> Result<(), String> {
84 let schema = registry.get(schema_name)?;
85 if !can_write_schema(registry.role_rules(), schema, ctx) {
86 return Err(ERR_NO_WRITE.to_string());
87 }
88 ensure_write(registry, schema, ctx, WriteAction::Insert)?;
90 let Some(obj) = data.as_object() else {
91 return Err("mutation 数据必须是对象".to_string());
92 };
93
94 let mut field_data = Map::new();
96 let mut relation_data: Vec<(&String, &Value)> = Vec::new();
97 for (key, val) in obj {
98 if let Some(rel_def) = schema.relations.get(key) {
99 let allowed = match ctx {
101 None => true,
102 Some(c) => match &rel_def.read {
103 Some(rl) => evaluate(
104 registry.role_rules(),
105 Some(c),
106 Some(rl),
107 Doc::Missing,
108 schema.owner_key(),
109 ),
110 None => true,
111 },
112 };
113 if allowed {
114 relation_data.push((key, val));
115 } else {
116 out.degraded.push(json!({
119 "code": "relationSkipped",
120 "layer": "mutation",
121 "message": format!(
122 "模型 {schema_name} 的关系 \"{key}\" 不可读(read 权限不通过),其写入数据已跳过"
123 ),
124 "hint": "授予该关系 read 权限;如需该情形显式报错,请在应用层先校验权限",
125 }));
126 }
127 } else {
128 field_data.insert(key.clone(), val.clone());
129 }
130 }
131
132 let filtered_field = match ctx {
133 Some(_) => filter_writable_data_overlay(registry, schema, ctx, &Value::Object(field_data)),
135 None => Value::Object(field_data),
136 };
137
138 let step_idx = out.steps.len();
140 let parent_ph = step_id_placeholder(step_idx);
141 let or_conditions = build_upsert_conditions(schema, &filtered_field);
142 let command = if or_conditions.is_empty() {
143 let new_id = if needs_new_id(schema, &filtered_field) {
145 ids.next()?
146 } else {
147 ""
148 };
149 let doc = build_insert_doc(registry, schema, ctx, &filtered_field, now, new_id)?;
150 cmd_insert_one(schema, &Value::Object(doc))
151 } else {
152 let new_id = if needs_new_id(schema, &filtered_field) {
154 ids.next()?
155 } else {
156 ""
157 };
158 let update_doc = build_upsert_update(schema, &filtered_field, new_id, now);
159 let fu_options = json!({ "upsert": true, "returnDocument": "after" });
160 cmd_find_one_and_update(
161 schema,
162 &json!({ "$or": or_conditions }),
163 &update_doc,
164 &fu_options,
165 )
166 };
167 out.steps
168 .push(json!({ "model": schema_name, "command": command }));
169
170 for (rel_name, rel_val) in relation_data {
172 if rel_val.is_null() {
173 continue;
174 }
175 let rel_def = schema
177 .relations
178 .get(rel_name)
179 .ok_or_else(|| format!("模型 {} 的数据含未定义关系字段 {}", schema_name, rel_name))?;
180 let rel_schema = registry.get(&rel_def.model)?;
181
182 if rel_def.rel_type == "one" {
183 if !can_write_schema(registry.role_rules(), rel_schema, ctx) {
187 return Err(ERR_NO_WRITE.to_string());
188 }
189 ensure_write(registry, rel_schema, ctx, WriteAction::Insert)?;
191 let mut child = object_of(rel_val);
192 child.insert(rel_def.foreign_field.clone(), json!(parent_ph));
193 let child_val = match ctx {
195 Some(_) => {
197 filter_writable_data_overlay(registry, rel_schema, ctx, &Value::Object(child))
198 }
199 None => Value::Object(child),
200 };
201 let new_id = if needs_new_id(rel_schema, &child_val) {
203 ids.next()?
204 } else {
205 ""
206 };
207 let update_doc = upsert_one_update(rel_schema, &child_val, new_id, now);
208 let filter = json!({ rel_def.foreign_field.clone(): parent_ph });
209 let fu_options = json!({ "upsert": true, "returnDocument": "after" });
210 let cmd = cmd_find_one_and_update(rel_schema, &filter, &update_doc, &fu_options);
211 out.steps
212 .push(json!({ "model": rel_def.model, "command": cmd }));
213 } else if rel_def.rel_type == "many" {
214 let arr: Vec<Value> = match rel_val {
215 Value::Array(a) => a.clone(),
216 _ => vec![rel_val.clone()],
217 };
218 for child in arr {
219 if child.is_null() {
220 continue;
221 }
222 let mut c = object_of(&child);
223 c.insert(rel_def.foreign_field.clone(), json!(parent_ph));
224 plan_mutation_node(
225 &rel_def.model,
226 registry,
227 ctx,
228 &Value::Object(c),
229 now,
230 ids,
231 out,
232 )?;
233 }
234 } else {
235 return Err(format!(
238 "关系 \"{rel_name}\" 的 rel_type \"{}\" 不被支持(仅支持 one / many;D2:绝不静默跳过)",
239 rel_def.rel_type
240 ));
241 }
242 }
243
244 Ok(())
245}