Pharos 0.7.23
Modern Web Framework & Web App Hosting Service.
Loading...
Searching...
No Matches
native_ops_bridge.c
Go to the documentation of this file.
1/**
2 * @file
3 * @brief Bridges native ops commands to doctor, drift, deploy-check, and migration verification report generation.
4 */
5
6#include "native_ops_bridge.h"
7
11#include "native_shell_bridge.h"
12#include "native_support.h"
16
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20#ifdef _WIN32
21#define native_ops_strdup _strdup
22#else
23#include <unistd.h>
24#define native_ops_strdup strdup
25#endif
26
27static int native_environment_valid(const char *value) {
28 return value != NULL && (
29 streq(value, "development") ||
30 streq(value, "test") ||
31 streq(value, "testing") ||
32 streq(value, "staging") ||
33 streq(value, "production")
34 );
35}
36
37static int native_command_exists(const char *command) {
38#ifndef _WIN32
39 char *path_copy;
40 char *segment;
41 char *state = NULL;
42 const char *path_env;
43 if (command == NULL || command[0] == '\0') {
44 return 0;
45 }
46 if (strchr(command, '/') != NULL) {
47 return access(command, X_OK) == 0;
48 }
49 path_env = getenv("PATH");
50 if (path_env == NULL || path_env[0] == '\0') {
51 return 0;
52 }
53 path_copy = native_ops_strdup(path_env);
54 if (path_copy == NULL) {
55 return 0;
56 }
57 for (segment = strtok_r(path_copy, ":", &state);
58 segment != NULL;
59 segment = strtok_r(NULL, ":", &state)) {
60 char candidate[4096];
61 if (segment[0] == '\0') {
62 continue;
63 }
64 snprintf(candidate, sizeof(candidate), "%s/%s", segment, command);
65 if (access(candidate, X_OK) == 0) {
66 free(path_copy);
67 return 1;
68 }
69 }
70 free(path_copy);
71#endif
72 return 0;
73}
74
75static int native_cross_deb_packaging_available(const char *repo_root) {
76#ifndef _WIN32
77 char compose_path[4096];
78 FILE *handle;
79 if (repo_root == NULL || repo_root[0] == '\0') {
80 return 0;
81 }
82 snprintf(compose_path, sizeof(compose_path), "%s/cross/docker/docker-compose.yml", repo_root);
83 handle = fopen(compose_path, "r");
84 if (handle == NULL) {
85 return 0;
86 }
87 fclose(handle);
88 return native_command_exists("docker");
89#else
90 (void)repo_root;
91 return 0;
92#endif
93}
94
95static char *native_default_environment(const AppRuntime *app) {
96 char *environment = app != NULL ? conf_lookup_value(app->runtime_conf_path, "environment") : NULL;
97 if (environment == NULL || environment[0] == '\0') {
98 free(environment);
99 return native_ops_strdup("development");
100 }
101 return environment;
102}
103
104static char *native_default_backend(const AppRuntime *app) {
105 char *backend = app != NULL ? conf_lookup_value(app->runtime_conf_path, "db_backend") : NULL;
106 if (backend == NULL || backend[0] == '\0') {
107 free(backend);
108 return native_ops_strdup("sqlite");
109 }
110 return backend;
111}
112
113static char *native_default_sqlite_path(const AppRuntime *app) {
114 char *db_path = app != NULL ? conf_lookup_value(app->runtime_conf_path, "db_path") : NULL;
115 char buffer[4096];
116 if (db_path != NULL && db_path[0] != '\0') {
117 return db_path;
118 }
119 free(db_path);
120 snprintf(buffer, sizeof(buffer), "/var/lib/pharos/%s.sqlite3", app != NULL && app->app_id != NULL ? app->app_id : "app");
121 return native_ops_strdup(buffer);
122}
123
124static char *native_capture_trimmed(char *const argv[], int *code_out) {
125 char *output = NULL;
126 int code = run_process_capture_native(argv, &output);
127 if (output != NULL) {
128 trim_in_place(output);
129 }
130 if (code_out != NULL) {
131 *code_out = code;
132 }
133 return output;
134}
135
136
137
138static int native_identifier_safe(const char *value) {
139 const char *cursor = value;
140 if (value == NULL || value[0] == '\0') {
141 return 0;
142 }
143 while (*cursor != '\0') {
144 if (!( (*cursor >= 'A' && *cursor <= 'Z') || (*cursor >= 'a' && *cursor <= 'z') || (*cursor >= '0' && *cursor <= '9') || *cursor == '_' )) {
145 return 0;
146 }
147 cursor++;
148 }
149 return 1;
150}
151
152static int native_append_escaped_sql_char(Buffer *buffer, char value) {
153 if (buffer == NULL) {
154 return -1;
155 }
156 switch (value) {
157 case '\'':
158 return buffer_append(buffer, "''", 2);
159 default:
160 return buffer_append(buffer, &value, 1);
161 }
162}
163
164static char *native_sql_literal_escape(const char *value) {
165 Buffer buffer;
166 const char *cursor = value != NULL ? value : "";
167 buffer_init(&buffer);
168 while (*cursor != '\0') {
169 if (native_append_escaped_sql_char(&buffer, *cursor) != 0) {
170 buffer_free(&buffer);
171 return NULL;
172 }
173 cursor++;
174 }
175 return buffer.data;
176}
177
178static const char *native_output_path_status(int file_exists, int file_writable, int parent_exists, int parent_writable) {
179 if (file_exists && !file_writable) {
180 return "not_writable";
181 }
182 if (!file_exists && !parent_exists) {
183 return "missing_parent_dir";
184 }
185 if (!file_exists && !parent_writable) {
186 return "parent_not_writable";
187 }
188 return "ok";
189}
190
191static char *native_sqlite_query_value(const char *db_path, const char *sql) {
192 char *argv_list[4];
193 int code = 0;
194 if (db_path == NULL || sql == NULL) {
195 return NULL;
196 }
197 argv_list[0] = "sqlite3";
198 argv_list[1] = (char *)db_path;
199 argv_list[2] = (char *)sql;
200 argv_list[3] = NULL;
201 return native_capture_trimmed(argv_list, &code);
202}
203
204static int native_sqlite_table_exists(const char *db_path, const char *table_name) {
205 char query[1024];
206 char *escaped = NULL;
207 char *result = NULL;
208 int exists = 0;
209 if (db_path == NULL || !native_identifier_safe(table_name)) {
210 return 0;
211 }
212 escaped = native_sql_literal_escape(table_name);
213 if (escaped == NULL) {
214 return 0;
215 }
216 snprintf(query, sizeof(query), "SELECT name FROM sqlite_master WHERE type = 'table' AND name = '%s' LIMIT 1;", escaped);
217 result = native_sqlite_query_value(db_path, query);
218 if (result != NULL && streq(result, table_name)) {
219 exists = 1;
220 }
221 free(escaped);
222 free(result);
223 return exists;
224}
225
226static long native_sqlite_table_row_count(const char *db_path, const char *table_name) {
227 char query[1024];
228 char *result = NULL;
229 long value = 0;
230 if (db_path == NULL || !native_identifier_safe(table_name)) {
231 return 0;
232 }
233 snprintf(query, sizeof(query), "SELECT COUNT(*) FROM \"%s\";", table_name);
234 result = native_sqlite_query_value(db_path, query);
235 if (result != NULL && result[0] != '\0') {
236 value = strtol(result, NULL, 10);
237 }
238 free(result);
239 return value;
240}
241
242static long native_sqlite_migration_applied_count(const char *db_path, const char *migration_id, const char *backend) {
243 char query[2048];
244 char *escaped_id = NULL;
245 char *escaped_backend = NULL;
246 char *result = NULL;
247 long value = 0;
248 if (db_path == NULL || migration_id == NULL || backend == NULL) {
249 return 0;
250 }
251 escaped_id = native_sql_literal_escape(migration_id);
252 escaped_backend = native_sql_literal_escape(backend);
253 if (escaped_id == NULL || escaped_backend == NULL) {
254 free(escaped_id);
255 free(escaped_backend);
256 return 0;
257 }
258 snprintf(query, sizeof(query), "SELECT COUNT(*) FROM pharos_schema_migration WHERE migration_id = '%s' AND backend = '%s';", escaped_id, escaped_backend);
259 result = native_sqlite_query_value(db_path, query);
260 if (result != NULL && result[0] != '\0') {
261 value = strtol(result, NULL, 10);
262 }
263 free(escaped_id);
264 free(escaped_backend);
265 free(result);
266 return value;
267}
268
269static char *native_sqlite_migration_applied_schema_version(const char *db_path, const char *migration_id, const char *backend) {
270 char query[2048];
271 char *escaped_id = NULL;
272 char *escaped_backend = NULL;
273 char *result = NULL;
274 if (db_path == NULL || migration_id == NULL || backend == NULL) {
275 return NULL;
276 }
277 escaped_id = native_sql_literal_escape(migration_id);
278 escaped_backend = native_sql_literal_escape(backend);
279 if (escaped_id == NULL || escaped_backend == NULL) {
280 free(escaped_id);
281 free(escaped_backend);
282 return NULL;
283 }
284 snprintf(query, sizeof(query), "SELECT schema_version FROM pharos_schema_migration WHERE migration_id = '%s' AND backend = '%s' ORDER BY applied_at DESC LIMIT 1;", escaped_id, escaped_backend);
285 result = native_sqlite_query_value(db_path, query);
286 free(escaped_id);
287 free(escaped_backend);
288 return result;
289}
290
291static char *native_sqlite_migration_applied_checksum(const char *db_path, const char *migration_id, const char *backend) {
292 char query[2048];
293 char *escaped_id = NULL;
294 char *escaped_backend = NULL;
295 char *result = NULL;
296 if (db_path == NULL || migration_id == NULL || backend == NULL) {
297 return NULL;
298 }
299 escaped_id = native_sql_literal_escape(migration_id);
300 escaped_backend = native_sql_literal_escape(backend);
301 if (escaped_id == NULL || escaped_backend == NULL) {
302 free(escaped_id);
303 free(escaped_backend);
304 return NULL;
305 }
306 snprintf(query, sizeof(query), "SELECT checksum_sha256 FROM pharos_schema_migration WHERE migration_id = '%s' AND backend = '%s' ORDER BY applied_at DESC LIMIT 1;", escaped_id, escaped_backend);
307 result = native_sqlite_query_value(db_path, query);
308 free(escaped_id);
309 free(escaped_backend);
310 return result;
311}
312
313
314
315static char *native_output_path_check_json(const char *file_path, const char *label) {
316 char *label_json = json_string_dup_native(label != NULL ? label : "path");
317 char *path_json = NULL;
318 char *parent_json = NULL;
319 char *result = NULL;
320 char *parent_dir = NULL;
321 int file_exists = 0;
322 int file_writable = 0;
323 int parent_exists = 0;
324 int parent_writable = 0;
325 const char *status = "ok";
326 size_t needed;
327 if (file_path == NULL || file_path[0] == '\0') {
328 needed = strlen(label != NULL ? label : "path") + 160;
329 result = (char *)malloc(needed);
330 if (result != NULL) {
331 snprintf(result, needed, "{\"label\":\"%s\",\"configured\":false,\"exists\":false,\"parent_exists\":false,\"parent_writable\":false,\"file_writable\":false,\"status\":\"not_configured\"}", label != NULL ? label : "path");
332 }
333 free(label_json);
334 return result;
335 }
336 parent_dir = path_dirname(file_path);
337 file_exists = path_exists(file_path);
338 file_writable = file_exists && path_is_writable(file_path);
339 parent_exists = parent_dir != NULL && path_is_directory(parent_dir);
340 parent_writable = parent_exists && path_is_writable(parent_dir);
341 status = native_output_path_status(file_exists, file_writable, parent_exists, parent_writable);
342 path_json = json_string_dup_native(file_path);
343 parent_json = json_string_dup_native(parent_dir != NULL ? parent_dir : "");
344 needed = (label_json != NULL ? strlen(label_json) : 0) + (path_json != NULL ? strlen(path_json) : 0) + (parent_json != NULL ? strlen(parent_json) : 0) + strlen(status) + 192;
345 result = (char *)malloc(needed);
346 if (result != NULL && label_json != NULL && path_json != NULL && parent_json != NULL) {
347 snprintf(result, needed, "{\"label\":%s,\"configured\":true,\"path\":%s,\"parent_dir\":%s,\"exists\":%s,\"parent_exists\":%s,\"parent_writable\":%s,\"file_writable\":%s,\"status\":\"%s\"}", label_json, path_json, parent_json, file_exists ? "true" : "false", parent_exists ? "true" : "false", parent_writable ? "true" : "false", file_writable ? "true" : "false", status);
348 }
349 free(label_json);
350 free(path_json);
351 free(parent_json);
352 free(parent_dir);
353 return result;
354}
355
356static char *native_db_check_json(const AppRuntime *app, const char *backend) {
357 char *backend_json = json_string_dup_native(backend != NULL ? backend : "sqlite");
358 char *result = NULL;
359 size_t needed;
360 if (backend != NULL && streq(backend, "sqlite")) {
361 char *sqlite_path = native_default_sqlite_path(app);
362 char *sqlite_path_json = json_string_dup_native(sqlite_path != NULL ? sqlite_path : "");
363 char *parent_dir = path_dirname(sqlite_path != NULL ? sqlite_path : "");
364 char *parent_json = json_string_dup_native(parent_dir != NULL ? parent_dir : "");
365 int path_exists_value = sqlite_path != NULL && path_exists(sqlite_path);
366 int parent_exists_value = parent_dir != NULL && path_is_directory(parent_dir);
367 needed = (backend_json != NULL ? strlen(backend_json) : 0) + (sqlite_path_json != NULL ? strlen(sqlite_path_json) : 0) + (parent_json != NULL ? strlen(parent_json) : 0) + 128;
368 result = (char *)malloc(needed);
369 if (result != NULL && backend_json != NULL && sqlite_path_json != NULL && parent_json != NULL) {
370 snprintf(result, needed, "[{\"backend\":%s,\"kind\":\"sqlite_path\",\"path\":%s,\"parent_dir\":%s,\"path_exists\":%s,\"parent_exists\":%s,\"status\":\"%s\"}]", backend_json, sqlite_path_json, parent_json, path_exists_value ? "true" : "false", parent_exists_value ? "true" : "false", parent_exists_value ? "ok" : "missing_parent_dir");
371 }
372 free(sqlite_path);
373 free(sqlite_path_json);
374 free(parent_dir);
375 free(parent_json);
376 } else {
377 char *host = app != NULL ? conf_lookup_value(app->runtime_conf_path, "db_host") : NULL;
378 char *port = app != NULL ? conf_lookup_value(app->runtime_conf_path, "db_port") : NULL;
379 char *name = app != NULL ? conf_lookup_value(app->runtime_conf_path, "db_name") : NULL;
380 char *user = app != NULL ? conf_lookup_value(app->runtime_conf_path, "db_user") : NULL;
381 char *host_json = json_string_dup_native(host != NULL ? host : "");
382 char *port_json = json_string_dup_native(port != NULL ? port : "");
383 char *name_json = json_string_dup_native(name != NULL ? name : "");
384 char *user_json = json_string_dup_native(user != NULL ? user : "");
385 int host_present = host != NULL && host[0] != '\0';
386 int port_present = port != NULL && port[0] != '\0';
387 int name_present = name != NULL && name[0] != '\0';
388 int user_present = user != NULL && user[0] != '\0';
389 int ok = host_present && port_present && name_present && user_present;
390 needed = (backend_json != NULL ? strlen(backend_json) : 0) + (host_json != NULL ? strlen(host_json) : 0) + (port_json != NULL ? strlen(port_json) : 0) + (name_json != NULL ? strlen(name_json) : 0) + (user_json != NULL ? strlen(user_json) : 0) + 256;
391 result = (char *)malloc(needed);
392 if (result != NULL && backend_json != NULL && host_json != NULL && port_json != NULL && name_json != NULL && user_json != NULL) {
393 snprintf(result, needed, "[{\"backend\":%s,\"kind\":\"postgresql_connection\",\"host\":%s,\"port\":%s,\"database\":%s,\"user\":%s,\"host_present\":%s,\"port_present\":%s,\"database_present\":%s,\"user_present\":%s,\"tls_ok\":true,\"status\":\"%s\"}]", backend_json, host_json, port_json, name_json, user_json, host_present ? "true" : "false", port_present ? "true" : "false", name_present ? "true" : "false", user_present ? "true" : "false", ok ? "ok" : "incomplete");
394 }
395 free(host);
396 free(port);
397 free(name);
398 free(user);
399 free(host_json);
400 free(port_json);
401 free(name_json);
402 free(user_json);
403 }
404 free(backend_json);
405 return result;
406}
407
409 char *environment = native_default_environment(app);
410 char *backend = native_default_backend(app);
411 char *environment_json = json_string_dup_native(environment != NULL ? environment : "development");
412 char *backend_json = json_string_dup_native(backend != NULL ? backend : "sqlite");
413 char *log_path_json = json_string_dup_native(app != NULL && app->log_path != NULL ? app->log_path : "");
414 char *error_report_path_json = (app != NULL && app->error_report_path != NULL && app->error_report_path[0] != '\0') ? json_string_dup_native(app->error_report_path) : native_ops_strdup("null");
415 char *db_checks_json = native_db_check_json(app, backend);
416 char *log_check_json = native_output_path_check_json(app != NULL ? app->log_path : NULL, "app_log_path");
417 char *error_check_json = native_output_path_check_json(app != NULL ? app->error_report_path : NULL, "app_error_report_path");
418 int environment_valid = native_environment_valid(environment != NULL ? environment : "development");
419 int db_ok = db_checks_json != NULL && strstr(db_checks_json, "\"status\":\"ok\"") != NULL && strstr(db_checks_json, "incomplete") == NULL && strstr(db_checks_json, "missing_parent_dir") == NULL;
420 int log_ok = log_check_json != NULL && strstr(log_check_json, "\"status\":\"ok\"") != NULL;
421 int error_ok = error_check_json != NULL && (strstr(error_check_json, "\"status\":\"ok\"") != NULL || strstr(error_check_json, "\"status\":\"not_configured\"") != NULL);
422 const char *status = (environment_valid && db_ok && log_ok && error_ok) ? "ok" : "invalid";
423 char *result = NULL;
424 size_t needed = (environment_json != NULL ? strlen(environment_json) : 0) + (backend_json != NULL ? strlen(backend_json) : 0) + (log_path_json != NULL ? strlen(log_path_json) : 0) + (error_report_path_json != NULL ? strlen(error_report_path_json) : 0) + (db_checks_json != NULL ? strlen(db_checks_json) : 0) + (log_check_json != NULL ? strlen(log_check_json) : 0) + (error_check_json != NULL ? strlen(error_check_json) : 0) + 512;
425 result = (char *)malloc(needed);
426 if (result != NULL && environment_json != NULL && backend_json != NULL && log_path_json != NULL && error_report_path_json != NULL && db_checks_json != NULL && log_check_json != NULL && error_check_json != NULL) {
427 snprintf(result, needed, "{\"environment\":%s,\"backend\":%s,\"environment_valid\":%s,\"log_path\":%s,\"error_report_path\":%s,\"required_env_refs\":[],\"secret_env_refs\":[],\"required_env_checks\":[],\"secret_env_checks\":[],\"db_checks\":%s,\"log_checks\":[%s,%s],\"issues\":[],\"status\":\"%s\"}", environment_json, backend_json, environment_valid ? "true" : "false", log_path_json, error_report_path_json, db_checks_json, log_check_json, error_check_json, status);
428 }
429 free(environment);
430 free(backend);
431 free(environment_json);
432 free(backend_json);
433 free(log_path_json);
434 free(error_report_path_json);
435 free(db_checks_json);
436 free(log_check_json);
437 free(error_check_json);
438 return result;
439}
440
442 char *app_id_json = json_string_dup_native(app != NULL && app->app_id != NULL ? app->app_id : "");
443 char *mount_json = json_string_dup_native(app != NULL && app->base_path != NULL ? app->base_path : "");
444 char *app_root_json = json_string_dup_native(app != NULL && app->app_root != NULL ? app->app_root : "");
445 char *build_dir_json = json_string_dup_native(app != NULL && app->build_dir != NULL ? app->build_dir : "");
446 char *state_path_json = json_string_dup_native(app != NULL && app->state_path != NULL ? app->state_path : "");
447 char *log_path_json = json_string_dup_native(app != NULL && app->log_path != NULL ? app->log_path : "");
448 char *log_format_json = json_string_dup_native(app != NULL && app->log_format != NULL ? app->log_format : "service");
449 char *error_report_path_json = json_string_dup_native(app != NULL && app->error_report_path != NULL ? app->error_report_path : "");
450 char *debug_json = json_string_dup_native(app != NULL && app->debug != NULL ? app->debug : "false");
451 char *host_profile_json = json_string_dup_native(app != NULL && app->host_profile != NULL ? app->host_profile : "dynamic-app");
452 char *compiled_entrypoint_json = json_string_dup_native(app != NULL && app->compiled_entrypoint != NULL ? app->compiled_entrypoint : "");
453 char *runtime_conf_path_json = json_string_dup_native(app != NULL && app->runtime_conf_path != NULL ? app->runtime_conf_path : "");
454 char *result = NULL;
455 size_t needed = (app_id_json != NULL ? strlen(app_id_json) : 0) + (mount_json != NULL ? strlen(mount_json) : 0) + (app_root_json != NULL ? strlen(app_root_json) : 0) + (build_dir_json != NULL ? strlen(build_dir_json) : 0) + (state_path_json != NULL ? strlen(state_path_json) : 0) + (log_path_json != NULL ? strlen(log_path_json) : 0) + (log_format_json != NULL ? strlen(log_format_json) : 0) + (error_report_path_json != NULL ? strlen(error_report_path_json) : 0) + (debug_json != NULL ? strlen(debug_json) : 0) + (host_profile_json != NULL ? strlen(host_profile_json) : 0) + (compiled_entrypoint_json != NULL ? strlen(compiled_entrypoint_json) : 0) + (runtime_conf_path_json != NULL ? strlen(runtime_conf_path_json) : 0) + 384;
456 result = (char *)malloc(needed);
457 if (result != NULL && app_id_json != NULL && mount_json != NULL && app_root_json != NULL && build_dir_json != NULL && state_path_json != NULL && log_path_json != NULL && log_format_json != NULL && error_report_path_json != NULL && debug_json != NULL && host_profile_json != NULL && compiled_entrypoint_json != NULL && runtime_conf_path_json != NULL) {
458 snprintf(result, needed, "{\"app_id\":%s,\"mount_path\":%s,\"app_root\":%s,\"build_dir\":%s,\"state_path\":%s,\"log_path\":%s,\"log_format\":%s,\"error_report_path\":%s,\"debug\":%s,\"host_profile\":%s,\"compiled_entrypoint\":%s,\"runtime_conf_path\":%s}", app_id_json, mount_json, app_root_json, build_dir_json, state_path_json, log_path_json, log_format_json, error_report_path_json, debug_json, host_profile_json, compiled_entrypoint_json, runtime_conf_path_json);
459 }
460 free(app_id_json);
461 free(mount_json);
462 free(app_root_json);
463 free(build_dir_json);
464 free(state_path_json);
465 free(log_path_json);
466 free(log_format_json);
467 free(error_report_path_json);
468 free(debug_json);
469 free(host_profile_json);
470 free(compiled_entrypoint_json);
471 free(runtime_conf_path_json);
472 return result;
473}
474
475static void append_app_common_flags(const ServeConfig *config, char *argv_list[], int *argc) {
476 if (config->conf_path != NULL) {
477 argv_list[(*argc)++] = "--conf";
478 argv_list[(*argc)++] = config->conf_path;
479 }
480 if (config->app_dir != NULL) {
481 argv_list[(*argc)++] = "--app-dir";
482 argv_list[(*argc)++] = config->app_dir;
483 }
484 if (config->build_dir != NULL) {
485 argv_list[(*argc)++] = "--build-dir";
486 argv_list[(*argc)++] = config->build_dir;
487 }
488 if (config->state_dir != NULL) {
489 argv_list[(*argc)++] = "--state-dir";
490 argv_list[(*argc)++] = config->state_dir;
491 }
492 if (config->state_path != NULL) {
493 argv_list[(*argc)++] = "--state-path";
494 argv_list[(*argc)++] = config->state_path;
495 }
496 if (config->base_path != NULL) {
497 argv_list[(*argc)++] = "--base-path";
498 argv_list[(*argc)++] = config->base_path;
499 }
500}
501
503 const ServeConfig *config,
504 const AppRuntime *app,
505 const char *error_text) {
506 char *target_id_json = NULL;
507 char *status_json = NULL;
508 char *error_json = NULL;
509 char *db_path = NULL;
510 char *db_path_json = NULL;
511 char *manifest_path = NULL;
512 char *migration_fixture_rel = NULL;
513 char *entity_fixture_rel = NULL;
514 char *query_fixture_rel = NULL;
515 char *migration_fixture_path = NULL;
516 char *entity_fixture_path = NULL;
517 char *query_fixture_path = NULL;
518 char *migration_fixture_json = NULL;
519 char *entity_fixture_json = NULL;
520 char *query_fixture_json = NULL;
521 char *result = NULL;
522 yyjson_doc *manifest_doc = NULL;
523 yyjson_val *manifest_root = NULL;
524 yyjson_val *database_obj = NULL;
525 size_t needed;
526
527 if (config == NULL || app == NULL) {
528 return NULL;
529 }
530 target_id_json = json_string_dup_native(
531 config->target_id != NULL ? config->target_id : app->app_id);
532 status_json = json_string_dup_native("verification_failed");
533 error_json = json_string_dup_native(
534 error_text != NULL ? error_text : "native sqlite migrate verify unavailable");
535 db_path = native_default_sqlite_path(app);
536 db_path_json = json_string_dup_native(db_path != NULL ? db_path : "");
537
538 manifest_path = path_join(app->app_root, "pharos.app.json");
539 if (manifest_path != NULL && path_exists(manifest_path)) {
540 manifest_doc = native_json_doc_load_file(manifest_path);
541 }
542 if (manifest_doc != NULL) {
543 manifest_root = yyjson_doc_get_root(manifest_doc);
544 database_obj = native_json_obj_get(manifest_root, "database");
545 if (database_obj != NULL) {
546 migration_fixture_rel =
547 native_json_obj_get_string_dup(database_obj, "migration_fixture");
548 entity_fixture_rel =
549 native_json_obj_get_string_dup(database_obj, "entity_fixture");
550 query_fixture_rel = native_json_obj_get_string_dup(database_obj, "query_fixture");
551 }
552 }
553 migration_fixture_path =
554 migration_fixture_rel != NULL ? resolve_relative_to(app->app_root, migration_fixture_rel)
555 : NULL;
556 entity_fixture_path =
557 entity_fixture_rel != NULL ? resolve_relative_to(app->app_root, entity_fixture_rel) : NULL;
558 query_fixture_path =
559 query_fixture_rel != NULL ? resolve_relative_to(app->app_root, query_fixture_rel) : NULL;
560 migration_fixture_json = migration_fixture_path != NULL
561 ? json_string_dup_native(migration_fixture_path)
562 : native_ops_strdup("null");
563 entity_fixture_json = entity_fixture_path != NULL
564 ? json_string_dup_native(entity_fixture_path)
565 : native_ops_strdup("null");
566 query_fixture_json = query_fixture_path != NULL
567 ? json_string_dup_native(query_fixture_path)
568 : native_ops_strdup("null");
569
570 if (target_id_json == NULL || status_json == NULL || error_json == NULL ||
571 db_path_json == NULL || migration_fixture_json == NULL ||
572 entity_fixture_json == NULL || query_fixture_json == NULL) {
573 goto cleanup;
574 }
575
576 needed = strlen(target_id_json) + strlen(status_json) + strlen(error_json) +
577 strlen(db_path_json) + strlen(migration_fixture_json) +
578 strlen(entity_fixture_json) + strlen(query_fixture_json) + 2048;
579 result = (char *)malloc(needed);
580 if (result != NULL) {
581 snprintf(result, needed,
582 "{\"report_id\":\"pharos-migrate-verify-v1\","
583 "\"target_kind\":\"app\",\"target_id\":%s,"
584 "\"backend\":\"sqlite\",\"status\":%s,"
585 "\"error\":%s,\"live_database_verified\":false,"
586 "\"database_present\":false,\"ledger_table_present\":false,"
587 "\"db_path\":%s,\"schema_version\":0,"
588 "\"preferred_backend\":\"sqlite\","
589 "\"development_backend\":\"sqlite\","
590 "\"testing_backend\":\"sqlite\","
591 "\"migration_fixture\":%s,\"entity_fixture\":%s,"
592 "\"query_fixture\":%s,\"db_config\":{\"backend\":\"sqlite\"},"
593 "\"verification_contract\":{},\"required_tables\":[],"
594 "\"required_migration_ids\":[],\"seed_expectations\":[],"
595 "\"table_checks\":[],\"missing_tables\":[],"
596 "\"seed_checks\":[],\"failed_seed_expectations\":[],"
597 "\"migration_checks\":[],\"missing_migrations\":[],"
598 "\"drifted_migrations\":[]}",
599 target_id_json, status_json, error_json, db_path_json,
600 migration_fixture_json, entity_fixture_json, query_fixture_json);
601 }
602
603cleanup:
604 free(target_id_json);
605 free(status_json);
606 free(error_json);
607 free(db_path);
608 free(db_path_json);
609 free(manifest_path);
610 free(migration_fixture_rel);
611 free(entity_fixture_rel);
612 free(query_fixture_rel);
613 free(migration_fixture_path);
614 free(entity_fixture_path);
615 free(query_fixture_path);
616 free(migration_fixture_json);
617 free(entity_fixture_json);
618 free(query_fixture_json);
619 native_json_doc_free(manifest_doc);
620 return result;
621}
622
624 char *manifest_path = NULL;
625 char *migration_fixture_rel = NULL;
626 char *migration_fixture_path = NULL;
627 yyjson_doc *manifest_doc = NULL;
628 yyjson_doc *fixture_doc = NULL;
629 yyjson_val *manifest_root = NULL;
630 yyjson_val *database_obj = NULL;
631 yyjson_val *fixture_root = NULL;
632 yyjson_val *sqlite_backend = NULL;
633 yyjson_val *verification_contract = NULL;
634 yyjson_val *required_tables = NULL;
635 yyjson_val *required_migration_ids = NULL;
636 yyjson_arr_iter migration_iter;
637 yyjson_val *migration_id_value = NULL;
638 int ok = 0;
639
640 if (app == NULL || app->app_root == NULL) {
641 return 0;
642 }
643 manifest_path = path_join(app->app_root, "pharos.app.json");
644 if (manifest_path == NULL || !path_exists(manifest_path)) {
645 goto cleanup;
646 }
647 manifest_doc = native_json_doc_load_file(manifest_path);
648 if (manifest_doc == NULL) {
649 goto cleanup;
650 }
651 manifest_root = yyjson_doc_get_root(manifest_doc);
652 database_obj = native_json_obj_get(manifest_root, "database");
653 if (database_obj == NULL) {
654 goto cleanup;
655 }
656 migration_fixture_rel =
657 native_json_obj_get_string_dup(database_obj, "migration_fixture");
658 if (migration_fixture_rel == NULL || migration_fixture_rel[0] == '\0') {
659 goto cleanup;
660 }
661 migration_fixture_path = resolve_relative_to(app->app_root, migration_fixture_rel);
662 if (migration_fixture_path == NULL || !path_exists(migration_fixture_path)) {
663 goto cleanup;
664 }
665 fixture_doc = native_json_doc_load_file(migration_fixture_path);
666 if (fixture_doc == NULL) {
667 goto cleanup;
668 }
669 fixture_root = yyjson_doc_get_root(fixture_doc);
670 sqlite_backend = native_json_find_backend_by_name(fixture_root, "sqlite");
671 if (sqlite_backend == NULL) {
672 goto cleanup;
673 }
674 verification_contract = native_json_obj_get(sqlite_backend, "verification_contract");
675 if (verification_contract == NULL) {
676 goto cleanup;
677 }
678 required_tables = native_json_obj_get_array(verification_contract, "required_tables");
679 required_migration_ids =
680 native_json_obj_get_array(verification_contract, "required_migration_ids");
681 if (required_tables == NULL || required_migration_ids == NULL) {
682 goto cleanup;
683 }
684 if (!yyjson_arr_iter_init(required_migration_ids, &migration_iter)) {
685 goto cleanup;
686 }
687 while ((migration_id_value = yyjson_arr_iter_next(&migration_iter)) != NULL) {
688 const char *migration_id = NULL;
689 if (!yyjson_is_str(migration_id_value)) {
690 goto cleanup;
691 }
692 migration_id = yyjson_get_str(migration_id_value);
693 if (native_json_find_migration_by_id(fixture_root, migration_id) == NULL) {
694 goto cleanup;
695 }
696 }
697 ok = 1;
698
699cleanup:
700 free(manifest_path);
701 free(migration_fixture_rel);
702 free(migration_fixture_path);
703 native_json_doc_free(manifest_doc);
704 native_json_doc_free(fixture_doc);
705 return ok;
706}
707
708static char *capture_migrate_verify_report_json(const char *repo_root, const ServeConfig *config, const AppRuntime *app, const char *backend, char **status_out, char **error_out) {
709 char *native_report = NULL;
710 char *argv_list[32];
711 int argc = 0;
712 char *output = NULL;
713 int code;
714 int output_is_json = 0;
715 char *status = NULL;
716 if (backend != NULL && streq(backend, "sqlite") && app != NULL) {
719 config, app, status_out, error_out);
720 if (native_report != NULL) {
721 if (strstr(native_report, "\"status\":\"verification_failed\"") != NULL) {
722 free(native_report);
723 if (status_out != NULL) {
724 free(*status_out);
725 *status_out = native_ops_strdup("verification_failed");
726 }
727 if (error_out != NULL) {
728 free(*error_out);
729 *error_out =
730 native_ops_strdup("native sqlite migrate verify unavailable");
731 }
733 config, app, "native sqlite migrate verify unavailable");
734 }
735 if (native_report != NULL) {
736 return native_report;
737 }
738 }
739 } else {
740 if (status_out != NULL) {
741 *status_out = native_ops_strdup("verification_failed");
742 }
743 if (error_out != NULL) {
744 *error_out =
745 native_ops_strdup("native sqlite migrate verify unavailable");
746 }
748 config, app, "native sqlite migrate verify unavailable");
749 if (native_report != NULL) {
750 return native_report;
751 }
752 }
753 }
754 argv_list[argc++] = "migrate";
755 argv_list[argc++] = "verify";
756 argv_list[argc++] = "app";
757 argv_list[argc++] = config->target_id;
758 append_app_common_flags(config, argv_list, &argc);
759 if (backend != NULL && backend[0] != '\0') {
760 argv_list[argc++] = "--backend";
761 argv_list[argc++] = (char *)backend;
762 }
763 argv_list[argc++] = "--text";
764 code = shell_runtime_command_capture_native(repo_root, argc, argv_list, &output);
765 if (output != NULL) {
766 trim_in_place(output);
767 output_is_json = output[0] == '{';
768 }
769 if (output_is_json) {
770 status = json_find_string(output, "status");
771 }
772 if (status_out != NULL) {
773 *status_out = status != NULL ? status : native_ops_strdup(code == 0 ? "verified" : "failed");
774 } else {
775 free(status);
776 }
777 if (output_is_json) {
778 if (error_out != NULL) {
779 *error_out = NULL;
780 }
781 return output;
782 }
783 if (error_out != NULL && *error_out == NULL && output != NULL && output[0] != '\0') {
784 *error_out = native_ops_strdup(output);
785 }
786 free(output);
787 return native_ops_strdup("null");
788}
789
790char *build_native_app_doctor_report_local_json(const char *repo_root, const ServeConfig *config, const char *artifact_dir, const AppRuntime *app, const char *host_target) {
791 char *backend = native_default_backend(app);
792 char *runtime_summary_json = build_runtime_summary_json_from_app(app);
793 char *config_report_json = build_native_app_config_report_local_json(app);
794 char *verify_status = NULL;
795 char *verify_error = NULL;
796 char *verify_report_json = capture_migrate_verify_report_json(repo_root, config, app, backend, &verify_status, &verify_error);
797 char *build_verify_report_json = build_native_app_runtime_report_json("pharos-local-app-build-v1", "verify", host_target, "native-host-target", artifact_dir, app);
798 char *build_verify_status = native_ops_strdup(build_verify_report_json != NULL ? "ok" : "failed");
799 char *build_verify_error = NULL;
800 char *target_id_json = json_string_dup_native(app != NULL && app->app_id != NULL ? app->app_id : "");
801 char *backend_json = json_string_dup_native(backend != NULL ? backend : "sqlite");
802 char *verify_status_json = json_string_dup_native(verify_status != NULL ? verify_status : "failed");
803 char *build_verify_status_json = json_string_dup_native(build_verify_status != NULL ? build_verify_status : "failed");
804 char *verify_error_json = verify_error != NULL && verify_error[0] != '\0' ? json_string_dup_native(verify_error) : native_ops_strdup("null");
805 char *build_verify_error_json = build_verify_error != NULL && build_verify_error[0] != '\0' ? json_string_dup_native(build_verify_error) : native_ops_strdup("null");
806 const char *overall_status = (config_report_json != NULL && strstr(config_report_json, "\"status\":\"ok\"") != NULL && verify_status != NULL && streq(verify_status, "verified") && build_verify_report_json != NULL) ? "ok" : "needs_attention";
807 char *overall_status_json = json_string_dup_native(overall_status);
808 char *result = NULL;
809 size_t needed = (target_id_json != NULL ? strlen(target_id_json) : 0) + (backend_json != NULL ? strlen(backend_json) : 0) + (runtime_summary_json != NULL ? strlen(runtime_summary_json) : 0) + (config_report_json != NULL ? strlen(config_report_json) : 0) + (verify_status_json != NULL ? strlen(verify_status_json) : 0) + (build_verify_status_json != NULL ? strlen(build_verify_status_json) : 0) + (verify_error_json != NULL ? strlen(verify_error_json) : 0) + (build_verify_error_json != NULL ? strlen(build_verify_error_json) : 0) + (verify_report_json != NULL ? strlen(verify_report_json) : 0) + (build_verify_report_json != NULL ? strlen(build_verify_report_json) : 0) + (overall_status_json != NULL ? strlen(overall_status_json) : 0) + 4096;
810 result = (char *)malloc(needed);
811 if (result != NULL && target_id_json != NULL && backend_json != NULL && runtime_summary_json != NULL && config_report_json != NULL && verify_status_json != NULL && build_verify_status_json != NULL && verify_error_json != NULL && build_verify_error_json != NULL && verify_report_json != NULL && build_verify_report_json != NULL && overall_status_json != NULL) {
812 snprintf(result, needed, "{\"report_id\":\"pharos-doctor-v1\",\"target_kind\":\"app\",\"target_id\":%s,\"backend\":%s,\"status\":%s,\"runtime_summary\":%s,\"config\":%s,\"migrate_verify_status\":%s,\"build_verify_status\":%s,\"migrate_verify_error\":%s,\"build_verify_error\":%s,\"migrate_verify_report\":%s,\"build_verify_report\":%s}", target_id_json, backend_json, overall_status_json, runtime_summary_json, config_report_json, verify_status_json, build_verify_status_json, verify_error_json, build_verify_error_json, verify_report_json, build_verify_report_json);
813 }
814 free(backend);
815 free(runtime_summary_json);
816 free(config_report_json);
817 free(verify_status);
818 free(verify_error);
819 free(verify_report_json);
820 free(build_verify_report_json);
821 free(build_verify_status);
822 free(build_verify_error);
823 free(target_id_json);
824 free(backend_json);
825 free(verify_status_json);
826 free(build_verify_status_json);
827 free(verify_error_json);
828 free(build_verify_error_json);
829 free(overall_status_json);
830 return result;
831}
832
833char *build_native_app_drift_report_local_json(const char *repo_root, const ServeConfig *config, const AppRuntime *app) {
834 char *backend = native_default_backend(app);
835 char *verify_status = NULL;
836 char *verify_error = NULL;
837 char *verify_report_json = capture_migrate_verify_report_json(repo_root, config, app, backend, &verify_status, &verify_error);
838 char *target_id_json = json_string_dup_native(app != NULL && app->app_id != NULL ? app->app_id : "");
839 char *backend_json = json_string_dup_native(backend != NULL ? backend : "sqlite");
840 char *stderr_json = verify_error != NULL && verify_error[0] != '\0' ? json_string_dup_native(verify_error) : native_ops_strdup("null");
841 const char *status = verify_status != NULL && streq(verify_status, "verified") ? "clean" : "failed";
842 char *status_json = json_string_dup_native(status);
843 char *result = NULL;
844 size_t needed = (target_id_json != NULL ? strlen(target_id_json) : 0) + (backend_json != NULL ? strlen(backend_json) : 0) + (stderr_json != NULL ? strlen(stderr_json) : 0) + (verify_report_json != NULL ? strlen(verify_report_json) : 0) + (status_json != NULL ? strlen(status_json) : 0) + 2048;
845 result = (char *)malloc(needed);
846 if (result != NULL && target_id_json != NULL && backend_json != NULL && stderr_json != NULL && verify_report_json != NULL && status_json != NULL) {
847 snprintf(result, needed, "{\"report_id\":\"pharos-drift-check-v1\",\"target_kind\":\"app\",\"target_id\":%s,\"backend\":%s,\"status\":%s,\"drifted_migrations\":[],\"missing_tables\":[],\"missing_migrations\":[],\"stderr\":%s,\"verify_report\":%s}", target_id_json, backend_json, status_json, stderr_json, verify_report_json);
848 }
849 free(backend);
850 free(verify_status);
851 free(verify_error);
852 free(verify_report_json);
853 free(target_id_json);
854 free(backend_json);
855 free(stderr_json);
856 free(status_json);
857 return result;
858}
859
860char *build_native_app_deploy_check_report_local_json(const char *repo_root, const ServeConfig *config, const char *artifact_dir, const AppRuntime *app, const char *host_target) {
861 char *doctor_json = build_native_app_doctor_report_local_json(repo_root, config, artifact_dir, app, host_target);
862 const char *doctor_ok = doctor_json != NULL && strstr(doctor_json, "\"status\":\"ok\"") != NULL ? "ready" : "blocked";
863 char *target_id_json = json_string_dup_native(app != NULL && app->app_id != NULL ? app->app_id : "");
864 char *status_json = json_string_dup_native(doctor_ok);
865 char *package_formats_json = native_cross_deb_packaging_available(repo_root) ? native_ops_strdup("[\"deb\"]") : native_ops_strdup("[]");
866 char *result = NULL;
867 size_t needed = (target_id_json != NULL ? strlen(target_id_json) : 0) + (status_json != NULL ? strlen(status_json) : 0) + (package_formats_json != NULL ? strlen(package_formats_json) : 0) + (doctor_json != NULL ? strlen(doctor_json) : 0) + 2048;
868 result = (char *)malloc(needed);
869 if (result != NULL && target_id_json != NULL && status_json != NULL && package_formats_json != NULL && doctor_json != NULL) {
870 snprintf(result, needed, "{\"report_id\":\"pharos-deploy-check-v1\",\"target_kind\":\"app\",\"target_id\":%s,\"status\":%s,\"package_formats\":%s,\"doctor\":%s}", target_id_json, status_json, package_formats_json, doctor_json);
871 }
872 free(doctor_json);
873 free(target_id_json);
874 free(status_json);
875 free(package_formats_json);
876 return result;
877}
char * json_string_dup_native(const char *value)
Duplicates a string while escaping it for JSON string output.
Declares helpers that build native artifact and report payload strings.
char * conf_lookup_value(const char *path, const char *key)
Reads a Pharos conf file and looks up one key.
char * json_find_string(const char *text, const char *key)
Finds the first JSON string value associated with a key.
Declares lightweight JSON and conf value lookup helpers.
char * native_capture_migrate_verify_sqlite_report_json(const ServeConfig *config, const AppRuntime *app, char **status_out, char **error_out)
Captures a sqlite migration verification report for one app.
Declares native migration verification entry points.
static int native_command_exists(const char *command)
static char * native_default_environment(const AppRuntime *app)
static char * native_default_sqlite_path(const AppRuntime *app)
static const char * native_output_path_status(int file_exists, int file_writable, int parent_exists, int parent_writable)
char * build_native_app_drift_report_local_json(const char *repo_root, const ServeConfig *config, const AppRuntime *app)
Builds a JSON drift report for one app using native ops bridges.
static char * native_capture_trimmed(char *const argv[], int *code_out)
#define native_ops_strdup
static int native_environment_valid(const char *value)
static void append_app_common_flags(const ServeConfig *config, char *argv_list[], int *argc)
static char * native_sqlite_query_value(const char *db_path, const char *sql)
char * build_native_app_doctor_report_local_json(const char *repo_root, const ServeConfig *config, const char *artifact_dir, const AppRuntime *app, const char *host_target)
Builds a JSON doctor report for one app using native ops bridges.
static char * build_native_app_config_report_local_json(const AppRuntime *app)
static int native_sqlite_migrate_verify_preflight_ok(const AppRuntime *app)
char * build_native_app_deploy_check_report_local_json(const char *repo_root, const ServeConfig *config, const char *artifact_dir, const AppRuntime *app, const char *host_target)
Builds a JSON deploy-check report for one app using native ops bridges.
static char * native_db_check_json(const AppRuntime *app, const char *backend)
static char * native_sqlite_migration_applied_schema_version(const char *db_path, const char *migration_id, const char *backend)
static char * native_build_sqlite_migrate_verify_failure_report_json(const ServeConfig *config, const AppRuntime *app, const char *error_text)
static char * native_sql_literal_escape(const char *value)
static long native_sqlite_migration_applied_count(const char *db_path, const char *migration_id, const char *backend)
static int native_identifier_safe(const char *value)
static char * native_output_path_check_json(const char *file_path, const char *label)
static char * capture_migrate_verify_report_json(const char *repo_root, const ServeConfig *config, const AppRuntime *app, const char *backend, char **status_out, char **error_out)
static char * native_default_backend(const AppRuntime *app)
static char * native_sqlite_migration_applied_checksum(const char *db_path, const char *migration_id, const char *backend)
static int native_sqlite_table_exists(const char *db_path, const char *table_name)
static int native_append_escaped_sql_char(Buffer *buffer, char value)
static char * build_runtime_summary_json_from_app(const AppRuntime *app)
static long native_sqlite_table_row_count(const char *db_path, const char *table_name)
static int native_cross_deb_packaging_available(const char *repo_root)
Declares native bridge helpers for Pharos ops commands.
int run_process_capture_native(char *const argv[], char **output)
Executes a process and captures its combined output text.
Declares child-process capture helpers.
char * build_native_app_runtime_report_json(const char *report_id, const char *verb, const char *host_target, const char *host_mode, const char *artifact_dir, const AppRuntime *app)
Builds a JSON runtime report for one app artifact and host surface.
Declares builders for native runtime report payloads.
int shell_runtime_command_capture_native(const char *repo_root, int argc, char *const argv[], char **output)
Executes a runtime shell command and captures its combined output.
Declares bridge helpers for invoking Pharos shell command surfaces.
void buffer_init(Buffer *buffer)
Initializes a growable buffer to an empty state.
char * path_join(const char *left, const char *right)
Joins two path segments using the native path separator.
void trim_in_place(char *text)
Trims leading and trailing ASCII whitespace from a string in place.
void buffer_free(Buffer *buffer)
Releases memory owned by a growable buffer.
int streq(const char *a, const char *b)
Tests whether two strings are exactly equal.
int path_is_directory(const char *path)
Tests whether a filesystem path refers to a directory.
char * path_dirname(const char *path)
Returns the parent directory component of a path.
char * resolve_relative_to(const char *base_dir, const char *value)
Resolves a path value relative to a base directory when needed.
int buffer_append(Buffer *buffer, const void *data, size_t len)
Appends bytes to a growable buffer and maintains a trailing NUL byte.
int path_is_writable(const char *path)
Tests whether a filesystem path is writable by the current process.
int path_exists(const char *path)
Tests whether a filesystem path currently exists.
Declares shared low-level support helpers used across the Pharos native runtime.
yyjson_doc * native_json_doc_load_file(const char *path)
Parses a JSON file into an owned yyjson document.
yyjson_val * native_json_find_migration_by_id(yyjson_val *root, const char *migration_id)
Finds a migration record by migration_id under a fixture root.
void native_json_doc_free(yyjson_doc *doc)
Releases a document returned by the native yyjson helpers.
yyjson_val * native_json_find_backend_by_name(yyjson_val *root, const char *backend_name)
Finds a backend record by backend name under a fixture root.
char * native_json_obj_get_string_dup(yyjson_val *obj, const char *key)
Duplicates a string-valued key from a JSON object.
yyjson_val * native_json_obj_get_array(yyjson_val *obj, const char *key)
Looks up an array-valued key from a JSON object value.
yyjson_val * native_json_obj_get(yyjson_val *obj, const char *key)
Looks up a key from a JSON object value.
Declares shared helper wrappers around vendored yyjson parsing and serialization APIs.
Loaded runtime metadata for one Pharos app artifact.
Growable byte buffer used by parsing and string assembly helpers.
char * data
Parsed native serve configuration used to bootstrap the runtime listener.
yyjson_api_inline bool yyjson_is_str(const yyjson_val *val)
Definition yyjson.h:5390
yyjson_api_inline yyjson_val * yyjson_doc_get_root(const yyjson_doc *doc)
Definition yyjson.h:5323
yyjson_api_inline yyjson_val * yyjson_arr_iter_next(yyjson_arr_iter *iter)
Definition yyjson.h:5672
yyjson_api_inline const char * yyjson_get_str(const yyjson_val *val)
Definition yyjson.h:5469
yyjson_api_inline bool yyjson_arr_iter_init(const yyjson_val *arr, yyjson_arr_iter *iter)
Definition yyjson.h:5650