emme coverage


Directory: src/
File: src/config.c
Date: 2026-03-27 20:24:50
Exec Total Coverage
Lines: 216 312 69.2%
Functions: 14 14 100.0%
Branches: 133 246 54.1%

Line Branch Exec Source
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <strings.h>
5 #include <errno.h>
6 #include <limits.h>
7 #include <yaml.h>
8 #include "log.h"
9 #include "config.h"
10
11 240 static yaml_node_t *find_yaml_node(yaml_document_t *doc, yaml_node_t *node, const char *key)
12 {
13
2/4
✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 240 times.
240 if (!node || node->type != YAML_MAPPING_NODE)
14 return NULL;
15
16 240 for (yaml_node_pair_t *pair = node->data.mapping.pairs.start;
17
2/2
✓ Branch 0 taken 624 times.
✓ Branch 1 taken 26 times.
650 pair < node->data.mapping.pairs.top; pair++) {
18 624 yaml_node_t *key_node = yaml_document_get_node(doc, pair->key);
19
2/4
✓ Branch 0 taken 624 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 624 times.
✗ Branch 3 not taken.
624 if (key_node && key_node->type == YAML_SCALAR_NODE &&
20
2/2
✓ Branch 0 taken 214 times.
✓ Branch 1 taken 410 times.
624 strcmp((char *)key_node->data.scalar.value, key) == 0) {
21 214 return yaml_document_get_node(doc, pair->value);
22 }
23 }
24 26 return NULL;
25 }
26
27 13 static void set_config_defaults(ServerConfig *config)
28 {
29 13 memset(config, 0, sizeof(*config));
30 13 config->port = 8443;
31 13 config->max_connections = 100;
32 13 snprintf(config->log_level, sizeof(config->log_level), "info");
33
34 13 config->logging.level = LOG_LEVEL_DEBUG;
35 13 config->logging.format = LOG_FORMAT_PLAIN;
36 13 config->logging.buffer_size = 16384;
37 13 config->logging.rollover_size = 10485760;
38 13 config->logging.rollover_daily = 1;
39 13 config->logging.appender_flags = APPENDER_FILE | APPENDER_CONSOLE;
40 13 snprintf(config->logging.file, sizeof(config->logging.file), "emme.log");
41
42 13 snprintf(config->ssl.certificate, sizeof(config->ssl.certificate), "certs/dev.crt");
43 13 snprintf(config->ssl.private_key, sizeof(config->ssl.private_key), "certs/dev.key");
44 13 }
45
46 116 static int get_yaml_string(yaml_node_t *node, const char *field, char *buffer, size_t size)
47 {
48 int written;
49 const char *value;
50
51
2/4
✓ Branch 0 taken 116 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 116 times.
116 if (!node || node->type != YAML_SCALAR_NODE) {
52 fprintf(stderr, "Invalid '%s': expected a scalar string\n", field);
53 return -1;
54 }
55
56 116 value = (const char *)node->data.scalar.value;
57 116 written = snprintf(buffer, size, "%s", value);
58
2/4
✓ Branch 0 taken 116 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 116 times.
116 if (written < 0 || (size_t)written >= size) {
59 fprintf(stderr, "Invalid '%s': value too long\n", field);
60 return -1;
61 }
62 116 return 0;
63 }
64
65 19 static int parse_int_in_range(const char *text, int min, int max, int *result)
66 {
67 19 char *end = NULL;
68 long value;
69
70 19 errno = 0;
71 19 value = strtol(text, &end, 10);
72
4/6
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 18 times.
19 if (errno != 0 || end == text || *end != '\0')
73 1 return -1;
74
2/4
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 18 times.
18 if (value < min || value > max)
75 return -1;
76
77 18 *result = (int)value;
78 18 return 0;
79 }
80
81 18 static int parse_size_in_range(const char *text, size_t min, size_t max, size_t *result)
82 {
83 18 char *end = NULL;
84 unsigned long long value;
85
86 18 errno = 0;
87 18 value = strtoull(text, &end, 10);
88
3/6
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 18 times.
18 if (errno != 0 || end == text || *end != '\0')
89 return -1;
90
2/4
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 18 times.
18 if (value < min || value > max)
91 return -1;
92
93 18 *result = (size_t)value;
94 18 return 0;
95 }
96
97 19 static int get_yaml_int_in_range(yaml_node_t *node, const char *field,
98 int min, int max, int *result)
99 {
100
2/4
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 19 times.
19 if (!node || node->type != YAML_SCALAR_NODE) {
101 fprintf(stderr, "Invalid '%s': expected integer scalar\n", field);
102 return -1;
103 }
104
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 18 times.
19 if (parse_int_in_range((const char *)node->data.scalar.value, min, max, result) != 0) {
105 1 fprintf(stderr, "Invalid '%s': expected integer in range [%d, %d]\n",
106 field, min, max);
107 1 return -1;
108 }
109 18 return 0;
110 }
111
112 18 static int get_yaml_size_in_range(yaml_node_t *node, const char *field,
113 size_t min, size_t max, size_t *result)
114 {
115
2/4
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 18 times.
18 if (!node || node->type != YAML_SCALAR_NODE) {
116 fprintf(stderr, "Invalid '%s': expected integer scalar\n", field);
117 return -1;
118 }
119
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
18 if (parse_size_in_range((const char *)node->data.scalar.value, min, max, result) != 0) {
120 fprintf(stderr, "Invalid '%s': expected integer in range [%zu, %zu]\n",
121 field, min, max);
122 return -1;
123 }
124 18 return 0;
125 }
126
127 9 static int get_yaml_bool(yaml_node_t *node, const char *field, int *result)
128 {
129 const char *value;
130
131
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
9 if (!node || node->type != YAML_SCALAR_NODE) {
132 fprintf(stderr, "Invalid '%s': expected boolean scalar\n", field);
133 return -1;
134 }
135
136 9 value = (const char *)node->data.scalar.value;
137
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
9 if (strcasecmp(value, "true") == 0 || strcmp(value, "1") == 0 || strcasecmp(value, "yes") == 0) {
138 9 *result = 1;
139 9 return 0;
140 }
141 if (strcasecmp(value, "false") == 0 || strcmp(value, "0") == 0 || strcasecmp(value, "no") == 0) {
142 *result = 0;
143 return 0;
144 }
145
146 fprintf(stderr, "Invalid '%s': expected true/false\n", field);
147 return -1;
148 }
149
150 9 static int parse_log_level(const char *value, LogLevel *level)
151 {
152
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if (strcasecmp(value, "debug") == 0)
153 9 *level = LOG_LEVEL_DEBUG;
154 else if (strcasecmp(value, "info") == 0)
155 *level = LOG_LEVEL_INFO;
156 else if (strcasecmp(value, "warn") == 0)
157 *level = LOG_LEVEL_WARN;
158 else if (strcasecmp(value, "error") == 0)
159 *level = LOG_LEVEL_ERROR;
160 else
161 return -1;
162 9 return 0;
163 }
164
165 9 static int parse_log_format(const char *value, LogFormat *format)
166 {
167
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if (strcasecmp(value, "plain") == 0)
168 9 *format = LOG_FORMAT_PLAIN;
169 else if (strcasecmp(value, "json") == 0)
170 *format = LOG_FORMAT_JSON;
171 else
172 return -1;
173 9 return 0;
174 }
175
176 9 static int parse_appender_flags(yaml_document_t *doc, yaml_node_t *node, LoggingConfig *log_cfg)
177 {
178
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (!node)
179 return 0;
180
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (node->type != YAML_SEQUENCE_NODE) {
181 fprintf(stderr, "Invalid 'logging.appender_flags': expected sequence\n");
182 return -1;
183 }
184
185 9 log_cfg->appender_flags = 0;
186 9 for (yaml_node_item_t *item = node->data.sequence.items.start;
187
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 9 times.
27 item < node->data.sequence.items.top; item++) {
188 18 yaml_node_t *flag_node = yaml_document_get_node(doc, *item);
189
2/4
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 18 times.
18 if (!flag_node || flag_node->type != YAML_SCALAR_NODE) {
190 fprintf(stderr, "Invalid 'logging.appender_flags': entries must be scalars\n");
191 return -1;
192 }
193 18 const char *flag = (const char *)flag_node->data.scalar.value;
194
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 9 times.
18 if (strcasecmp(flag, "file") == 0)
195 9 log_cfg->appender_flags |= APPENDER_FILE;
196
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 else if (strcasecmp(flag, "console") == 0)
197 9 log_cfg->appender_flags |= APPENDER_CONSOLE;
198 else {
199 fprintf(stderr, "Invalid appender flag '%s': expected 'file' or 'console'\n", flag);
200 return -1;
201 }
202 }
203
204
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (log_cfg->appender_flags == 0) {
205 fprintf(stderr, "Invalid 'logging.appender_flags': at least one appender required\n");
206 return -1;
207 }
208 9 return 0;
209 }
210
211 8 static int validate_backend(const char *backend)
212 {
213 char host[64];
214 int port;
215 char trailing;
216
217
2/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
8 if (!backend || backend[0] == '\0')
218 return -1;
219
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
8 if (sscanf(backend, "%63[^:]:%d%c", host, &port, &trailing) != 2)
220 1 return -1;
221
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (host[0] == '\0')
222 return -1;
223
2/4
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
7 if (port < 1 || port > 65535)
224 return -1;
225 7 return 0;
226 }
227
228 12 static int validate_routes(const ServerConfig *config)
229 {
230
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 9 times.
28 for (int i = 0; i < config->route_count; i++) {
231 19 const Route *route = &config->routes[i];
232
233
2/4
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 19 times.
19 if (route->path[0] == '\0' || route->path[0] != '/') {
234 fprintf(stderr, "Invalid routes[%d].path: must start with '/'\n", i);
235 return -1;
236 }
237
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
19 if (route->technology[0] == '\0') {
238 fprintf(stderr, "Invalid routes[%d].technology: required\n", i);
239 return -1;
240 }
241
242
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 9 times.
19 if (strcmp(route->technology, "static") == 0) {
243
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 9 times.
10 if (route->document_root[0] == '\0') {
244 1 fprintf(stderr, "Invalid routes[%d].document_root: required for static route\n", i);
245 1 return -1;
246 }
247
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 times.
9 } else if (strcmp(route->technology, "reverse_proxy") == 0) {
248
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 7 times.
8 if (validate_backend(route->backend) != 0) {
249 1 fprintf(stderr, "Invalid routes[%d].backend: expected host:port\n", i);
250 1 return -1;
251 }
252 } else {
253 1 fprintf(stderr, "Invalid routes[%d].technology '%s': unsupported\n", i, route->technology);
254 1 return -1;
255 }
256 }
257
258 9 return 0;
259 }
260
261 13 int load_config(ServerConfig *config, const char *file_path)
262 {
263 13 FILE *fh = NULL;
264 yaml_parser_t parser;
265 yaml_document_t document;
266 yaml_node_t *root;
267 yaml_node_t *node;
268 13 int parser_ready = 0;
269 13 int document_ready = 0;
270 13 int rc = -1;
271
272
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
13 if (!config || !file_path) {
273 fprintf(stderr, "Invalid input: config and file_path are required\n");
274 return -1;
275 }
276
277 13 set_config_defaults(config);
278
279 13 fh = fopen(file_path, "rb");
280
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if (!fh) {
281 fprintf(stderr, "Error opening configuration file: %s\n", file_path);
282 return -1;
283 }
284
285
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
13 if (!yaml_parser_initialize(&parser)) {
286 fprintf(stderr, "Unable to initialize YAML parser\n");
287 goto cleanup;
288 }
289 13 parser_ready = 1;
290 13 yaml_parser_set_input_file(&parser, fh);
291
292
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
13 if (!yaml_parser_load(&parser, &document)) {
293 fprintf(stderr, "Error parsing YAML configuration file\n");
294 goto cleanup;
295 }
296 13 document_ready = 1;
297
298 13 root = yaml_document_get_root_node(&document);
299
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
13 if (!root || root->type != YAML_MAPPING_NODE) {
300 fprintf(stderr, "YAML document is not a mapping\n");
301 goto cleanup;
302 }
303
304 13 node = find_yaml_node(&document, root, "server");
305
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 3 times.
13 if (node) {
306
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (node->type != YAML_MAPPING_NODE) {
307 fprintf(stderr, "Invalid 'server': expected mapping\n");
308 goto cleanup;
309 }
310
311 10 yaml_node_t *port_node = find_yaml_node(&document, node, "port");
312
3/4
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 9 times.
10 if (port_node && get_yaml_int_in_range(port_node, "server.port", 1, 65535, &config->port) != 0)
313 1 goto cleanup;
314
315 9 yaml_node_t *max_conn_node = find_yaml_node(&document, node, "max_connections");
316
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
18 if (max_conn_node &&
317 9 get_yaml_int_in_range(max_conn_node, "server.max_connections", 1, 1000000,
318 &config->max_connections) != 0)
319 goto cleanup;
320
321 9 yaml_node_t *log_level_node = find_yaml_node(&document, node, "log_level");
322
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
18 if (log_level_node &&
323 9 get_yaml_string(log_level_node, "server.log_level",
324 9 config->log_level, sizeof(config->log_level)) != 0)
325 goto cleanup;
326 }
327
328 12 node = find_yaml_node(&document, root, "logging");
329
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 3 times.
12 if (node) {
330
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (node->type != YAML_MAPPING_NODE) {
331 fprintf(stderr, "Invalid 'logging': expected mapping\n");
332 goto cleanup;
333 }
334
335 9 yaml_node_t *file_node = find_yaml_node(&document, node, "file");
336
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
18 if (file_node &&
337 9 get_yaml_string(file_node, "logging.file",
338 9 config->logging.file, sizeof(config->logging.file)) != 0)
339 goto cleanup;
340
341 9 yaml_node_t *level_node = find_yaml_node(&document, node, "level");
342
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if (level_node) {
343 char level_str[16];
344
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
9 if (get_yaml_string(level_node, "logging.level", level_str, sizeof(level_str)) != 0)
345 goto cleanup;
346
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
9 if (parse_log_level(level_str, &config->logging.level) != 0) {
347 fprintf(stderr, "Invalid 'logging.level': expected debug/info/warn/error\n");
348 goto cleanup;
349 }
350 }
351
352 9 yaml_node_t *format_node = find_yaml_node(&document, node, "format");
353
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if (format_node) {
354 char format_str[16];
355
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
9 if (get_yaml_string(format_node, "logging.format", format_str, sizeof(format_str)) != 0)
356 goto cleanup;
357
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
9 if (parse_log_format(format_str, &config->logging.format) != 0) {
358 fprintf(stderr, "Invalid 'logging.format': expected plain/json\n");
359 goto cleanup;
360 }
361 }
362
363 9 yaml_node_t *buffer_size_node = find_yaml_node(&document, node, "buffer_size");
364
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
18 if (buffer_size_node &&
365 9 get_yaml_size_in_range(buffer_size_node, "logging.buffer_size", 1, 1048576,
366 &config->logging.buffer_size) != 0)
367 goto cleanup;
368
369 9 yaml_node_t *rollover_size_node = find_yaml_node(&document, node, "rollover_size");
370
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
18 if (rollover_size_node &&
371 9 get_yaml_size_in_range(rollover_size_node, "logging.rollover_size", 0, 1099511627776ULL,
372 &config->logging.rollover_size) != 0)
373 goto cleanup;
374
375 9 yaml_node_t *rollover_daily_node = find_yaml_node(&document, node, "rollover_daily");
376
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
18 if (rollover_daily_node &&
377 9 get_yaml_bool(rollover_daily_node, "logging.rollover_daily",
378 &config->logging.rollover_daily) != 0)
379 goto cleanup;
380
381 9 yaml_node_t *appender_flags_node = find_yaml_node(&document, node, "appender_flags");
382
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
9 if (parse_appender_flags(&document, appender_flags_node, &config->logging) != 0)
383 goto cleanup;
384 }
385
386 12 node = find_yaml_node(&document, root, "ssl");
387
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if (node) {
388
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (node->type != YAML_MAPPING_NODE) {
389 fprintf(stderr, "Invalid 'ssl': expected mapping\n");
390 goto cleanup;
391 }
392
393 12 yaml_node_t *cert_node = find_yaml_node(&document, node, "certificate");
394
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
24 if (cert_node &&
395 12 get_yaml_string(cert_node, "ssl.certificate",
396 12 config->ssl.certificate, sizeof(config->ssl.certificate)) != 0)
397 goto cleanup;
398
399 12 yaml_node_t *key_node = find_yaml_node(&document, node, "private_key");
400
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
24 if (key_node &&
401 12 get_yaml_string(key_node, "ssl.private_key",
402 12 config->ssl.private_key, sizeof(config->ssl.private_key)) != 0)
403 goto cleanup;
404 }
405
406 12 node = find_yaml_node(&document, root, "routes");
407
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if (node) {
408
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (node->type != YAML_SEQUENCE_NODE) {
409 fprintf(stderr, "Invalid 'routes': expected sequence\n");
410 goto cleanup;
411 }
412
413 12 for (yaml_node_item_t *item = node->data.sequence.items.start;
414
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 12 times.
31 item < node->data.sequence.items.top; item++) {
415 19 yaml_node_t *route_node = yaml_document_get_node(&document, *item);
416 Route *route;
417 yaml_node_t *route_field;
418
419
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
19 if (config->route_count >= MAX_ROUTES) {
420 fprintf(stderr, "Too many routes: maximum supported is %d\n", MAX_ROUTES);
421 goto cleanup;
422 }
423
2/4
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 19 times.
19 if (!route_node || route_node->type != YAML_MAPPING_NODE) {
424 fprintf(stderr, "Invalid route entry: expected mapping\n");
425 goto cleanup;
426 }
427
428 19 route = &config->routes[config->route_count];
429 19 memset(route, 0, sizeof(*route));
430
431 19 route_field = find_yaml_node(&document, route_node, "path");
432
2/4
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 19 times.
38 if (!route_field ||
433 19 get_yaml_string(route_field, "routes[].path",
434 19 route->path, sizeof(route->path)) != 0) {
435 fprintf(stderr, "Invalid route: missing or invalid 'path'\n");
436 goto cleanup;
437 }
438
439 19 route_field = find_yaml_node(&document, route_node, "technology");
440
2/4
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 19 times.
38 if (!route_field ||
441 19 get_yaml_string(route_field, "routes[].technology",
442 19 route->technology, sizeof(route->technology)) != 0) {
443 fprintf(stderr, "Invalid route: missing or invalid 'technology'\n");
444 goto cleanup;
445 }
446
447 19 route_field = find_yaml_node(&document, route_node, "document_root");
448
3/4
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
28 if (route_field &&
449 9 get_yaml_string(route_field, "routes[].document_root",
450 9 route->document_root, sizeof(route->document_root)) != 0)
451 goto cleanup;
452
453 19 route_field = find_yaml_node(&document, route_node, "backend");
454
3/4
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
28 if (route_field &&
455 9 get_yaml_string(route_field, "routes[].backend",
456 9 route->backend, sizeof(route->backend)) != 0)
457 goto cleanup;
458
459 19 config->route_count++;
460 }
461 }
462
463
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
12 if (config->ssl.certificate[0] == '\0' || config->ssl.private_key[0] == '\0') {
464 fprintf(stderr, "Invalid SSL config: certificate and private_key are required\n");
465 goto cleanup;
466 }
467
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (config->logging.appender_flags == 0) {
468 fprintf(stderr, "Invalid logging config: at least one appender must be enabled\n");
469 goto cleanup;
470 }
471
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
12 if ((config->logging.appender_flags & APPENDER_FILE) && config->logging.file[0] == '\0') {
472 fprintf(stderr, "Invalid logging config: file appender requires 'logging.file'\n");
473 goto cleanup;
474 }
475
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 9 times.
12 if (validate_routes(config) != 0)
476 3 goto cleanup;
477
478 9 rc = 0;
479
480 13 cleanup:
481
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 if (document_ready)
482 13 yaml_document_delete(&document);
483
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 if (parser_ready)
484 13 yaml_parser_delete(&parser);
485
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 if (fh)
486 13 fclose(fh);
487 13 return rc;
488 }
489