graphddb_runtime 0.7.7

Rust runtime for GraphDDB — interprets the language-neutral IR (manifest.json + operations.json) and executes the validated access patterns against DynamoDB.
Documentation
//! Manifest key-attribute derivation shared by the put / transaction write paths
//! — a port of the `_add_key_attributes` / `_fill` / `_fillable` helpers.
//!
//! Given an entity's resolved item fields (a `(name, value)` list in insertion
//! order), derive `PK` / `SK` and each fillable `<index>PK` / `<index>SK` from the
//! manifest templates. `pkTemplate` is self-contained (segment key model), so no
//! entity prefix is prepended.

use serde_json::Value as Json;

use crate::templates::{each_placeholder_pub, json_to_template_string};

/// Look up a value by field name in the ordered item list.
fn lookup<'a>(item: &'a [(String, Json)], name: &str) -> Option<&'a Json> {
    item.iter().find(|(k, _)| k == name).map(|(_, v)| v)
}

/// Fill a template from the item values (`str(value)`, missing → empty), matching
/// the Python `_fill`.
fn fill(template: &str, item: &[(String, Json)]) -> String {
    let mut out = String::new();
    let bytes = template.as_bytes();
    let mut i = 0;
    while i < bytes.len() {
        if bytes[i] == b'{' {
            if let Some(close) = template[i + 1..].find('}') {
                let name = &template[i + 1..i + 1 + close];
                if !name.is_empty() && !name.contains('{') {
                    let v = lookup(item, name)
                        .map(json_to_template_string)
                        .unwrap_or_default();
                    out.push_str(&v);
                    i += 1 + close + 1;
                    continue;
                }
            }
        }
        out.push(bytes[i] as char);
        i += 1;
    }
    out
}

/// True when every placeholder in `template` has a value in `item`.
fn fillable(template: &str, item: &[(String, Json)]) -> bool {
    each_placeholder_pub(template)
        .iter()
        .all(|name| lookup(item, name).is_some())
}

/// Append `PK` / `SK` / GSI key attributes to `item` (in place), derived from the
/// entity's manifest key + GSI templates. A GSI key is only added when all its
/// placeholders are fillable (parity with `_add_key_attributes`).
pub fn add_key_attributes(manifest: &Json, entity_name: &str, item: &mut Vec<(String, Json)>) {
    let entity = manifest
        .get("entities")
        .and_then(Json::as_object)
        .and_then(|e| e.get(entity_name));
    let entity = match entity {
        Some(e) => e,
        None => return,
    };
    if let Some(key) = entity.get("key").filter(|k| !k.is_null()) {
        let pk_tmpl = key.get("pkTemplate").and_then(Json::as_str).unwrap_or("");
        let pk = fill(pk_tmpl, item);
        set(item, "PK", Json::String(pk));
        if let Some(sk_tmpl) = key.get("skTemplate").and_then(Json::as_str) {
            let sk = fill(sk_tmpl, item);
            set(item, "SK", Json::String(sk));
        }
    }
    if let Some(gsis) = entity.get("gsis").and_then(Json::as_array) {
        for gsi in gsis {
            let index = gsi.get("indexName").and_then(Json::as_str).unwrap_or("");
            if let Some(pk_tmpl) = gsi.get("pkTemplate").and_then(Json::as_str) {
                if fillable(pk_tmpl, item) {
                    set(
                        item,
                        &format!("{index}PK"),
                        Json::String(fill(pk_tmpl, item)),
                    );
                }
            }
            if let Some(sk_tmpl) = gsi.get("skTemplate").and_then(Json::as_str) {
                if fillable(sk_tmpl, item) {
                    set(
                        item,
                        &format!("{index}SK"),
                        Json::String(fill(sk_tmpl, item)),
                    );
                }
            }
        }
    }
}

fn set(item: &mut Vec<(String, Json)>, key: &str, value: Json) {
    if let Some(slot) = item.iter_mut().find(|(k, _)| k == key) {
        slot.1 = value;
    } else {
        item.push((key.to_string(), value));
    }
}