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