This is an automated email from the ASF dual-hosted git repository. pnoltes pushed a commit to branch feature/685-refactor-manifest-format in repository https://gitbox.apache.org/repos/asf/celix.git
commit a40923049a569dd958355d6c2170ebee7a0e4aa4 Author: Pepijn Noltes <pnol...@apache.org> AuthorDate: Mon Jul 1 20:09:29 2024 +0200 gh-685: Add manifest validation --- libs/framework/gtest/src/ManifestTestSuite.cc | 90 ++++++++++++++-- libs/framework/src/celix_manifest.c | 143 ++++++++++++++++++++++++-- libs/framework/src/celix_manifest.h | 49 ++++++--- libs/utils/include/celix_err.h | 22 ++++ libs/utils/include/celix_errno.h | 12 +++ libs/utils/include/celix_properties.h | 3 +- libs/utils/src/properties.c | 5 +- 7 files changed, 290 insertions(+), 34 deletions(-) diff --git a/libs/framework/gtest/src/ManifestTestSuite.cc b/libs/framework/gtest/src/ManifestTestSuite.cc index 66b4f0fa5..9c6d64135 100644 --- a/libs/framework/gtest/src/ManifestTestSuite.cc +++ b/libs/framework/gtest/src/ManifestTestSuite.cc @@ -22,26 +22,100 @@ #include "celix_manifest.h" #include "celix_properties.h" +#include "celix_err.h" class ManifestTestSuite : public ::testing::Test { + public: + ManifestTestSuite() { celix_err_resetErrors(); } }; TEST_F(ManifestTestSuite, CreateManifestTest) { + //Given a properties set with all the mandatory manifest attributes celix_properties_t *properties = celix_properties_create(); - celix_properties_set(properties, "key", "value"); + celix_version_t* v = celix_version_create(2, 0, 0, nullptr); + celix_properties_assignVersion(properties, "CELIX_MANIFEST_VERSION", v); + celix_properties_set(properties, "CELIX_BUNDLE_VERSION", "1.0.0"); + celix_properties_set(properties, "CELIX_BUNDLE_NAME", "my_bundle"); + celix_properties_set(properties, "CELIX_BUNDLE_SYMBOLIC_NAME", "celix_my_bundle"); + + //When creating a manifest from the attributes celix_autoptr(celix_manifest_t) manifest = nullptr; celix_status_t status = celix_manifest_create(properties, &manifest); + + //Then the creation is successful EXPECT_EQ(CELIX_SUCCESS, status); - EXPECT_EQ(1, celix_properties_size(celix_manifest_getAttributes(manifest))); + EXPECT_EQ(4, celix_properties_size(celix_manifest_getAttributes(manifest))); +} + +TEST_F(ManifestTestSuite, MissingOrInvalidMandatoryManifestAttributesTest) { + //Given an empty properties set + celix_properties_t *properties = celix_properties_create(); + + //When creating a manifest from the attributes + celix_autoptr(celix_manifest_t) manifest = nullptr; + celix_status_t status = celix_manifest_create(properties, &manifest); + + //Then the creation fails + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status); + + //And 4 celix err log entries are logged (4 missing attributes) + EXPECT_EQ(celix_err_getErrorCount(), 4); + celix_err_printErrors(stdout, "Expect Errors: ", nullptr); + + //Given a properties set with not versions attributes for bundle/manifest version. + properties = celix_properties_create(); + celix_properties_set(properties, "CELIX_BUNDLE_VERSION", "not a version"); + celix_properties_setBool(properties, "CELIX_MANIFEST_VERSION", false); + celix_properties_set(properties, "CELIX_BUNDLE_NAME", "my_bundle"); + celix_properties_set(properties, "CELIX_BUNDLE_SYMBOLIC_NAME", "celix_my_bundle"); + //When creating a manifest from the attributes celix_autoptr(celix_manifest_t) manifest2 = nullptr; - status = celix_manifest_create(nullptr, &manifest2); - EXPECT_EQ(CELIX_SUCCESS, status); - EXPECT_EQ(0, celix_properties_size(celix_manifest_getAttributes(manifest2))); + status = celix_manifest_create(properties, &manifest2); + + //Then the creation fails + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status); + + //And 4 celix err log entries are logged (4x invalid versions) + EXPECT_EQ(celix_err_getErrorCount(), 2); + celix_err_printErrors(stdout, "Expect Errors: ", nullptr); + //Given a properties with an unsupported manifest version + properties = celix_properties_create(); + celix_properties_set(properties, "CELIX_MANIFEST_VERSION", "1.0.0"); //note must be 2.0.* + celix_properties_set(properties, "CELIX_BUNDLE_VERSION", "1.0.0"); + celix_properties_set(properties, "CELIX_BUNDLE_NAME", "my_bundle"); + celix_properties_set(properties, "CELIX_BUNDLE_SYMBOLIC_NAME", "celix_my_bundle"); + + //When creating a manifest from the attributes celix_autoptr(celix_manifest_t) manifest3 = nullptr; - status = celix_manifest_createFromFile("empty.properties", &manifest3); - EXPECT_EQ(CELIX_SUCCESS, status); - EXPECT_EQ(0, celix_properties_size(celix_manifest_getAttributes(manifest3))); + status = celix_manifest_create(properties, &manifest2); + + //Then the creation fails + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status); + + //And 1 celix err log entries is logged + EXPECT_EQ(celix_err_getErrorCount(), 1); + celix_err_printErrors(stdout, "Expect Errors: ", nullptr); + + //Given a properties set with an invalid private libraries list + properties = celix_properties_create(); + celix_properties_set(properties, "CELIX_MANIFEST_VERSION", "2.0.2"); + celix_properties_set(properties, "CELIX_BUNDLE_VERSION", "1.0.0"); + celix_properties_set(properties, "CELIX_BUNDLE_NAME", "my_bundle"); + celix_properties_set(properties, "CELIX_BUNDLE_SYMBOLIC_NAME", "celix_my_bundle"); + celix_properties_setBool(properties, "CELIX_BUNDLE_PRIVATE_LIBRARIES", true); + + //When creating a manifest from the attributes + celix_autoptr(celix_manifest_t) manifest4 = nullptr; + status = celix_manifest_create(properties, &manifest2); + + //Then the creation fails + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status); + + //And 1 celix err log entries is logged + EXPECT_EQ(celix_err_getErrorCount(), 1); + celix_err_printErrors(stdout, "Expect Errors: ", nullptr); } +//TODO check if the mandatory and optional attributes are set and can be retreived with getters diff --git a/libs/framework/src/celix_manifest.c b/libs/framework/src/celix_manifest.c index 7dc550480..31e5db809 100644 --- a/libs/framework/src/celix_manifest.c +++ b/libs/framework/src/celix_manifest.c @@ -19,26 +19,58 @@ #include "celix_manifest.h" +#include "celix_err.h" #include "celix_properties.h" #include "celix_stdlib_cleanup.h" -#include "celix_err.h" +#include "celix_utils.h" +#include "celix_version.h" + +// Mandatory manifest attributes +#define CELIX_MANIFEST_VERSION "CELIX_MANIFEST_VERSION" +#define CELIX_BUNDLE_SYMBOLIC_NAME "CELIX_BUNDLE_SYMBOLIC_NAME" +#define CELIX_BUNDLE_VERSION "CELIX_BUNDLE_VERSION" +#define CELIX_BUNDLE_NAME "CELIX_BUNDLE_NAME" + +// Optional manifest attributes +#define CELIX_BUNDLE_PRIVATE_LIBRARIES "CELIX_BUNDLE_PRIVATE_LIBRARIES" +#define CELIX_BUNDLE_ACTIVATOR_LIBRARY "CELIX_BUNDLE_ACTIVATOR_LIBRARY" +#define CELIX_BUNDLE_GROUP "CELIX_BUNDLE_GROUP" struct celix_manifest{ celix_properties_t* attributes; + + //Mandatory fields + celix_version_t* manifestVersion; + celix_version_t* bundleVersion; + char* symbolicName; + char* bundleName; + + //Optional fields + char* bundleGroup; + char* activatorLibrary; + celix_array_list_t* privateLibraries; }; +/** + * @brief Set and validate the provided manifest by checking if all mandatory attributes are present and of the correct + * type and checking if the optional attributes, when present, are of the correct type. + */ +static celix_status_t celix_manifest_setAttributes(celix_manifest_t* manifest); + celix_status_t celix_manifest_create(celix_properties_t* attributes, celix_manifest_t** manifestOut) { + if (!attributes) { + return CELIX_ILLEGAL_ARGUMENT; + } + celix_autofree celix_manifest_t* manifest = calloc(1, sizeof(*manifest)); if (!manifest) { celix_properties_destroy(attributes); celix_err_push("Failed to allocate memory for manifest"); return ENOMEM; } + manifest->attributes = attributes; - manifest->attributes = attributes ? attributes : celix_properties_create(); - if (!manifest->attributes) { - return ENOMEM; - } + CELIX_EPROP(celix_manifest_setAttributes(manifest)); *manifestOut = celix_steal_ptr(manifest); return CELIX_SUCCESS; @@ -51,13 +83,22 @@ celix_status_t celix_manifest_createFromFile(const char* filename, celix_manifes if (status != CELIX_SUCCESS) { return status; } - return celix_manifest_create(properties, manifestOut); } void celix_manifest_destroy(celix_manifest_t* manifest) { if (manifest) { celix_properties_destroy(manifest->attributes); + + free(manifest->symbolicName); + free(manifest->bundleName); + celix_version_destroy(manifest->manifestVersion); + celix_version_destroy(manifest->bundleVersion); + + free(manifest->activatorLibrary); + free(manifest->bundleGroup); + celix_arrayList_destroy(manifest->privateLibraries); + free(manifest); } } @@ -66,6 +107,92 @@ const celix_properties_t* celix_manifest_getAttributes(celix_manifest_t* manifes return manifest->attributes; } -bool celix_manifest_validate(celix_manifest_t* manifest) { - return true; //TODO implement +static celix_status_t celix_manifest_setMandatoryAttributes(celix_manifest_t* manifest) { + const char* symbolicName = celix_properties_get(manifest->attributes, CELIX_BUNDLE_SYMBOLIC_NAME, NULL); + const char* bundleName = celix_properties_get(manifest->attributes, CELIX_BUNDLE_NAME, NULL); + + celix_autoptr(celix_version_t) manifestVersion = NULL; + celix_status_t getVersionStatus = + celix_properties_getAsVersion(manifest->attributes, CELIX_MANIFEST_VERSION, NULL, &manifestVersion); + CELIX_ERR_RET_IF_ENOMEM(getVersionStatus); + + celix_autoptr(celix_version_t) bundleVersion = NULL; + getVersionStatus = celix_properties_getAsVersion(manifest->attributes, CELIX_BUNDLE_VERSION, NULL, &bundleVersion); + CELIX_ERR_RET_IF_ENOMEM(getVersionStatus); + + celix_status_t status = CELIX_SUCCESS; + if (!bundleName) { + celix_err_push(CELIX_BUNDLE_NAME " is missing"); + status = CELIX_ILLEGAL_ARGUMENT; + } + if (!symbolicName) { + celix_err_push(CELIX_BUNDLE_SYMBOLIC_NAME " is missing"); + status = CELIX_ILLEGAL_ARGUMENT; + } + if (!manifestVersion) { + celix_err_push(CELIX_MANIFEST_VERSION " is missing or not a version"); + status = CELIX_ILLEGAL_ARGUMENT; + } + if (!bundleVersion) { + celix_err_push(CELIX_BUNDLE_VERSION " is missing or not a version"); + status = CELIX_ILLEGAL_ARGUMENT; + } + + if (manifestVersion && celix_version_compareToMajorMinor(manifestVersion, 2, 0) != 0) { + celix_err_push(CELIX_MANIFEST_VERSION " is not 2.0.*"); + status = CELIX_ILLEGAL_ARGUMENT; + } + + if (status == CELIX_SUCCESS) { + manifest->symbolicName = celix_utils_strdup(symbolicName); + CELIX_ERR_RET_IF_NULL(manifest->symbolicName); + manifest->bundleName = celix_utils_strdup(bundleName); + CELIX_ERR_RET_IF_NULL(manifest->bundleName); + manifest->manifestVersion = celix_steal_ptr(manifestVersion); + manifest->bundleVersion = celix_steal_ptr(bundleVersion); + } + + return status; +} + +static celix_status_t celix_manifest_setOptionalAttributes(celix_manifest_t* manifest) { + celix_status_t status = CELIX_SUCCESS; + + const char* l = celix_properties_getAsString(manifest->attributes, CELIX_BUNDLE_ACTIVATOR_LIBRARY, NULL); + celix_autofree char* activatorLib = NULL; + if (l) { + activatorLib = celix_utils_strdup(l); + CELIX_ERR_RET_IF_NULL(activatorLib); + } + + const char* g = celix_properties_getAsString(manifest->attributes, CELIX_BUNDLE_GROUP, NULL); + celix_autofree char* bundleGroup = NULL; + if (g) { + bundleGroup = celix_utils_strdup(g); + CELIX_ERR_RET_IF_NULL(bundleGroup); + } + + celix_autoptr(celix_array_list_t) privateLibraries = NULL; + celix_status_t getStatus = celix_properties_getAsStringArrayList( + manifest->attributes, CELIX_BUNDLE_ACTIVATOR_LIBRARY, NULL, &privateLibraries); + CELIX_ERR_RET_IF_ENOMEM(getStatus); + if (celix_properties_hasKey(manifest->attributes, CELIX_BUNDLE_PRIVATE_LIBRARIES) && !privateLibraries) { + celix_err_pushf(CELIX_BUNDLE_PRIVATE_LIBRARIES " is not a string array. Got: '%s'", + celix_properties_get(manifest->attributes, CELIX_BUNDLE_PRIVATE_LIBRARIES, NULL)); + status = CELIX_ILLEGAL_ARGUMENT; + } + + if (status == CELIX_SUCCESS) { + manifest->activatorLibrary = celix_steal_ptr(activatorLib); + manifest->bundleGroup = celix_steal_ptr(bundleGroup); + manifest->privateLibraries = celix_steal_ptr(privateLibraries); + } + + return status; +} + +static celix_status_t celix_manifest_setAttributes(celix_manifest_t* manifest) { + celix_status_t mStatus = celix_manifest_setMandatoryAttributes(manifest); + celix_status_t oStatus = celix_manifest_setOptionalAttributes(manifest); + return mStatus != CELIX_SUCCESS ? mStatus : oStatus; } \ No newline at end of file diff --git a/libs/framework/src/celix_manifest.h b/libs/framework/src/celix_manifest.h index 1efdd0f6e..8704c645c 100644 --- a/libs/framework/src/celix_manifest.h +++ b/libs/framework/src/celix_manifest.h @@ -28,17 +28,46 @@ extern "C" { #endif +/** + * @file celix_manifest.h + * @brief Header file for celix_manifest_t. + * + * + * The manifest consists of a set of attributes, including a set of mandatory attributes. + * A manifest is used to describe the contents of a bundle. + * + * A manifest must contain the following attributes: + * - CELIX_MANIFEST_VERSION, type celix_version_t, the version of the manifest. + * - CELIX_BUNDLE_SYMBOLIC_NAME, type string, the symbolic name of the bundle. + * - CELIX_BUNDLE_VERSION, type celix_version_t, the version of the bundle. + * - CELIX_BUNDLE_NAME, type string, the name of the bundle. + * + * The manifest may contain the following attributes: + * - CELIX_BUNDLE_ACTIVATOR_LIBRARY, type string, the activator library of the bundle. + * - CELIX_BUNDLE_PRIVATE_LIBRARIES, type string array, the private libraries of the bundle. + * - CELIX_BUNDLE_GROUP, type string, the group of the bundle. Helps in grouping sets of bundles. + * + * And a manifest may contain any other attributes of any type, this can be retrieved using + * celix_manifest_getAttributes. + * + * A bundle symbolic name can only contain the following characters: [a-zA-Z0-9_-:] + */ + +/** + * @brief The definition of the celix_manifest_t* abstract data type. + */ typedef struct celix_manifest celix_manifest_t; /** * Create a new manifest using the provided properties. * - * If an error occurs, an error message is logged on celix_err. + * If an error occurs, an error message is logged on celix_err and in case of an CELIX_ILLEGAL_ARGUMENT error, there + * can be multiple error messages. * * @param[in] properties The properties to use for the manifest. Takes ownership of the properties. * @param[out] manifest The created manifest. - * @return CELIX_SUCCESS if no errors occurred, ENOMEM if memory allocation failed. In case of an error, the provided - * attributes are destroyed. + * @return CELIX_SUCCESS if no errors occurred, ENOMEM if memory allocation failed and CELIX_ILLEGAL_ARGUMENT if the + * provided attributes is incorrect. In case of an error, the provided attributes are destroyed. */ celix_status_t celix_manifest_create(celix_properties_t* attributes, celix_manifest_t** manifest); @@ -65,20 +94,12 @@ void celix_manifest_destroy(celix_manifest_t* manifest); CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_manifest_t, celix_manifest_destroy) /** - * @brief Get the main attributes of the manifest. - * The attributes are valid as long as the manifest is valid. + * @brief Get the manifest attributes. + * The manifest attributes are valid as long as the manifest is valid. */ const celix_properties_t* celix_manifest_getAttributes(celix_manifest_t* manifest); -/** - * @brief Validate the provided manifest. - * - * Logs an error message on celix_err if the manifest is invalid. - * - * @param[in] manifest The manifest to validate. - * @return true if the manifest is valid, false otherwise. - */ -bool celix_manifest_validate(celix_manifest_t* manifest); +//TODO getters for mandatory and optional attributes #ifdef __cplusplus } diff --git a/libs/utils/include/celix_err.h b/libs/utils/include/celix_err.h index 95ce0d612..d617dcec0 100644 --- a/libs/utils/include/celix_err.h +++ b/libs/utils/include/celix_err.h @@ -94,6 +94,28 @@ CELIX_UTILS_EXPORT void celix_err_printErrors(FILE* stream, const char* prefix, */ CELIX_UTILS_EXPORT int celix_err_dump(char* buf, size_t size, const char* prefix, const char* postfix); +/*! + * Helper macro that return with ENOMEM if the status is ENOMEM and logs an "<func>: Out of memory" error to celix_err. + */ +#define CELIX_ERR_RET_IF_ENOMEM(status) \ + do { \ + if ((status) == ENOMEM) { \ + celix_err_pushf("%s: Out of memory", __func__); \ + return status; \ + } \ + } while (0) + +/*! + * Helper macro that return with ENOMEM if the arg is NULL and logs an "<func>: Out of memory" error to celix_err. + */ +#define CELIX_ERR_RET_IF_NULL(arg) \ + do { \ + if ((arg) == NULL) { \ + celix_err_pushf("%s: Out of memory", __func__); \ + return status; \ + } \ + } while (0) + #ifdef __cplusplus } #endif diff --git a/libs/utils/include/celix_errno.h b/libs/utils/include/celix_errno.h index 8189192b3..dcab62801 100644 --- a/libs/utils/include/celix_errno.h +++ b/libs/utils/include/celix_errno.h @@ -56,6 +56,18 @@ extern "C" { */ #define CELIX_DO_IF(status, expr) ((status) == CELIX_SUCCESS) ? (expr) : (status) +/*! + * Helper macro which helps with error propagation. It evaluates the provided expression that returns a status and + * returns the status if the status is not CELIX_SUCCESS (0). If the status is CELIX_SUCCESS (0) nothing is done. + */ +#define CELIX_EPROP(expr) \ + do { \ + celix_status_t __status = expr; \ + if (__status != CELIX_SUCCESS) { \ + return __status; \ + } \ + } while (0) + /*! * Helper macro which check the current status and executes a goto the provided label if the * status is not CELIX_SUCCESS (0) diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index b145fc72d..08a2d773b 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -450,7 +450,8 @@ CELIX_UTILS_EXPORT const celix_version_t* celix_properties_getVersion(const celi * @brief Get a value of a property as a copied Celix version. * * If the property value is a Celix version, a copy of the found version will be returned. - * If the property value is a string, this function will attempt to convert it to a new Celix version. + * If the property value is present, but not a Celix version, this function will attempt to convert the property value + * to a new Celix version. * If the property is not set or is not a valid Celix version string, a copy of the provided defaultValue is returned. * * @note The caller is responsible for deallocating the memory of the returned version. diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 3ec19a2f7..f48c1eb31 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -616,9 +616,8 @@ celix_status_t celix_properties_getAsVersion(const celix_properties_t* propertie } *version = copy; return CELIX_SUCCESS; - } - if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING) { - celix_status_t parseStatus = celix_version_parse(entry->value, version); + } else if (entry != NULL) { + celix_status_t parseStatus = celix_version_tryParse(entry->value, version); if (parseStatus != CELIX_ILLEGAL_ARGUMENT) { return parseStatus; }