You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@celix.apache.org by pn...@apache.org on 2024/04/09 21:16:20 UTC
(celix) 03/06: gh-685: Add json prop loading for primitive and initial arr.
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 de0725649450bcbcfbc8145d652b7fb0ea6f0716
Author: Pepijn Noltes <pn...@apache.org>
AuthorDate: Mon Apr 8 23:03:03 2024 +0200
gh-685: Add json prop loading for primitive and initial arr.
---
.../gtest/src/PropertiesSerializationTestSuite.cc | 311 ++++++++++++++-
libs/utils/src/properties_serialization.c | 420 ++++++++++++++++++---
2 files changed, 653 insertions(+), 78 deletions(-)
diff --git a/libs/utils/gtest/src/PropertiesSerializationTestSuite.cc b/libs/utils/gtest/src/PropertiesSerializationTestSuite.cc
index 99cbeceb..a5d6a1df 100644
--- a/libs/utils/gtest/src/PropertiesSerializationTestSuite.cc
+++ b/libs/utils/gtest/src/PropertiesSerializationTestSuite.cc
@@ -17,6 +17,7 @@
* under the License.
*/
+#include <cmath>
#include <gtest/gtest.h>
#include <jansson.h>
@@ -49,23 +50,7 @@ TEST_F(PropertiesSerializationTestSuite, SaveEmptyPropertiesTest) {
EXPECT_STREQ("{}", buf);
}
-TEST_F(PropertiesSerializationTestSuite, LoadEmptyPropertiesTest) {
- //Given an empty JSON object
- const char* json = "{}";
- FILE* stream = fmemopen((void*)json, strlen(json), "r");
-
- //When loading the properties from the stream
- celix_autoptr(celix_properties_t) props = nullptr;
- auto status = celix_properties_loadFromStream(stream, &props);
- EXPECT_EQ(CELIX_SUCCESS, status);
-
- //Then the properties object is empty
- EXPECT_EQ(0, celix_properties_size(props));
-
- fclose(stream);
-}
-
-TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithSingelValuesTest) {
+TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithSingleValuesTest) {
//Given a properties object with single values
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_properties_set(props, "key1", "value1");
@@ -102,14 +87,115 @@ TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithSingelValuesTest) {
json_decref(root);
}
+TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithNaNAndInfValuesTest) {
+ //Given a NAN, INF and -INF value
+ auto keys = {"NAN", "INF", "-INF"};
+ for (const auto& key : keys) {
+ //For every value
+
+ //Given a properties object with a NAN, INF or -INF value
+ celix_autoptr(celix_properties_t) props = celix_properties_create();
+ celix_properties_setDouble(props, key, strtod(key, nullptr));
+
+ //And an in-memory stream
+ celix_autofree char* buf = nullptr;
+ size_t bufLen = 0;
+ FILE* stream = open_memstream(&buf, &bufLen);
+
+ //Then saving the properties to the stream fails, because JSON does not support NAN, INF and -INF
+ celix_err_resetErrors();
+ auto status = celix_properties_saveToStream(props, stream);
+ EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
+
+ //And an error msg is added to celix_err
+ EXPECT_EQ(1, celix_err_getErrorCount());
+ }
+}
+
+
TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithArrayListsTest) {
- //Given a properties object with array list values
+ // 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
+ celix_array_list_t* list2 = celix_arrayList_createLongArray();
+ celix_arrayList_addLong(list2, 1);
+ celix_arrayList_addLong(list2, 2);
+ celix_properties_assignArrayList(props, "key2", list2);
+ celix_array_list_t* list3 = celix_arrayList_createDoubleArray();
+ celix_arrayList_addDouble(list3, 1.0);
+ celix_arrayList_addDouble(list3, 2.0);
+ celix_properties_assignArrayList(props, "key3", list3);
+ celix_array_list_t* list4 = celix_arrayList_createBoolArray();
+ celix_arrayList_addBool(list4, true);
+ celix_arrayList_addBool(list4, false);
+ celix_properties_assignArrayList(props, "key4", list4);
+ celix_array_list_t* list5 = celix_arrayList_createVersionArray();
+ celix_arrayList_addVersion(list5, celix_version_create(1, 2, 3, "qualifier"));
+ celix_arrayList_addVersion(list5, celix_version_create(4, 5, 6, "qualifier"));
+ celix_properties_assignArrayList(props, "key5", list5);
+
+ // 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;
+ EXPECT_NE(nullptr, strstr(buf, R"("key2":[1,2])")) << "JSON: " << buf;
+ EXPECT_NE(nullptr, strstr(buf, R"("key3":[1.0,2.0])")) << "JSON: " << buf;
+ EXPECT_NE(nullptr, strstr(buf, R"("key4":[true,false])")) << "JSON: " << buf;
+ EXPECT_NE(nullptr, strstr(buf, R"("key5":["celix_version<1.2.3.qualifier>","celix_version<4.5.6.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, SaveEmptyArrayTest) {
+ //Given a properties object with an empty array list of with el types string, long, double, bool, version
+ celix_autoptr(celix_properties_t) props = celix_properties_create();
+ celix_properties_assignArrayList(props, "key1", celix_arrayList_createStringArray());
+ celix_properties_assignArrayList(props, "key2", celix_arrayList_createLongArray());
+ celix_properties_assignArrayList(props, "key3", celix_arrayList_createDoubleArray());
+ celix_properties_assignArrayList(props, "key4", celix_arrayList_createBoolArray());
+ celix_properties_assignArrayList(props, "key5", celix_arrayList_createVersionArray());
+ EXPECT_EQ(5, celix_properties_size(props));
+
+ //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 an empty JSON object, because empty arrays are treated as unset
+ fclose(stream);
+ EXPECT_STREQ("{}", buf);
+}
+
+TEST_F(PropertiesSerializationTestSuite, SaveJPathKeysTest) {
+ //Given a properties object with jpath keys
+ celix_autoptr(celix_properties_t) props = celix_properties_create();
+ celix_properties_set(props, "key1", "value1");
+ celix_properties_set(props, "key2", "value2");
+ celix_properties_set(props, "object1/key3", "value3");
+ celix_properties_set(props, "object1/key4", "value4");
+ celix_properties_set(props, "object2/key5", "value5");
+ celix_properties_set(props, "object3/object4/key6", "value6");
//And an in-memory stream
celix_autofree char* buf = nullptr;
@@ -122,7 +208,44 @@ TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithArrayListsTest) {
//Then the stream contains the JSON representation snippets of the properties
fclose(stream);
- EXPECT_NE(nullptr, strstr(buf, R"("key1":["value1","value2"])")) << "JSON: " << buf;
+ 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"("object1":{"key3":"value3","key4":"value4"})")) << "JSON: " << buf;
+ EXPECT_NE(nullptr, strstr(buf, R"("object2":{"key5":"value5"})")) << "JSON: " << buf;
+ EXPECT_NE(nullptr, strstr(buf, R"("object3":{"object4":{"key6":"value6"}})")) << "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, SaveJPathKeysWithCollisionTest) {
+ //Given a properties object with jpath keys that collide
+ celix_autoptr(celix_properties_t) props = celix_properties_create();
+ celix_properties_set(props, "key1/key2/key3", "value1");
+ celix_properties_set(props, "key1/key2", "value2"); //collision with object "key1/key2"
+ celix_properties_set(props, "key4/key5/key6", "value3");
+ celix_properties_set(props, "key4/key5/key6/key7", "value4"); //collision with field "key3/key4/key5"
+
+ //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":{"key2":{"key3":"value1"}})")) << "JSON: " << buf;
+ EXPECT_NE(nullptr, strstr(buf, R"("key1/key2":"value2")")) << "JSON: " << buf;
+ EXPECT_NE(nullptr, strstr(buf, R"("key4/key5/key6":"value3")")) << "JSON: " << buf;
+ EXPECT_NE(nullptr, strstr(buf, R"("key4":{"key5":{"key6":{"key7":"value4"}}})")) << "JSON: " << buf;
+ //Note whether "key1/key2/key3" or "key1/key2" is serializer first depends on the hash order of the keys,
+ //so this test can change if the string hash map implementation changes.
//And the buf is a valid JSON object
json_error_t error;
@@ -130,3 +253,151 @@ TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithArrayListsTest) {
EXPECT_NE(nullptr, root) << "Unexpected JSON error: " << error.text;
json_decref(root);
}
+
+TEST_F(PropertiesSerializationTestSuite, LoadEmptyPropertiesTest) {
+ //Given an empty JSON object
+ const char* json = "{}";
+ FILE* stream = fmemopen((void*)json, strlen(json), "r");
+
+ //When loading the properties from the stream
+ celix_autoptr(celix_properties_t) props = nullptr;
+ auto status = celix_properties_loadFromStream(stream, &props);
+ EXPECT_EQ(CELIX_SUCCESS, status);
+
+ //Then the properties object is empty
+ EXPECT_EQ(0, celix_properties_size(props));
+
+ fclose(stream);
+}
+
+TEST_F(PropertiesSerializationTestSuite, LoadPropertiesWithSingleValuesTest) {
+ //Given a JSON object with single values for types string, long, double, bool and version
+ const char* jsonInput = R"({
+ "strKey":"strValue",
+ "longKey":42,
+ "doubleKey":2.0,
+ "boolKey":true,
+ "versionKey":"celix_version<1.2.3.qualifier>"
+ })";
+
+ //And a stream with the JSON object
+ FILE* stream = fmemopen((void*)jsonInput, strlen(jsonInput), "r");
+
+
+ //When loading the properties from the stream
+ celix_autoptr(celix_properties_t) props = nullptr;
+ auto status = celix_properties_loadFromStream(stream, &props);
+ EXPECT_EQ(CELIX_SUCCESS, status);
+
+ //Then the properties object contains the single values
+ EXPECT_EQ(5, celix_properties_size(props));
+ EXPECT_STREQ("strValue", celix_properties_getString(props, "strKey"));
+ EXPECT_EQ(42, celix_properties_getLong(props, "longKey", -1));
+ EXPECT_DOUBLE_EQ(2.0, celix_properties_getDouble(props, "doubleKey", NAN));
+ EXPECT_TRUE(celix_properties_getBool(props, "boolKey", false));
+ auto* v = celix_properties_getVersion(props, "versionKey");
+ ASSERT_NE(nullptr, v);
+ celix_autofree char* vStr = celix_version_toString(v);
+ EXPECT_STREQ("1.2.3.qualifier", vStr);
+}
+
+TEST_F(PropertiesSerializationTestSuite, LoadPropertiesWithArrayListsTest) {
+ //Given a JSON object with array values for types string, long, double, bool and version
+ const char* jsonInput = R"({
+ "strArr":["value1","value2"],
+ "intArr":[1,2],
+ "realArr":[1.0,2.0],
+ "boolArr":[true,false],
+ "versionArr":["celix_version<1.2.3.qualifier>","celix_version<4.5.6.qualifier>"]
+ })";
+
+ //And a stream with the JSON object
+ FILE* stream = fmemopen((void*)jsonInput, strlen(jsonInput), "r");
+
+ //When loading the properties from the stream
+ celix_autoptr(celix_properties_t) props = nullptr;
+ auto status = celix_properties_loadFromStream(stream, &props);
+ EXPECT_EQ(CELIX_SUCCESS, status);
+
+ //Then the properties object contains the array values
+ EXPECT_EQ(5, celix_properties_size(props));
+
+ //And the string array is correctly loaded
+ auto* strArr = celix_properties_getArrayList(props, "strArr");
+ ASSERT_NE(nullptr, strArr);
+ EXPECT_EQ(CELIX_ARRAY_LIST_ELEMENT_TYPE_STRING, celix_arrayList_getElementType(strArr));
+ EXPECT_EQ(2, celix_arrayList_size(strArr));
+ EXPECT_STREQ("value1", celix_arrayList_getString(strArr, 0));
+ EXPECT_STREQ("value2", celix_arrayList_getString(strArr, 1));
+
+ //And the long array is correctly loaded
+ auto* intArr = celix_properties_getArrayList(props, "intArr");
+ ASSERT_NE(nullptr, intArr);
+ EXPECT_EQ(CELIX_ARRAY_LIST_ELEMENT_TYPE_LONG, celix_arrayList_getElementType(intArr));
+ EXPECT_EQ(2, celix_arrayList_size(intArr));
+ EXPECT_EQ(1, celix_arrayList_getLong(intArr, 0));
+ EXPECT_EQ(2, celix_arrayList_getLong(intArr, 1));
+
+ //And the double array is correctly loaded
+ auto* realArr = celix_properties_getArrayList(props, "realArr");
+ ASSERT_NE(nullptr, realArr);
+ EXPECT_EQ(CELIX_ARRAY_LIST_ELEMENT_TYPE_DOUBLE, celix_arrayList_getElementType(realArr));
+ EXPECT_EQ(2, celix_arrayList_size(realArr));
+ EXPECT_DOUBLE_EQ(1.0, celix_arrayList_getDouble(realArr, 0));
+ EXPECT_DOUBLE_EQ(2.0, celix_arrayList_getDouble(realArr, 1));
+
+ //And the bool array is correctly loaded
+ auto* boolArr = celix_properties_getArrayList(props, "boolArr");
+ ASSERT_NE(nullptr, boolArr);
+ EXPECT_EQ(CELIX_ARRAY_LIST_ELEMENT_TYPE_BOOL, celix_arrayList_getElementType(boolArr));
+ EXPECT_EQ(2, celix_arrayList_size(boolArr));
+ EXPECT_TRUE(celix_arrayList_getBool(boolArr, 0));
+ EXPECT_FALSE(celix_arrayList_getBool(boolArr, 1));
+
+ //And the version array is correctly loaded
+ auto* versionArr = celix_properties_getArrayList(props, "versionArr");
+ ASSERT_NE(nullptr, versionArr);
+ EXPECT_EQ(CELIX_ARRAY_LIST_ELEMENT_TYPE_VERSION, celix_arrayList_getElementType(versionArr));
+ EXPECT_EQ(2, celix_arrayList_size(versionArr));
+ auto* v1 = celix_arrayList_getVersion(versionArr, 0);
+ ASSERT_NE(nullptr, v1);
+ celix_autofree char* v1Str = celix_version_toString(v1);
+ EXPECT_STREQ("1.2.3.qualifier", v1Str);
+ auto* v2 = celix_arrayList_getVersion(versionArr, 1);
+ ASSERT_NE(nullptr, v2);
+ celix_autofree char* v2Str = celix_version_toString(v2);
+ EXPECT_STREQ("4.5.6.qualifier", v2Str);
+}
+
+//TODO test with combination json_int and json_real, this should be promoted to double
+
+TEST_F(PropertiesSerializationTestSuite, LoadPropertiesWithInvalidInputTest) {
+ auto invalidInputs = {
+ R"({)", // invalid JSON (caught by jansson)
+ R"({"emptyArr":[]})", // Empty array, not supported
+ R"({"mixedArr":["string", true]})", // Mixed array, not supported
+ R"({"mixedArr":[1.9, 2]})", // Mixed array, TODO this should be supported
+ };
+ for (auto& invalidInput: invalidInputs) {
+ //Given an invalid JSON object
+ FILE* stream = fmemopen((void*)invalidInput, strlen(invalidInput), "r");
+
+ //When loading the properties from the stream
+ celix_autoptr(celix_properties_t) props = nullptr;
+ auto status = celix_properties_loadFromStream(stream, &props);
+
+ //Then loading fails
+ EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
+
+ //And at least one error message is added to celix_err
+ EXPECT_GE(celix_err_getErrorCount(), 1);
+ celix_err_printErrors(stderr, "Error: ", "\n");
+
+ fclose(stream);
+ }
+}
+
+//TODO test deserialize null values
+//TODO test serialize with empty array (treated as unset)
+//TODO test with jpath subset keys and json serialization
+//TODO test with key starting and ending with slash
diff --git a/libs/utils/src/properties_serialization.c b/libs/utils/src/properties_serialization.c
index d52fa6ef..0b4c5f8b 100644
--- a/libs/utils/src/properties_serialization.c
+++ b/libs/utils/src/properties_serialization.c
@@ -21,83 +21,194 @@
#include "celix_err.h"
#include "celix_stdlib_cleanup.h"
+#include "celix_utils.h"
+#include <assert.h>
#include <jansson.h>
+#include <math.h>
+#include <string.h>
-//TODO make jansson wrapper header for auto cleanup, wrap json_object_set_new and json_dumpf for error injection
+static celix_status_t celix_properties_loadValue(celix_properties_t* props, const char* key, const json_t* jsonValue);
-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);
+// TODO make jansson wrapper header for auto cleanup, wrap json_object_set_new and json_dumpf for error injection
+
+static celix_status_t celix_properties_versionToJson(const celix_version_t* version, json_t** out) {
+ celix_autofree char* versionStr = celix_version_toString(version);
+ if (!versionStr) {
+ celix_err_push("Failed to create version string");
+ return CELIX_ENOMEM;
+ }
+ *out = json_sprintf("celix_version<%s>", versionStr);
+ if (!*out) {
+ celix_err_push("Failed to create json string");
+ return CELIX_ENOMEM;
+ }
+ return CELIX_SUCCESS;
}
-static json_t* celix_properties_arrayElementEntryValueToJson(celix_array_list_element_type_t elType,
- celix_array_list_entry_t entry) {
+static celix_status_t celix_properties_arrayElementEntryValueToJson(celix_array_list_element_type_t elType,
+ celix_array_list_entry_t entry,
+ json_t** out) {
+ *out = NULL;
switch (elType) {
case CELIX_ARRAY_LIST_ELEMENT_TYPE_STRING:
- return json_string(entry.stringVal);
+ *out = json_string(entry.stringVal);
+ break;
case CELIX_ARRAY_LIST_ELEMENT_TYPE_LONG:
- return json_integer(entry.longVal);
+ *out = json_integer(entry.longVal);
+ break;
case CELIX_ARRAY_LIST_ELEMENT_TYPE_DOUBLE:
- return json_real(entry.doubleVal);
+ *out = json_real(entry.doubleVal);
+ break;
case CELIX_ARRAY_LIST_ELEMENT_TYPE_BOOL:
- return json_boolean(entry.boolVal);
+ *out = json_boolean(entry.boolVal);
+ break;
case CELIX_ARRAY_LIST_ELEMENT_TYPE_VERSION:
- celix_properties_versionToJson(entry.versionVal);
+ return celix_properties_versionToJson(entry.versionVal, out);
default:
// LCOV_EXCL_START
celix_err_pushf("Unexpected array list element type %d", elType);
- return NULL;
+ return CELIX_ILLEGAL_ARGUMENT;
// LCOV_EXCL_STOP
}
+ if (!*out) {
+ celix_err_push("Failed to create json value");
+ return CELIX_ENOMEM;
+ }
+ return CELIX_SUCCESS;
}
-static json_t* celix_properties_arrayEntryValueToJson(const celix_properties_entry_t* entry) {
+static celix_status_t celix_properties_arrayEntryValueToJson(const celix_properties_entry_t* entry, json_t** out) {
+ *out = NULL;
+ if (celix_arrayList_size(entry->typed.arrayValue) == 0) {
+ return CELIX_SUCCESS; // empty array -> treat as unset property
+ }
+
json_t* array = json_array();
if (!array) {
celix_err_push("Failed to create json array");
- return NULL;
+ return CELIX_ENOMEM;
}
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_t* jsonValue;
+ celix_status_t status = celix_properties_arrayElementEntryValueToJson(elType, arrayEntry, &jsonValue);
+ if (status != CELIX_SUCCESS) {
json_decref(array);
- return NULL;
+ return status;
+ } else if (!jsonValue) {
+ // ignore unset values
+ } else {
+ 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 CELIX_ENOMEM;
+ }
}
}
- return array;
+
+ *out = array;
+ return CELIX_SUCCESS;
}
-static json_t* celix_properties_entryValueToJson(const celix_properties_entry_t* entry) {
+static celix_status_t celix_properties_entryValueToJson(const celix_properties_entry_t* entry, json_t** out) {
+ *out = NULL;
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
+ case CELIX_PROPERTIES_VALUE_TYPE_STRING:
+ *out = json_string(entry->value);
+ break;
+ case CELIX_PROPERTIES_VALUE_TYPE_LONG:
+ *out = json_integer(entry->typed.longValue);
+ break;
+ case CELIX_PROPERTIES_VALUE_TYPE_DOUBLE:
+ if (isnan(entry->typed.doubleValue) || isinf(entry->typed.doubleValue)) {
+ celix_err_pushf("Double NaN or Inf not supported in JSON.");
+ return CELIX_ILLEGAL_ARGUMENT;
+ }
+ *out = json_real(entry->typed.doubleValue);
+ break;
+ case CELIX_PROPERTIES_VALUE_TYPE_BOOL:
+ *out = json_boolean(entry->typed.boolValue);
+ break;
+ case CELIX_PROPERTIES_VALUE_TYPE_VERSION:
+ return celix_properties_versionToJson(entry->typed.versionValue, out);
+ case CELIX_PROPERTIES_VALUE_TYPE_ARRAY_LIST:
+ return celix_properties_arrayEntryValueToJson(entry, out);
+ default:
+ // LCOV_EXCL_START
+ celix_err_pushf("Unexpected properties entry type %d", entry->valueType);
+ return CELIX_ILLEGAL_ARGUMENT;
+ // LCOV_EXCL_STOP
+ }
+
+ if (!*out) {
+ celix_err_push("Failed to create json value");
+ return CELIX_ENOMEM;
}
+ return CELIX_SUCCESS;
+}
+
+static celix_status_t
+celix_properties_addEntryToJson(const celix_properties_entry_t* entry, const char* key, json_t* root) {
+ json_t* jsonObj = root;
+ const char* subKey = key;
+ const char* slash = strstr(key, "/");
+ while (slash) {
+ celix_autofree const char* name = strndup(subKey, slash - subKey);
+ if (!name) {
+ celix_err_push("Failed to create name string");
+ return CELIX_ENOMEM;
+ }
+ json_t* subObj = json_object_get(jsonObj, name);
+ if (!subObj) {
+ subObj = json_object();
+ if (!subObj) {
+ celix_err_push("Failed to create json object");
+ return CELIX_ENOMEM;
+ }
+ int rc = json_object_set_new(jsonObj, name, subObj);
+ if (rc != 0) {
+ celix_err_push("Failed to set json object");
+ return CELIX_ENOMEM;
+ }
+ } else if (!json_is_object(subObj)) {
+ // subObj is not an object, so obj cannot be added -> adding obj flat
+ jsonObj = root;
+ subKey = key;
+ break;
+ }
+
+ jsonObj = subObj;
+ subKey = slash + 1;
+ slash = strstr(subKey, "/");
+
+ json_t* field = json_object_get(jsonObj, subKey);
+ if (field) {
+ // field already exists, so adding obj flat
+ jsonObj = root;
+ subKey = key;
+ break;
+ }
+ }
+
+ json_t* value;
+ celix_status_t status = celix_properties_entryValueToJson(entry, &value);
+ if (status != CELIX_SUCCESS) {
+ return status;
+ } else if (!value) {
+ // ignore unset values
+ } else {
+ int rc = json_object_set_new(jsonObj, subKey, value);
+ if (rc != 0) {
+ celix_err_push("Failed to set json object");
+ return CELIX_ENOMEM;
+ }
+ }
+
+ return CELIX_SUCCESS;
}
celix_status_t celix_properties_saveToStream(const celix_properties_t* properties, FILE* stream) {
@@ -107,33 +218,226 @@ celix_status_t celix_properties_saveToStream(const celix_properties_t* propertie
}
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");
+ celix_status_t status = celix_properties_addEntryToJson(&iter.entry, iter.key, root);
+ if (status != CELIX_SUCCESS) {
json_decref(root);
- return CELIX_ENOMEM; //TODO improve error
+ return status;
}
}
- int rc = json_dumpf(root, stream, JSON_COMPACT); //TODO make celix properties flags for COMPACT and INDENT and maybe other json flags
+ 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_ENOMEM; // TODO improve error
}
return CELIX_SUCCESS;
}
-celix_status_t celix_properties_loadFromStream(FILE* stream, celix_properties_t** out) {
+static celix_version_t* celix_properties_parseVersion(const char* value) {
+ // precondition: value is a valid version string (14 chars prefix and 1 char suffix)
+ celix_version_t* version = NULL;
+ char buf[32];
+ char* corrected = celix_utils_writeOrCreateString(buf, sizeof(buf), "%.*s", (int)strlen(value) - 15, value + 14);
+ if (!corrected) {
+ celix_err_push("Failed to create corrected version string");
+ return NULL;
+ }
+ celix_status_t status = celix_version_parse(corrected, &version);
+ celix_utils_freeStringIfNotEqual(buf, corrected);
+ if (status != CELIX_SUCCESS) {
+ celix_err_push("Failed to parse version string");
+ return NULL;
+ }
+ return version;
+}
+
+static bool celix_properties_isVersionString(const char* value) {
+ return strncmp(value, "celix_version<", 14) == 0 && value[strlen(value) - 1] == '>';
+}
+
+/**
+ * @brief Determine the array list element type based on the json value.
+ *
+ * If the array is empty or of a mixed type, the element type cannot be determined and a CELIX_ILLEGAL_ARGUMENT is
+ * returned.
+ *
+ * @param[in] value The json value.
+ * @param[out] out The array list element type.
+ * @return CELIX_SUCCESS if the array list element type could be determined or CELIX_ILLEGAL_ARGUMENT if the array
+ * type could not be determined.
+ */
+static celix_status_t celix_properties_determineArrayType(const json_t* jsonArray,
+ celix_array_list_element_type_t* out) {
+ size_t size = json_array_size(jsonArray);
+ if (size == 0) {
+ celix_err_push("Empty array");
+ return CELIX_ILLEGAL_ARGUMENT;
+ }
+
+ json_t* value;
+ int index;
+ json_type type = JSON_NULL;
+ bool versionType = false;
+ json_array_foreach(jsonArray, index, value) {
+ if (index == 0) {
+ type = json_typeof(value);
+ if (type == JSON_STRING && celix_properties_isVersionString(json_string_value(value))) {
+ versionType = true;
+ }
+ } else if ((type == JSON_TRUE || type == JSON_FALSE) &&
+ (json_typeof(value) == JSON_TRUE || json_typeof(value) == JSON_FALSE)) {
+ // bool, ok.
+ continue;
+ } else if (type != json_typeof(value)) {
+ celix_err_push("Mixed types in array");
+ return CELIX_ILLEGAL_ARGUMENT;
+ } else if (versionType) {
+ if (json_typeof(value) != JSON_STRING || !celix_properties_isVersionString(json_string_value(value))) {
+ celix_err_push("Mixed version and non-version strings in array");
+ return CELIX_ILLEGAL_ARGUMENT;
+ }
+ }
+ }
+
+ switch (type) {
+ case JSON_STRING:
+ *out = versionType ? CELIX_ARRAY_LIST_ELEMENT_TYPE_VERSION : CELIX_ARRAY_LIST_ELEMENT_TYPE_STRING;
+ break;
+ case JSON_INTEGER:
+ *out = CELIX_ARRAY_LIST_ELEMENT_TYPE_LONG;
+ break;
+ case JSON_REAL:
+ *out = CELIX_ARRAY_LIST_ELEMENT_TYPE_DOUBLE;
+ break;
+ case JSON_TRUE:
+ case JSON_FALSE:
+ *out = CELIX_ARRAY_LIST_ELEMENT_TYPE_BOOL;
+ break;
+ default:
+ celix_err_pushf("Unexpected json array type %d", type);
+ return CELIX_ILLEGAL_ARGUMENT;
+ }
+
+ return CELIX_SUCCESS;
+}
+
+static celix_status_t celix_properties_loadArray(celix_properties_t* props, const char* key, const json_t* jsonArray) {
+ celix_array_list_element_type_t elType;
+ celix_status_t status = celix_properties_determineArrayType(jsonArray, &elType);
+ if (status != CELIX_SUCCESS) {
+ return status;
+ }
+
+ celix_array_list_create_options_t opts = CELIX_EMPTY_ARRAY_LIST_CREATE_OPTIONS;
+ opts.elementType = elType;
+ celix_autoptr(celix_array_list_t) array = celix_arrayList_createWithOptions(&opts);
+ if (!array) {
+ return CELIX_ENOMEM;
+ }
+
+ json_t* value;
+ int index;
+ json_array_foreach(jsonArray, index, value) {
+ switch (elType) {
+ case CELIX_ARRAY_LIST_ELEMENT_TYPE_STRING:
+ status = celix_arrayList_addString(array, json_string_value(value));
+ break;
+ case CELIX_ARRAY_LIST_ELEMENT_TYPE_LONG:
+ status = celix_arrayList_addLong(array, (long)json_integer_value(value));
+ break;
+ case CELIX_ARRAY_LIST_ELEMENT_TYPE_DOUBLE:
+ status = celix_arrayList_addDouble(array, json_real_value(value));
+ break;
+ case CELIX_ARRAY_LIST_ELEMENT_TYPE_BOOL:
+ status = celix_arrayList_addBool(array, json_boolean_value(value));
+ break;
+ case CELIX_ARRAY_LIST_ELEMENT_TYPE_VERSION: {
+ celix_version_t* v = celix_properties_parseVersion(json_string_value(value));
+ if (!v) {
+ return CELIX_ILLEGAL_ARGUMENT;
+ }
+ status = celix_arrayList_addVersion(array, v);
+ break;
+ }
+ default:
+ // LCOV_EXCL_START
+ celix_err_pushf("Unexpected array list element type %d", elType);
+ return CELIX_ILLEGAL_ARGUMENT;
+ // LCOV_EXCL_STOP
+ }
+ if (status != CELIX_SUCCESS) {
+ return status;
+ }
+ }
+ return celix_properties_assignArrayList(props, key, celix_steal_ptr(array));
+}
+
+static celix_status_t celix_properties_loadValue(celix_properties_t* props, const char* key, const json_t* jsonValue) {
celix_status_t status = CELIX_SUCCESS;
- (void)stream;
+ if (json_is_string(jsonValue) && celix_properties_isVersionString(json_string_value(jsonValue))) {
+ celix_version_t* version = celix_properties_parseVersion(json_string_value(jsonValue));
+ if (!version) {
+ return CELIX_ILLEGAL_ARGUMENT;
+ }
+ status = celix_properties_setVersion(props, key, version);
+ } else if (json_is_string(jsonValue)) {
+ status = celix_properties_setString(props, key, json_string_value(jsonValue));
+ } else if (json_is_integer(jsonValue)) {
+ status = celix_properties_setLong(props, key, json_integer_value(jsonValue));
+ } else if (json_is_real(jsonValue)) {
+ status = celix_properties_setDouble(props, key, json_real_value(jsonValue));
+ } else if (json_is_boolean(jsonValue)) {
+ status = celix_properties_setBool(props, key, json_boolean_value(jsonValue));
+ } else if (json_is_object(jsonValue)) {
+ // TODO
+ status = CELIX_ILLEGAL_ARGUMENT;
+ } else if (json_is_array(jsonValue)) {
+ status = celix_properties_loadArray(props, key, jsonValue);
+ } else {
+ // LCOV_EXCL_START
+ celix_err_pushf("Unexpected json value type");
+ return CELIX_ILLEGAL_ARGUMENT;
+ // LCOV_EXCL_STOP
+ }
+ return status;
+}
+
+static celix_status_t celix_properties_loadFromJson(json_t* obj, celix_properties_t** out) {
+ assert(obj != NULL && json_is_object(obj));
celix_autoptr(celix_properties_t) props = celix_properties_create();
+ if (!props) {
+ return CELIX_ENOMEM;
+ }
+
+ // add loop (obj=root, prefix="" and extend prefix when going into sub objects)
+ const char* key;
+ json_t* value;
+ json_object_foreach(obj, key, value) {
+ if (json_is_object(value)) {
+ // TODO
+ return CELIX_ILLEGAL_ARGUMENT;
+ }
+ celix_status_t status = celix_properties_loadValue(props, key, value);
+ if (status != CELIX_SUCCESS) {
+ return status;
+ }
+ }
+
*out = celix_steal_ptr(props);
- return status;
+ return CELIX_SUCCESS;
+}
+
+celix_status_t celix_properties_loadFromStream(FILE* stream, celix_properties_t** out) {
+ json_error_t jsonError;
+ json_t* root = json_loadf(stream, 0, &jsonError);
+ if (!root) {
+ celix_err_pushf("Failed to parse json: %s", jsonError.text);
+ return CELIX_ILLEGAL_ARGUMENT;
+ }
+
+ return celix_properties_loadFromJson(root, out);
}
\ No newline at end of file