Skip to main content

graphddb_runtime/
entity.rs

1//! Manifest key-attribute derivation shared by the put / transaction write paths
2//! — a port of the `_add_key_attributes` / `_fill` / `_fillable` helpers.
3//!
4//! Given an entity's resolved item fields (a `(name, value)` list in insertion
5//! order), derive `PK` / `SK` and each fillable `<index>PK` / `<index>SK` from the
6//! manifest templates. `pkTemplate` is self-contained (segment key model), so no
7//! entity prefix is prepended.
8
9use serde_json::Value as Json;
10
11use crate::templates::{each_placeholder_pub, json_to_template_string};
12
13/// Look up a value by field name in the ordered item list.
14fn lookup<'a>(item: &'a [(String, Json)], name: &str) -> Option<&'a Json> {
15    item.iter().find(|(k, _)| k == name).map(|(_, v)| v)
16}
17
18/// Fill a template from the item values (`str(value)`, missing → empty), matching
19/// the Python `_fill`.
20fn fill(template: &str, item: &[(String, Json)]) -> String {
21    let mut out = String::new();
22    let bytes = template.as_bytes();
23    let mut i = 0;
24    while i < bytes.len() {
25        if bytes[i] == b'{' {
26            if let Some(close) = template[i + 1..].find('}') {
27                let name = &template[i + 1..i + 1 + close];
28                if !name.is_empty() && !name.contains('{') {
29                    let v = lookup(item, name)
30                        .map(json_to_template_string)
31                        .unwrap_or_default();
32                    out.push_str(&v);
33                    i += 1 + close + 1;
34                    continue;
35                }
36            }
37        }
38        out.push(bytes[i] as char);
39        i += 1;
40    }
41    out
42}
43
44/// True when every placeholder in `template` has a value in `item`.
45fn fillable(template: &str, item: &[(String, Json)]) -> bool {
46    each_placeholder_pub(template)
47        .iter()
48        .all(|name| lookup(item, name).is_some())
49}
50
51/// Append `PK` / `SK` / GSI key attributes to `item` (in place), derived from the
52/// entity's manifest key + GSI templates. A GSI key is only added when all its
53/// placeholders are fillable (parity with `_add_key_attributes`).
54pub fn add_key_attributes(manifest: &Json, entity_name: &str, item: &mut Vec<(String, Json)>) {
55    let entity = manifest
56        .get("entities")
57        .and_then(Json::as_object)
58        .and_then(|e| e.get(entity_name));
59    let entity = match entity {
60        Some(e) => e,
61        None => return,
62    };
63    if let Some(key) = entity.get("key").filter(|k| !k.is_null()) {
64        let pk_tmpl = key.get("pkTemplate").and_then(Json::as_str).unwrap_or("");
65        let pk = fill(pk_tmpl, item);
66        set(item, "PK", Json::String(pk));
67        if let Some(sk_tmpl) = key.get("skTemplate").and_then(Json::as_str) {
68            let sk = fill(sk_tmpl, item);
69            set(item, "SK", Json::String(sk));
70        }
71    }
72    if let Some(gsis) = entity.get("gsis").and_then(Json::as_array) {
73        for gsi in gsis {
74            let index = gsi.get("indexName").and_then(Json::as_str).unwrap_or("");
75            if let Some(pk_tmpl) = gsi.get("pkTemplate").and_then(Json::as_str) {
76                if fillable(pk_tmpl, item) {
77                    set(
78                        item,
79                        &format!("{index}PK"),
80                        Json::String(fill(pk_tmpl, item)),
81                    );
82                }
83            }
84            if let Some(sk_tmpl) = gsi.get("skTemplate").and_then(Json::as_str) {
85                if fillable(sk_tmpl, item) {
86                    set(
87                        item,
88                        &format!("{index}SK"),
89                        Json::String(fill(sk_tmpl, item)),
90                    );
91                }
92            }
93        }
94    }
95}
96
97fn set(item: &mut Vec<(String, Json)>, key: &str, value: Json) {
98    if let Some(slot) = item.iter_mut().find(|(k, _)| k == key) {
99        slot.1 = value;
100    } else {
101        item.push((key.to_string(), value));
102    }
103}