16#include <nlohmann/json.hpp>
21using json = nlohmann::json;
32 std::vector<std::string> warnings;
41inline std::string get_string(
const json& j,
const std::string& key,
const std::string& default_val =
"") {
42 if (j.contains(key) && j[key].is_string()) {
43 return j[key].get<std::string>();
48inline std::vector<std::string> get_string_array(
const json& j,
const std::string& key) {
49 std::vector<std::string> result;
50 if (j.contains(key) && j[key].is_array()) {
51 for (
const auto& item : j[key]) {
52 if (item.is_string()) {
53 result.push_back(item.get<std::string>());
60inline bool get_bool(
const json& j,
const std::string& key,
bool default_val =
false) {
61 if (j.contains(key) && j[key].is_boolean()) {
62 return j[key].get<
bool>();
73inline core::EnvValue parse_env_value(
const json& j) {
77 ev.op = core::EnvOp::Set;
78 ev.value = j.get<std::string>();
83 std::string op_str = detail::get_string(j,
"op",
"set");
84 auto op = core::parse_env_op(op_str);
85 ev.op = op.value_or(core::EnvOp::Set);
86 ev.value = detail::get_string(j,
"value");
87 ev.separator = detail::get_string(j,
"separator",
":");
93inline core::EnvMap parse_env_map(
const json& j) {
96 for (
auto& [key, val] : j.items()) {
97 result[key] = parse_env_value(val);
107inline core::TrustInfo parse_trust_info(
const json& j) {
110 std::string state_str = detail::get_string(j,
"state",
"unknown");
111 auto state = core::parse_trust_state(state_str);
112 ti.state = state.value_or(core::TrustState::Unknown);
114 ti.source = detail::get_string(j,
"source");
115 ti.evaluated_at = detail::get_string(j,
"evaluated_at");
116 ti.expires_at = detail::get_string(j,
"expires_at");
117 ti.inputs_hash = detail::get_string(j,
"inputs_hash");
119 if (j.contains(
"details") && j[
"details"].is_object()) {
120 for (
auto& [key, val] : j[
"details"].items()) {
121 if (val.is_string()) {
122 ti.details[key] = val.get<std::string>();
134inline core::LoaderConfig parse_loader_config(
const json& j) {
135 core::LoaderConfig lc;
136 lc.exec_path = detail::get_string(j,
"exec_path");
137 lc.args_template = detail::get_string_array(j,
"args_template");
145inline core::ComponentDecl parse_component(
const json& j) {
146 core::ComponentDecl comp;
148 comp.id = detail::get_string(j,
"id");
149 comp.name = detail::get_string(j,
"name");
150 comp.description = detail::get_string(j,
"description");
151 comp.icon = detail::get_string(j,
"icon");
152 comp.entrypoint = detail::get_string(j,
"entrypoint");
153 comp.uri_pattern = detail::get_string(j,
"uri_pattern");
154 comp.loader = detail::get_string(j,
"loader");
155 comp.standalone = detail::get_bool(j,
"standalone",
true);
156 comp.hidden = detail::get_bool(j,
"hidden",
false);
159 if (j.contains(
"environment") && j[
"environment"].is_object()) {
160 comp.environment = parse_env_map(j[
"environment"]);
164 if (j.contains(
"permissions") && j[
"permissions"].is_object()) {
165 comp.permissions_filesystem = detail::get_string_array(j[
"permissions"],
"filesystem");
166 comp.permissions_network = detail::get_string_array(j[
"permissions"],
"network");
170 if (j.contains(
"metadata") && j[
"metadata"].is_object()) {
171 for (
auto& [key, value] : j[
"metadata"].items()) {
172 if (value.is_string()) {
173 comp.metadata[key] = value.get<std::string>();
185inline ParseResult<core::AppDeclaration> parse_app_declaration(
const std::string& json_str) {
186 ParseResult<core::AppDeclaration> result;
189 json j = json::parse(json_str);
192 if (j.contains(
"app") && j[
"app"].is_object()) {
196 auto& app = result.value;
199 if (j.contains(
"identity") && j[
"identity"].is_object()) {
201 auto& identity = j[
"identity"];
202 app.id = detail::get_string(identity,
"id");
203 app.version = detail::get_string(identity,
"version");
204 app.nak_id = detail::get_string(identity,
"nak_id");
205 app.nak_version_req = detail::get_string(identity,
"nak_version_req");
208 app.id = detail::get_string(j,
"id");
209 app.version = detail::get_string(j,
"version");
212 if (j.contains(
"nak") && j[
"nak"].is_object()) {
214 app.nak_id = detail::get_string(j[
"nak"],
"id");
215 app.nak_version_req = detail::get_string(j[
"nak"],
"version_req");
218 app.nak_id = detail::get_string(j,
"nak_id");
219 app.nak_version_req = detail::get_string(j,
"nak_version_req");
223 if (app.id.empty()) {
224 result.error =
"missing required field: id";
227 if (app.version.empty()) {
228 result.error =
"missing required field: version";
233 if (j.contains(
"execution") && j[
"execution"].is_object()) {
235 auto& execution = j[
"execution"];
236 app.entrypoint_path = detail::get_string(execution,
"entrypoint");
237 app.entrypoint_args = detail::get_string_array(execution,
"args");
238 app.nak_loader = detail::get_string(execution,
"loader");
239 }
else if (j.contains(
"entrypoint")) {
241 if (j[
"entrypoint"].is_object()) {
242 app.entrypoint_path = detail::get_string(j[
"entrypoint"],
"path");
243 app.entrypoint_args = detail::get_string_array(j[
"entrypoint"],
"args");
244 }
else if (j[
"entrypoint"].is_string()) {
245 app.entrypoint_path = j[
"entrypoint"].get<std::string>();
248 app.entrypoint_path = detail::get_string(j,
"entrypoint_path");
249 app.entrypoint_args = detail::get_string_array(j,
"entrypoint_args");
252 if (app.entrypoint_path.empty()) {
253 result.error =
"missing required field: entrypoint path";
258 if (j.contains(
"layout") && j[
"layout"].is_object()) {
260 auto& layout = j[
"layout"];
261 app.lib_dirs = detail::get_string_array(layout,
"lib_dirs");
262 app.asset_dirs = detail::get_string_array(layout,
"asset_dirs");
265 app.lib_dirs = detail::get_string_array(j,
"lib_dirs");
266 app.asset_dirs = detail::get_string_array(j,
"asset_dirs");
270 app.env_vars = detail::get_string_array(j,
"env_vars");
271 if (j.contains(
"environment") && j[
"environment"].is_object()) {
273 for (
auto& [key, value] : j[
"environment"].items()) {
274 if (value.is_string()) {
275 app.env_vars.push_back(key +
"=" + value.get<std::string>());
281 if (j.contains(
"exports") && j[
"exports"].is_array()) {
282 for (
const auto& exp : j[
"exports"]) {
283 core::AssetExportDecl aed;
284 aed.id = detail::get_string(exp,
"id");
285 aed.path = detail::get_string(exp,
"path");
286 aed.type = detail::get_string(exp,
"type");
287 app.asset_exports.push_back(aed);
289 }
else if (j.contains(
"asset_exports") && j[
"asset_exports"].is_array()) {
290 for (
const auto& exp : j[
"asset_exports"]) {
291 core::AssetExportDecl aed;
292 aed.id = detail::get_string(exp,
"id");
293 aed.path = detail::get_string(exp,
"path");
294 aed.type = detail::get_string(exp,
"type");
295 app.asset_exports.push_back(aed);
300 if (j.contains(
"permissions") && j[
"permissions"].is_object()) {
301 app.permissions_filesystem = detail::get_string_array(j[
"permissions"],
"filesystem");
302 app.permissions_network = detail::get_string_array(j[
"permissions"],
"network");
306 if (j.contains(
"metadata") && j[
"metadata"].is_object()) {
307 app.description = detail::get_string(j[
"metadata"],
"description");
308 app.author = detail::get_string(j[
"metadata"],
"author");
309 app.license = detail::get_string(j[
"metadata"],
"license");
310 app.homepage = detail::get_string(j[
"metadata"],
"homepage");
312 app.description = detail::get_string(j,
"description");
313 app.author = detail::get_string(j,
"author");
314 app.license = detail::get_string(j,
"license");
315 app.homepage = detail::get_string(j,
"homepage");
319 if (j.contains(
"components") && j[
"components"].is_object()) {
320 const auto& comps = j[
"components"];
321 if (comps.contains(
"provides") && comps[
"provides"].is_array()) {
322 for (
const auto& comp_json : comps[
"provides"]) {
323 app.components.push_back(parse_component(comp_json));
330 }
catch (
const json::exception& e) {
331 result.error = std::string(
"JSON parse error: ") + e.what();
341inline ParseResult<core::HostEnvironment> parse_host_environment(
const json& j,
342 const std::string& source_path =
"") {
343 ParseResult<core::HostEnvironment> result;
346 auto& host_env = result.value;
348 host_env.source_path = source_path;
351 if (j.contains(
"environment") && j[
"environment"].is_object()) {
352 host_env.vars = parse_env_map(j[
"environment"]);
356 if (j.contains(
"paths") && j[
"paths"].is_object()) {
357 host_env.paths.library_prepend = detail::get_string_array(j[
"paths"],
"library_prepend");
358 host_env.paths.library_append = detail::get_string_array(j[
"paths"],
"library_append");
362 if (j.contains(
"overrides") && j[
"overrides"].is_object()) {
363 const auto& ovr = j[
"overrides"];
364 host_env.overrides.allow_env_overrides = detail::get_bool(ovr,
"allow_env_overrides",
true);
365 host_env.overrides.allowed_env_keys = detail::get_string_array(ovr,
"allowed_env_keys");
370 }
catch (
const json::exception& e) {
371 result.error = std::string(
"JSON parse error: ") + e.what();
377inline ParseResult<core::HostEnvironment> parse_host_environment(
const std::string& json_str,
378 const std::string& source_path =
"") {
379 ParseResult<core::HostEnvironment> result;
382 json j = json::parse(json_str);
383 return parse_host_environment(j, source_path);
384 }
catch (
const json::exception& e) {
385 result.error = std::string(
"JSON parse error: ") + e.what();
395inline ParseResult<core::InstallRecord> parse_install_record(
const std::string& json_str,
396 const std::string& source_path =
"") {
397 ParseResult<core::InstallRecord> result;
400 json j = json::parse(json_str);
401 auto& ir = result.value;
403 ir.source_path = source_path;
406 if (j.contains(
"install") && j[
"install"].is_object()) {
407 ir.install.instance_id = detail::get_string(j[
"install"],
"instance_id");
410 if (ir.install.instance_id.empty()) {
411 result.error =
"missing required field: install.instance_id";
416 if (j.contains(
"app") && j[
"app"].is_object()) {
417 ir.app.id = detail::get_string(j[
"app"],
"id");
418 ir.app.version = detail::get_string(j[
"app"],
"version");
419 ir.app.nak_id = detail::get_string(j[
"app"],
"nak_id");
420 ir.app.nak_version_req = detail::get_string(j[
"app"],
"nak_version_req");
424 if (j.contains(
"nak") && j[
"nak"].is_object()) {
425 ir.nak.id = detail::get_string(j[
"nak"],
"id");
426 ir.nak.version = detail::get_string(j[
"nak"],
"version");
427 ir.nak.record_ref = detail::get_string(j[
"nak"],
"record_ref");
428 ir.nak.loader = detail::get_string(j[
"nak"],
"loader");
429 ir.nak.selection_reason = detail::get_string(j[
"nak"],
"selection_reason");
433 if (j.contains(
"paths") && j[
"paths"].is_object()) {
434 ir.paths.install_root = detail::get_string(j[
"paths"],
"install_root");
437 if (ir.paths.install_root.empty()) {
438 result.error =
"missing required field: paths.install_root";
443 if (j.contains(
"provenance") && j[
"provenance"].is_object()) {
444 ir.provenance.package_hash = detail::get_string(j[
"provenance"],
"package_hash");
445 ir.provenance.installed_at = detail::get_string(j[
"provenance"],
"installed_at");
446 ir.provenance.installed_by = detail::get_string(j[
"provenance"],
"installed_by");
447 ir.provenance.source = detail::get_string(j[
"provenance"],
"source");
451 if (j.contains(
"trust") && j[
"trust"].is_object()) {
452 ir.trust = parse_trust_info(j[
"trust"]);
456 if (j.contains(
"overrides") && j[
"overrides"].is_object()) {
457 const auto& ovr = j[
"overrides"];
459 if (ovr.contains(
"environment") && ovr[
"environment"].is_object()) {
460 ir.overrides.environment = parse_env_map(ovr[
"environment"]);
463 if (ovr.contains(
"arguments") && ovr[
"arguments"].is_object()) {
464 ir.overrides.arguments.prepend = detail::get_string_array(ovr[
"arguments"],
"prepend");
465 ir.overrides.arguments.append = detail::get_string_array(ovr[
"arguments"],
"append");
468 if (ovr.contains(
"paths") && ovr[
"paths"].is_object()) {
469 ir.overrides.paths.library_prepend = detail::get_string_array(ovr[
"paths"],
"library_prepend");
475 }
catch (
const json::exception& e) {
476 result.error = std::string(
"JSON parse error: ") + e.what();
486inline ParseResult<core::RuntimeDescriptor> parse_runtime_descriptor(
const std::string& json_str,
487 const std::string& source_path =
"") {
488 ParseResult<core::RuntimeDescriptor> result;
491 json j = json::parse(json_str);
492 auto& rd = result.value;
494 rd.source_path = source_path;
497 if (j.contains(
"nak") && j[
"nak"].is_object()) {
498 rd.nak.id = detail::get_string(j[
"nak"],
"id");
499 rd.nak.version = detail::get_string(j[
"nak"],
"version");
502 if (rd.nak.id.empty()) {
503 result.error =
"missing required field: nak.id";
506 if (rd.nak.version.empty()) {
507 result.error =
"missing required field: nak.version";
512 if (j.contains(
"paths") && j[
"paths"].is_object()) {
513 rd.paths.root = detail::get_string(j[
"paths"],
"root");
514 rd.paths.resource_root = detail::get_string(j[
"paths"],
"resource_root");
515 rd.paths.lib_dirs = detail::get_string_array(j[
"paths"],
"lib_dirs");
518 if (rd.paths.root.empty()) {
519 result.error =
"missing required field: paths.root";
523 if (rd.paths.resource_root.empty()) {
524 rd.paths.resource_root = rd.paths.root;
528 if (j.contains(
"environment") && j[
"environment"].is_object()) {
529 rd.environment = parse_env_map(j[
"environment"]);
533 if (j.contains(
"loaders") && j[
"loaders"].is_object()) {
534 for (
auto& [name, config] : j[
"loaders"].items()) {
535 rd.loaders[name] = parse_loader_config(config);
540 if (j.contains(
"execution") && j[
"execution"].is_object()) {
541 rd.execution.present =
true;
542 rd.execution.cwd = detail::get_string(j[
"execution"],
"cwd");
546 if (j.contains(
"provenance") && j[
"provenance"].is_object()) {
547 rd.provenance.package_hash = detail::get_string(j[
"provenance"],
"package_hash");
548 rd.provenance.installed_at = detail::get_string(j[
"provenance"],
"installed_at");
549 rd.provenance.installed_by = detail::get_string(j[
"provenance"],
"installed_by");
550 rd.provenance.source = detail::get_string(j[
"provenance"],
"source");
555 }
catch (
const json::exception& e) {
556 result.error = std::string(
"JSON parse error: ") + e.what();
566using core::serialize_contract;
567using core::serialize_result;
573inline ParseResult<core::LaunchContract> parse_launch_contract(
const std::string& json_str) {
574 ParseResult<core::LaunchContract> result;
577 json j = json::parse(json_str);
578 auto& c = result.value;
581 if (j.contains(
"app") && j[
"app"].is_object()) {
582 c.app.id = detail::get_string(j[
"app"],
"id");
583 c.app.version = detail::get_string(j[
"app"],
"version");
584 c.app.root = detail::get_string(j[
"app"],
"root");
585 c.app.entrypoint = detail::get_string(j[
"app"],
"entrypoint");
589 if (j.contains(
"nak") && j[
"nak"].is_object()) {
590 c.nak.id = detail::get_string(j[
"nak"],
"id");
591 c.nak.version = detail::get_string(j[
"nak"],
"version");
592 c.nak.root = detail::get_string(j[
"nak"],
"root");
593 c.nak.resource_root = detail::get_string(j[
"nak"],
"resource_root");
594 c.nak.record_ref = detail::get_string(j[
"nak"],
"record_ref");
598 if (j.contains(
"execution") && j[
"execution"].is_object()) {
599 c.execution.binary = detail::get_string(j[
"execution"],
"binary");
600 c.execution.arguments = detail::get_string_array(j[
"execution"],
"arguments");
601 c.execution.cwd = detail::get_string(j[
"execution"],
"cwd");
602 c.execution.library_path_env_key = detail::get_string(j[
"execution"],
"library_path_env_key");
603 c.execution.library_paths = detail::get_string_array(j[
"execution"],
"library_paths");
607 if (j.contains(
"environment") && j[
"environment"].is_object()) {
608 for (
auto& [key, val] : j[
"environment"].items()) {
609 if (val.is_string()) {
610 c.environment[key] = val.get<std::string>();
616 if (j.contains(
"enforcement") && j[
"enforcement"].is_object()) {
617 c.enforcement.filesystem = detail::get_string_array(j[
"enforcement"],
"filesystem");
618 c.enforcement.network = detail::get_string_array(j[
"enforcement"],
"network");
622 if (j.contains(
"trust") && j[
"trust"].is_object()) {
623 c.trust = parse_trust_info(j[
"trust"]);
627 if (j.contains(
"capability_usage") && j[
"capability_usage"].is_object()) {
628 c.capability_usage.present = detail::get_bool(j[
"capability_usage"],
"present");
629 c.capability_usage.required_capabilities =
630 detail::get_string_array(j[
"capability_usage"],
"required_capabilities");
631 c.capability_usage.optional_capabilities =
632 detail::get_string_array(j[
"capability_usage"],
"optional_capabilities");
633 c.capability_usage.critical_capabilities =
634 detail::get_string_array(j[
"capability_usage"],
"critical_capabilities");
639 }
catch (
const json::exception& e) {
640 result.error = std::string(
"JSON parse error: ") + e.what();