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(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                // §11.4 静默点收口(D2:绝不静默):关系 read 不通过时写入数据被跳过,
111                // 但必须显式声明 —— 记入 `degraded:relationSkipped`,Host 经反馈通道告警。
112                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        // RBAC 感知版:静态 writable ∩ RBAC writeFields(无策略时直通静态过滤)
128        Some(_) => filter_writable_data_overlay(registry, schema, ctx, &Value::Object(field_data)),
129        None => Value::Object(field_data),
130    };
131
132    // ── 2/3. 写入 parent(upsert 或 insert 路径在规划期即可确定) ──
133    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        // Insert 路径(复用 JS `insert` 逻辑;仅需要新 `_id` 时才消耗游标)
138        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        // Upsert 路径(JS `_buildUpsertUpdate` 仅在无 _id 时才 `_generateId`)
147        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    // ── 4. 处理 relation 子文档 ──
165    for (rel_name, rel_val) in relation_data {
166        if rel_val.is_null() {
167            continue;
168        }
169        // checked 索引:数据键未在 relations 定义时显式报错,绝不 panic
170        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            // type:'one' 子文档强制按 foreignKey upsert(写入形态对齐 JS `_upsertOne`;
178            // 权限门禁与字段过滤与 many 路径对齐:can_write_schema + filter_writable_data,
179            // 拥有父模型写权限 ≠ 拥有子模型写权限)
180            if !can_write_schema(registry.role_rules(), rel_schema, ctx) {
181                return Err(ERR_NO_WRITE.to_string());
182            }
183            // RBAC 表级写判定(one 子步骤同样叠加,deny-wins)
184            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            // 注入外键后再过滤(与 many 路径 plan_mutation_node 的过滤时机一致)
188            let child_val = match ctx {
189                // RBAC 感知版:静态 writable ∩ RBAC writeFields
190                Some(_) => {
191                    filter_writable_data_overlay(registry, rel_schema, ctx, &Value::Object(child))
192                }
193                None => Value::Object(child),
194            };
195            // JS `_upsertOne`:仅无 _id 时才 `_generateId`
196            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            // §11.4 静默点收口(D2:绝不静默):未知 rel_type 无法规划写入步骤,
230            // 显式报错(原行为为静默跳过该关系)。
231            return Err(format!(
232                "关系 \"{rel_name}\" 的 rel_type \"{}\" 不被支持(仅支持 one / many;D2:绝不静默跳过)",
233                rel_def.rel_type
234            ));
235        }
236    }
237
238    Ok(())
239}