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