⎇ 4d50bfc5879839a835e26ffda3bbc7ae3b927cc7
crates/modules/nakui/core/src/delta.rs
Historia
Quién tocó qué
crudo
chore: absorbe nakui (ERP matemático) en modules/nakui
1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use uuid::Uuid;
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6pub struct FieldPath {
7 pub entity: String,
8 pub id: Uuid,
9 pub field: String,
10}
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13#[serde(tag = "op", rename_all = "snake_case")]
14pub enum FieldOp {
15 Set {
16 path: FieldPath,
17 value: Value,
18 },
19 Create {
20 entity: String,
21 id: Uuid,
22 data: Value,
23 },
24 Delete {
25 entity: String,
26 id: Uuid,
27 },
28}
29
30impl FieldOp {
31 /// Token a manifest's `writes` list matches against.
32 /// "Caja.saldo" for field updates, "Movimiento" for whole-record ops.
33 pub fn capability_token(&self) -> String {
34 match self {
35 FieldOp::Set { path, .. } => format!("{}.{}", path.entity, path.field),
36 FieldOp::Create { entity, .. } => entity.clone(),
37 FieldOp::Delete { entity, .. } => entity.clone(),
38 }
39 }
40}
41
42/// Apply only the ops that target `(entity, id)` to `state` and return the
43/// new value. Returns `None` if a Delete op removes the target — callers
44/// should skip post-checks against a deleted entity rather than running
45/// them against the stale prior state.
46pub fn simulate_on(state: &Value, entity: &str, id: Uuid, ops: &[FieldOp]) -> Option<Value> {
47 let mut s: Option<Value> = Some(state.clone());
48 for op in ops {
49 match op {
50 FieldOp::Set { path, value } if path.entity == entity && path.id == id => {
51 if let Some(Value::Object(map)) = s.as_mut() {
52 map.insert(path.field.clone(), value.clone());
53 }
54 }
55 FieldOp::Create {
56 entity: e,
57 id: i,
58 data,
59 } if e == entity && *i == id => {
60 s = Some(data.clone());
61 }
62 FieldOp::Delete {
63 entity: e,
64 id: i,
65 } if e == entity && *i == id => {
66 s = None;
67 }
68 _ => {}
69 }
70 }
71 s
72}