libs/utils/src/properties_encoding.c (350 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include "celix_properties.h" #include "celix_properties_private.h" #include "celix_err.h" #include "celix_stdlib_cleanup.h" #include "celix_utils.h" #include "celix_array_list_encoding_private.h" #include "celix_json_utils_private.h" #include <assert.h> #include <jansson.h> #include <math.h> #include <string.h> #define CELIX_PROPERTIES_JSONPATH_SEPARATOR '.' static celix_status_t celix_properties_decodeValue(celix_properties_t* props, const char* key, json_t* jsonValue, int flags); static celix_status_t celix_properties_arrayEntryValueToJson(const char* key, const celix_properties_entry_t* entry, int flags, json_t** out) { *out = NULL; int encodeArrayFlags = 0; if (flags & CELIX_PROPERTIES_ENCODE_ERROR_ON_EMPTY_ARRAYS) { encodeArrayFlags |= CELIX_ARRAY_LIST_ENCODE_ERROR_ON_EMPTY_ARRAYS; } if (flags & CELIX_PROPERTIES_ENCODE_ERROR_ON_NAN_INF) { encodeArrayFlags |= CELIX_ARRAY_LIST_ENCODE_ERROR_ON_NAN_INF; } json_auto_t* array = NULL; celix_status_t status = celix_arrayList_encodeToJson(entry->typed.arrayValue, encodeArrayFlags, &array); if (status != CELIX_SUCCESS) { celix_err_pushf("Failed to encode array list for key %s.", key); return status; } if (json_array_size(array) == 0) {//allow empty arrays, but treat as unset property return CELIX_SUCCESS; } *out = celix_steal_ptr(array); return CELIX_SUCCESS; } static celix_status_t celix_properties_entryValueToJson(const char* key, const celix_properties_entry_t* entry, int flags, json_t** out) { *out = NULL; switch (entry->valueType) { 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)) { if (flags & CELIX_PROPERTIES_ENCODE_ERROR_ON_NAN_INF) { celix_err_pushf("Invalid NaN or Inf in key '%s'.", key); return CELIX_ILLEGAL_ARGUMENT; } return CELIX_SUCCESS; // ignore NaN and Inf } *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_utils_versionToJson(entry->typed.versionValue, out); case CELIX_PROPERTIES_VALUE_TYPE_ARRAY_LIST: return celix_properties_arrayEntryValueToJson(key, entry, flags, 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_pushf("Failed to create json value for key '%s'.", key); return ENOMEM; } return CELIX_SUCCESS; } static celix_status_t celix_properties_addJsonValueToJson(json_t* value, const char* key, json_t* obj, int flags) { if (!value) { // ignore unset values return CELIX_SUCCESS; } json_t* field = json_object_get(obj, key); if (field) { if (flags & CELIX_PROPERTIES_ENCODE_ERROR_ON_COLLISIONS) { celix_err_pushf("Invalid key collision. key '%s' already exists.", key); json_decref(value); return CELIX_ILLEGAL_ARGUMENT; } } int rc = json_object_set_new(obj, key, value); if (rc != 0) { celix_err_push("Failed to set json object"); return ENOMEM; } return CELIX_SUCCESS; } static celix_status_t celix_properties_addPropertiesEntryFlatToJson(const celix_properties_entry_t* entry, const char* key, json_t* root, int flags) { json_t* value; celix_status_t status = celix_properties_entryValueToJson(key, entry, flags, &value); status = CELIX_DO_IF(status, celix_properties_addJsonValueToJson(value, key, root, flags)); return status; } static celix_status_t celix_properties_addPropertiesEntryToJson(const celix_properties_entry_t* entry, const char* key, json_t* root, int flags) { json_t* jsonObj = root; const char* fieldName = key; const char* slash = strchr(key, CELIX_PROPERTIES_JSONPATH_SEPARATOR); while (slash) { char buf[64]; char* name = celix_utils_writeOrCreateString(buf, sizeof(buf), "%.*s", (int)(slash - fieldName), fieldName); celix_auto(celix_utils_string_guard_t) strGuard = celix_utils_stringGuard_init(buf, name); if (!name) { celix_err_push("Failed to create name string"); return ENOMEM; } json_t* subObj = json_object_get(jsonObj, name); if (subObj && !json_is_object(subObj)) { if (flags & CELIX_PROPERTIES_ENCODE_ERROR_ON_COLLISIONS) { celix_err_pushf("Invalid key collision. Key '%s' already exists.", name); return CELIX_ILLEGAL_ARGUMENT; } return CELIX_SUCCESS; } if (!subObj) { subObj = json_object(); if (!subObj) { celix_err_push("Failed to create json object"); return ENOMEM; } int rc = json_object_set_new(jsonObj, name, subObj); if (rc != 0) { celix_err_push("Failed to set json object"); return ENOMEM; } } jsonObj = subObj; fieldName = slash + 1; slash = strchr(fieldName, CELIX_PROPERTIES_JSONPATH_SEPARATOR); } json_t* value; celix_status_t status = celix_properties_entryValueToJson(fieldName, entry, flags, &value); if (status != CELIX_SUCCESS) { return status; } return celix_properties_addJsonValueToJson(value, fieldName, jsonObj, flags); } celix_status_t celix_properties_saveToStream(const celix_properties_t* properties, FILE* stream, int encodeFlags) { json_auto_t* root = json_object(); if (!root) { celix_err_push("Failed to create json object"); return ENOMEM; } if (!(encodeFlags & CELIX_PROPERTIES_ENCODE_FLAT_STYLE) && !(encodeFlags & CELIX_PROPERTIES_ENCODE_NESTED_STYLE)) { //no encoding flags set, default to flat encodeFlags |= CELIX_PROPERTIES_ENCODE_FLAT_STYLE; } CELIX_PROPERTIES_ITERATE(properties, iter) { celix_status_t status; if (encodeFlags & CELIX_PROPERTIES_ENCODE_FLAT_STYLE) { status = celix_properties_addPropertiesEntryFlatToJson(&iter.entry, iter.key, root, encodeFlags); } else { assert(encodeFlags & CELIX_PROPERTIES_ENCODE_NESTED_STYLE); status = celix_properties_addPropertiesEntryToJson(&iter.entry, iter.key, root, encodeFlags); } if (status != CELIX_SUCCESS) { return status; } } size_t jsonFlags = JSON_COMPACT; if (encodeFlags & CELIX_PROPERTIES_ENCODE_PRETTY) { jsonFlags = JSON_INDENT(2); } int rc = json_dumpf(root, stream, jsonFlags); if (rc != 0) { celix_err_push("Failed to dump json object to stream."); return CELIX_FILE_IO_EXCEPTION; } return CELIX_SUCCESS; } celix_status_t celix_properties_save(const celix_properties_t* properties, const char* filename, int encodeFlags) { FILE* stream = fopen(filename, "w"); if (!stream) { celix_err_pushf("Failed to open file %s.", filename); return CELIX_FILE_IO_EXCEPTION; } celix_status_t status = celix_properties_saveToStream(properties, stream, encodeFlags); int rc = fclose(stream); if (rc != 0) { celix_err_pushf("Failed to close file %s: %s", filename, strerror(errno)); return CELIX_FILE_IO_EXCEPTION; } return status; } celix_status_t celix_properties_saveToString(const celix_properties_t* properties, int encodeFlags, char** out) { *out = NULL; celix_autofree char* buffer = NULL; size_t size = 0; FILE* stream = open_memstream(&buffer, &size); if (!stream) { celix_err_push("Failed to open memstream."); return ENOMEM; } celix_status_t status = celix_properties_saveToStream(properties, stream, encodeFlags); (void)fclose(stream); if (!buffer || status != CELIX_SUCCESS) { if (!buffer || status == CELIX_FILE_IO_EXCEPTION) { return ENOMEM; // Using memstream as stream, return ENOMEM instead of CELIX_FILE_IO_EXCEPTION } return status; } *out = celix_steal_ptr(buffer); return CELIX_SUCCESS; } static celix_status_t celix_properties_decodeArray(celix_properties_t* props, const char* key, const json_t* jsonArray, int flags) { int decodeArrayFlags = 0; if (flags & CELIX_PROPERTIES_DECODE_ERROR_ON_EMPTY_ARRAYS) { decodeArrayFlags |= CELIX_ARRAY_LIST_DECODE_ERROR_ON_EMPTY_ARRAYS; } if (flags & CELIX_PROPERTIES_DECODE_ERROR_ON_UNSUPPORTED_ARRAYS) { decodeArrayFlags |= CELIX_ARRAY_LIST_DECODE_ERROR_ON_UNSUPPORTED_ARRAYS; } celix_autoptr(celix_array_list_t) array = NULL; celix_status_t status = celix_arrayList_decodeFromJson(jsonArray, decodeArrayFlags, &array); if (status != CELIX_SUCCESS) { celix_err_pushf("Failed to decode array list for key '%s'.", key); return status; } if (array == NULL) { // ignore empty arrays or mixed type arrays return CELIX_SUCCESS; } return celix_properties_assignArrayList(props, key, celix_steal_ptr(array)); } static celix_status_t celix_properties_decodeValue(celix_properties_t* props, const char* key, json_t* jsonValue, int flags) { if (strncmp(key, "", 1) == 0) { if (flags & CELIX_PROPERTIES_DECODE_ERROR_ON_EMPTY_KEYS) { celix_err_push("Key cannot be empty."); return CELIX_ILLEGAL_ARGUMENT; } } if (!json_is_object(jsonValue) && celix_properties_hasKey(props, key) && (flags & CELIX_PROPERTIES_DECODE_ERROR_ON_COLLISIONS)) { celix_err_pushf("Invalid key collision. Key '%s' already exists.", key); return CELIX_ILLEGAL_ARGUMENT; } celix_status_t status = CELIX_SUCCESS; if (celix_utils_isVersionJsonString(jsonValue)) { celix_version_t* version; status = celix_utils_jsonToVersion(jsonValue, &version); status = CELIX_DO_IF(status, celix_properties_assignVersion(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)) { const char* fieldName; json_t* fieldValue; json_object_foreach(jsonValue, fieldName, fieldValue) { char buf[64]; char* combinedKey = celix_utils_writeOrCreateString(buf, sizeof(buf), "%s%c%s", key, CELIX_PROPERTIES_JSONPATH_SEPARATOR, fieldName); celix_auto(celix_utils_string_guard_t) strGuard = celix_utils_stringGuard_init(buf, combinedKey); if (!combinedKey) { celix_err_push("Failed to create sub key."); return ENOMEM; } status = celix_properties_decodeValue(props, combinedKey, fieldValue, flags); if (status != CELIX_SUCCESS) { return status; } } return CELIX_SUCCESS; } else if (json_is_array(jsonValue)) { status = celix_properties_decodeArray(props, key, jsonValue, flags); } else if (json_is_null(jsonValue)) { if (flags & CELIX_PROPERTIES_DECODE_ERROR_ON_NULL_VALUES) { celix_err_pushf("Invalid null value for key '%s'.", key); return CELIX_ILLEGAL_ARGUMENT; } // ignore null values return CELIX_SUCCESS; } else { // LCOV_EXCL_START celix_err_pushf("Unexpected json value type for key '%s'.", key); return CELIX_ILLEGAL_ARGUMENT; // LCOV_EXCL_STOP } return status; } static celix_status_t celix_properties_decodeFromJson(json_t* obj, int flags, celix_properties_t** out) { *out = NULL; if (!json_is_object(obj)) { celix_err_push("Expected json object."); return CELIX_ILLEGAL_ARGUMENT; } celix_autoptr(celix_properties_t) props = celix_properties_create(); if (!props) { return ENOMEM; } const char* key; json_t* value; json_object_foreach(obj, key, value) { celix_status_t status = celix_properties_decodeValue(props, key, value, flags); if (status != CELIX_SUCCESS) { return status; } } *out = celix_steal_ptr(props); return CELIX_SUCCESS; } celix_status_t celix_properties_loadFromStream(FILE* stream, int decodeFlags, celix_properties_t** out) { json_error_t jsonError; size_t jsonFlags = 0; if (decodeFlags & CELIX_PROPERTIES_DECODE_ERROR_ON_DUPLICATES) { jsonFlags = JSON_REJECT_DUPLICATES; } json_auto_t* root = json_loadf(stream, jsonFlags, &jsonError); if (!root) { celix_err_pushf("Failed to parse json from %s:%i:%i: %s.", jsonError.source, jsonError.line, jsonError.column, jsonError.text); return celix_utils_jsonErrorToStatus(json_error_code(&jsonError)); } return celix_properties_decodeFromJson(root, decodeFlags, out); } celix_status_t celix_properties_load(const char* filename, int decodeFlags, celix_properties_t** out) { FILE* stream = fopen(filename, "r"); if (!stream) { celix_err_pushf("Failed to open file %s.", filename); return CELIX_FILE_IO_EXCEPTION; } celix_status_t status = celix_properties_loadFromStream(stream, decodeFlags, out); fclose(stream); return status; } celix_status_t celix_properties_loadFromString(const char* input, int decodeFlags, celix_properties_t** out) { FILE* stream = fmemopen((void*)input, strlen(input), "r"); if (!stream) { celix_err_push("Failed to open memstream."); return ENOMEM; } celix_status_t status = celix_properties_loadFromStream(stream, decodeFlags, out); fclose(stream); return status; }