diff options
-rw-r--r-- | include/asterisk/sorcery.h | 26 | ||||
-rw-r--r-- | main/sorcery.c | 55 | ||||
-rw-r--r-- | tests/test_sorcery.c | 182 |
3 files changed, 249 insertions, 14 deletions
diff --git a/include/asterisk/sorcery.h b/include/asterisk/sorcery.h index b5344c61c..54d2f0d46 100644 --- a/include/asterisk/sorcery.h +++ b/include/asterisk/sorcery.h @@ -132,6 +132,17 @@ struct ast_sorcery; typedef int (*sorcery_field_handler)(const void *obj, const intptr_t *args, char **buf); /*! + * \brief A callback function for translating multiple values into an ast_variable list + * + * \param obj Object to get values from + * \param fields Pointer to store the list of fields + * + * \retval 0 success + * \retval -1 failure + */ +typedef int (*sorcery_fields_handler)(const void *obj, struct ast_variable **fields); + +/*! * \brief A callback function for performing a transformation on an object set * * \param set The existing object set @@ -342,6 +353,21 @@ void ast_sorcery_object_set_copy_handler(struct ast_sorcery *sorcery, const char void ast_sorcery_object_set_diff_handler(struct ast_sorcery *sorcery, const char *type, sorcery_diff_handler diff); /*! + * \brief Register a regex for multiple fields within an object + * + * \param sorcery Pointer to a sorcery structure + * \param type Type of object + * \param regex A regular expression pattern for the fields + * \param config_handler A custom handler for translating the string representation of the fields + * \param sorcery_handler A custom handler for translating the native representation of the fields + * + * \retval 0 success + * \retval -1 failure + */ +int ast_sorcery_object_fields_register(struct ast_sorcery *sorcery, const char *type, const char *regex, aco_option_handler config_handler, + sorcery_fields_handler sorcery_handler); + +/*! * \brief Register a field within an object * * \param sorcery Pointer to a sorcery structure diff --git a/main/sorcery.c b/main/sorcery.c index 242d65bdc..df2f57740 100644 --- a/main/sorcery.c +++ b/main/sorcery.c @@ -90,9 +90,12 @@ struct ast_sorcery_object_field { /*! \brief Name of the field */ char name[MAX_OBJECT_FIELD]; - /*! \brief Callback function for translation */ + /*! \brief Callback function for translation of a single value */ sorcery_field_handler handler; + /*! \brief Callback function for translation of multiple values */ + sorcery_fields_handler multiple_handler; + /*! \brief Position of the field */ intptr_t args[]; }; @@ -516,6 +519,24 @@ void ast_sorcery_object_set_diff_handler(struct ast_sorcery *sorcery, const char object_type->diff = diff; } +int ast_sorcery_object_fields_register(struct ast_sorcery *sorcery, const char *type, const char *regex, aco_option_handler config_handler, sorcery_fields_handler sorcery_handler) +{ + RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup); + RAII_VAR(struct ast_sorcery_object_field *, object_field, NULL, ao2_cleanup); + + if (!object_type || !object_type->type.item_alloc || !config_handler || !(object_field = ao2_alloc(sizeof(*object_field), NULL))) { + return -1; + } + + ast_copy_string(object_field->name, regex, sizeof(object_field->name)); + object_field->multiple_handler = sorcery_handler; + + ao2_link(object_type->fields, object_field); + __aco_option_register(object_type->info, regex, ACO_REGEX, object_type->file->types, "", OPT_CUSTOM_T, config_handler, 0, 0); + + return 0; +} + int __ast_sorcery_object_field_register(struct ast_sorcery *sorcery, const char *type, const char *name, const char *default_val, enum aco_option_type opt_type, aco_option_handler config_handler, sorcery_field_handler sorcery_handler, unsigned int flags, size_t argc, ...) { @@ -662,24 +683,30 @@ struct ast_variable *ast_sorcery_objectset_create(const struct ast_sorcery *sorc i = ao2_iterator_init(object_type->fields, 0); - for (; (object_field = ao2_iterator_next(&i)); ao2_ref(object_field, -1)) { - RAII_VAR(char *, buf, NULL, ast_free); - struct ast_variable *tmp; + for (; (object_field = ao2_iterator_next(&i)) && !res; ao2_ref(object_field, -1)) { + struct ast_variable *tmp = NULL; + + if (object_field->multiple_handler) { + if ((res = object_field->multiple_handler(object, &tmp))) { + ast_variables_destroy(tmp); + } + } else if (object_field->handler) { + char *buf = NULL; + + if ((res = object_field->handler(object, object_field->args, &buf)) || + !(tmp = ast_variable_new(object_field->name, S_OR(buf, ""), ""))) { + res = -1; + } - /* Any fields with no handler just get skipped */ - if (!object_field->handler) { + ast_free(buf); + } else { continue; } - if ((res = object_field->handler(object, object_field->args, &buf)) || - !(tmp = ast_variable_new(object_field->name, S_OR(buf, ""), ""))) { - res = -1; - ao2_ref(object_field, -1); - break; + if (!res) { + tmp->next = fields; + fields = tmp; } - - tmp->next = fields; - fields = tmp; } ao2_iterator_destroy(&i); diff --git a/tests/test_sorcery.c b/tests/test_sorcery.c index 74b15fdcc..4a3deb5c7 100644 --- a/tests/test_sorcery.c +++ b/tests/test_sorcery.c @@ -93,6 +93,24 @@ static int test_sorcery_diff(const void *original, const void *modified, struct return 0; } +/*! \brief Internal function which sets some values */ +static int test_sorcery_regex_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct test_sorcery_object *test = obj; + + test->bob = 256; + + return 0; +} + +/*! \brief Internal function which creates some ast_variable structures */ +static int test_sorcery_regex_fields(const void *obj, struct ast_variable **fields) +{ + *fields = ast_variable_new("toast-bob", "10", ""); + + return 0; +} + /*! \brief Test structure for caching */ struct sorcery_test_caching { /*! \brief Whether the object has been created in the cache or not */ @@ -435,6 +453,55 @@ AST_TEST_DEFINE(object_field_register) return AST_TEST_PASS; } +AST_TEST_DEFINE(object_fields_register) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "object_fields_register"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object regex fields registration unit test"; + info->description = + "Test object regex fields registration in sorcery with a provided id"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = ast_sorcery_open())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (!ast_sorcery_object_fields_register(sorcery, "test", "^toast-", test_sorcery_regex_handler, test_sorcery_regex_fields)) { + ast_test_status_update(test, "Registered a regex object field successfully when no mappings or object types exist\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_apply_default(sorcery, "test", "memory", NULL)) { + ast_test_status_update(test, "Failed to set a known wizard as a default\n"); + return AST_TEST_FAIL; + } + + if (!ast_sorcery_object_fields_register(sorcery, "test", "^toast-", test_sorcery_regex_handler, test_sorcery_regex_fields)) { + ast_test_status_update(test, "Registered a regex object field successfully when object type does not exist\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) { + ast_test_status_update(test, "Failed to register object type\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_object_fields_register(sorcery, "test", "^toast-", test_sorcery_regex_handler, test_sorcery_regex_fields)) { + ast_test_status_update(test, "Registered a regex object field successfully when no mappings or object types exist\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + AST_TEST_DEFINE(object_alloc_with_id) { int res = AST_TEST_PASS; @@ -801,6 +868,64 @@ AST_TEST_DEFINE(objectset_create) return res; } +AST_TEST_DEFINE(objectset_create_regex) +{ + int res = AST_TEST_PASS; + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + RAII_VAR(struct ast_variable *, objset, NULL, ast_variables_destroy); + struct ast_variable *field; + + switch (cmd) { + case TEST_INIT: + info->name = "objectset_create_regex"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object set creation with regex fields unit test"; + info->description = + "Test object set creation with regex fields in sorcery"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = ast_sorcery_open())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_apply_default(sorcery, "test", "memory", NULL) || + ast_sorcery_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, test_apply_handler)) { + ast_test_status_update(test, "Failed to register 'test' object type\n"); + return AST_TEST_FAIL; + } + + ast_sorcery_object_fields_register(sorcery, "test", "^toast-", test_sorcery_regex_handler, test_sorcery_regex_fields); + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + return AST_TEST_FAIL; + } + + if (!(objset = ast_sorcery_objectset_create(sorcery, obj))) { + ast_test_status_update(test, "Failed to create an object set for a known sane object\n"); + return AST_TEST_FAIL; + } + + for (field = objset; field; field = field->next) { + if (!strcmp(field->name, "toast-bob")) { + if (strcmp(field->value, "10")) { + ast_test_status_update(test, "Object set failed to create proper value for 'bob'\n"); + res = AST_TEST_FAIL; + } + } else { + ast_test_status_update(test, "Object set created field '%s' which is unknown\n", field->name); + res = AST_TEST_FAIL; + } + } + + return res; +} + AST_TEST_DEFINE(objectset_apply) { int res = AST_TEST_PASS; @@ -1005,6 +1130,57 @@ AST_TEST_DEFINE(objectset_transform) return AST_TEST_PASS; } +AST_TEST_DEFINE(objectset_apply_fields) +{ + int res = AST_TEST_PASS; + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + RAII_VAR(struct ast_variable *, objset, NULL, ast_variables_destroy); + + switch (cmd) { + case TEST_INIT: + info->name = "objectset_apply_fields"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object apply regex fields unit test"; + info->description = + "Test object set apply with regex fields in sorcery"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = ast_sorcery_open())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_apply_default(sorcery, "test", "memory", NULL) || + ast_sorcery_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, test_apply_handler)) { + ast_test_status_update(test, "Failed to register 'test' object type\n"); + return AST_TEST_FAIL; + } + + ast_sorcery_object_fields_register(sorcery, "test", "^toast-", test_sorcery_regex_handler, test_sorcery_regex_fields); + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + return AST_TEST_FAIL; + } + + if (!(objset = ast_variable_new("toast-bob", "20", ""))) { + ast_test_status_update(test, "Failed to create an object set, test could not occur\n"); + res = AST_TEST_FAIL; + } else if (ast_sorcery_objectset_apply(sorcery, obj, objset)) { + ast_test_status_update(test, "Failed to apply valid object set to object\n"); + res = AST_TEST_FAIL; + } else if (obj->bob != 256) { + ast_test_status_update(test, "Regex field handler was not called when it should have been\n"); + res = AST_TEST_FAIL; + } + + return res; +} + AST_TEST_DEFINE(changeset_create) { int res = AST_TEST_PASS; @@ -2149,6 +2325,7 @@ static int unload_module(void) AST_TEST_UNREGISTER(object_register); AST_TEST_UNREGISTER(object_register_without_mapping); AST_TEST_UNREGISTER(object_field_register); + AST_TEST_UNREGISTER(object_fields_register); AST_TEST_UNREGISTER(object_alloc_with_id); AST_TEST_UNREGISTER(object_alloc_without_id); AST_TEST_UNREGISTER(object_copy); @@ -2156,10 +2333,12 @@ static int unload_module(void) AST_TEST_UNREGISTER(object_diff); AST_TEST_UNREGISTER(object_diff_native); AST_TEST_UNREGISTER(objectset_create); + AST_TEST_UNREGISTER(objectset_create_regex); AST_TEST_UNREGISTER(objectset_apply); AST_TEST_UNREGISTER(objectset_apply_handler); AST_TEST_UNREGISTER(objectset_apply_invalid); AST_TEST_UNREGISTER(objectset_transform); + AST_TEST_UNREGISTER(objectset_apply_fields); AST_TEST_UNREGISTER(changeset_create); AST_TEST_UNREGISTER(changeset_create_unchanged); AST_TEST_UNREGISTER(object_create); @@ -2191,6 +2370,7 @@ static int load_module(void) AST_TEST_REGISTER(object_register); AST_TEST_REGISTER(object_register_without_mapping); AST_TEST_REGISTER(object_field_register); + AST_TEST_REGISTER(object_fields_register); AST_TEST_REGISTER(object_alloc_with_id); AST_TEST_REGISTER(object_alloc_without_id); AST_TEST_REGISTER(object_copy); @@ -2198,10 +2378,12 @@ static int load_module(void) AST_TEST_REGISTER(object_diff); AST_TEST_REGISTER(object_diff_native); AST_TEST_REGISTER(objectset_create); + AST_TEST_REGISTER(objectset_create_regex); AST_TEST_REGISTER(objectset_apply); AST_TEST_REGISTER(objectset_apply_handler); AST_TEST_REGISTER(objectset_apply_invalid); AST_TEST_REGISTER(objectset_transform); + AST_TEST_REGISTER(objectset_apply_fields); AST_TEST_REGISTER(changeset_create); AST_TEST_REGISTER(changeset_create_unchanged); AST_TEST_REGISTER(object_create); |