diff --git a/libs/dfi/gtest/CMakeLists.txt b/libs/dfi/gtest/CMakeLists.txt index ded190e6b..c780e3214 100644 --- a/libs/dfi/gtest/CMakeLists.txt +++ b/libs/dfi/gtest/CMakeLists.txt @@ -45,6 +45,7 @@ if (EI_TESTS) src/dyn_type_ei_tests.cc src/dyn_message_ei_tests.cc src/dyn_function_ei_tests.cc + src/json_serializer_ei_tests.cc ) target_link_libraries(test_dfi_with_ei PRIVATE dfi_cut diff --git a/libs/dfi/gtest/src/json_rpc_tests.cpp b/libs/dfi/gtest/src/json_rpc_tests.cpp index 35db8c53b..b98d9961b 100644 --- a/libs/dfi/gtest/src/json_rpc_tests.cpp +++ b/libs/dfi/gtest/src/json_rpc_tests.cpp @@ -879,54 +879,6 @@ extern "C" { free(result); - dynInterface_destroy(intf); - } - - void testResponseForwardCompatibility(void) { - dyn_interface_type *intf = nullptr; - FILE *desc = fopen("descriptors/example6.descriptor", "r"); - ASSERT_TRUE(desc != nullptr); - int rc = dynInterface_parse(desc, &intf); - ASSERT_EQ(0, rc); - fclose(desc); - - const struct methods_head* head = dynInterface_methods(intf); - dyn_function_type *func = nullptr; - struct method_entry *entry = nullptr; - TAILQ_FOREACH(entry, head, entries) { - if (strcmp(dynFunction_getName(entry->dynFunc), "cpt") == 0) { - func = entry->dynFunc; - break; - } - } - ASSERT_TRUE(func != nullptr); - - struct tst_CptData *cptData{nullptr}; - void *out = &cptData; - - void *args[3]; - args[0] = nullptr; - args[1] = nullptr; - args[2] = &out; - int rsErrno = 0; - - //provider has more reply - rc = jsonRpc_handleReply(func, R"({"r":{"d":1.0, "t":"hello compatibility", "s":[1.0,2.0,3.0], "e":"v1", "e2":"v2"}})", args, &rsErrno); - EXPECT_EQ(0, rc); - EXPECT_EQ(0, rsErrno); - EXPECT_NE(cptData , nullptr); - EXPECT_EQ(cptData->d , 1.0); - EXPECT_STREQ(cptData->t , "hello compatibility"); - EXPECT_EQ(cptData->s.len , 3); - EXPECT_EQ(cptData->s.cap , 3); - EXPECT_EQ(cptData->s.buf[0] , 1.0); - EXPECT_EQ(cptData->s.buf[1] , 2.0); - EXPECT_EQ(cptData->s.buf[2] , 3.0); - EXPECT_EQ(cptData->e , v1); - free(cptData->t); - free(cptData->s.buf); - free(cptData); - dynInterface_destroy(intf); } } @@ -1035,8 +987,4 @@ TEST_F(JsonRpcTests, callTestConstChar) { TEST_F(JsonRpcTests, testRequestBackwardCompatibility) { testRequestBackwardCompatibility(); -} - -TEST_F(JsonRpcTests, testResponseForwardCompatibility) { - testResponseForwardCompatibility(); -} +} \ No newline at end of file diff --git a/libs/dfi/gtest/src/json_serializer_ei_tests.cc b/libs/dfi/gtest/src/json_serializer_ei_tests.cc new file mode 100644 index 000000000..dfd18b8b0 --- /dev/null +++ b/libs/dfi/gtest/src/json_serializer_ei_tests.cc @@ -0,0 +1,53 @@ +/* + * 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 "json_serializer.h" +#include "dyn_type.h" +#include "celix_err.h" +#include "malloc_ei.h" + +#include + +class JsonSerializerErrorInjectionTestSuite : public ::testing::Test { +public: + JsonSerializerErrorInjectionTestSuite() = default; + ~JsonSerializerErrorInjectionTestSuite() override { + celix_ei_expect_calloc(nullptr, 0, nullptr); + celix_err_resetErrors(); + } +}; + +TEST_F(JsonSerializerErrorInjectionTestSuite, SerilizationError) { + int rc; + dyn_type *type; + void *inst; + + //simple string + type = nullptr; + inst = nullptr; + rc = dynType_parseWithStr("t", nullptr, nullptr, &type); + ASSERT_EQ(0, rc); + auto inputStr = R"("hello")"; + celix_ei_expect_calloc((void*) dynType_alloc, 0, nullptr); + rc = jsonSerializer_deserialize(type, inputStr, strlen(inputStr), &inst); + ASSERT_NE(0, rc); + EXPECT_STREQ("Error cannot deserialize json. Input is '\"hello\"'", celix_err_popLastError()); + EXPECT_STREQ("Error allocating memory for type 't'", celix_err_popLastError()); + dynType_destroy(type); +} \ No newline at end of file diff --git a/libs/dfi/gtest/src/json_serializer_tests.cpp b/libs/dfi/gtest/src/json_serializer_tests.cpp index 6105928d3..0d924f71e 100644 --- a/libs/dfi/gtest/src/json_serializer_tests.cpp +++ b/libs/dfi/gtest/src/json_serializer_tests.cpp @@ -559,6 +559,25 @@ static void parseTests() { ASSERT_EQ(1, rc); celix_err_printErrors(stderr, nullptr, nullptr); dynType_destroy(type); + + // unknown member + rc = dynType_parseWithStr("{t a}", nullptr, nullptr, &type); + ASSERT_EQ(0, rc); + inputStr = R"({"a":"hello", "b":"world"})"; + rc = jsonSerializer_deserialize(type, inputStr, strlen(inputStr), &inst); + ASSERT_EQ(1, rc); + celix_err_printErrors(stderr, nullptr, nullptr); + dynType_destroy(type); + + //simple string + rc = dynType_parseWithStr("t", nullptr, nullptr, &type); + ASSERT_EQ(0, rc); + inputStr = R"("hello")"; + rc = jsonSerializer_deserialize(type, inputStr, strlen(inputStr), &inst); + ASSERT_EQ(0, rc); + EXPECT_STREQ("hello", *(char**)inst); + dynType_free(type, inst); + dynType_destroy(type); } /*********** write example 1 ************************/ diff --git a/libs/dfi/include/json_serializer.h b/libs/dfi/include/json_serializer.h index 5d38b7ebc..1c4cb44c7 100644 --- a/libs/dfi/include/json_serializer.h +++ b/libs/dfi/include/json_serializer.h @@ -31,19 +31,21 @@ extern "C" { #endif /** - * @brief Deserialize a JSON string to a given type. + * @brief Deserialize a JSON string buffer to a given type. + * @note The string buffer doesn't need to be null-terminated. * * Caller is the owner of the out parameter and should release it using dynType_free. * * In case of an error, an error message is added to celix_err. * * @param[in] type The type to deserialize to. - * @param[in] input The JSON string to deserialize. - * @param[out] out The deserialized result. + * @param[in] input The JSON string buffer to deserialize. + * @param[in] length The length of the given JSON string buffer. + * @param[out] result The deserialized result. * @return 0 if successful, otherwise 1. * */ -CELIX_DFI_EXPORT int jsonSerializer_deserialize(dyn_type *type, const char *input, size_t length, void **result); +CELIX_DFI_EXPORT int jsonSerializer_deserialize(const dyn_type* type, const char* input, size_t length, void** result); /** * @brief Deserialize a JSON object to a given type. @@ -58,7 +60,7 @@ CELIX_DFI_EXPORT int jsonSerializer_deserialize(dyn_type *type, const char *inpu * @return 0 if successful, otherwise 1. * */ -CELIX_DFI_EXPORT int jsonSerializer_deserializeJson(const dyn_type *type, json_t *input, void **result); +CELIX_DFI_EXPORT int jsonSerializer_deserializeJson(const dyn_type* type, json_t* input, void** result); /** * @brief Serialize a given type to a JSON string. @@ -73,7 +75,7 @@ CELIX_DFI_EXPORT int jsonSerializer_deserializeJson(const dyn_type *type, json_t * @return 0 if successful, otherwise 1. * */ -CELIX_DFI_EXPORT int jsonSerializer_serialize(dyn_type *type, const void* input, char **output); +CELIX_DFI_EXPORT int jsonSerializer_serialize(const dyn_type* type, const void* input, char** output); /** * @brief Serialize a given type to a JSON object. @@ -88,7 +90,7 @@ CELIX_DFI_EXPORT int jsonSerializer_serialize(dyn_type *type, const void* input, * @return 0 if successful, otherwise 1. * */ -CELIX_DFI_EXPORT int jsonSerializer_serializeJson(const dyn_type *type, const void* input, json_t **out); +CELIX_DFI_EXPORT int jsonSerializer_serializeJson(const dyn_type* type, const void* input, json_t** out); #ifdef __cplusplus } diff --git a/libs/dfi/src/json_serializer.c b/libs/dfi/src/json_serializer.c index a98e3ea85..da9953669 100644 --- a/libs/dfi/src/json_serializer.c +++ b/libs/dfi/src/json_serializer.c @@ -28,87 +28,67 @@ #include #include -static int jsonSerializer_createType(const dyn_type *type, json_t *object, void **result); -static int jsonSerializer_parseObject(const dyn_type *type, json_t *object, void *inst); -static int jsonSerializer_parseObjectMember(const dyn_type *type, const char *name, json_t *val, void *inst); -static int jsonSerializer_parseSequence(const dyn_type *seq, json_t *array, void *seqLoc); -static int jsonSerializer_parseAny(const dyn_type *type, void *input, json_t *val); -static int jsonSerializer_parseEnum(const dyn_type *type, const char* enum_name, int32_t *out); +static int jsonSerializer_createType(const dyn_type* type, json_t* object, void** result); +static int jsonSerializer_parseObject(const dyn_type* type, json_t* object, void* inst); +static int jsonSerializer_parseObjectMember(const dyn_type* type, const char* name, json_t* val, void* inst); +static int jsonSerializer_parseSequence(const dyn_type* seq, json_t* array, void* seqLoc); +static int jsonSerializer_parseAny(const dyn_type* type, void* input, json_t* val); +static int jsonSerializer_parseEnum(const dyn_type* type, const char* enum_name, int32_t* out); -static int jsonSerializer_writeAny(const dyn_type *type, void *input, json_t **val); -static int jsonSerializer_writeComplex(const dyn_type *type, void *input, json_t **val); -static int jsonSerializer_writeSequence(const dyn_type *type, void *input, json_t **out); -static int jsonSerializer_writeEnum(const dyn_type *type, int32_t enum_value, json_t **out); +static int jsonSerializer_writeAny(const dyn_type* type, void* input, json_t** val); +static int jsonSerializer_writeComplex(const dyn_type* type, void* input, json_t** val); +static int jsonSerializer_writeSequence(const dyn_type* type, void* input, json_t** out); +static int jsonSerializer_writeEnum(const dyn_type* type, int32_t enum_value, json_t** out); static int OK = 0; static int ERROR = 1; -int jsonSerializer_deserialize(dyn_type *type, const char *input, size_t length, void **result) { - assert(dynType_type(type) == DYN_TYPE_COMPLEX || dynType_type(type) == DYN_TYPE_SEQUENCE); +int jsonSerializer_deserialize(const dyn_type* type, const char* input, size_t length, void** result) { int status = 0; json_error_t error; - json_t *root = json_loadb(input, length, JSON_DECODE_ANY, &error); - - if (root != NULL) { - status = jsonSerializer_deserializeJson(type, root, result); - json_decref(root); - } else { - status = ERROR; + json_auto_t* root = json_loadb(input, length, JSON_DECODE_ANY, &error); + if (root == NULL) { celix_err_pushf("Error parsing json input '%.*s'. Error is: %s\n", (int)length, input, error.text); + return ERROR; } + status = jsonSerializer_deserializeJson(type, root, result); if (status != OK) { - celix_err_pushf("Error cannot deserialize json. Input is '%s'\n", input); + celix_err_pushf("Error cannot deserialize json. Input is '%s'", input); } return status; } -int jsonSerializer_deserializeJson(const dyn_type *type, json_t *input, void **out) { +int jsonSerializer_deserializeJson(const dyn_type* type, json_t* input, void** out) { return jsonSerializer_createType(type, input, out); } -static int jsonSerializer_createType(const dyn_type *type, json_t *val, void **result) { +static int jsonSerializer_createType(const dyn_type* type, json_t* val, void** result) { assert(val != NULL); int status = OK; - void *inst = NULL; - - if (dynType_descriptorType(type) == 't') { - if (json_typeof(val) == JSON_STRING) { - //note a deserialized C string is a sequence of memory for the actual string and a - //pointer to that sequence. That pointer also needs to reside in the memory (heap). - const char *s = json_string_value(val); - inst = calloc(1, sizeof(char*)); - *((char**)inst) = strdup(s); - } else { - status = ERROR; - celix_err_pushf("Expected json_string type got %i\n", json_typeof(val)); - } - } else { - status = dynType_alloc(type, &inst); + void* inst = NULL; - if (status == OK) { - assert(inst != NULL); - status = jsonSerializer_parseAny(type, inst, val); - } + if ((status = dynType_alloc(type, &inst)) != OK) { + return status; } - if (status == OK) { - *result = inst; - } else { + if ((status = jsonSerializer_parseAny(type, inst, val)) != OK) { + dynType_free(type, inst); *result = NULL; - dynType_free(type, inst); + return status; } - return status; + *result = inst; + return OK; } -static int jsonSerializer_parseObject(const dyn_type *type, json_t *object, void *inst) { +static int jsonSerializer_parseObject(const dyn_type* type, json_t* object, void* inst) { assert(object != NULL); int status = 0; - json_t *value; - const char *key; + json_t* value; + const char* key; json_object_foreach(object, key, value) { status = jsonSerializer_parseObjectMember(type, key, value, inst); @@ -120,13 +100,14 @@ static int jsonSerializer_parseObject(const dyn_type *type, json_t *object, void return status; } -static int jsonSerializer_parseObjectMember(const dyn_type *type, const char *name, json_t *val, void *inst) { - void *valp = NULL; +static int jsonSerializer_parseObjectMember(const dyn_type* type, const char* name, json_t* val, void* inst) { + void* valp = NULL; const dyn_type* valType = NULL; int index = dynType_complex_indexForName(type, name); if (index < 0) { - return OK;//We should ignore unknown name in request or response. Satisfy forward compatibility for responses. + celix_err_pushf("Cannot find index for member '%s'", name); + return ERROR; } valp = dynType_complex_valLocAt(type, index, inst); @@ -135,87 +116,54 @@ static int jsonSerializer_parseObjectMember(const dyn_type *type, const char *na return jsonSerializer_parseAny(valType, valp, val); } -static int jsonSerializer_parseAny(const dyn_type *type, void *loc, json_t *val) { +static int jsonSerializer_parseAny(const dyn_type* type, void* loc, json_t* val) { int status = OK; - const dyn_type *subType = NULL; + const dyn_type* subType = NULL; char c = dynType_descriptorType(type); - /* - printf("parseAny with descriptor '%c' :", c); - json_dumpf(val, stdout, 0); //TODO remove - printf("\n"); - */ - - bool *z; //Z - float *f; //F - double *d; //D - char *b; //B - int *n; //N - int16_t *s; //S - int32_t *i; //I - int32_t *E; //E - int64_t *l; //J - uint8_t *ub; //b - uint16_t *us; //s - uint32_t *ui; //i - uint64_t *ul; //j - switch (c) { case 'Z' : - z = loc; - *z = (bool) json_is_true(val); + *(bool*)loc = (bool) json_is_true(val); break; case 'F' : - f = loc; - *f = (float) json_real_value(val); + *(float*)loc = (float) json_real_value(val); break; case 'D' : - d = loc; - *d = json_real_value(val); + *(double*)loc = json_real_value(val); break; case 'N' : - n = loc; - *n = (int) json_integer_value(val); + *(int*)loc = (int) json_integer_value(val); break; case 'B' : - b = loc; - *b = (char) json_integer_value(val); + *(char*)loc = (char) json_integer_value(val); break; case 'S' : - s = loc; - *s = (int16_t) json_integer_value(val); + *(int16_t*)loc = (int16_t) json_integer_value(val); break; case 'I' : - i = loc; - *i = (int32_t) json_integer_value(val); + *(int32_t*)loc = (int32_t) json_integer_value(val); break; case 'J' : - l = loc; - *l = (int64_t) json_integer_value(val); + *(int64_t*)loc = (int64_t) json_integer_value(val); break; case 'b' : - ub = loc; - *ub = (uint8_t) json_integer_value(val); + *(uint8_t*)loc = (uint8_t) json_integer_value(val); break; case 's' : - us = loc; - *us = (uint16_t) json_integer_value(val); + *(uint16_t*)loc = (uint16_t) json_integer_value(val); break; case 'i' : - ui = loc; - *ui = (uint32_t) json_integer_value(val); + *(uint32_t*)loc = (uint32_t) json_integer_value(val); break; case 'j' : - ul = loc; - *ul = (uint64_t) json_integer_value(val); + *(uint64_t*)loc = (uint64_t) json_integer_value(val); break; case 'E' : if (json_is_null(val)) { //nop } else if (json_is_string(val)){ - E = loc; - status = jsonSerializer_parseEnum(type, json_string_value(val), E); + status = jsonSerializer_parseEnum(type, json_string_value(val), loc); } else { status = ERROR; celix_err_pushf("Expected json string for enum type but got %i", json_typeof(val)); @@ -248,13 +196,10 @@ static int jsonSerializer_parseAny(const dyn_type *type, void *loc, json_t *val) subType = dynType_typedPointer_getTypedType(type); status = jsonSerializer_createType(subType, val, (void **) loc); break; - case 'P' : - status = ERROR; - celix_err_pushf("Untyped pointer are not supported for serialization"); - break; case 'l': status = jsonSerializer_parseAny(type->ref.ref, loc, val); break; + case 'P' : default : status = ERROR; celix_err_pushf("Error provided type '%c' not supported for JSON\n", dynType_descriptorType(type)); @@ -264,7 +209,7 @@ static int jsonSerializer_parseAny(const dyn_type *type, void *loc, json_t *val) return status; } -static int jsonSerializer_parseSequence(const dyn_type *seq, json_t *array, void *seqLoc) { +static int jsonSerializer_parseSequence(const dyn_type* seq, json_t* array, void* seqLoc) { assert(dynType_type(seq) == DYN_TYPE_SEQUENCE); int status = OK; @@ -272,11 +217,11 @@ static int jsonSerializer_parseSequence(const dyn_type *seq, json_t *array, void status = dynType_sequence_alloc(seq, seqLoc, (int) size); if (status == OK) { - const dyn_type *itemType = dynType_sequence_itemType(seq); + const dyn_type* itemType = dynType_sequence_itemType(seq); size_t index; - json_t *val; + json_t* val; json_array_foreach(array, index, val) { - void *valLoc = NULL; + void* valLoc = NULL; status = dynType_sequence_increaseLengthAndReturnLastLoc(seq, seqLoc, &valLoc); if (status == OK) { status = jsonSerializer_parseAny(itemType, valLoc, val); @@ -290,10 +235,10 @@ static int jsonSerializer_parseSequence(const dyn_type *seq, json_t *array, void return status; } -int jsonSerializer_serialize(dyn_type *type, const void* input, char **output) { +int jsonSerializer_serialize(const dyn_type* type, const void* input, char** output) { int status = OK; - json_t *root = NULL; + json_t* root = NULL; status = jsonSerializer_serializeJson(type, input, &root); if (status == OK) { @@ -304,8 +249,8 @@ int jsonSerializer_serialize(dyn_type *type, const void* input, char **output) { return status; } -static int jsonSerializer_parseEnum(const dyn_type *type, const char* enum_name, int32_t *out) { - struct meta_entry * entry; +static int jsonSerializer_parseEnum(const dyn_type* type, const char* enum_name, int32_t* out) { + struct meta_entry* entry; TAILQ_FOREACH(entry, &type->metaProperties, entries) { if (0 == strcmp(enum_name, entry->name)) { @@ -318,7 +263,7 @@ static int jsonSerializer_parseEnum(const dyn_type *type, const char* enum_name, return ERROR; } -int jsonSerializer_serializeJson(const dyn_type *type, const void* input, json_t **out) { +int jsonSerializer_serializeJson(const dyn_type* type, const void* input, json_t** out) { return jsonSerializer_writeAny(type, (void*)input /*TODO update static function to take const void**/, out); }