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(registry.role_rules(), Some(c), Some(rl), Doc::Missing),
104 None => true,
105 },
106 };
107 if allowed {
108 relation_data.push((key, val));
109 } else {
110 out.degraded.push(json!({
113 "code": "relationSkipped",
114 "layer": "mutation",
115 "message": format!(
116 "模型 {schema_name} 的关系 \"{key}\" 不可读(read 权限不通过),其写入数据已跳过"
117 ),
118 "hint": "授予该关系 read 权限;如需该情形显式报错,请在应用层先校验权限",
119 }));
120 }
121 } else {
122 field_data.insert(key.clone(), val.clone());
123 }
124 }
125
126 let filtered_field = match ctx {
127 Some(_) => filter_writable_data_overlay(registry, schema, ctx, &Value::Object(field_data)),
129 None => Value::Object(field_data),
130 };
131
132 let step_idx = out.steps.len();
134 let parent_ph = step_id_placeholder(step_idx);
135 let or_conditions = build_upsert_conditions(schema, &filtered_field);
136 let command = if or_conditions.is_empty() {
137 let new_id = if needs_new_id(schema, &filtered_field) {
139 ids.next()?
140 } else {
141 ""
142 };
143 let doc = build_insert_doc(registry, schema, ctx, &filtered_field, now, new_id)?;
144 cmd_insert_one(schema, &Value::Object(doc))
145 } else {
146 let new_id = if needs_new_id(schema, &filtered_field) {
148 ids.next()?
149 } else {
150 ""
151 };
152 let update_doc = build_upsert_update(schema, &filtered_field, new_id, now);
153 let fu_options = json!({ "upsert": true, "returnDocument": "after" });
154 cmd_find_one_and_update(
155 schema,
156 &json!({ "$or": or_conditions }),
157 &update_doc,
158 &fu_options,
159 )
160 };
161 out.steps
162 .push(json!({ "model": schema_name, "command": command }));
163
164 for (rel_name, rel_val) in relation_data {
166 if rel_val.is_null() {
167 continue;
168 }
169 let rel_def = schema
171 .relations
172 .get(rel_name)
173 .ok_or_else(|| format!("模型 {} 的数据含未定义关系字段 {}", schema_name, rel_name))?;
174 let rel_schema = registry.get(&rel_def.model)?;
175
176 if rel_def.rel_type == "one" {
177 if !can_write_schema(registry.role_rules(), rel_schema, ctx) {
181 return Err(ERR_NO_WRITE.to_string());
182 }
183 ensure_write(registry, rel_schema, ctx, WriteAction::Insert)?;
185 let mut child = object_of(rel_val);
186 child.insert(rel_def.foreign_field.clone(), json!(parent_ph));
187 let child_val = match ctx {
189 Some(_) => {
191 filter_writable_data_overlay(registry, rel_schema, ctx, &Value::Object(child))
192 }
193 None => Value::Object(child),
194 };
195 let new_id = if needs_new_id(rel_schema, &child_val) {
197 ids.next()?
198 } else {
199 ""
200 };
201 let update_doc = upsert_one_update(rel_schema, &child_val, new_id, now);
202 let filter = json!({ rel_def.foreign_field.clone(): parent_ph });
203 let fu_options = json!({ "upsert": true, "returnDocument": "after" });
204 let cmd = cmd_find_one_and_update(rel_schema, &filter, &update_doc, &fu_options);
205 out.steps
206 .push(json!({ "model": rel_def.model, "command": cmd }));
207 } else if rel_def.rel_type == "many" {
208 let arr: Vec<Value> = match rel_val {
209 Value::Array(a) => a.clone(),
210 _ => vec![rel_val.clone()],
211 };
212 for child in arr {
213 if child.is_null() {
214 continue;
215 }
216 let mut c = object_of(&child);
217 c.insert(rel_def.foreign_field.clone(), json!(parent_ph));
218 plan_mutation_node(
219 &rel_def.model,
220 registry,
221 ctx,
222 &Value::Object(c),
223 now,
224 ids,
225 out,
226 )?;
227 }
228 } else {
229 return Err(format!(
232 "关系 \"{rel_name}\" 的 rel_type \"{}\" 不被支持(仅支持 one / many;D2:绝不静默跳过)",
233 rel_def.rel_type
234 ));
235 }
236 }
237
238 Ok(())
239}