16#include <initializer_list>
17#include <nlohmann/json.hpp>
22using json = nlohmann::json;
33 std::vector<std::string> warnings;
42inline std::string validate_keys(
const json& value,
43 std::initializer_list<const char*> allowed,
44 const std::string& context) {
45 if (!value.is_object())
return context +
" must be an object";
46 for (
auto item = value.begin(); item != value.end(); ++item) {
47 const std::string key = item.key();
48 const bool known = std::any_of(allowed.begin(), allowed.end(),
49 [&key](
const char* candidate) { return key == candidate; });
50 if (!known)
return "unknown field in " + context +
": " + key;
55inline std::string validate_string_array(
const json& value,
const std::string& context) {
56 if (!value.is_array())
return context +
" must be an array";
57 for (
const auto& item : value) {
58 if (!item.is_string())
return context +
" must contain only strings";
63inline std::string get_string(
const json& j,
const std::string& key,
const std::string& default_val =
"") {
64 if (j.contains(key) && j[key].is_string()) {
65 return j[key].get<std::string>();
70inline std::vector<std::string> get_string_array(
const json& j,
const std::string& key) {
71 std::vector<std::string> result;
72 if (j.contains(key) && j[key].is_array()) {
73 for (
const auto& item : j[key]) {
74 if (item.is_string()) {
75 result.push_back(item.get<std::string>());
82inline bool get_bool(
const json& j,
const std::string& key,
bool default_val =
false) {
83 if (j.contains(key) && j[key].is_boolean()) {
84 return j[key].get<
bool>();
95inline core::EnvValue parse_env_value(
const json& j) {
99 ev.op = core::EnvOp::Set;
100 ev.value = j.get<std::string>();
105 std::string op_str = detail::get_string(j,
"op",
"set");
106 auto op = core::parse_env_op(op_str);
107 ev.op = op.value_or(core::EnvOp::Set);
108 ev.value = detail::get_string(j,
"value");
109 ev.separator = detail::get_string(j,
"separator",
":");
115inline core::EnvMap parse_env_map(
const json& j) {
118 for (
auto& [key, val] : j.items()) {
119 result[key] = parse_env_value(val);
125inline std::string validate_env_map(
const json& values) {
126 if (!values.is_object())
return "environment must be an object";
127 for (
const auto& [key, value] : values.items()) {
128 if (key.empty())
return "environment keys must not be empty";
129 if (key.find(
'=') != std::string::npos || key.find(
'\0') != std::string::npos) {
130 return "invalid environment key: '" + key +
"'";
132 if (value.is_string())
continue;
133 if (!value.is_object())
return "environment value for '" + key +
"' must be a string or object";
134 if (
const auto error = detail::validate_keys(value, {
"op",
"value",
"separator"},
135 "environment operation for '" + key +
"'"); !error.empty())
return error;
136 if (!value.contains(
"op") || !value[
"op"].is_string()) {
137 return "environment operation for '" + key +
"' requires a string op";
139 const auto op = core::parse_env_op(value[
"op"].get<std::string>());
140 if (!op)
return "unknown environment operation for '" + key +
"'";
141 if (*op != core::EnvOp::Unset && (!value.contains(
"value") || !value[
"value"].is_string())) {
142 return "environment operation for '" + key +
"' requires a string value";
144 if (value.contains(
"separator") && !value[
"separator"].is_string()) {
145 return "environment separator for '" + key +
"' must be a string";
155inline core::TrustInfo parse_trust_info(
const json& j) {
158 std::string state_str = detail::get_string(j,
"state",
"unknown");
159 auto state = core::parse_trust_state(state_str);
160 ti.state = state.value_or(core::TrustState::Unknown);
162 ti.source = detail::get_string(j,
"source");
163 ti.evaluated_at = detail::get_string(j,
"evaluated_at");
164 ti.expires_at = detail::get_string(j,
"expires_at");
165 ti.inputs_hash = detail::get_string(j,
"inputs_hash");
167 if (j.contains(
"details") && j[
"details"].is_object()) {
168 for (
auto& [key, val] : j[
"details"].items()) {
169 if (val.is_string()) {
170 ti.details[key] = val.get<std::string>();
182inline core::LoaderConfig parse_loader_config(
const json& j) {
183 core::LoaderConfig lc;
184 lc.exec_path = detail::get_string(j,
"exec_path");
185 lc.args_template = detail::get_string_array(j,
"args_template");
193inline ParseResult<core::AppDeclaration> parse_app_declaration(
const std::string& json_str) {
194 ParseResult<core::AppDeclaration> result;
197 json j = json::parse(json_str);
199 if (!j.is_object()) {
200 result.error =
"app manifest must be an object";
203 if (
const auto error = detail::validate_keys(j, {
"$schema",
"app"},
"app manifest"); !error.empty()) {
204 result.error = error;
207 if (j.contains(
"$schema") && (!j[
"$schema"].is_string() ||
208 j[
"$schema"].get<std::string>() !=
"https://nah.rtorr.com/schemas/nap.v2.json")) {
209 result.error =
"unsupported app manifest schema";
212 if (!j.contains(
"app") || !j[
"app"].is_object()) {
213 result.error =
"missing required object: app";
217 if (
const auto error = detail::validate_keys(j,
218 {
"identity",
"execution",
"layout",
"environment",
"permissions",
"exports",
"metadata"},
219 "app"); !error.empty()) {
220 result.error = error;
224 auto& app = result.value;
225 if (!j.contains(
"identity") || !j[
"identity"].is_object()) {
226 result.error =
"missing required object: app.identity";
229 const auto& identity = j[
"identity"];
230 if (
const auto error = detail::validate_keys(identity,
231 {
"id",
"version",
"nak_id",
"nak_version_req"},
"app.identity"); !error.empty()) {
232 result.error = error;
235 app.id = detail::get_string(identity,
"id");
236 app.version = detail::get_string(identity,
"version");
237 app.nak_id = detail::get_string(identity,
"nak_id");
238 app.nak_version_req = detail::get_string(identity,
"nak_version_req");
240 if (app.id.empty()) {
241 result.error =
"missing required field: id";
244 if (app.version.empty()) {
245 result.error =
"missing required field: version";
249 if (!j.contains(
"execution") || !j[
"execution"].is_object()) {
250 result.error =
"missing required object: app.execution";
253 const auto& execution = j[
"execution"];
254 if (
const auto error = detail::validate_keys(execution, {
"entrypoint",
"loader",
"args"},
255 "app.execution"); !error.empty()) {
256 result.error = error;
259 if (execution.contains(
"args")) {
260 if (
const auto error = detail::validate_string_array(execution[
"args"],
"app.execution.args");
261 !error.empty()) { result.error = error;
return result; }
263 app.entrypoint_path = detail::get_string(execution,
"entrypoint");
264 app.entrypoint_args = detail::get_string_array(execution,
"args");
265 app.nak_loader = detail::get_string(execution,
"loader");
267 if (app.entrypoint_path.empty()) {
268 result.error =
"missing required field: entrypoint path";
272 if (j.contains(
"layout")) {
273 const auto& layout = j[
"layout"];
274 if (
const auto error = detail::validate_keys(layout, {
"lib_dirs",
"asset_dirs"},
"app.layout");
275 !error.empty()) { result.error = error;
return result; }
276 for (
const char* key : {
"lib_dirs",
"asset_dirs"}) {
277 if (layout.contains(key)) {
278 if (
const auto error = detail::validate_string_array(layout[key], std::string(
"app.layout.") + key);
279 !error.empty()) { result.error = error;
return result; }
282 app.lib_dirs = detail::get_string_array(layout,
"lib_dirs");
283 app.asset_dirs = detail::get_string_array(layout,
"asset_dirs");
286 if (j.contains(
"environment") && j[
"environment"].is_object()) {
287 const auto error = validate_env_map(j[
"environment"]);
288 if (!error.empty()) {
289 result.error = error;
292 app.environment = parse_env_map(j[
"environment"]);
293 }
else if (j.contains(
"environment")) {
294 result.error =
"environment must be an object";
298 if (j.contains(
"exports")) {
299 if (!j[
"exports"].is_array()) { result.error =
"app.exports must be an array";
return result; }
300 for (
const auto& exp : j[
"exports"]) {
301 if (
const auto error = detail::validate_keys(exp, {
"id",
"path",
"type"},
"app.exports item");
302 !error.empty()) { result.error = error;
return result; }
303 core::AssetExportDecl aed;
304 aed.id = detail::get_string(exp,
"id");
305 aed.path = detail::get_string(exp,
"path");
306 aed.type = detail::get_string(exp,
"type");
307 if (aed.id.empty() || aed.path.empty()) {
308 result.error =
"app.exports items require string id and path";
311 app.asset_exports.push_back(aed);
315 if (j.contains(
"permissions")) {
316 const auto& permissions = j[
"permissions"];
317 if (
const auto error = detail::validate_keys(permissions, {
"filesystem",
"network"},
"app.permissions");
318 !error.empty()) { result.error = error;
return result; }
319 for (
const char* key : {
"filesystem",
"network"}) {
320 if (permissions.contains(key)) {
321 if (
const auto error = detail::validate_string_array(permissions[key], std::string(
"app.permissions.") + key);
322 !error.empty()) { result.error = error;
return result; }
325 app.permissions_filesystem = detail::get_string_array(permissions,
"filesystem");
326 app.permissions_network = detail::get_string_array(permissions,
"network");
329 if (j.contains(
"metadata")) {
330 if (!j[
"metadata"].is_object()) { result.error =
"app.metadata must be an object";
return result; }
331 for (
const char* key : {
"description",
"author",
"license",
"homepage"}) {
332 if (j[
"metadata"].contains(key) && !j[
"metadata"][key].is_string()) {
333 result.error = std::string(
"app.metadata.") + key +
" must be a string";
337 app.description = detail::get_string(j[
"metadata"],
"description");
338 app.author = detail::get_string(j[
"metadata"],
"author");
339 app.license = detail::get_string(j[
"metadata"],
"license");
340 app.homepage = detail::get_string(j[
"metadata"],
"homepage");
345 }
catch (
const json::exception& e) {
346 result.error = std::string(
"JSON parse error: ") + e.what();
356inline ParseResult<core::HostEnvironment> parse_host_environment(
const json& j,
357 const std::string& source_path =
"") {
358 ParseResult<core::HostEnvironment> result;
361 if (j.contains(
"$schema") &&
362 (!j[
"$schema"].is_string() ||
363 j[
"$schema"].get<std::string>() !=
"https://nah.rtorr.com/schemas/nah.v2.json")) {
364 result.error =
"unsupported host schema";
367 if (
const auto error = detail::validate_keys(j, {
"$schema",
"environment",
"paths"},
"host configuration");
368 !error.empty()) { result.error = error;
return result; }
369 auto& host_env = result.value;
370 const json& config = j;
372 host_env.source_path = source_path;
375 if (config.contains(
"environment") && config[
"environment"].is_object()) {
376 const auto error = validate_env_map(config[
"environment"]);
377 if (!error.empty()) {
378 result.error = error;
381 host_env.vars = parse_env_map(config[
"environment"]);
382 }
else if (config.contains(
"environment")) {
383 result.error =
"environment must be an object";
388 if (config.contains(
"paths")) {
389 if (
const auto error = detail::validate_keys(config[
"paths"], {
"library_prepend",
"library_append"},
390 "host paths"); !error.empty()) {
391 result.error = error;
return result;
393 for (
const char* key : {
"library_prepend",
"library_append"}) {
394 if (config[
"paths"].contains(key)) {
395 if (
const auto error = detail::validate_string_array(config[
"paths"][key],
396 std::string(
"host paths.") + key); !error.empty()) {
397 result.error = error;
return result;
401 host_env.paths.library_prepend = detail::get_string_array(config[
"paths"],
"library_prepend");
402 host_env.paths.library_append = detail::get_string_array(config[
"paths"],
"library_append");
407 }
catch (
const json::exception& e) {
408 result.error = std::string(
"JSON parse error: ") + e.what();
414inline ParseResult<core::HostEnvironment> parse_host_environment(
const std::string& json_str,
415 const std::string& source_path =
"") {
416 ParseResult<core::HostEnvironment> result;
419 json j = json::parse(json_str);
420 return parse_host_environment(j, source_path);
421 }
catch (
const json::exception& e) {
422 result.error = std::string(
"JSON parse error: ") + e.what();
432inline ParseResult<core::InstallRecord> parse_install_record(
const std::string& json_str,
433 const std::string& source_path =
"") {
434 ParseResult<core::InstallRecord> result;
437 json j = json::parse(json_str);
438 auto& ir = result.value;
440 if (j.contains(
"$schema") &&
441 (!j[
"$schema"].is_string() ||
442 j[
"$schema"].get<std::string>() !=
"https://nah.rtorr.com/schemas/app-record.v2.json")) {
443 result.error =
"unsupported app record schema";
446 if (
const auto error = detail::validate_keys(j,
447 {
"$schema",
"install",
"app",
"nak",
"paths",
"provenance",
"trust",
"overrides"},
448 "app install record"); !error.empty()) { result.error = error;
return result; }
449 for (
const char* section : {
"install",
"app",
"paths"}) {
450 if (!j.contains(section) || !j[section].is_object()) {
451 result.error = std::string(
"missing required object: ") + section;
456 ir.source_path = source_path;
459 if (j.contains(
"install") && j[
"install"].is_object()) {
460 ir.install.instance_id = detail::get_string(j[
"install"],
"instance_id");
463 if (ir.install.instance_id.empty()) {
464 result.error =
"missing required field: install.instance_id";
469 if (j.contains(
"app") && j[
"app"].is_object()) {
470 ir.app.id = detail::get_string(j[
"app"],
"id");
471 ir.app.version = detail::get_string(j[
"app"],
"version");
472 ir.app.nak_id = detail::get_string(j[
"app"],
"nak_id");
473 ir.app.nak_version_req = detail::get_string(j[
"app"],
"nak_version_req");
475 if (ir.app.id.empty() || ir.app.version.empty()) {
476 result.error =
"app record requires app.id and app.version";
481 if (j.contains(
"nak") && j[
"nak"].is_object()) {
482 ir.nak.id = detail::get_string(j[
"nak"],
"id");
483 ir.nak.version = detail::get_string(j[
"nak"],
"version");
484 ir.nak.record_ref = detail::get_string(j[
"nak"],
"record_ref");
485 ir.nak.loader = detail::get_string(j[
"nak"],
"loader");
486 ir.nak.selection_reason = detail::get_string(j[
"nak"],
"selection_reason");
490 if (j.contains(
"paths") && j[
"paths"].is_object()) {
491 ir.paths.install_root = detail::get_string(j[
"paths"],
"install_root");
494 if (ir.paths.install_root.empty()) {
495 result.error =
"missing required field: paths.install_root";
500 if (j.contains(
"provenance") && j[
"provenance"].is_object()) {
501 ir.provenance.package_hash = detail::get_string(j[
"provenance"],
"package_hash");
502 ir.provenance.installed_at = detail::get_string(j[
"provenance"],
"installed_at");
503 ir.provenance.installed_by = detail::get_string(j[
"provenance"],
"installed_by");
504 ir.provenance.source = detail::get_string(j[
"provenance"],
"source");
508 if (j.contains(
"trust") && j[
"trust"].is_object()) {
509 ir.trust = parse_trust_info(j[
"trust"]);
513 if (j.contains(
"overrides") && j[
"overrides"].is_object()) {
514 const auto& ovr = j[
"overrides"];
516 if (ovr.contains(
"environment") && ovr[
"environment"].is_object()) {
517 const auto error = validate_env_map(ovr[
"environment"]);
518 if (!error.empty()) {
519 result.error = error;
522 ir.overrides.environment = parse_env_map(ovr[
"environment"]);
523 }
else if (ovr.contains(
"environment")) {
524 result.error =
"environment must be an object";
528 if (ovr.contains(
"arguments") && ovr[
"arguments"].is_object()) {
529 ir.overrides.arguments.prepend = detail::get_string_array(ovr[
"arguments"],
"prepend");
530 ir.overrides.arguments.append = detail::get_string_array(ovr[
"arguments"],
"append");
533 if (ovr.contains(
"paths") && ovr[
"paths"].is_object()) {
534 ir.overrides.paths.library_prepend = detail::get_string_array(ovr[
"paths"],
"library_prepend");
535 ir.overrides.paths.library_append = detail::get_string_array(ovr[
"paths"],
"library_append");
541 }
catch (
const json::exception& e) {
542 result.error = std::string(
"JSON parse error: ") + e.what();
552inline ParseResult<core::RuntimeDescriptor> parse_runtime_descriptor(
const std::string& json_str,
553 const std::string& source_path =
"") {
554 ParseResult<core::RuntimeDescriptor> result;
557 json j = json::parse(json_str);
558 auto& rd = result.value;
560 if (j.contains(
"$schema") &&
561 (!j[
"$schema"].is_string() ||
562 j[
"$schema"].get<std::string>() !=
"https://nah.rtorr.com/schemas/nak-record.v1.json")) {
563 result.error =
"unsupported NAK record schema";
566 if (
const auto error = detail::validate_keys(j,
567 {
"$schema",
"nak",
"paths",
"environment",
"loaders",
"execution",
"provenance",
"trust"},
568 "NAK install record"); !error.empty()) { result.error = error;
return result; }
569 for (
const char* section : {
"nak",
"paths"}) {
570 if (!j.contains(section) || !j[section].is_object()) {
571 result.error = std::string(
"missing required object: ") + section;
576 rd.source_path = source_path;
579 if (j.contains(
"nak") && j[
"nak"].is_object()) {
580 rd.nak.id = detail::get_string(j[
"nak"],
"id");
581 rd.nak.version = detail::get_string(j[
"nak"],
"version");
584 if (rd.nak.id.empty()) {
585 result.error =
"missing required field: nak.id";
588 if (rd.nak.version.empty()) {
589 result.error =
"missing required field: nak.version";
594 if (j.contains(
"paths") && j[
"paths"].is_object()) {
595 rd.paths.root = detail::get_string(j[
"paths"],
"root");
596 rd.paths.resource_root = detail::get_string(j[
"paths"],
"resource_root");
597 rd.paths.lib_dirs = detail::get_string_array(j[
"paths"],
"lib_dirs");
600 if (rd.paths.root.empty()) {
601 result.error =
"missing required field: paths.root";
606 if (j.contains(
"environment") && j[
"environment"].is_object()) {
607 const auto error = validate_env_map(j[
"environment"]);
608 if (!error.empty()) {
609 result.error = error;
612 rd.environment = parse_env_map(j[
"environment"]);
613 }
else if (j.contains(
"environment")) {
614 result.error =
"environment must be an object";
619 if (j.contains(
"loaders") && j[
"loaders"].is_object()) {
620 for (
auto& [name, config] : j[
"loaders"].items()) {
621 if (
const auto error = detail::validate_keys(config, {
"exec_path",
"args_template"},
622 "NAK loader " + name); !error.empty()) { result.error = error;
return result; }
623 if (!config.contains(
"exec_path") || !config[
"exec_path"].is_string() ||
624 config[
"exec_path"].get<std::string>().empty()) {
625 result.error =
"NAK loader requires a non-empty exec_path: " + name;
628 if (config.contains(
"args_template")) {
629 if (
const auto error = detail::validate_string_array(config[
"args_template"],
630 "NAK loader args_template"); !error.empty()) { result.error = error;
return result; }
632 rd.loaders[name] = parse_loader_config(config);
634 }
else if (j.contains(
"loaders")) {
635 result.error =
"loaders must be an object";
640 if (j.contains(
"execution") && j[
"execution"].is_object()) {
641 rd.execution.present =
true;
642 rd.execution.cwd = detail::get_string(j[
"execution"],
"cwd");
646 if (j.contains(
"provenance") && j[
"provenance"].is_object()) {
647 rd.provenance.package_hash = detail::get_string(j[
"provenance"],
"package_hash");
648 rd.provenance.installed_at = detail::get_string(j[
"provenance"],
"installed_at");
649 rd.provenance.installed_by = detail::get_string(j[
"provenance"],
"installed_by");
650 rd.provenance.source = detail::get_string(j[
"provenance"],
"source");
653 if (j.contains(
"trust") && j[
"trust"].is_object()) {
654 rd.trust = parse_trust_info(j[
"trust"]);
659 }
catch (
const json::exception& e) {
660 result.error = std::string(
"JSON parse error: ") + e.what();
670using core::serialize_contract;
671using core::serialize_result;
677inline ParseResult<core::LaunchContract> parse_launch_contract(
const std::string& json_str) {
678 ParseResult<core::LaunchContract> result;
681 json j = json::parse(json_str);
682 auto& c = result.value;
684 if (
const auto error = detail::validate_keys(j,
685 {
"schema",
"app",
"nak",
"execution",
"environment",
"permissions",
"trust"},
686 "launch contract"); !error.empty()) { result.error = error;
return result; }
687 for (
const char* section : {
"app",
"nak",
"execution",
"environment",
"permissions",
"trust"}) {
688 if (!j.contains(section) || !j[section].is_object()) {
689 result.error = std::string(
"missing required object: ") + section;
693 if (!j.contains(
"schema") || !j[
"schema"].is_string() ||
694 j[
"schema"].get<std::string>() != core::NAH_CONTRACT_SCHEMA) {
695 result.error =
"unsupported launch contract schema";
699 if (
const auto error = detail::validate_keys(j[
"app"],
700 {
"id",
"version",
"root",
"entrypoint",
"package_hash"},
"launch app");
701 !error.empty()) { result.error = error;
return result; }
702 for (
const char* key : {
"id",
"version",
"root",
"entrypoint",
"package_hash"}) {
703 if (!j[
"app"].contains(key) || !j[
"app"][key].is_string()) {
704 result.error = std::string(
"launch app requires string field: ") + key;
708 if (
const auto error = detail::validate_keys(j[
"nak"],
709 {
"id",
"version",
"root",
"resource_root",
"record_ref",
"package_hash"},
"launch NAK");
710 !error.empty()) { result.error = error;
return result; }
711 for (
const char* key : {
"id",
"version",
"root",
"resource_root",
"record_ref",
"package_hash"}) {
712 if (!j[
"nak"].contains(key) || !j[
"nak"][key].is_string()) {
713 result.error = std::string(
"launch NAK requires string field: ") + key;
717 if (
const auto error = detail::validate_keys(j[
"execution"],
718 {
"binary",
"arguments",
"cwd",
"library_path_env_key",
"library_paths"},
"launch execution");
719 !error.empty()) { result.error = error;
return result; }
720 for (
const char* key : {
"binary",
"cwd",
"library_path_env_key"}) {
721 if (!j[
"execution"].contains(key) || !j[
"execution"][key].is_string()) {
722 result.error = std::string(
"launch execution requires string field: ") + key;
726 for (
const char* key : {
"arguments",
"library_paths"}) {
727 if (!j[
"execution"].contains(key)) {
728 result.error = std::string(
"launch execution requires field: ") + key;
731 if (
const auto error = detail::validate_string_array(j[
"execution"][key],
732 std::string(
"launch execution.") + key); !error.empty()) {
733 result.error = error;
return result;
736 if (
const auto error = detail::validate_keys(j[
"permissions"], {
"filesystem",
"network"},
737 "launch permissions"); !error.empty()) {
738 result.error = error;
return result;
740 for (
const char* key : {
"filesystem",
"network"}) {
741 if (!j[
"permissions"].contains(key)) {
742 result.error = std::string(
"launch permissions requires field: ") + key;
745 if (
const auto error = detail::validate_string_array(j[
"permissions"][key],
746 std::string(
"launch permissions.") + key); !error.empty()) {
747 result.error = error;
return result;
750 for (
const auto& [key, value] : j[
"environment"].items()) {
751 if (!value.is_string()) { result.error =
"launch environment value must be a string: " + key;
return result; }
753 if (
const auto error = detail::validate_keys(j[
"trust"],
754 {
"state",
"source",
"evaluated_at",
"expires_at"},
"launch trust");
755 !error.empty()) { result.error = error;
return result; }
756 for (
const char* key : {
"state",
"source",
"evaluated_at",
"expires_at"}) {
757 if (!j[
"trust"].contains(key) || !j[
"trust"][key].is_string()) {
758 result.error = std::string(
"launch trust requires string field: ") + key;
762 if (!core::parse_trust_state(j[
"trust"][
"state"].get<std::string>())) {
763 result.error =
"launch trust state is invalid";
768 if (j.contains(
"app") && j[
"app"].is_object()) {
769 c.app.id = detail::get_string(j[
"app"],
"id");
770 c.app.version = detail::get_string(j[
"app"],
"version");
771 c.app.root = detail::get_string(j[
"app"],
"root");
772 c.app.entrypoint = detail::get_string(j[
"app"],
"entrypoint");
773 c.app.package_hash = detail::get_string(j[
"app"],
"package_hash");
777 if (j.contains(
"nak") && j[
"nak"].is_object()) {
778 c.nak.id = detail::get_string(j[
"nak"],
"id");
779 c.nak.version = detail::get_string(j[
"nak"],
"version");
780 c.nak.root = detail::get_string(j[
"nak"],
"root");
781 c.nak.resource_root = detail::get_string(j[
"nak"],
"resource_root");
782 c.nak.record_ref = detail::get_string(j[
"nak"],
"record_ref");
783 c.nak.package_hash = detail::get_string(j[
"nak"],
"package_hash");
787 if (j.contains(
"execution") && j[
"execution"].is_object()) {
788 c.execution.binary = detail::get_string(j[
"execution"],
"binary");
789 c.execution.arguments = detail::get_string_array(j[
"execution"],
"arguments");
790 c.execution.cwd = detail::get_string(j[
"execution"],
"cwd");
791 c.execution.library_path_env_key = detail::get_string(j[
"execution"],
"library_path_env_key");
792 c.execution.library_paths = detail::get_string_array(j[
"execution"],
"library_paths");
796 if (j.contains(
"environment") && j[
"environment"].is_object()) {
797 for (
auto& [key, val] : j[
"environment"].items()) {
798 if (val.is_string()) {
799 c.environment[key] = val.get<std::string>();
805 if (j.contains(
"permissions") && j[
"permissions"].is_object()) {
806 c.permissions.filesystem = detail::get_string_array(j[
"permissions"],
"filesystem");
807 c.permissions.network = detail::get_string_array(j[
"permissions"],
"network");
811 if (j.contains(
"trust") && j[
"trust"].is_object()) {
812 c.trust = parse_trust_info(j[
"trust"]);
817 }
catch (
const json::exception& e) {
818 result.error = std::string(
"JSON parse error: ") + e.what();