safesight-edge/src/utils/config_expand.cpp

307 lines
11 KiB
C++

#include "utils/config_expand.h"
#include <sstream>
namespace rk3588 {
namespace {
std::string ToStringForPlaceholder(const SimpleJson& v) {
if (v.IsString()) return v.AsString("");
if (v.IsBool()) return v.AsBool(false) ? "true" : "false";
if (v.IsNumber()) {
std::ostringstream oss;
// Keep simple (no locale, no trailing zeros control).
oss << v.AsNumber(0.0);
return oss.str();
}
return "";
}
std::string ReplaceAllPlaceholders(std::string s, const std::map<std::string, std::string>& vars) {
// Replace occurrences of ${key}.
for (const auto& kv : vars) {
const std::string needle = "${" + kv.first + "}";
if (needle.size() <= 3) continue;
size_t pos = 0;
while ((pos = s.find(needle, pos)) != std::string::npos) {
s.replace(pos, needle.size(), kv.second);
pos += kv.second.size();
}
}
return s;
}
SimpleJson ReplacePlaceholders(const SimpleJson& in, const std::map<std::string, std::string>& vars) {
if (in.IsString()) {
return SimpleJson(ReplaceAllPlaceholders(in.AsString(""), vars));
}
if (in.IsArray()) {
SimpleJson::Array out;
out.reserve(in.AsArray().size());
for (const auto& el : in.AsArray()) {
out.push_back(ReplacePlaceholders(el, vars));
}
return SimpleJson(std::move(out));
}
if (in.IsObject()) {
SimpleJson::Object out;
for (const auto& kv : in.AsObject()) {
out.emplace(kv.first, ReplacePlaceholders(kv.second, vars));
}
return SimpleJson(std::move(out));
}
// Null/bool/number unchanged.
return in;
}
SimpleJson DeepMerge(const SimpleJson& base, const SimpleJson& override_v) {
if (!base.IsObject() || !override_v.IsObject()) return override_v;
SimpleJson::Object merged = base.AsObject();
for (const auto& kv : override_v.AsObject()) {
auto it = merged.find(kv.first);
if (it != merged.end() && it->second.IsObject() && kv.second.IsObject()) {
it->second = DeepMerge(it->second, kv.second);
} else {
it->second = kv.second;
}
}
return SimpleJson(std::move(merged));
}
bool HasKey(const SimpleJson& obj, const std::string& key) {
return obj.IsObject() && obj.AsObject().find(key) != obj.AsObject().end();
}
bool ExpandInstances(const SimpleJson& in_root, SimpleJson::Array& out_graphs, std::string& err) {
const SimpleJson* templates_cfg = in_root.Find("templates");
const SimpleJson* instances_cfg = in_root.Find("instances");
if (!instances_cfg) return true;
if (!instances_cfg->IsArray()) {
err = "Root 'instances' must be an array";
return false;
}
const auto& templates_obj = templates_cfg ? templates_cfg->AsObject() : SimpleJson::Object{};
for (const auto& inst_val : instances_cfg->AsArray()) {
if (!inst_val.IsObject()) {
err = "Instance entry is not object";
return false;
}
const std::string inst_name = inst_val.ValueOr<std::string>("name", "");
const std::string tpl_name = inst_val.ValueOr<std::string>("template", "");
if (inst_name.empty() || tpl_name.empty()) {
err = "Instance missing 'name' or 'template'";
return false;
}
auto tpl_it = templates_obj.find(tpl_name);
if (tpl_it == templates_obj.end() || !tpl_it->second.IsObject()) {
err = "Template not found or invalid: " + tpl_name;
return false;
}
const SimpleJson& tpl = tpl_it->second;
const SimpleJson* tpl_nodes = tpl.Find("nodes");
const SimpleJson* tpl_edges = tpl.Find("edges");
if (!tpl_nodes || !tpl_nodes->IsArray() || !tpl_edges || !tpl_edges->IsArray()) {
err = "Template must contain arrays: 'nodes' and 'edges'";
return false;
}
// Placeholder vars.
std::map<std::string, std::string> vars;
vars.emplace("name", inst_name);
if (const SimpleJson* params = inst_val.Find("params"); params && params->IsObject()) {
for (const auto& kv : params->AsObject()) {
vars.emplace(kv.first, ToStringForPlaceholder(kv.second));
}
}
// Override object.
const SimpleJson* override_nodes = nullptr;
if (const SimpleJson* ov = inst_val.Find("override")) {
if (!ov->IsObject()) {
err = "Instance override must be object";
return false;
}
if (const SimpleJson* nodes_ov = ov->Find("nodes")) {
if (!nodes_ov->IsObject()) {
err = "Instance override.nodes must be object";
return false;
}
override_nodes = nodes_ov;
}
}
// Build id mapping for nodes.
std::map<std::string, std::string> id_map;
SimpleJson::Array inst_nodes;
inst_nodes.reserve(tpl_nodes->AsArray().size());
for (const auto& node_val : tpl_nodes->AsArray()) {
if (!node_val.IsObject()) {
err = "Template node entry is not object";
return false;
}
const std::string old_id = node_val.ValueOr<std::string>("id", "");
if (old_id.empty()) {
err = "Template node missing 'id'";
return false;
}
const std::string new_id = inst_name + "_" + old_id;
id_map.emplace(old_id, new_id);
// Apply placeholders first.
SimpleJson expanded_node = ReplacePlaceholders(node_val, vars);
// Apply per-node override by template node id.
if (override_nodes) {
if (const SimpleJson* n_ov = override_nodes->Find(old_id)) {
if (!n_ov->IsObject()) {
err = "override.nodes." + old_id + " must be object";
return false;
}
expanded_node = DeepMerge(expanded_node, *n_ov);
}
}
// Rewrite id.
SimpleJson::Object obj = expanded_node.AsObject();
obj["id"] = SimpleJson(new_id);
inst_nodes.emplace_back(SimpleJson(std::move(obj)));
}
// Rewrite edges.
SimpleJson::Array inst_edges;
inst_edges.reserve(tpl_edges->AsArray().size());
for (const auto& e : tpl_edges->AsArray()) {
if (e.IsArray()) {
const auto& arr = e.AsArray();
if (arr.size() < 2) {
err = "Template edge array must be [from, to]";
return false;
}
std::string from = arr[0].AsString("");
std::string to = arr[1].AsString("");
auto fi = id_map.find(from);
auto ti = id_map.find(to);
if (fi == id_map.end() || ti == id_map.end()) {
err = "Template edge references unknown node: " + from + " -> " + to;
return false;
}
SimpleJson::Array out_arr;
out_arr.emplace_back(SimpleJson(fi->second));
out_arr.emplace_back(SimpleJson(ti->second));
// Preserve optional third element (queue config) as-is.
if (arr.size() >= 3) {
out_arr.push_back(ReplacePlaceholders(arr[2], vars));
}
inst_edges.emplace_back(SimpleJson(std::move(out_arr)));
} else if (e.IsObject()) {
std::string from = e.ValueOr<std::string>("from", "");
std::string to = e.ValueOr<std::string>("to", "");
auto fi = id_map.find(from);
auto ti = id_map.find(to);
if (fi == id_map.end() || ti == id_map.end()) {
err = "Template edge references unknown node: " + from + " -> " + to;
return false;
}
SimpleJson obj = ReplacePlaceholders(e, vars);
SimpleJson::Object out_obj = obj.AsObject();
out_obj["from"] = SimpleJson(fi->second);
out_obj["to"] = SimpleJson(ti->second);
inst_edges.emplace_back(SimpleJson(std::move(out_obj)));
} else {
err = "Template edge must be array or object";
return false;
}
}
SimpleJson::Object graph;
graph.emplace("name", SimpleJson(inst_name));
graph.emplace("nodes", SimpleJson(std::move(inst_nodes)));
graph.emplace("edges", SimpleJson(std::move(inst_edges)));
out_graphs.emplace_back(SimpleJson(std::move(graph)));
}
return true;
}
} // namespace
bool ExpandRootConfig(const SimpleJson& in_root, SimpleJson& out_root, std::string& err) {
if (!in_root.IsObject()) {
err = "Root config must be an object";
return false;
}
SimpleJson::Object out;
// Preserve queue if provided.
if (const SimpleJson* q = in_root.Find("queue")) {
out.emplace("queue", *q);
}
// Preserve global if provided (used for plugin_path/hot_reload).
if (const SimpleJson* g = in_root.Find("global")) {
out.emplace("global", *g);
}
SimpleJson::Array graphs;
// Expand instances (optional).
if (!ExpandInstances(in_root, graphs, err)) {
return false;
}
// Append explicit graphs (optional).
if (const SimpleJson* g = in_root.Find("graphs")) {
if (!g->IsArray()) {
err = "Root 'graphs' must be an array";
return false;
}
for (const auto& gv : g->AsArray()) {
graphs.push_back(gv);
}
}
// Ensure graphs exists.
out.emplace("graphs", SimpleJson(std::move(graphs)));
out_root = SimpleJson(std::move(out));
return true;
}
bool JsonDeepEqual(const SimpleJson& a, const SimpleJson& b) {
if (a.type() != b.type()) return false;
if (a.IsNull()) return true;
if (a.IsBool()) return a.AsBool(false) == b.AsBool(false);
if (a.IsNumber()) return a.AsNumber(0.0) == b.AsNumber(0.0);
if (a.IsString()) return a.AsString("") == b.AsString("");
if (a.IsArray()) {
const auto& aa = a.AsArray();
const auto& bb = b.AsArray();
if (aa.size() != bb.size()) return false;
for (size_t i = 0; i < aa.size(); ++i) {
if (!JsonDeepEqual(aa[i], bb[i])) return false;
}
return true;
}
if (a.IsObject()) {
const auto& ao = a.AsObject();
const auto& bo = b.AsObject();
if (ao.size() != bo.size()) return false;
for (const auto& kv : ao) {
auto it = bo.find(kv.first);
if (it == bo.end()) return false;
if (!JsonDeepEqual(kv.second, it->second)) return false;
}
return true;
}
return false;
}
} // namespace rk3588