Move to kernel style SPDX license identifiers
[babeltrace.git] / src / plugins / common / param-validation / param-validation.c
CommitLineData
d0d4e0ed 1/*
0235b0db 2 * SPDX-License-Identifier: MIT
d0d4e0ed 3 *
0235b0db 4 * Copyright 2019 EfficiOS Inc.
d0d4e0ed
SM
5 */
6
7#include "param-validation.h"
8
9#include <babeltrace2/babeltrace.h>
10#include <glib.h>
11#include <inttypes.h>
c4f23e30 12#include <stdbool.h>
d0d4e0ed
SM
13
14#include "common/common.h"
15
16struct bt_param_validation_context {
17 gchar *error;
18 GArray *scope_stack;
19};
20
21struct validate_ctx_stack_element {
22 enum {
23 VALIDATE_CTX_STACK_ELEMENT_MAP,
24 VALIDATE_CTX_STACK_ELEMENT_ARRAY,
25 } type;
26
27 union {
28 const char *map_key_name;
29 uint64_t array_index;
30 };
31};
32
33static
34void validate_ctx_push_map_scope(
35 struct bt_param_validation_context *ctx,
36 const char *key)
37{
38 struct validate_ctx_stack_element stack_element = {
39 .type = VALIDATE_CTX_STACK_ELEMENT_MAP,
40 .map_key_name = key,
41 };
42
43 g_array_append_val(ctx->scope_stack, stack_element);
44}
45
46static
47void validate_ctx_push_array_scope(
48 struct bt_param_validation_context *ctx, uint64_t index)
49{
50 struct validate_ctx_stack_element stack_element = {
51 .type = VALIDATE_CTX_STACK_ELEMENT_ARRAY,
52 .array_index = index,
53 };
54
55 g_array_append_val(ctx->scope_stack, stack_element);
56}
57
58static
59void validate_ctx_pop_scope(struct bt_param_validation_context *ctx)
60{
61 BT_ASSERT(ctx->scope_stack->len > 0);
62
63 g_array_remove_index_fast(ctx->scope_stack, ctx->scope_stack->len - 1);
64}
65
66static
67void append_scope_to_string(GString *str,
68 const struct validate_ctx_stack_element *elem,
69 bool first)
70{
71 switch (elem->type) {
72 case VALIDATE_CTX_STACK_ELEMENT_MAP:
73 if (!first) {
74 g_string_append_c(str, '.');
75 }
76
77 g_string_append(str, elem->map_key_name);
78 break;
79 case VALIDATE_CTX_STACK_ELEMENT_ARRAY:
80 g_string_append_printf(str, "[%" PRIu64 "]", elem->array_index);
81 break;
82 default:
498e7994 83 bt_common_abort();
d0d4e0ed
SM
84 }
85}
86
87enum bt_param_validation_status bt_param_validation_error(
88 struct bt_param_validation_context *ctx,
89 const char *format, ...) {
90 va_list ap;
91 enum bt_param_validation_status status;
92
93 GString *str = g_string_new(NULL);
94 if (!str) {
95 status = BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR;
96 goto end;
97 }
98
99 if (ctx->scope_stack->len > 0) {
100 guint i;
101
102 g_string_assign(str, "Error validating parameter `");
103
104 append_scope_to_string(str, &g_array_index(ctx->scope_stack,
105 struct validate_ctx_stack_element, 0), true);
106
107 for (i = 1; i < ctx->scope_stack->len; i++) {
108 append_scope_to_string(str,
109 &g_array_index(ctx->scope_stack,
110 struct validate_ctx_stack_element, i), false);
111 }
112
113 g_string_append(str, "`: ");
114 } else {
115 g_string_assign(str, "Error validating parameters: ");
116 }
117
118 va_start(ap, format);
119 g_string_append_vprintf(str, format, ap);
120 va_end(ap);
121
122 ctx->error = g_string_free(str, FALSE);
123 status = BT_PARAM_VALIDATION_STATUS_VALIDATION_ERROR;
124
125end:
126 return status;
127}
128
129struct validate_map_value_data
130{
131 GPtrArray *available_keys;
132 enum bt_param_validation_status status;
133 struct bt_param_validation_context *ctx;
134};
135
136static
137enum bt_param_validation_status validate_value(
138 const bt_value *value,
139 const struct bt_param_validation_value_descr *descr,
140 struct bt_param_validation_context *ctx);
141
142static
27c61ce8
PP
143bt_value_map_foreach_entry_const_func_status validate_map_value_entry(
144 const char *key, const bt_value *value, void *v_data)
d0d4e0ed
SM
145{
146 struct validate_map_value_data *data = v_data;
5a039e2a 147 const struct bt_param_validation_map_value_entry_descr *entry = NULL;
d0d4e0ed
SM
148 guint i;
149
150 /* Check if this key is in the available keys. */
151 for (i = 0; i < data->available_keys->len; i++) {
5a039e2a
SM
152 const struct bt_param_validation_map_value_entry_descr *candidate =
153 g_ptr_array_index(data->available_keys, i);
d0d4e0ed
SM
154
155 if (g_str_equal(key, candidate->key)) {
5a039e2a 156 entry = candidate;
d0d4e0ed
SM
157 break;
158 }
159 }
160
5a039e2a 161 if (entry) {
d0d4e0ed
SM
162 /* Key was found in available keys. */
163 g_ptr_array_remove_index_fast(data->available_keys, i);
164
165 /* Push key name as the scope. */
166 validate_ctx_push_map_scope(data->ctx, key);
167
168 /* Validate the value of the entry. */
5a039e2a 169 data->status = validate_value(value, &entry->value_descr,
d0d4e0ed
SM
170 data->ctx);
171
172 validate_ctx_pop_scope(data->ctx);
173 } else {
174 data->status = bt_param_validation_error(data->ctx,
175 "unexpected key `%s`.", key);
176 }
177
178 /* Continue iterating if everything is good so far. */
27c61ce8
PP
179 return data->status == BT_PARAM_VALIDATION_STATUS_OK ?
180 BT_VALUE_MAP_FOREACH_ENTRY_CONST_FUNC_STATUS_OK :
181 BT_VALUE_MAP_FOREACH_ENTRY_CONST_FUNC_STATUS_INTERRUPT;
d0d4e0ed
SM
182}
183
184static
185enum bt_param_validation_status validate_map_value(
186 const struct bt_param_validation_map_value_descr *descr,
187 const bt_value *map,
188 struct bt_param_validation_context *ctx) {
189 enum bt_param_validation_status status;
190 struct validate_map_value_data data;
191 bt_value_map_foreach_entry_const_status foreach_entry_status;
192 GPtrArray *available_keys = NULL;
193 const struct bt_param_validation_map_value_entry_descr *descr_iter;
194 guint i;
195
196 BT_ASSERT(bt_value_get_type(map) == BT_VALUE_TYPE_MAP);
197
198 available_keys = g_ptr_array_new();
199 if (!available_keys) {
200 status = BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR;
201 goto end;
202 }
203
204 for (descr_iter = descr->entries; descr_iter->key; descr_iter++) {
205 g_ptr_array_add(available_keys, (gpointer) descr_iter);
206 }
207
208 /* Initialize `status` to OK, in case the map is empty. */
209 data.status = BT_PARAM_VALIDATION_STATUS_OK;
210 data.available_keys = available_keys;
211 data.ctx = ctx;
212
213 foreach_entry_status = bt_value_map_foreach_entry_const(map,
214 validate_map_value_entry, &data);
605adc18
SM
215 if (foreach_entry_status == BT_VALUE_MAP_FOREACH_ENTRY_CONST_STATUS_INTERRUPTED) {
216 BT_ASSERT(data.status != BT_PARAM_VALIDATION_STATUS_OK);
d0d4e0ed
SM
217 status = data.status;
218 goto end;
219 }
220
605adc18
SM
221 BT_ASSERT(data.status == BT_PARAM_VALIDATION_STATUS_OK);
222
d0d4e0ed
SM
223 for (i = 0; i < data.available_keys->len; i++) {
224 const struct bt_param_validation_map_value_entry_descr *entry =
225 g_ptr_array_index(data.available_keys, i);
226
227 if (!entry->is_optional) {
228 status = bt_param_validation_error(ctx,
229 "missing mandatory entry `%s`",
230 entry->key);
231 goto end;
232 }
233 }
234
235 status = BT_PARAM_VALIDATION_STATUS_OK;
236
237end:
238 g_ptr_array_free(available_keys, TRUE);
239 return status;
240}
241
242static
243enum bt_param_validation_status validate_array_value(
244 const struct bt_param_validation_array_value_descr *descr,
245 const bt_value *array,
246 struct bt_param_validation_context *ctx) {
247 enum bt_param_validation_status status;
248 uint64_t i;
249
250 BT_ASSERT(bt_value_get_type(array) == BT_VALUE_TYPE_ARRAY);
251
252 if (bt_value_array_get_length(array) < descr->min_length) {
253 status = bt_param_validation_error(ctx,
254 "array is smaller than the minimum length: "
255 "array-length=%" PRIu64 ", min-length=%" PRIu64,
256 bt_value_array_get_length(array),
257 descr->min_length);
258 goto end;
259 }
260
261 if (bt_value_array_get_length(array) > descr->max_length) {
262 status = bt_param_validation_error(ctx,
263 "array is larger than the maximum length: "
264 "array-length=%" PRIu64 ", max-length=%" PRIu64,
265 bt_value_array_get_length(array),
266 descr->max_length);
267 goto end;
268 }
269
270 for (i = 0; i < bt_value_array_get_length(array); i++) {
271 const bt_value *element =
272 bt_value_array_borrow_element_by_index_const(array, i);
273
274 validate_ctx_push_array_scope(ctx, i);
275
276 status = validate_value(element, descr->element_type, ctx);
277
278 validate_ctx_pop_scope(ctx);
279
280 if (status != BT_PARAM_VALIDATION_STATUS_OK) {
281 goto end;
282 }
283 }
284
285 status = BT_PARAM_VALIDATION_STATUS_OK;
286
287end:
288 return status;
289}
290
291static
292enum bt_param_validation_status validate_string_value(
293 const struct bt_param_validation_string_value_descr *descr,
294 const bt_value *string,
295 struct bt_param_validation_context *ctx) {
296 enum bt_param_validation_status status;
297 const char *s = bt_value_string_get(string);
298 gchar *joined_choices = NULL;
299
300 BT_ASSERT(bt_value_get_type(string) == BT_VALUE_TYPE_STRING);
301
302 if (descr->choices) {
303 const char **choice;
304
305 for (choice = descr->choices; *choice; choice++) {
306 if (strcmp(s, *choice) == 0) {
307 break;
308 }
309 }
310
311 if (!*choice) {
312 /*
313 * g_strjoinv takes a gchar **, but it doesn't modify
314 * the array of the strings (yet).
315 */
316 joined_choices = g_strjoinv(", ", (gchar **) descr->choices);
317 if (!joined_choices) {
318 status = BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR;
319 goto end;
320 }
321
322 status = bt_param_validation_error(ctx,
323 "string is not amongst the available choices: "
324 "string=%s, choices=[%s]", s, joined_choices);
325 goto end;
326 }
327 }
328
329 status = BT_PARAM_VALIDATION_STATUS_OK;
330end:
331 g_free(joined_choices);
332
333 return status;
334}
335
336static
337enum bt_param_validation_status validate_value(
338 const bt_value *value,
339 const struct bt_param_validation_value_descr *descr,
340 struct bt_param_validation_context *ctx) {
341 enum bt_param_validation_status status;
342
343 /* If there is a custom validation func, we call it and ignore the rest. */
344 if (descr->validation_func) {
345 status = descr->validation_func(value, ctx);
346
347 if (status == BT_PARAM_VALIDATION_STATUS_VALIDATION_ERROR) {
348 BT_ASSERT(ctx->error);
349 }
350
351 goto end;
352 }
353
354 if (bt_value_get_type(value) != descr->type) {
355 bt_param_validation_error(ctx,
356 "unexpected type: expected-type=%s, actual-type=%s",
357 bt_common_value_type_string(descr->type),
358 bt_common_value_type_string(bt_value_get_type(value)));
359 status = BT_PARAM_VALIDATION_STATUS_VALIDATION_ERROR;
360 goto end;
361 }
362
363 switch (bt_value_get_type(value)) {
364 case BT_VALUE_TYPE_MAP:
365 status = validate_map_value(&descr->map, value, ctx);
366 break;
367 case BT_VALUE_TYPE_ARRAY:
368 status = validate_array_value(&descr->array, value, ctx);
369 break;
370 case BT_VALUE_TYPE_STRING:
371 status = validate_string_value(&descr->string, value, ctx);
372 break;
373 default:
374 status = BT_PARAM_VALIDATION_STATUS_OK;
375 break;
376 }
377
378end:
379 return status;
380}
381
382enum bt_param_validation_status bt_param_validation_validate(
383 const bt_value *params,
384 const struct bt_param_validation_map_value_entry_descr *entries,
385 gchar **error) {
386 struct bt_param_validation_context ctx;
387 struct bt_param_validation_map_value_descr map_value_descr;
388 enum bt_param_validation_status status;
389
8839434e
SM
390 memset(&ctx, '\0', sizeof(ctx));
391
d0d4e0ed
SM
392 ctx.scope_stack = g_array_new(FALSE, FALSE,
393 sizeof(struct validate_ctx_stack_element));
d0d4e0ed
SM
394 if (!ctx.scope_stack) {
395 status = BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR;
396 goto end;
397 }
398
399 map_value_descr.entries = entries;
400
401 status = validate_map_value(&map_value_descr, params, &ctx);
402
403end:
404 *error = ctx.error;
405 ctx.error = NULL;
406
8839434e
SM
407 if (ctx.scope_stack) {
408 g_array_free(ctx.scope_stack, TRUE);
409 }
410
d0d4e0ed
SM
411 return status;
412}
This page took 0.051377 seconds and 4 git commands to generate.