11#ifdef FLUXGRAPH_JSON_ENABLED
22
33#include " fluxgraph/loaders/json_loader.hpp"
4+ #include " param_parse_limits.hpp"
45#include < fstream>
56#include < nlohmann/json.hpp>
67#include < stdexcept>
@@ -11,18 +12,44 @@ namespace fluxgraph::loaders {
1112
1213namespace {
1314
14- ParamValue json_to_param_value (const json &j, const std::string &path) {
15+ ParamValue json_to_param_value (const json &j, const std::string &path,
16+ detail::ParamParseBudget &budget,
17+ size_t depth = 0 ) {
18+ budget.check_depth (depth, path);
19+ budget.consume_node (path);
20+
1521 if (j.is_number_float ()) {
1622 return j.get <double >();
1723 } else if (j.is_number_integer ()) {
1824 return j.get <int64_t >();
1925 } else if (j.is_boolean ()) {
2026 return j.get <bool >();
2127 } else if (j.is_string ()) {
22- return j.get <std::string>();
28+ const std::string value = j.get <std::string>();
29+ detail::check_string_size (value.size (), path);
30+ return value;
31+ } else if (j.is_array ()) {
32+ detail::check_array_size (j.size (), path);
33+ ParamArray arr;
34+ arr.reserve (j.size ());
35+ for (size_t i = 0 ; i < j.size (); ++i) {
36+ arr.push_back (
37+ json_to_param_value (j[i], path + " /" + std::to_string (i), budget,
38+ depth + 1 ));
39+ }
40+ return arr;
41+ } else if (j.is_object ()) {
42+ detail::check_object_size (j.size (), path);
43+ ParamObject obj;
44+ for (auto &[key, value] : j.items ()) {
45+ detail::check_string_size (key.size (), path + " /<key>" );
46+ obj.emplace (key, json_to_param_value (value, path + " /" + key, budget,
47+ depth + 1 ));
48+ }
49+ return obj;
2350 } else {
24- throw std::runtime_error (" JSON parse error at " + path +
25- " : Unsupported type for Variant " );
51+ throw std::runtime_error (" Parameter parse error at " + path +
52+ " : Unsupported JSON type " );
2653 }
2754}
2855
@@ -37,13 +64,15 @@ Variant json_to_variant(const json &j, const std::string &path) {
3764 } else if (j.is_string ()) {
3865 return j.get <std::string>();
3966 } else {
40- throw std::runtime_error (" JSON parse error at " + path +
41- " : Unsupported type for Variant" );
67+ throw std::runtime_error (
68+ " JSON parse error at " + path +
69+ " : Command args must be scalar (double/int64/bool/string)" );
4270 }
4371}
4472
4573// Parse transform specification
46- TransformSpec parse_transform (const json &j, const std::string &base_path) {
74+ TransformSpec parse_transform (const json &j, const std::string &base_path,
75+ detail::ParamParseBudget &budget) {
4776 TransformSpec spec;
4877
4978 std::string path = base_path + " /transform" ;
@@ -55,10 +84,14 @@ TransformSpec parse_transform(const json &j, const std::string &base_path) {
5584
5685 spec.type = j[" type" ].get <std::string>();
5786
58- if (j.contains (" params" ) && j[" params" ].is_object ()) {
87+ if (j.contains (" params" )) {
88+ if (!j[" params" ].is_object ()) {
89+ throw std::runtime_error (" JSON parse error at " + path +
90+ " /params: Expected object" );
91+ }
5992 for (auto &[key, value] : j[" params" ].items ()) {
6093 std::string param_path = path + " /params/" + key;
61- spec.params [key] = json_to_param_value (value, param_path);
94+ spec.params [key] = json_to_param_value (value, param_path, budget );
6295 }
6396 }
6497
@@ -85,7 +118,8 @@ SignalSpec parse_signal(const json &j, const std::string &base_path,
85118}
86119
87120// Parse edge specification
88- EdgeSpec parse_edge (const json &j, const std::string &base_path, size_t index) {
121+ EdgeSpec parse_edge (const json &j, const std::string &base_path, size_t index,
122+ detail::ParamParseBudget &budget) {
89123 EdgeSpec spec;
90124
91125 std::string path = base_path + " /" + std::to_string (index);
@@ -102,17 +136,21 @@ EdgeSpec parse_edge(const json &j, const std::string &base_path, size_t index) {
102136 throw std::runtime_error (" JSON parse error at " + path +
103137 " : Missing required field 'transform'" );
104138 }
139+ if (!j[" transform" ].is_object ()) {
140+ throw std::runtime_error (" JSON parse error at " + path +
141+ " /transform: Expected object" );
142+ }
105143
106144 spec.source_path = j[" source" ].get <std::string>();
107145 spec.target_path = j[" target" ].get <std::string>();
108- spec.transform = parse_transform (j[" transform" ], path);
146+ spec.transform = parse_transform (j[" transform" ], path, budget );
109147
110148 return spec;
111149}
112150
113151// Parse model specification
114152ModelSpec parse_model (const json &j, const std::string &base_path,
115- size_t index) {
153+ size_t index, detail::ParamParseBudget &budget ) {
116154 ModelSpec spec;
117155
118156 std::string path = base_path + " /" + std::to_string (index);
@@ -129,10 +167,14 @@ ModelSpec parse_model(const json &j, const std::string &base_path,
129167 spec.id = j[" id" ].get <std::string>();
130168 spec.type = j[" type" ].get <std::string>();
131169
132- if (j.contains (" params" ) && j[" params" ].is_object ()) {
170+ if (j.contains (" params" )) {
171+ if (!j[" params" ].is_object ()) {
172+ throw std::runtime_error (" JSON parse error at " + path +
173+ " /params: Expected object" );
174+ }
133175 for (auto &[key, value] : j[" params" ].items ()) {
134176 std::string param_path = path + " /params/" + key;
135- spec.params [key] = json_to_param_value (value, param_path);
177+ spec.params [key] = json_to_param_value (value, param_path, budget );
136178 }
137179 }
138180
@@ -176,7 +218,11 @@ RuleSpec parse_rule(const json &j, const std::string &base_path, size_t index) {
176218 action.device = action_json[" device" ].get <std::string>();
177219 action.function = action_json[" function" ].get <std::string>();
178220
179- if (action_json.contains (" args" ) && action_json[" args" ].is_object ()) {
221+ if (action_json.contains (" args" )) {
222+ if (!action_json[" args" ].is_object ()) {
223+ throw std::runtime_error (" JSON parse error at " + action_path +
224+ " /args: Expected object" );
225+ }
180226 for (auto &[key, value] : action_json[" args" ].items ()) {
181227 std::string arg_path = action_path + " /args/" + key;
182228 action.args [key] = json_to_variant (value, arg_path);
@@ -199,6 +245,7 @@ RuleSpec parse_rule(const json &j, const std::string &base_path, size_t index) {
199245// Parse complete GraphSpec from JSON
200246GraphSpec parse_json (const json &j) {
201247 GraphSpec spec;
248+ detail::ParamParseBudget param_budget;
202249
203250 // Parse signals (optional)
204251 if (j.contains (" signals" ) && j[" signals" ].is_array ()) {
@@ -212,15 +259,17 @@ GraphSpec parse_json(const json &j) {
212259 if (j.contains (" models" ) && j[" models" ].is_array ()) {
213260 size_t index = 0 ;
214261 for (auto &model_json : j[" models" ]) {
215- spec.models .push_back (parse_model (model_json, " /models" , index++));
262+ spec.models .push_back (
263+ parse_model (model_json, " /models" , index++, param_budget));
216264 }
217265 }
218266
219267 // Parse edges (optional)
220268 if (j.contains (" edges" ) && j[" edges" ].is_array ()) {
221269 size_t index = 0 ;
222270 for (auto &edge_json : j[" edges" ]) {
223- spec.edges .push_back (parse_edge (edge_json, " /edges" , index++));
271+ spec.edges .push_back (
272+ parse_edge (edge_json, " /edges" , index++, param_budget));
224273 }
225274 }
226275
0 commit comments