2 * SPDX-License-Identifier: MIT
4 * Copyright 2019 EfficiOS Inc.
7 #include "param-validation.h"
9 #include <babeltrace2/babeltrace.h>
14 #include "common/common.h"
16 struct bt_param_validation_context
{
21 struct validate_ctx_stack_element
{
23 VALIDATE_CTX_STACK_ELEMENT_MAP
,
24 VALIDATE_CTX_STACK_ELEMENT_ARRAY
,
28 const char *map_key_name
;
34 void validate_ctx_push_map_scope(
35 struct bt_param_validation_context
*ctx
,
38 struct validate_ctx_stack_element stack_element
= {
39 .type
= VALIDATE_CTX_STACK_ELEMENT_MAP
,
43 g_array_append_val(ctx
->scope_stack
, stack_element
);
47 void validate_ctx_push_array_scope(
48 struct bt_param_validation_context
*ctx
, uint64_t index
)
50 struct validate_ctx_stack_element stack_element
= {
51 .type
= VALIDATE_CTX_STACK_ELEMENT_ARRAY
,
55 g_array_append_val(ctx
->scope_stack
, stack_element
);
59 void validate_ctx_pop_scope(struct bt_param_validation_context
*ctx
)
61 BT_ASSERT(ctx
->scope_stack
->len
> 0);
63 g_array_remove_index_fast(ctx
->scope_stack
, ctx
->scope_stack
->len
- 1);
67 void append_scope_to_string(GString
*str
,
68 const struct validate_ctx_stack_element
*elem
,
72 case VALIDATE_CTX_STACK_ELEMENT_MAP
:
74 g_string_append_c(str
, '.');
77 g_string_append(str
, elem
->map_key_name
);
79 case VALIDATE_CTX_STACK_ELEMENT_ARRAY
:
80 g_string_append_printf(str
, "[%" PRIu64
"]", elem
->array_index
);
87 enum bt_param_validation_status
bt_param_validation_error(
88 struct bt_param_validation_context
*ctx
,
89 const char *format
, ...) {
91 enum bt_param_validation_status status
;
93 GString
*str
= g_string_new(NULL
);
95 status
= BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR
;
99 if (ctx
->scope_stack
->len
> 0) {
102 g_string_assign(str
, "Error validating parameter `");
104 append_scope_to_string(str
, &bt_g_array_index(ctx
->scope_stack
,
105 struct validate_ctx_stack_element
, 0), true);
107 for (i
= 1; i
< ctx
->scope_stack
->len
; i
++) {
108 append_scope_to_string(str
,
109 &bt_g_array_index(ctx
->scope_stack
,
110 struct validate_ctx_stack_element
, i
), false);
113 g_string_append(str
, "`: ");
115 g_string_assign(str
, "Error validating parameters: ");
118 va_start(ap
, format
);
119 g_string_append_vprintf(str
, format
, ap
);
122 ctx
->error
= g_string_free(str
, FALSE
);
123 status
= BT_PARAM_VALIDATION_STATUS_VALIDATION_ERROR
;
129 struct validate_map_value_data
131 GPtrArray
*available_keys
;
132 enum bt_param_validation_status status
;
133 struct bt_param_validation_context
*ctx
;
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
);
143 bt_value_map_foreach_entry_const_func_status
validate_map_value_entry(
144 const char *key
, const bt_value
*value
, void *v_data
)
146 struct validate_map_value_data
*data
= v_data
;
147 const struct bt_param_validation_map_value_entry_descr
*entry
= NULL
;
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
);
155 if (g_str_equal(key
, candidate
->key
)) {
162 /* Key was found in available keys. */
163 g_ptr_array_remove_index_fast(data
->available_keys
, i
);
165 /* Push key name as the scope. */
166 validate_ctx_push_map_scope(data
->ctx
, key
);
168 /* Validate the value of the entry. */
169 data
->status
= validate_value(value
, &entry
->value_descr
,
172 validate_ctx_pop_scope(data
->ctx
);
174 data
->status
= bt_param_validation_error(data
->ctx
,
175 "unexpected key `%s`.", key
);
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
;
185 enum bt_param_validation_status
validate_map_value(
186 const struct bt_param_validation_map_value_descr
*descr
,
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
;
196 BT_ASSERT(bt_value_get_type(map
) == BT_VALUE_TYPE_MAP
);
198 available_keys
= g_ptr_array_new();
199 if (!available_keys
) {
200 status
= BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR
;
204 for (descr_iter
= descr
->entries
; descr_iter
->key
; descr_iter
++) {
205 g_ptr_array_add(available_keys
, (gpointer
) descr_iter
);
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
;
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
;
221 BT_ASSERT(data
.status
== BT_PARAM_VALIDATION_STATUS_OK
);
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
);
227 if (!entry
->is_optional
) {
228 status
= bt_param_validation_error(ctx
,
229 "missing mandatory entry `%s`",
235 status
= BT_PARAM_VALIDATION_STATUS_OK
;
238 g_ptr_array_free(available_keys
, TRUE
);
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
;
250 BT_ASSERT(bt_value_get_type(array
) == BT_VALUE_TYPE_ARRAY
);
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
),
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
),
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
);
274 validate_ctx_push_array_scope(ctx
, i
);
276 status
= validate_value(element
, descr
->element_type
, ctx
);
278 validate_ctx_pop_scope(ctx
);
280 if (status
!= BT_PARAM_VALIDATION_STATUS_OK
) {
285 status
= BT_PARAM_VALIDATION_STATUS_OK
;
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
;
300 BT_ASSERT(bt_value_get_type(string
) == BT_VALUE_TYPE_STRING
);
302 if (descr
->choices
) {
305 for (choice
= descr
->choices
; *choice
; choice
++) {
306 if (strcmp(s
, *choice
) == 0) {
313 * g_strjoinv takes a gchar **, but it doesn't modify
314 * the array of the strings (yet).
316 joined_choices
= g_strjoinv(", ", (gchar
**) descr
->choices
);
317 if (!joined_choices
) {
318 status
= BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR
;
322 status
= bt_param_validation_error(ctx
,
323 "string is not amongst the available choices: "
324 "string=%s, choices=[%s]", s
, joined_choices
);
329 status
= BT_PARAM_VALIDATION_STATUS_OK
;
331 g_free(joined_choices
);
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
;
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
);
347 if (status
== BT_PARAM_VALIDATION_STATUS_VALIDATION_ERROR
) {
348 BT_ASSERT(ctx
->error
);
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
;
363 switch (bt_value_get_type(value
)) {
364 case BT_VALUE_TYPE_MAP
:
365 status
= validate_map_value(&descr
->map
, value
, ctx
);
367 case BT_VALUE_TYPE_ARRAY
:
368 status
= validate_array_value(&descr
->array
, value
, ctx
);
370 case BT_VALUE_TYPE_STRING
:
371 status
= validate_string_value(&descr
->string
, value
, ctx
);
374 status
= BT_PARAM_VALIDATION_STATUS_OK
;
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
,
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
;
390 memset(&ctx
, '\0', sizeof(ctx
));
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
;
399 map_value_descr
.entries
= entries
;
401 status
= validate_map_value(&map_value_descr
, params
, &ctx
);
407 if (ctx
.scope_stack
) {
408 g_array_free(ctx
.scope_stack
, TRUE
);