Move to kernel style SPDX license identifiers
[babeltrace.git] / src / plugins / common / param-validation / param-validation.c
1 /*
2 * SPDX-License-Identifier: MIT
3 *
4 * Copyright 2019 EfficiOS Inc.
5 */
6
7 #include "param-validation.h"
8
9 #include <babeltrace2/babeltrace.h>
10 #include <glib.h>
11 #include <inttypes.h>
12 #include <stdbool.h>
13
14 #include "common/common.h"
15
16 struct bt_param_validation_context {
17 gchar *error;
18 GArray *scope_stack;
19 };
20
21 struct 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
33 static
34 void 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
46 static
47 void 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
58 static
59 void 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
66 static
67 void 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:
83 bt_common_abort();
84 }
85 }
86
87 enum 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
125 end:
126 return status;
127 }
128
129 struct 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
136 static
137 enum 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
142 static
143 bt_value_map_foreach_entry_const_func_status validate_map_value_entry(
144 const char *key, const bt_value *value, void *v_data)
145 {
146 struct validate_map_value_data *data = v_data;
147 const struct bt_param_validation_map_value_entry_descr *entry = NULL;
148 guint i;
149
150 /* Check if this key is in the available keys. */
151 for (i = 0; i < data->available_keys->len; i++) {
152 const struct bt_param_validation_map_value_entry_descr *candidate =
153 g_ptr_array_index(data->available_keys, i);
154
155 if (g_str_equal(key, candidate->key)) {
156 entry = candidate;
157 break;
158 }
159 }
160
161 if (entry) {
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. */
169 data->status = validate_value(value, &entry->value_descr,
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. */
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;
182 }
183
184 static
185 enum 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);
215 if (foreach_entry_status == BT_VALUE_MAP_FOREACH_ENTRY_CONST_STATUS_INTERRUPTED) {
216 BT_ASSERT(data.status != BT_PARAM_VALIDATION_STATUS_OK);
217 status = data.status;
218 goto end;
219 }
220
221 BT_ASSERT(data.status == BT_PARAM_VALIDATION_STATUS_OK);
222
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
237 end:
238 g_ptr_array_free(available_keys, TRUE);
239 return status;
240 }
241
242 static
243 enum 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
287 end:
288 return status;
289 }
290
291 static
292 enum 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;
330 end:
331 g_free(joined_choices);
332
333 return status;
334 }
335
336 static
337 enum 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
378 end:
379 return status;
380 }
381
382 enum 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
390 memset(&ctx, '\0', sizeof(ctx));
391
392 ctx.scope_stack = g_array_new(FALSE, FALSE,
393 sizeof(struct validate_ctx_stack_element));
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
403 end:
404 *error = ctx.error;
405 ctx.error = NULL;
406
407 if (ctx.scope_stack) {
408 g_array_free(ctx.scope_stack, TRUE);
409 }
410
411 return status;
412 }
This page took 0.037279 seconds and 4 git commands to generate.