Skip to content

Commit

Permalink
gh-685: Encode list containing NAN/INF and some minor documentation …
Browse files Browse the repository at this point in the history
…improvements.
  • Loading branch information
PengZheng committed May 23, 2024
1 parent f3cec78 commit 83682fd
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ TEST_F(PropertiesEncodingErrorInjectionTestSuite, EncodeArrayErrorTest) {
EXPECT_EQ(ENOMEM, status);

// And I expect 3 error message in celix_err
EXPECT_EQ(3, celix_err_getErrorCount());
EXPECT_EQ(4, celix_err_getErrorCount());
celix_err_printErrors(stderr, "Test Error: ", "\n");
}

Expand Down
30 changes: 30 additions & 0 deletions libs/utils/gtest/src/PropertiesEncodingTestSuite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,36 @@ TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithNaNAndInfValuesTest)
}
}

TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithArrayListsContainingNaNAndInfValueTest) {
auto keys = {"NAN", "INF", "-INF"};
for (const auto& key : keys) {
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_autoptr(celix_array_list_t) list = celix_arrayList_createDoubleArray();
celix_arrayList_addDouble(list, strtod(key, nullptr));
celix_properties_assignArrayList(props, key, celix_steal_ptr(list));

// Then saving the properties to a string succeeds, but value is not added to the JSON (because JSON does not
// support NAN, INF and -INF)
celix_autofree char* output;
auto status = celix_properties_saveToString(props, 0, &output);
ASSERT_EQ(CELIX_SUCCESS, status);
EXPECT_STREQ("{}", output);

//And saving the properties to a string with the flag CELIX_PROPERTIES_ENCODE_ERROR_ON_NAN_INF fails
celix_err_resetErrors();
char* output2;
status = celix_properties_saveToString(props, CELIX_PROPERTIES_ENCODE_ERROR_ON_NAN_INF, &output2);
EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
//And an error msg is added to celix_err
EXPECT_EQ(2, celix_err_getErrorCount());

celix_err_resetErrors();
char* output3;
status = celix_properties_saveToString(props, CELIX_PROPERTIES_ENCODE_ERROR_ON_EMPTY_ARRAYS, &output3);
EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
EXPECT_EQ(1, celix_err_getErrorCount());
}
}

TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithArrayListsTest) {
// Given a properties object with array list values
Expand Down
2 changes: 1 addition & 1 deletion libs/utils/include/celix/Properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -1054,7 +1054,7 @@ namespace celix {
/**
* @brief Load a Properties object from a file.
*
* @warning The name if temporary and will be renamed to celix::Properties::load in the future (when
* @warning The name is temporary and will be renamed to celix::Properties::load in the future (when
* the current celix::Properties::load is removed).
*
* The content of the filename file is expected to be in the format of a JSON object.
Expand Down
16 changes: 8 additions & 8 deletions libs/utils/include/celix_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ extern "C" {
*/
typedef enum celix_properties_value_type {
CELIX_PROPERTIES_VALUE_TYPE_UNSET = 0, /**< Property value is not set. */
CELIX_PROPERTIES_VALUE_TYPE_STRING = 1, /**< Property value is a string. */
CELIX_PROPERTIES_VALUE_TYPE_STRING = 1, /**< Property value is a UTF-8 encoded string. */
CELIX_PROPERTIES_VALUE_TYPE_LONG = 2, /**< Property value is a long integer. */
CELIX_PROPERTIES_VALUE_TYPE_DOUBLE = 3, /**< Property value is a double. */
CELIX_PROPERTIES_VALUE_TYPE_BOOL = 4, /**< Property value is a boolean. */
Expand Down Expand Up @@ -1087,7 +1087,7 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_saveToStream(const celix_prop
*
* @param[in] properties The properties object to encode.
* @param[in] filename The file to write the JSON representation of the properties object to.
* @param[in] encodeFlags The flags to use when encoding the input string.
* @param[in] encodeFlags The flags to use when encoding the input properties.
* @return CELIX_SUCCESS if the operation was successful, CELIX_ILLEGAL_ARGUMENT if the provided properties cannot be
* encoded to a JSON representation and ENOMEM if there was not enough memory. CELIX_FILE_IO_EXCEPTION if the file
* could not be opened or written to.
Expand All @@ -1105,7 +1105,7 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_save(const celix_properties_t
* The default encoding style is a compact and flat JSON representation.
*
* @param[in] properties The properties object to encode.
* @param[in] encodeFlags The flags to use when encoding the input string.
* @param[in] encodeFlags The flags to use when encoding the input properties.
* @param[out] out The JSON string representation of the properties object. The caller is responsible for freeing the
* returned string using free.
* @return CELIX_SUCCESS if the operation was successful, CELIX_ILLEGAL_ARGUMENT if the provided properties cannot be
Expand Down Expand Up @@ -1183,8 +1183,7 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_saveToString(const celix_prop
*
* Note that empty keys are valid in JSON and valid in properties, but not always desired.
*
* If this flag is set, the decoding will fail if the input contains an empty key and if this flag is not set, the
* decoding will not fail and the JSON empty key entry will be ignored.
* If this flag is set, the decoding will fail if the input contains an empty key.
*/
#define CELIX_PROPERTIES_DECODE_ERROR_ON_EMPTY_KEYS 0x20

Expand Down Expand Up @@ -1232,7 +1231,8 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_saveToString(const celix_prop
* @param[out] out The properties object that will be created from the input string. The caller is responsible for
* freeing the returned properties object using celix_properties_destroy.
* @return CELIX_SUCCESS if the operation was successful, CELIX_ILLEGAL_ARGUMENT if the provided input cannot be
* decoded to a properties object and ENOMEM if there was not enough memory.
* decoded to a properties object and ENOMEM if there was not enough memory. CELIX_FILE_IO_EXCEPTION if the file
* could not be read.
*/
CELIX_UTILS_EXPORT celix_status_t celix_properties_loadFromStream(FILE* stream,
int decodeFlags,
Expand All @@ -1241,7 +1241,7 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_loadFromStream(FILE* stream,
/**
* @brief Load properties from a file.
*
* @warning The name if temporary and will be renamed to celix_properties_load in the future (when
* @warning The name is temporary and will be renamed to celix_properties_load in the future (when
* the current celix_properties_load is removed).
*
* The content of the filename file is expected to be in the format of a JSON object.
Expand All @@ -1266,7 +1266,7 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_load2(const char* filename,
/**
* @brief Load properties from a string.
*
* @warning The name if temporary and will be renamed to celix_properties_loadFromString in the future (when
* @warning The name is temporary and will be renamed to celix_properties_loadFromString in the future (when
* the current celix_properties_loadFromString is removed).
*
* The input string is expected to be in the format of a JSON object.
Expand Down
24 changes: 21 additions & 3 deletions libs/utils/src/properties_encoding.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ static celix_status_t celix_properties_versionToJson(const celix_version_t* vers

static celix_status_t celix_properties_arrayElementEntryValueToJson(celix_array_list_element_type_t elType,
celix_array_list_entry_t entry,
int flags,
json_t** out) {
*out = NULL;
switch (elType) {
Expand All @@ -60,6 +61,13 @@ static celix_status_t celix_properties_arrayElementEntryValueToJson(celix_array_
*out = json_integer(entry.longVal);
break;
case CELIX_ARRAY_LIST_ELEMENT_TYPE_DOUBLE:
if (isnan(entry.doubleVal) || isinf(entry.doubleVal)) {
if (flags & CELIX_PROPERTIES_ENCODE_ERROR_ON_NAN_INF) {
celix_err_push("Invalid NaN or Inf.");
return CELIX_ILLEGAL_ARGUMENT;
}
return CELIX_SUCCESS; // ignore NaN and Inf
}
*out = json_real(entry.doubleVal);
break;
case CELIX_ARRAY_LIST_ELEMENT_TYPE_BOOL:
Expand Down Expand Up @@ -99,12 +107,14 @@ static celix_status_t celix_properties_arrayEntryValueToJson(const char* key,
return ENOMEM;
}

for (int i = 0; i < celix_arrayList_size(entry->typed.arrayValue); ++i) {
int size = celix_arrayList_size(entry->typed.arrayValue);
celix_array_list_element_type_t elType = celix_arrayList_getElementType(entry->typed.arrayValue);
for (int i = 0; i < size; ++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_status_t status = celix_properties_arrayElementEntryValueToJson(elType, arrayEntry, &jsonValue);
celix_status_t status = celix_properties_arrayElementEntryValueToJson(elType, arrayEntry, flags, &jsonValue);
if (status != CELIX_SUCCESS) {
celix_err_pushf("Failed to encode array element(%d) for key %s.", i, key);
return status;
} else if (!jsonValue) {
// ignore unset values
Expand All @@ -117,6 +127,14 @@ static celix_status_t celix_properties_arrayEntryValueToJson(const char* key,
}
}

if (json_array_size(array) == 0) {
if (flags & CELIX_PROPERTIES_ENCODE_ERROR_ON_EMPTY_ARRAYS) {
celix_err_pushf("Invalid empty array for key %s.", key);
return CELIX_ILLEGAL_ARGUMENT;
}
return CELIX_SUCCESS; // empty array -> treat as unset property
}

*out = celix_steal_ptr(array);
return CELIX_SUCCESS;
}
Expand Down

0 comments on commit 83682fd

Please sign in to comment.