This is an automated email from the ASF dual-hosted git repository. pnoltes pushed a commit to branch feature/685-properties-json-serialization in repository https://gitbox.apache.org/repos/asf/celix.git
commit 9511610bb2c7379570c1bd3a9705202c0723a865 Author: Pepijn Noltes <pnol...@apache.org> AuthorDate: Tue Apr 2 20:25:09 2024 +0200 gh-685: Add jansson dep for simple properties save/load --- libs/utils/CMakeLists.txt | 3 +- .../gtest/src/PropertiesSerializationTestSuite.cc | 70 ++++++++++++- libs/utils/src/properties.c | 2 +- libs/utils/src/properties_serialization.c | 112 ++++++++++++++++++++- 4 files changed, 180 insertions(+), 7 deletions(-) diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt index 064c9683..4180b48b 100644 --- a/libs/utils/CMakeLists.txt +++ b/libs/utils/CMakeLists.txt @@ -18,6 +18,7 @@ celix_subproject(UTILS "Option to enable building the Utilities library" ON) if (UTILS) find_package(libzip REQUIRED) + find_package(jansson REQUIRED) #TODO add jansson dep info to build (conan) and documentation info set(MEMSTREAM_SOURCES ) set(MEMSTREAM_INCLUDES ) @@ -42,7 +43,7 @@ if (UTILS) src/celix_cleanup.c ${MEMSTREAM_SOURCES} ) - set(UTILS_PRIVATE_DEPS libzip::zip) + set(UTILS_PRIVATE_DEPS libzip::zip jansson::jansson) set(UTILS_PUBLIC_DEPS) add_library(utils SHARED ${UTILS_SRC}) diff --git a/libs/utils/gtest/src/PropertiesSerializationTestSuite.cc b/libs/utils/gtest/src/PropertiesSerializationTestSuite.cc index d3bf6aa0..99cbeceb 100644 --- a/libs/utils/gtest/src/PropertiesSerializationTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesSerializationTestSuite.cc @@ -18,9 +18,11 @@ */ #include <gtest/gtest.h> +#include <jansson.h> #include "celix_err.h" #include "celix_properties.h" +#include "celix_stdlib_cleanup.h" using ::testing::MatchesRegex; @@ -34,7 +36,7 @@ TEST_F(PropertiesSerializationTestSuite, SaveEmptyPropertiesTest) { celix_autoptr(celix_properties_t) props = celix_properties_create(); //And an in-memory stream - char* buf = nullptr; + celix_autofree char* buf = nullptr; size_t bufLen = 0; FILE* stream = open_memstream(&buf, &bufLen); @@ -62,3 +64,69 @@ TEST_F(PropertiesSerializationTestSuite, LoadEmptyPropertiesTest) { fclose(stream); } + +TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithSingelValuesTest) { + //Given a properties object with single values + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_set(props, "key1", "value1"); + celix_properties_set(props, "key2", "value2"); + celix_properties_setLong(props, "key3", 3); + celix_properties_setDouble(props, "key4", 4.0); + celix_properties_setBool(props, "key5", true); + celix_properties_assignVersion(props, "key6", celix_version_create(1, 2, 3, "qualifier")); + + //And an in-memory stream + celix_autofree char* buf = nullptr; + size_t bufLen = 0; + FILE* stream = open_memstream(&buf, &bufLen); + + //When saving the properties to the stream + auto status = celix_properties_saveToStream(props, stream); + EXPECT_EQ(CELIX_SUCCESS, status); + + //Then the stream contains the JSON representation snippets of the properties + fclose(stream); + EXPECT_NE(nullptr, strstr(buf, R"("key1":"value1")")) << "JSON: " << buf; + EXPECT_NE(nullptr, strstr(buf, R"("key2":"value2")")) << "JSON: " << buf; + EXPECT_NE(nullptr, strstr(buf, R"("key3":3)")) << "JSON: " << buf; + EXPECT_NE(nullptr, strstr(buf, R"("key4":4.0)")) << "JSON: " << buf; + EXPECT_NE(nullptr, strstr(buf, R"("key5":true)")) << "JSON: " << buf; + + //TODO how are versions serialized? A string representation is needed to reconstruct the version from JSON + EXPECT_NE(nullptr, strstr(buf, R"("key6":"celix_version<1.2.3.qualifier>")")) << "JSON: " << buf; + + //And the buf is a valid JSON object + json_error_t error; + json_t* root = json_loads(buf, 0, &error); + EXPECT_NE(nullptr, root) << "Unexpected JSON error: " << error.text; + json_decref(root); +} + +TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithArrayListsTest) { + //Given a properties object with array list values + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_array_list_t* list1 = celix_arrayList_createStringArray(); + celix_arrayList_addString(list1, "value1"); + celix_arrayList_addString(list1, "value2"); + celix_properties_assignArrayList(props, "key1", list1); + //TODO long, double, bool, version + + //And an in-memory stream + celix_autofree char* buf = nullptr; + size_t bufLen = 0; + FILE* stream = open_memstream(&buf, &bufLen); + + //When saving the properties to the stream + auto status = celix_properties_saveToStream(props, stream); + EXPECT_EQ(CELIX_SUCCESS, status); + + //Then the stream contains the JSON representation snippets of the properties + fclose(stream); + EXPECT_NE(nullptr, strstr(buf, R"("key1":["value1","value2"])")) << "JSON: " << buf; + + //And the buf is a valid JSON object + json_error_t error; + json_t* root = json_loads(buf, 0, &error); + EXPECT_NE(nullptr, root) << "Unexpected JSON error: " << error.text; + json_decref(root); +} diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index c5100112..024d849d 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -645,7 +645,7 @@ const celix_properties_entry_t* celix_properties_getEntry(const celix_properties return entry; } -static const bool celix_properties_isEntryArrayListWithElType(const celix_properties_entry_t* entry, +static bool celix_properties_isEntryArrayListWithElType(const celix_properties_entry_t* entry, celix_array_list_element_type_t elType) { return entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_ARRAY_LIST && celix_arrayList_getElementType(entry->typed.arrayValue) == elType; diff --git a/libs/utils/src/properties_serialization.c b/libs/utils/src/properties_serialization.c index 4ebda01e..d52fa6ef 100644 --- a/libs/utils/src/properties_serialization.c +++ b/libs/utils/src/properties_serialization.c @@ -19,11 +19,115 @@ #include "celix_properties.h" +#include "celix_err.h" +#include "celix_stdlib_cleanup.h" + +#include <jansson.h> + +//TODO make jansson wrapper header for auto cleanup, wrap json_object_set_new and json_dumpf for error injection + +static json_t* celix_properties_versionToJson(const celix_version_t *version) { + celix_autofree char* versionStr = celix_version_toString(version); // TODO error handling + return json_sprintf("celix_version<%s>", versionStr); +} + +static json_t* celix_properties_arrayElementEntryValueToJson(celix_array_list_element_type_t elType, + celix_array_list_entry_t entry) { + switch (elType) { + case CELIX_ARRAY_LIST_ELEMENT_TYPE_STRING: + return json_string(entry.stringVal); + case CELIX_ARRAY_LIST_ELEMENT_TYPE_LONG: + return json_integer(entry.longVal); + case CELIX_ARRAY_LIST_ELEMENT_TYPE_DOUBLE: + return json_real(entry.doubleVal); + case CELIX_ARRAY_LIST_ELEMENT_TYPE_BOOL: + return json_boolean(entry.boolVal); + case CELIX_ARRAY_LIST_ELEMENT_TYPE_VERSION: + celix_properties_versionToJson(entry.versionVal); + default: + // LCOV_EXCL_START + celix_err_pushf("Unexpected array list element type %d", elType); + return NULL; + // LCOV_EXCL_STOP + } +} + +static json_t* celix_properties_arrayEntryValueToJson(const celix_properties_entry_t* entry) { + json_t* array = json_array(); + if (!array) { + celix_err_push("Failed to create json array"); + return NULL; + } + + for (int i = 0; i < celix_arrayList_size(entry->typed.arrayValue); ++i) { + celix_array_list_entry_t arrayEntry = celix_arrayList_getEntry(entry->typed.arrayValue, i); + celix_array_list_element_type_t elType = celix_arrayList_getElementType(entry->typed.arrayValue); + json_t* jsonValue = celix_properties_arrayElementEntryValueToJson(elType, arrayEntry); + if (!jsonValue) { + celix_err_push("Failed to create json string"); + json_decref(array); + return NULL; + } + int rc = json_array_append_new(array, jsonValue); + if (rc != 0) { + celix_err_push("Failed to append json string to array"); + json_decref(array); + return NULL; + } + } + return array; +} + +static json_t* celix_properties_entryValueToJson(const celix_properties_entry_t* entry) { + switch (entry->valueType) { + case CELIX_PROPERTIES_VALUE_TYPE_STRING: + return json_string(entry->value); + case CELIX_PROPERTIES_VALUE_TYPE_LONG: + return json_integer(entry->typed.longValue); + case CELIX_PROPERTIES_VALUE_TYPE_DOUBLE: + return json_real(entry->typed.doubleValue); + case CELIX_PROPERTIES_VALUE_TYPE_BOOL: + return json_boolean(entry->typed.boolValue); + case CELIX_PROPERTIES_VALUE_TYPE_VERSION: + return celix_properties_versionToJson(entry->typed.versionValue); + case CELIX_PROPERTIES_VALUE_TYPE_ARRAY_LIST: + return celix_properties_arrayEntryValueToJson(entry); + default: + //LCOV_EXCL_START + celix_err_pushf("Unexpected properties entry type %d", entry->valueType);\ + return NULL; + //LCOV_EXCL_STOP + } +} + celix_status_t celix_properties_saveToStream(const celix_properties_t* properties, FILE* stream) { - celix_status_t status = CELIX_SUCCESS; - (void)properties; - fprintf(stream, "{}"); - return status; + json_t* root = json_object(); + if (!root) { + celix_err_push("Failed to create json object"); + } + + CELIX_PROPERTIES_ITERATE(properties, iter) { + const char* key = iter.key; + json_t* value = celix_properties_entryValueToJson(&iter.entry); + if (!value) { + json_decref(root); + return CELIX_ENOMEM; //TODO improve error + } + int rc = json_object_set_new(root, key, value); + if (rc != 0) { + celix_err_push("Failed to set json object"); + json_decref(root); + return CELIX_ENOMEM; //TODO improve error + } + } + + int rc = json_dumpf(root, stream, JSON_COMPACT); //TODO make celix properties flags for COMPACT and INDENT and maybe other json flags + json_decref(root); + if (rc != 0) { + celix_err_push("Failed to dump json object"); + return CELIX_ENOMEM; //TODO improve error + } + return CELIX_SUCCESS; } celix_status_t celix_properties_loadFromStream(FILE* stream, celix_properties_t** out) {