Skip to main content

rust_store_core/command/
mutation.rs

1//! mutation 规划器(对应 JS `_mutationOne` + `_applyRelations`)
2//!
3//! 把一次 mutation 展开为**有序步骤序列**:每个步骤是一条命令(insertOne 或
4//! findOneAndUpdate),父子步骤间的依赖(子文档外键 = 父文档 `_id`)用占位符表达:
5//!
6//! - `{{step.<N>._id}}`(见 [`step_id_placeholder`]):第 N 步执行结果文档的 `_id`,
7//!   Host 在该步执行完成后回填到后续命令中
8//! - 需要新生成 `_id` 的位置由 Host 供给的 `new_ids` 按序填充(与 JS `_generateId`
9//!   的调用顺序一致;core 无随机源)
10//!
11//! 根步骤永远是 `steps[0]`;Host 依次执行后,用根步骤结果文档调
12//! `apply_defaults_and_computes` 得到最终返回值。
13
14use 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
29/// 规划一条 mutation(对应 JS `mutation` 的单条分支 `_mutationOne`)。
30///
31/// 数组形态与空数组短路(`[]` / `null`)由 Host 处理:逐条调用本函数即可;
32/// `new_ids` 由多条 mutation 共享一个游标语义(每次调用传剩余数组)。
33pub 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    // 写链路:各 step 的 model 去重(同一 mutation 可写多个 schema)
48    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/// 规划期的可变累积器:有序步骤序列 + 降级声明(§11.4 静默点收口)。
68#[derive(Default)]
69struct PlanOut {
70    steps: Vec<Value>,
71    degraded: Vec<Value>,
72}
73
74/// 展开一个 mutation 节点:根写入步骤 + 各 relation 子步骤(`many` 递归)
75fn 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    // RBAC 表级写判定(mutation 步骤按 Insert 判,deny-wins;子模型递归同样受限)
89    ensure_write(registry, schema, ctx, WriteAction::Insert)?;
90    let Some(obj) = data.as_object() else {
91        return Err("mutation 数据必须是对象".to_string());
92    };
93
94    // ── 1. 按 schema.relations 拆分 fieldData + relationData(保持遍历顺序) ──
95    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            // JS:relDef.read && !evaluate(ctx, relDef.read) → 跳过该关系
100            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                // §11.4 静默点收口(D2:绝不静默):关系 read 不通过时写入数据被跳过,
117                // 但必须显式声明 —— 记入 `degraded:relationSkipped`,Host 经反馈通道告警。
118                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        // RBAC 感知版:静态 writable ∩ RBAC writeFields(无策略时直通静态过滤)
134        Some(_) => filter_writable_data_overlay(registry, schema, ctx, &Value::Object(field_data)),
135        None => Value::Object(field_data),
136    };
137
138    // ── 2/3. 写入 parent(upsert 或 insert 路径在规划期即可确定) ──
139    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        // Insert 路径(复用 JS `insert` 逻辑;仅需要新 `_id` 时才消耗游标)
144        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        // Upsert 路径(JS `_buildUpsertUpdate` 仅在无 _id 时才 `_generateId`)
153        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    // ── 4. 处理 relation 子文档 ──
171    for (rel_name, rel_val) in relation_data {
172        if rel_val.is_null() {
173            continue;
174        }
175        // checked 索引:数据键未在 relations 定义时显式报错,绝不 panic
176        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            // type:'one' 子文档强制按 foreignKey upsert(写入形态对齐 JS `_upsertOne`;
184            // 权限门禁与字段过滤与 many 路径对齐:can_write_schema + filter_writable_data,
185            // 拥有父模型写权限 ≠ 拥有子模型写权限)
186            if !can_write_schema(registry.role_rules(), rel_schema, ctx) {
187                return Err(ERR_NO_WRITE.to_string());
188            }
189            // RBAC 表级写判定(one 子步骤同样叠加,deny-wins)
190            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            // 注入外键后再过滤(与 many 路径 plan_mutation_node 的过滤时机一致)
194            let child_val = match ctx {
195                // RBAC 感知版:静态 writable ∩ RBAC writeFields
196                Some(_) => {
197                    filter_writable_data_overlay(registry, rel_schema, ctx, &Value::Object(child))
198                }
199                None => Value::Object(child),
200            };
201            // JS `_upsertOne`:仅无 _id 时才 `_generateId`
202            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            // §11.4 静默点收口(D2:绝不静默):未知 rel_type 无法规划写入步骤,
236            // 显式报错(原行为为静默跳过该关系)。
237            return Err(format!(
238                "关系 \"{rel_name}\" 的 rel_type \"{}\" 不被支持(仅支持 one / many;D2:绝不静默跳过)",
239                rel_def.rel_type
240            ));
241        }
242    }
243
244    Ok(())
245}