From 80cf1324c5522a516fc29aa40d5d25f5f550f1e5 Mon Sep 17 00:00:00 2001 From: Niklas Werner Date: Thu, 19 Jul 2018 06:22:38 +0200 Subject: [PATCH] C Generator Sample - Improvements (#558) * Added a .gitignore to ignore the build folder * Added a CMakeLists and a basic implementation of a double linked list * Added the pet model * changed the behaviour when a list gets freed - the data of each element doesn't get freed anymore * Added the tool uncrustify in order to make code look better * Uncrustified code * added an implementation(constructor and deconstructor) for the category model * Added a third party JSON library * The pet struct now uses pointers for its members; the pet struct now has a proper constructor and a basic toJSON method * The pet model now gets fully serialized into JSON * Fixed the example url... * Added third party library libcurl * Modified category struct and added an unit test * Added a foreach macro and added two functions * Added a tag model and an unit test * the pet struct now uses no double pointer for it's name anymore and no pointer for the enum status anymore; the pet struct can now be fully converted to json and parsed from json * Added the struct APIClient and an unit test * Uncrustified the unit test for category * Added ifdef in pet.h to prevent errors * Added one API endpoint to get a pet by id * Added a "== 0" comparison that I forgot * Added some kind of debug functionality to test-petApi.c * Removed the DEBUG define * Moved the c petstore example from samples/client/c to samples/client/petstore/c * Renamed function getPetById to petApi_getPetById to avoid name collisions * Removed unecessary method in list.c * Added POST functionality; added petApi_addPet method and improved unit-test for petApi; cleaned up some code in apiClient * removed two methods in list.c(string/tag to JSON) and moved their code directly in the pet_convertToJSON method * Removed old, already commented out, puts artifact in apiClient.c * Added a convertToJSON method to the category model * Added a convertToJSON method to the tag model * changed how the convertToJSON method works in the pet model * Adjusted the unit-tests on how the convertToJSON method now works(now returns a cJSON* instead of a char*) * apiClient_t now needs to be given to API methods as a parameter. This means apiClient_t can now be reused in multiple methods. * Added an untested concept for how authentication could be handled * Tested basicAuth using wireshark and added untested OAuth2 feature * Added support for api key authentication using the http-header and tested functionality using wireshark * Renamed the dataToPost parameter in apiClient_invoke to bodyParameters * Renamed apiKey_t to keyValuePair_t and implemented GET queries * Spaces are now being replaced with + in querryParameters * Renamed assembleHeader to assembleAPIKeyAuthentication and added support to change the request type * Implemented the option to provide custom httpHeader fields to apiClient_invoke * Added support for form parameters to the apiClient_invoke method * update petstore sample --- samples/client/petstore/c/api/petAPI.c | 10 +- samples/client/petstore/c/include/apiClient.h | 8 +- .../client/petstore/c/include/keyValuePair.h | 7 + samples/client/petstore/c/src/apiClient.c | 141 +++++++++++++++--- samples/client/petstore/c/src/apiKey.c | 16 +- samples/client/petstore/c/unit-tests/apiKey.c | 6 +- .../c/unit-tests/test-api-client-parameters.c | 68 +++++++++ .../petstore/c/unit-tests/test-api-client.c | 44 +++++- 8 files changed, 257 insertions(+), 43 deletions(-) create mode 100644 samples/client/petstore/c/include/keyValuePair.h create mode 100644 samples/client/petstore/c/unit-tests/test-api-client-parameters.c diff --git a/samples/client/petstore/c/api/petAPI.c b/samples/client/petstore/c/api/petAPI.c index 2eef6bed3b7..d9e8f4368f8 100644 --- a/samples/client/petstore/c/api/petAPI.c +++ b/samples/client/petstore/c/api/petAPI.c @@ -15,6 +15,10 @@ pet_t *petApi_getPetById(apiClient_t *apiClient, long petId) { apiClient_invoke(apiClient, "pet", petIdString, + NULL, + NULL, + NULL, + NULL, NULL); pet = pet_parseFromJSON(apiClient->dataReceived); free(apiClient->dataReceived); @@ -38,7 +42,11 @@ void *petApi_addPet(apiClient_t *apiClient, pet_t *pet) { apiClient_invoke(apiClient, "pet", NULL, - petJSONString); + NULL, + NULL, + NULL, + petJSONString, + "POST"); free(apiClient->dataReceived); free(petJSONString); cJSON_Delete(petJSONObject); diff --git a/samples/client/petstore/c/include/apiClient.h b/samples/client/petstore/c/include/apiClient.h index 352a196ff3f..7b493a42286 100644 --- a/samples/client/petstore/c/include/apiClient.h +++ b/samples/client/petstore/c/include/apiClient.h @@ -1,9 +1,11 @@ #ifndef INCLUDE_API_CLIENT_H #define INCLUDE_API_CLIENT_H -#ifdef API_KEY #include "list.h" -#endif // API_KEY + +typedef int bool; +#define true 1 +#define false 0 typedef struct apiClient_t { char *basePath; @@ -25,6 +27,6 @@ typedef struct apiClient_t { apiClient_t* apiClient_create(); void apiClient_free(apiClient_t *apiClient); -void apiClient_invoke(apiClient_t *apiClient, char* operationName, char* operationParameter, char *dataToPost); +void apiClient_invoke(apiClient_t *apiClient, char* operationName, char* operationParameter, list_t *queryParameters, list_t *headerParameters, list_t *formParameters, char *bodyParameters, char *requestType); #endif // INCLUDE_API_CLIENT_H \ No newline at end of file diff --git a/samples/client/petstore/c/include/keyValuePair.h b/samples/client/petstore/c/include/keyValuePair.h new file mode 100644 index 00000000000..bcc212d82d1 --- /dev/null +++ b/samples/client/petstore/c/include/keyValuePair.h @@ -0,0 +1,7 @@ +typedef struct keyValuePair_t { + char* key; + char* value; +} keyValuePair_t; + +keyValuePair_t *keyValuePair_create(char *key, char *value); +void keyValuePair_free(keyValuePair_t *keyValuePair); \ No newline at end of file diff --git a/samples/client/petstore/c/src/apiClient.c b/samples/client/petstore/c/src/apiClient.c index 69be9b22218..58a593e28f2 100644 --- a/samples/client/petstore/c/src/apiClient.c +++ b/samples/client/petstore/c/src/apiClient.c @@ -3,10 +3,8 @@ #include #include "apiClient.h" #include "pet.h" +#include "keyValuePair.h" -#ifdef API_KEY -#include "apiKey.h" -#endif size_t writeDataCallback(void *buffer, size_t size, size_t nmemb, void *userp); apiClient_t *apiClient_create() { @@ -28,25 +26,77 @@ void apiClient_free(apiClient_t *apiClient) { curl_global_cleanup(); } +void replaceSpaceWithPlus(char *stringToProcess) { + for(int i = 0; i < strlen(stringToProcess); i++) { + if(stringToProcess[i] == ' ') { + stringToProcess[i] = '+'; + } + } +} + char *assembleTargetUrl(char *basePath, char *operationName, - char *operationParameter) { + char *operationParameter, + list_t *queryParameters) { + int neededBufferSizeForQueryParameters = 0; + listEntry_t *listEntry; + + if(queryParameters != NULL) { + list_ForEach(listEntry, queryParameters) { + keyValuePair_t *pair = listEntry->data; + neededBufferSizeForQueryParameters += + strlen(pair->key) + strlen(pair->value); + } + + neededBufferSizeForQueryParameters += + (queryParameters->count * 2); // each keyValuePair is separated by a = and a & except the last, but this makes up for the ? at the beginning + } + + int operationParameterLength = 0; + int basePathLength = strlen(basePath); + bool slashNeedsToBeAppendedToBasePath = false; + + if(operationParameter != NULL) { + operationParameterLength = (1 + strlen(operationParameter)); + } + if(basePath[strlen(basePath) - 1] != '/') { + slashNeedsToBeAppendedToBasePath = true; + basePathLength++; + } + char *targetUrl = - malloc(strlen(operationName) + strlen( - basePath) + - ((operationParameter == NULL) ? 1 : (2 + strlen( - operationParameter)))); + malloc(strlen( + operationName) + neededBufferSizeForQueryParameters + basePathLength + operationParameterLength + 1 + ); strcpy(targetUrl, basePath); + if(slashNeedsToBeAppendedToBasePath) { + strcat(targetUrl, "/"); + } strcat(targetUrl, operationName); if(operationParameter != NULL) { strcat(targetUrl, "/"); strcat(targetUrl, operationParameter); } + if(queryParameters != NULL) { + strcat(targetUrl, "?"); + list_ForEach(listEntry, queryParameters) { + keyValuePair_t *pair = listEntry->data; + replaceSpaceWithPlus(pair->key); + strcat(targetUrl, pair->key); + strcat(targetUrl, "="); + replaceSpaceWithPlus(pair->value); + strcat(targetUrl, pair->value); + if(listEntry->nextListEntry != NULL) { + strcat(targetUrl, "&"); + } + } + } + return targetUrl; } -char *assembleHeader(char *key, char *value) { +char *assembleHeaderField(char *key, char *value) { char *header = malloc(strlen(key) + strlen(value) + 3); strcpy(header, key), @@ -56,40 +106,83 @@ char *assembleHeader(char *key, char *value) { return header; } -void postData(CURL *handle, char *dataToPost) { - curl_easy_setopt(handle, CURLOPT_POSTFIELDS, dataToPost); +void postData(CURL *handle, char *bodyParameters) { + curl_easy_setopt(handle, CURLOPT_POSTFIELDS, bodyParameters); curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE_LARGE, - strlen(dataToPost)); - curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "POST"); + strlen(bodyParameters)); } void apiClient_invoke(apiClient_t *apiClient, char *operationName, char *operationParameter, - char *dataToPost) { + list_t *queryParameters, + list_t *headerParameters, + list_t *formParameters, + char *bodyParameters, + char *requestType) { CURL *handle = curl_easy_init(); CURLcode res; if(handle) { + listEntry_t *listEntry; + curl_mime *mime = NULL; struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "accept: application/json"); headers = curl_slist_append(headers, "Content-Type: application/json"); + if(requestType != NULL) { + curl_easy_setopt(handle, + CURLOPT_CUSTOMREQUEST, + requestType); + } + if(formParameters != NULL) { + mime = curl_mime_init(handle); + list_ForEach(listEntry, formParameters) { + keyValuePair_t *keyValuePair = listEntry->data; + if((keyValuePair->key != NULL) && + (keyValuePair->value != NULL) ) + { + curl_mimepart *part = curl_mime_addpart( + mime); + curl_mime_data(part, + keyValuePair->key, + CURL_ZERO_TERMINATED); + curl_mime_name(part, + keyValuePair->value); + } + } + curl_easy_setopt(handle, CURLOPT_MIMEPOST, mime); + } + + list_ForEach(listEntry, headerParameters) { + keyValuePair_t *keyValuePair = listEntry->data; + if((keyValuePair->key != NULL) && + (keyValuePair->value != NULL) ) + { + char *headerValueToWrite = + assembleHeaderField( + keyValuePair->key, + keyValuePair->value); + curl_slist_append(headers, headerValueToWrite); + free(headerValueToWrite); + } + } // this would only be generated for apiKey authentication #ifdef API_KEY - listEntry_t *listEntry; list_ForEach(listEntry, apiClient->apiKeys) { - apiKey_t *apiKey = listEntry->data; + keyValuePair_t *apiKey = listEntry->data; if((apiKey->key != NULL) && (apiKey->value != NULL) ) { - char *headerValueToWrite = assembleHeader( - apiKey->key, - apiKey->value); + char *headerValueToWrite = + assembleHeaderField( + apiKey->key, + apiKey->value); curl_slist_append(headers, headerValueToWrite); free(headerValueToWrite); } @@ -99,7 +192,8 @@ void apiClient_invoke(apiClient_t *apiClient, char *targetUrl = assembleTargetUrl(apiClient->basePath, operationName, - operationParameter); + operationParameter, + queryParameters); curl_easy_setopt(handle, CURLOPT_URL, targetUrl); curl_easy_setopt(handle, @@ -148,8 +242,8 @@ void apiClient_invoke(apiClient_t *apiClient, #endif // BASIC_AUTH - if(dataToPost != NULL) { - postData(handle, dataToPost); + if(bodyParameters != NULL) { + postData(handle, bodyParameters); } res = curl_easy_perform(handle); @@ -170,6 +264,9 @@ void apiClient_invoke(apiClient_t *apiClient, } #endif // BASIC_AUTH curl_easy_cleanup(handle); + if(formParameters != NULL) { + curl_mime_free(mime); + } } } diff --git a/samples/client/petstore/c/src/apiKey.c b/samples/client/petstore/c/src/apiKey.c index 6bb2b8daec6..f6231130487 100644 --- a/samples/client/petstore/c/src/apiKey.c +++ b/samples/client/petstore/c/src/apiKey.c @@ -1,14 +1,14 @@ #include -#include "apiKey.h" +#include "keyValuePair.h" -apiKey_t *apiKey_create(char *key, char *value) { - apiKey_t *apiKey = malloc(sizeof(apiKey_t)); - apiKey->key = key; - apiKey->value = value; +keyValuePair_t *keyValuePair_create(char *key, char *value) { + keyValuePair_t *keyValuePair = malloc(sizeof(keyValuePair_t)); + keyValuePair->key = key; + keyValuePair->value = value; - return apiKey; + return keyValuePair; } -void apiKey_free(apiKey_t *apiKey) { - free(apiKey); +void keyValuePair_free(keyValuePair_t *keyValuePair) { + free(keyValuePair); } \ No newline at end of file diff --git a/samples/client/petstore/c/unit-tests/apiKey.c b/samples/client/petstore/c/unit-tests/apiKey.c index f2ea1b2bdfd..7cef9194511 100644 --- a/samples/client/petstore/c/unit-tests/apiKey.c +++ b/samples/client/petstore/c/unit-tests/apiKey.c @@ -1,6 +1,6 @@ -#include "apiKey.h" +#include "keyValuePair.h" int main() { - apiKey_t *apiKey = apiKey_create("key", "value"); - apiKey_free(apiKey); + keyValuePair_t *keyValuePair = keyValuePair_create("key", "value"); + keyValuePair_free(keyValuePair); } \ No newline at end of file diff --git a/samples/client/petstore/c/unit-tests/test-api-client-parameters.c b/samples/client/petstore/c/unit-tests/test-api-client-parameters.c new file mode 100644 index 00000000000..869989d7dc2 --- /dev/null +++ b/samples/client/petstore/c/unit-tests/test-api-client-parameters.c @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include "apiClient.h" +#include "list.h" +#include "keyValuePair.h" + +#define EXAMPLE_BASE_PATH "localhost" +#define EXAMPLE_OPERATION_NAME "pets" +#define OPERATION_PARAMETER "5" +#define EXAMPLE_KEY_1 "skin color" +#define EXAMPLE_VALUE_1 "red" +#define EXAMPLE_KEY_2 "legs" +#define EXAMPLE_VALUE_2 "4" + +#define OUTPUT_URL_1 "localhost/pets/5?skin+color=red" +#define OUTPUT_URL_2 "localhost/pets/5?skin+color=red&legs=4" + +char *assembleTargetUrl(char *basePath, + char *operationName, + char *operationParameter, + list_t *queryParameters); + +int main() { + char *keyOne = malloc(strlen(EXAMPLE_KEY_1) + 1); + char *valueOne = malloc(strlen(EXAMPLE_VALUE_1) + 1); + + strcpy(keyOne, EXAMPLE_KEY_1); + strcpy(valueOne, EXAMPLE_VALUE_1); + + keyValuePair_t *keyValuePairOne = keyValuePair_create(keyOne, valueOne); + list_t *list = list_create(); + list_addElement(list, keyValuePairOne); + + char *exampleUrlOne = assembleTargetUrl(EXAMPLE_BASE_PATH, + EXAMPLE_OPERATION_NAME, + OPERATION_PARAMETER, + list); + + assert(strcmp(exampleUrlOne, OUTPUT_URL_1) == 0); + + char *keyTwo = malloc(strlen(EXAMPLE_KEY_2) + 1); + char *valueTwo = malloc(strlen(EXAMPLE_VALUE_2) + 1); + + strcpy(keyTwo, EXAMPLE_KEY_2); + strcpy(valueTwo, EXAMPLE_VALUE_2); + + keyValuePair_t *keyValuePairTwo = keyValuePair_create(keyTwo, valueTwo); + list_addElement(list, keyValuePairTwo); + + char *exampleUrlTwo = assembleTargetUrl(EXAMPLE_BASE_PATH, + EXAMPLE_OPERATION_NAME, + OPERATION_PARAMETER, + list); + + assert(strcmp(exampleUrlTwo, OUTPUT_URL_2) == 0); + + free(keyOne); + free(keyTwo); + free(valueOne); + free(valueTwo); + free(exampleUrlOne); + free(exampleUrlTwo); + keyValuePair_free(keyValuePairOne); + keyValuePair_free(keyValuePairTwo); + list_free(list); +} \ No newline at end of file diff --git a/samples/client/petstore/c/unit-tests/test-api-client.c b/samples/client/petstore/c/unit-tests/test-api-client.c index 161cbaf3e5a..c63dbf358f0 100644 --- a/samples/client/petstore/c/unit-tests/test-api-client.c +++ b/samples/client/petstore/c/unit-tests/test-api-client.c @@ -1,18 +1,21 @@ #include +#include #include "apiClient.h" #include "cJSON.h" #include "pet.h" -#ifdef API_KEY #include "list.h" -#include "apiKey.h" -#endif // API_KEY +#include "keyValuePair.h" #ifdef DEBUG #include #endif // DEBUG #define EXAMPLE_OPERATION_NAME "pet" -#define EXAMPLE_OPERATION_PARAMETER "3" +#define EXAMPLE_OPERATION_PARAMETER "5" +#define EXAMPLE_KEYNAME_1 "MyExampleKey" +#define EXAMPLE_VALUENAME_1 "MyExampleValue" +#define EXAMPLE_KEYNAME_2 "MyExampleKeyTwo" +#define EXAMPLE_VALUENAME_2 "MyExampleValueTwo" int main() { apiClient_t *apiClient = apiClient_create(); @@ -21,13 +24,36 @@ int main() { #endif // OAUTH2 #ifdef API_KEY apiClient->apiKeys = list_create(); - apiKey_t *apiKey = apiKey_create("X-API-Key", "abcdef12345"); + keyValuePair_t *apiKey = apiKey_create("X-API-Key", "abcdef12345"); list_addElement(apiClient->apiKeys, apiKey); #endif // API_KEY + list_t *customHeaderFields = list_create(); + char *keyOne = malloc(strlen(EXAMPLE_KEYNAME_1) + 1); + char *valueOne = malloc(strlen(EXAMPLE_VALUENAME_1) + 1); + strcpy(keyOne, EXAMPLE_KEYNAME_1); + strcpy(valueOne, EXAMPLE_VALUENAME_1); + + char *keyTwo = malloc(strlen(EXAMPLE_KEYNAME_2) + 1); + char *valueTwo = malloc(strlen(EXAMPLE_VALUENAME_2) + 1); + strcpy(keyTwo, EXAMPLE_KEYNAME_2); + strcpy(valueTwo, EXAMPLE_VALUENAME_2); + + keyValuePair_t *firstCustomField = + keyValuePair_create(keyOne, valueOne); + keyValuePair_t *secondCustomField = + keyValuePair_create(keyTwo, valueTwo); + + list_addElement(customHeaderFields, firstCustomField); + list_addElement(customHeaderFields, secondCustomField); + apiClient_invoke(apiClient, EXAMPLE_OPERATION_NAME, EXAMPLE_OPERATION_PARAMETER, + NULL, + customHeaderFields, + NULL, + NULL, NULL); pet_t *pet = pet_parseFromJSON(apiClient->dataReceived); if(pet == NULL) { @@ -49,7 +75,13 @@ int main() { free(apiKey); list_free(apiClient->apiKeys); #endif // API_KEY - + free(keyOne); + free(valueOne); + free(keyTwo); + free(valueTwo); + keyValuePair_free(firstCustomField); + keyValuePair_free(secondCustomField); + list_free(customHeaderFields); apiClient_free(apiClient); pet_free(pet); } \ No newline at end of file