307 lines
11 KiB
C++
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
|