From 83682fd52919b0aaee3f286a8992fe484b825bd1 Mon Sep 17 00:00:00 2001 From: PengZheng Date: Thu, 23 May 2024 22:29:36 +0800 Subject: [PATCH] gh-685: Encode list containing NAN/INF and some minor documentation improvements. --- ...opertiesEncodingErrorInjectionTestSuite.cc | 2 +- .../gtest/src/PropertiesEncodingTestSuite.cc | 30 +++++++++++++++++++ libs/utils/include/celix/Properties.h | 2 +- libs/utils/include/celix_properties.h | 16 +++++----- libs/utils/src/properties_encoding.c | 24 +++++++++++++-- 5 files changed, 61 insertions(+), 13 deletions(-) diff --git a/libs/utils/gtest/src/PropertiesEncodingErrorInjectionTestSuite.cc b/libs/utils/gtest/src/PropertiesEncodingErrorInjectionTestSuite.cc index 28590e939..9a5c15f7e 100644 --- a/libs/utils/gtest/src/PropertiesEncodingErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesEncodingErrorInjectionTestSuite.cc @@ -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"); } diff --git a/libs/utils/gtest/src/PropertiesEncodingTestSuite.cc b/libs/utils/gtest/src/PropertiesEncodingTestSuite.cc index b25d12603..2d7348108 100644 --- a/libs/utils/gtest/src/PropertiesEncodingTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesEncodingTestSuite.cc @@ -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 diff --git a/libs/utils/include/celix/Properties.h b/libs/utils/include/celix/Properties.h index 166a59bb0..a924946f4 100644 --- a/libs/utils/include/celix/Properties.h +++ b/libs/utils/include/celix/Properties.h @@ -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. diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index 5ceec16b4..f49d4628b 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -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. */ @@ -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. @@ -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 @@ -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 @@ -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, @@ -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. @@ -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. diff --git a/libs/utils/src/properties_encoding.c b/libs/utils/src/properties_encoding.c index dd866a500..53af5ec4b 100644 --- a/libs/utils/src/properties_encoding.c +++ b/libs/utils/src/properties_encoding.c @@ -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) { @@ -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: @@ -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 @@ -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; }