diff --git a/CMakeLists.txt b/CMakeLists.txt index 46f9040d..c9def1af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ cmake_minimum_required( VERSION 3.12 FATAL_ERROR ) find_package( ecbuild 3.7.2 REQUIRED HINTS ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../ecbuild) -project( metkit LANGUAGES CXX ) +project( metkit LANGUAGES CXX C ) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/src/metkit/CMakeLists.txt b/src/metkit/CMakeLists.txt index 49a3426b..766e4548 100644 --- a/src/metkit/CMakeLists.txt +++ b/src/metkit/CMakeLists.txt @@ -4,7 +4,7 @@ ecbuild_generate_config_headers( DESTINATION ${INSTALL_INCLUDE_DIR}/metkit ) configure_file( metkit_config.h.in metkit_config.h ) configure_file( metkit_version.h.in metkit_version.h ) -configure_file( metkit_version.cc.in metkit_version.cc ) +configure_file( metkit_version.c.in ${CMAKE_CURRENT_BINARY_DIR}/metkit_version.c ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/metkit_config.h @@ -15,7 +15,7 @@ install(FILES ### metkit sources list( APPEND metkit_srcs - ${CMAKE_CURRENT_BINARY_DIR}/metkit_version.cc + ${CMAKE_CURRENT_BINARY_DIR}/metkit_version.c config/LibMetkit.cc config/LibMetkit.h mars/BaseProtocol.cc @@ -100,6 +100,8 @@ list( APPEND metkit_srcs hypercube/HyperCube.cc hypercube/HyperCube.h hypercube/HyperCubePayloaded.h + api/metkit_c.cc + api/metkit_c.h ) list( APPEND metkit_persistent_srcs diff --git a/src/metkit/api/metkit_c.cc b/src/metkit/api/metkit_c.cc new file mode 100644 index 00000000..8577aded --- /dev/null +++ b/src/metkit/api/metkit_c.cc @@ -0,0 +1,267 @@ +#include "metkit_c.h" +#include "metkit/mars/MarsExpension.h" +#include "metkit/mars/MarsRequest.h" +#include "metkit/metkit_version.h" +#include "eckit/runtime/Main.h" +#include + +// --------------------------------------------------------------------------------------------------------------------- + +struct metkit_marsrequest_t : public metkit::mars::MarsRequest { + using metkit::mars::MarsRequest::MarsRequest; + + metkit_marsrequest_t(metkit::mars::MarsRequest&& req) : + metkit::mars::MarsRequest(std::move(req)) {} +}; + +struct metkit_requestiterator_t { + explicit metkit_requestiterator_t(std::vector&& vec) : + vector_(std::move(vec)), iterator_(vector_.begin()) {} + + int next() { + if (iterator_ == vector_.end()) { + return METKIT_ITERATION_COMPLETE; + } + ++iterator_; + return iterator_ == vector_.end() ? METKIT_ITERATION_COMPLETE : METKIT_SUCCESS; + } + + void current(metkit_marsrequest_t* request) { + ASSERT(iterator_ != vector_.end()); + *request = std::move(*iterator_); + } + +private: + + std::vector vector_; + std::vector::iterator iterator_; +}; + +// --------------------------------------------------------------------------------------------------------------------- +// ERROR HANDLING + +static thread_local std::string g_current_error_string; + +const char* metkit_get_error_string(metkit_error_t err) { + switch (err) { + case METKIT_SUCCESS: + return "Success"; + case METKIT_ITERATION_COMPLETE: + return "Iteration complete"; + case METKIT_ERROR: + case METKIT_ERROR_USER: + case METKIT_ERROR_ASSERT: + return g_current_error_string.c_str(); + default: + return ""; + } +} + +metkit_error_t innerWrapFn(std::function f) { + return static_cast(f()); +} + +metkit_error_t innerWrapFn(std::function f) { + f(); + return METKIT_SUCCESS; +} + +template +[[nodiscard]] metkit_error_t tryCatchEnum(FN&& fn) { + try { + return innerWrapFn(std::forward(fn)); + } + catch (const eckit::UserError& e) { + g_current_error_string = e.what(); + return METKIT_ERROR_USER; + } + catch (const eckit::AssertionFailed& e) { + g_current_error_string = e.what(); + return METKIT_ERROR_ASSERT; + } + catch (const eckit::Exception& e) { + g_current_error_string = e.what(); + return METKIT_ERROR; + } + catch (const std::exception& e) { + g_current_error_string = e.what(); + return METKIT_ERROR_UNKNOWN; + } + catch (...) { + return METKIT_ERROR_UNKNOWN; + } +} + +// ----------------------------------------------------------------------------- +// HELPERS +// ----------------------------------------------------------------------------- + +metkit_error_t metkit_initialise() { + return tryCatchEnum([] { + static bool initialised = false; + + if (initialised) { + eckit::Log::warning() + << "Initialising Metkit library twice" << std::endl; + } + + if (!initialised) { + const char* argv[2] = {"metkit-api", 0}; + eckit::Main::initialise(1, const_cast(argv)); + initialised = true; + } + }); +} + +// ----------------------------------------------------------------------------- +// PARSING +// ----------------------------------------------------------------------------- + +metkit_error_t metkit_parse_marsrequests(const char* str, metkit_requestiterator_t** requests, bool strict) { + return tryCatchEnum([requests, str, strict] { + ASSERT(requests); + ASSERT(str); + std::istringstream in(str); + *requests = new metkit_requestiterator_t(metkit::mars::MarsRequest::parse(in, strict)); + }); +} + +// ----------------------------------------------------------------------------- +// REQUEST +// ----------------------------------------------------------------------------- + +metkit_error_t metkit_new_marsrequest(metkit_marsrequest_t** request) { + return tryCatchEnum([request] { + ASSERT(request); + *request = new metkit_marsrequest_t(); + }); +} + +metkit_error_t metkit_delete_marsrequest(const metkit_marsrequest_t* request) { + return tryCatchEnum([request] { + delete request; + }); +} + +metkit_error_t metkit_marsrequest_set(metkit_marsrequest_t* request, const char* param, const char* values[], int numValues) { + return tryCatchEnum([request, param, values, numValues] { + ASSERT(request); + ASSERT(param); + ASSERT(values); + std::string param_str(param); + std::vector values_vec; + values_vec.reserve(numValues); + std::copy(values, values + numValues, std::back_inserter(values_vec)); + + request->values(param_str, values_vec); + }); +} + +metkit_error_t metkit_marsrequest_set_one(metkit_marsrequest_t* request, const char* param, const char* value) { + return metkit_marsrequest_set(request, param, &value, 1); +} + +metkit_error_t metkit_marsrequest_set_verb(metkit_marsrequest_t* request, const char* verb) { + return tryCatchEnum([request, verb] { + ASSERT(request); + ASSERT(verb); + request->verb(verb); + }); +} + +metkit_error_t metkit_marsrequest_verb(const metkit_marsrequest_t* request, const char** verb) { + return tryCatchEnum([request, verb] { + ASSERT(request); + ASSERT(verb); + *verb = request->verb().c_str(); + }); +} + +metkit_error_t metkit_marsrequest_has_param(const metkit_marsrequest_t* request, const char* param, bool* has) { + return tryCatchEnum([request, param, has] { + ASSERT(request); + ASSERT(param); + ASSERT(has); + *has = request->has(param); + }); +} + +metkit_error_t metkit_marsrequest_count_params(const metkit_marsrequest_t* request, size_t* count) { + return tryCatchEnum([request, count] { + ASSERT(request); + ASSERT(count); + *count = request->params().size(); + }); +} + +metkit_error_t metkit_marsrequest_param(const metkit_marsrequest_t* request, size_t index, const char** param) { + return tryCatchEnum([request, index, param] { + ASSERT(request); + ASSERT(param); + *param = request->params()[index].c_str(); + }); +} + +metkit_error_t metkit_marsrequest_count_values(const metkit_marsrequest_t* request, const char* param, size_t* count) { + return tryCatchEnum([request, param, count] { + ASSERT(request); + ASSERT(param); + ASSERT(count); + *count = request->countValues(param); + }); +} + +metkit_error_t metkit_marsrequest_value(const metkit_marsrequest_t* request, const char* param, int index, const char** value) { + return tryCatchEnum([request, param, index, value] { + ASSERT(request); + ASSERT(param); + ASSERT(value); + *value = request->values(param, false)[index].c_str(); + }); +} +metkit_error_t metkit_marsrequest_expand(const metkit_marsrequest_t* request, bool inherit, bool strict, metkit_marsrequest_t* expandedRequest) { + return tryCatchEnum([request, expandedRequest, inherit, strict] { + ASSERT(request); + ASSERT(expandedRequest); + ASSERT(expandedRequest->empty()); + metkit::mars::MarsExpension expand(inherit, strict); + *expandedRequest = expand.expand(*request); + }); +} + +metkit_error_t metkit_marsrequest_merge(metkit_marsrequest_t* request, const metkit_marsrequest_t* otherRequest) { + return tryCatchEnum([request, otherRequest] { + ASSERT(request); + ASSERT(otherRequest); + request->merge(*otherRequest); + }); +} + +// ----------------------------------------------------------------------------- +// REQUEST ITERATOR +// ----------------------------------------------------------------------------- + +metkit_error_t metkit_delete_requestiterator(const metkit_requestiterator_t* it) { + return tryCatchEnum([it] { + delete it; + }); +} + +metkit_error_t metkit_requestiterator_next(metkit_requestiterator_t* it) { + return tryCatchEnum(std::function{[it] { + ASSERT(it); + return it->next(); + }}); +} + +metkit_error_t metkit_requestiterator_request(metkit_requestiterator_t* it, metkit_marsrequest_t* request) { + return tryCatchEnum([it, request] { + ASSERT(it); + ASSERT(request); + ASSERT(request->empty()); + + it->current(request); + }); +} + +// --------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/metkit/api/metkit_c.h b/src/metkit/api/metkit_c.h new file mode 100644 index 00000000..35af1ebe --- /dev/null +++ b/src/metkit/api/metkit_c.h @@ -0,0 +1,211 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* --------------------------------------------------------------------------------------------------------------------- + * TYPES + * -----*/ + +struct metkit_marsrequest_t; +typedef struct metkit_marsrequest_t metkit_marsrequest_t; + +struct metkit_requestiterator_t; +/** RequestIterator for iterating over vector of Request instances */ +typedef struct metkit_requestiterator_t metkit_requestiterator_t; + +struct metkit_paramiterator_t; +/** Iterator for iterating over parameters in Request */ +typedef struct metkit_paramiterator_t metkit_paramiterator_t; + +/* --------------------------------------------------------------------------------------------------------------------- + * ERROR HANDLING + * -------------- */ + +typedef enum metkit_error_values_t +{ + METKIT_SUCCESS = 0, /* Operation succeded. */ + METKIT_ITERATION_COMPLETE = 1, /* All elements have been returned */ + METKIT_ERROR = 2, /* Operation failed. */ + METKIT_ERROR_UNKNOWN = 3, /* Failed with an unknown error. */ + METKIT_ERROR_USER = 4, /* Failed with an user error. */ + METKIT_ERROR_ASSERT = 5 /* Failed with an assert() */ +} metkit_error_t; + +const char* metkit_get_error_string(enum metkit_error_values_t err); + +/* ----------------------------------------------------------------------------- + * HELPERS + * ------- */ + +/** + * @brief Get metkit version. + * + * @return const char* version string + */ +const char* metkit_version(); + +/** + * @brief Get metkit git sha1 version. + * + * @return const char* git sha1 version string + */ +const char* metkit_git_sha1(); + +/** + * @brief Initialise Main() context. + * + * @note This is ONLY required when Main() is NOT initialised, such as loading + * the MetKit as shared library in Python. + * @return metkit_error_t Error code + */ +metkit_error_t metkit_initialise(); + +/* --------------------------------------------------------------------------------------------------------------------- + * PARSING + * --- */ + +/** + * Parse MARS requests into RequestIterator of Request instances. Resulting RequestIterator + * must be deallocated with metkit_delete_requestiterator + * @param str MARS requests + * @param[out] requests Allocates RequestIterator object + * @return metkit_error_t Error code + */ +metkit_error_t metkit_parse_marsrequests(const char* str, metkit_requestiterator_t** requests, bool strict); + +/* --------------------------------------------------------------------------------------------------------------------- + * REQUEST + * --- */ + +/** Allocates new Request object. Must be deallocated with mekit_delete_request + * @param[out] request new Request instance + * @return metkit_error_t Error code + */ +metkit_error_t metkit_new_marsrequest(metkit_marsrequest_t** request); + +/** Deallocates Request object and associated resources. + * @param request Request instance + * @return metkit_error_t Error code + */ +metkit_error_t metkit_delete_marsrequest(const metkit_marsrequest_t* request); + +/** Add parameter and values to request + * @param request Request instance + * @param param parameter name + * @param values array of values for parameter + * @param numValues number of values + * @return metkit_error_t Error code + */ +metkit_error_t metkit_marsrequest_set(metkit_marsrequest_t* request, const char* param, const char* values[], int numValues); + +/** Add parameter and values to request + * @param request Request instance + * @param param parameter name + * @param values value to add + * @return metkit_error_t Error code + */ +metkit_error_t metkit_marsrequest_set_one(metkit_marsrequest_t* request, const char* param, const char* value); + +/** Set verb in Request object + * @param request Request instance + * @param verb verb to set + * @return metkit_error_t Error code + */ +metkit_error_t metkit_marsrequest_set_verb(metkit_marsrequest_t* request, const char* verb); + +/** Returns the verb in Request object + * @param request Request instance + * @param[out] verb verb in request + * @return metkit_error_t Error code + */ +metkit_error_t metkit_marsrequest_verb(const metkit_marsrequest_t* request, const char** verb); + +/** Returns whether parameter is in Request object + * @param request Request instance + * @param param parameter name + * @param[out] has whether parameter exists in request + * @return metkit_error_t Error code + */ +metkit_error_t metkit_marsrequest_has_param(const metkit_marsrequest_t* request, const char* param, bool* has); + + +/** Returns number of parameters in Request object + * @param request Request instance + * @param[out] count number of parameters in request + * @return metkit_error_t Error code + */ +metkit_error_t metkit_marsrequest_count_params(const metkit_marsrequest_t* request, size_t* count); + +/** Returns parameter name for specific index in Request object + * @param request Request instance + * @param index index of parameter to retrieve + * @param[out] param parameter name + * @return metkit_error_t Error code + */ +metkit_error_t metkit_marsrequest_param(const metkit_marsrequest_t* request, size_t index, const char** param); + + +/** Returns number of values for specific parameter in Request object + * @param request Request instance + * @param param parameter name in request + * @param[out] count number of values for param in request + * @return metkit_error_t Error code + */ +metkit_error_t metkit_marsrequest_count_values(const metkit_marsrequest_t* request, const char* param, size_t* count); + +/** Returns value for specific parameter and index in Request object + * @param request Request instance + * @param param parameter name in request + * @param index index of value to retrieve for param in request + * @param[out] value retrieved value + * @return metkit_error_t Error code + */ +metkit_error_t metkit_marsrequest_value(const metkit_marsrequest_t* request, const char* param, int index, const char** value); + +/** Populates empty Request object by expanding existing request + * @param request Request instance to be expanded + * @param inherit if true, populate expanded request with default values + * @param strict it true, raise error rather than warning on invalid values + * @param[out] expandedRequest empty Request instance to be populated + * @return metkit_error_t Error code + */ +metkit_error_t metkit_marsrequest_expand(const metkit_marsrequest_t* request, bool inherit, bool strict, metkit_marsrequest_t* expandedRequest); + +/** Merges other Request object into existing request + * @param request Request instance to contain result of merge + * @param otherRequest other Request instance to merge + * @return metkit_error_t Error code + */ +metkit_error_t metkit_marsrequest_merge(metkit_marsrequest_t* request, const metkit_marsrequest_t* otherRequest); + +/* --------------------------------------------------------------------------------------------------------------------- + * REQUEST ITERATOR + * --- */ + +/** Deallocates RequestIterator object and associated resources. + * @param it RequestIterator instance + * @return metkit_error_t Error code + */ +metkit_error_t metkit_delete_requestiterator(const metkit_requestiterator_t* it); + +/** Moves to the next Request element in RequestIterator + * @param it RequestIterator instance + * @return metkit_error_t Error code + */ +metkit_error_t metkit_requestiterator_next(metkit_requestiterator_t* it); + +/** Populates empty Requestion object with data from current element in RequestIterator + * @param it RequestIterator instance + * @param request empty Request instance to populate with data + * @return metkit_error_t Error code + */ +metkit_error_t metkit_requestiterator_request(metkit_requestiterator_t* it, metkit_marsrequest_t* request); + +#ifdef __cplusplus +} +#endif diff --git a/src/metkit/metkit_version.cc.in b/src/metkit/metkit_version.c.in similarity index 67% rename from src/metkit/metkit_version.cc.in rename to src/metkit/metkit_version.c.in index 60a39ecc..13b34cde 100644 --- a/src/metkit/metkit_version.cc.in +++ b/src/metkit/metkit_version.c.in @@ -1,20 +1,19 @@ -#include "metkit/metkit_version.h" +#include "metkit_version.h" #ifdef __cplusplus extern "C" { #endif -const char * metkit_version() { return metkit_VERSION; } +const char * metkit_version() { return metkit_VERSION; } -const char * metkit_version_str() { return metkit_VERSION_STR; } - -unsigned int metkit_version_int() -{ +unsigned int metkit_version_int() { return 10000*metkit_VERSION_MAJOR + 100*metkit_VERSION_MINOR + 1*metkit_VERSION_PATCH; } +const char * metkit_version_str() { return metkit_VERSION_STR; } + const char * metkit_git_sha1() { return "@metkit_GIT_SHA1@"; } #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/src/metkit/metkit_version.h.in b/src/metkit/metkit_version.h.in index 5f42d64b..8d5d65b9 100644 --- a/src/metkit/metkit_version.h.in +++ b/src/metkit/metkit_version.h.in @@ -8,6 +8,7 @@ #define metkit_VERSION_MINOR @metkit_VERSION_MINOR@ #define metkit_VERSION_PATCH @metkit_VERSION_PATCH@ + #ifdef __cplusplus extern "C" { #endif @@ -24,4 +25,5 @@ const char * metkit_git_sha1(); } #endif + #endif // metkit_version_h diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e59524ae..ec06cb12 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -71,7 +71,7 @@ ecbuild_add_test( TARGET metkit_test_gribhandle NO_AS_NEEDED ENVIRONMENT "${metkit_env}") -list(APPEND testFileSuffixes typesfactory expand request param_axis steprange_axis time hypercube type_levelist ) +list(APPEND testFileSuffixes typesfactory expand request param_axis steprange_axis time hypercube type_levelist c_api ) foreach(test IN LISTS testFileSuffixes) ecbuild_add_test( TARGET "metkit_test_${test}" @@ -82,6 +82,15 @@ foreach(test IN LISTS testFileSuffixes) LIBS metkit) endforeach() +# Compile C test +ecbuild_add_test( TARGET metkit_test_c_compiled + SOURCES test_c_api.c + INCLUDES "${ECKIT_INCLUDE_DIRS}" + LIBS metkit + NO_AS_NEEDED + ENVIRONMENT "${metkit_env}" + LINKER_LANGUAGE C) + # if ( HAVE_NETCDF ) # add_subdirectory(netcdf) # endif() diff --git a/tests/test_c_api.c b/tests/test_c_api.c new file mode 100644 index 00000000..d15dd508 --- /dev/null +++ b/tests/test_c_api.c @@ -0,0 +1,27 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +/// @file test_c_api.c +/// @date Dec 2024 +/// @author Christopher Bradley + +#include "metkit/api/metkit_c.h" +#include + +int main(int argc, char **argv) { + + const char* version = metkit_version(); + + metkit_error_t err = metkit_initialise(); + + fprintf(stdout, "MetKit version: %s\n", version); + + return 0; +} diff --git a/tests/test_c_api.cc b/tests/test_c_api.cc new file mode 100644 index 00000000..992aa571 --- /dev/null +++ b/tests/test_c_api.cc @@ -0,0 +1,169 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +/// @file test_c_api.cc +/// @date Dec 2024 +/// @author Christopher Bradley + +#include "metkit/api/metkit_c.h" +#include "eckit/testing/Test.h" +#include "eckit/types/Date.h" +#include + +using namespace eckit::testing; +namespace metkit::test { + +// Wrapper around the C function calls that will throw an exception if the function fails +// i.e. if the return value is not METKIT_SUCCESS + +void METKIT_TEST_C(int e) { + metkit_error_values_t err = static_cast(e); + if (err != METKIT_SUCCESS) + throw TestException("C-API error: " + std::string(metkit_get_error_string(err)), Here()); +} + +void EXPECT_STR_EQUAL(const char* a, const char* b) { + if (strcmp(a, b) != 0) + throw TestException("Expected: " + std::string(a) + " == " + std::string(b), Here()); +} + +// Fairly minimal test coverage +CASE( "metkit_marsrequest" ) { + + // ----------------------------------------------------------------- + // Basics + // ----------------------------------------------------------------- + + metkit_marsrequest_t* request{}; + + METKIT_TEST_C(metkit_new_marsrequest(&request)); + EXPECT(request); + + // set/get verb + METKIT_TEST_C(metkit_marsrequest_set_verb(request, "retrieve")); + const char* verb{}; + METKIT_TEST_C(metkit_marsrequest_verb(request, &verb)); + EXPECT_STR_EQUAL(verb, "retrieve"); + + // set array of values + const char* dates[] = {"20200101", "20200102", "-1"}; + METKIT_TEST_C(metkit_marsrequest_set(request, "date", dates, 3)); + + // set single value + const char* expver = "xxxx"; + METKIT_TEST_C(metkit_marsrequest_set_one(request, "expver", expver)); + METKIT_TEST_C(metkit_marsrequest_set_one(request, "param", "2t")); + + // check values + bool has = false; + METKIT_TEST_C(metkit_marsrequest_has_param(request, "date", &has)); + EXPECT(has); + + METKIT_TEST_C(metkit_marsrequest_has_param(request, "random", &has)); + EXPECT(!has); + + size_t count = 0; + METKIT_TEST_C(metkit_marsrequest_count_values(request, "date", &count)); + EXPECT_EQUAL(count, 3); + + for (size_t i = 0; i < count; i++) { + const char* value{}; + METKIT_TEST_C(metkit_marsrequest_value(request, "date", i, &value)); + EXPECT_STR_EQUAL(value, dates[i]); + } + + // ----------------------------------------------------------------- + // Expand + // ----------------------------------------------------------------- + + metkit_marsrequest_t* expandedRequest{}; + METKIT_TEST_C(metkit_new_marsrequest(&expandedRequest)); + METKIT_TEST_C(metkit_marsrequest_expand(request, false, true, expandedRequest)); + + // Check date expanded -1 -> yesterday + METKIT_TEST_C(metkit_marsrequest_count_values(expandedRequest, "date", &count)); + EXPECT_EQUAL(count, 3); + const char** dates_expanded = new const char*[count]; + for (size_t i = 0; i < count; i++) { + METKIT_TEST_C(metkit_marsrequest_value(expandedRequest, "date", i, &dates_expanded[i])); + } + + EXPECT_STR_EQUAL(dates_expanded[2], std::to_string(eckit::Date(-1).yyyymmdd()).c_str()); + // check param expanded 2t -> 167 + const char* param{}; + METKIT_TEST_C(metkit_marsrequest_value(expandedRequest, "param", 0, ¶m)); + EXPECT_STR_EQUAL(param, "167"); + + // ----------------------------------------------------------------- + // Merge + // ----------------------------------------------------------------- + + metkit_marsrequest_t* req_manydates{}; + METKIT_TEST_C(metkit_new_marsrequest(&req_manydates)); + const char* dates_many[] = {"19000101", "19000102", "19000103"}; + METKIT_TEST_C(metkit_marsrequest_set(req_manydates, "date", dates_many, 3)); + + METKIT_TEST_C(metkit_marsrequest_merge(request, req_manydates)); + METKIT_TEST_C(metkit_marsrequest_count_values(request, "date", &count)); + EXPECT_EQUAL(count, 6); + + // ----------------------------------------------------------------- + // done + + metkit_delete_marsrequest(request); + metkit_delete_marsrequest(expandedRequest); + metkit_delete_marsrequest(req_manydates); +} +//----------------------------------------------------------------------------- + +CASE( "metkit_requestiterator_t parsing" ) { + + metkit_marsrequest_t* request0{}; + metkit_marsrequest_t* request1{}; + metkit_marsrequest_t* request2{}; + METKIT_TEST_C(metkit_new_marsrequest(&request0)); + METKIT_TEST_C(metkit_new_marsrequest(&request1)); + METKIT_TEST_C(metkit_new_marsrequest(&request2)); + + metkit_requestiterator_t* it{}; + METKIT_TEST_C(metkit_parse_marsrequests("retrieve,date=-1,param=2t \n retrieve,date=20200102,param=2t,step=10/to/20/by/2", &it, true)); // two requests + + METKIT_TEST_C(metkit_requestiterator_request(it, request0)); + METKIT_TEST_C(metkit_requestiterator_next(it)); + METKIT_TEST_C(metkit_requestiterator_request(it, request1)); + + EXPECT_EQUAL(metkit_requestiterator_next(it), METKIT_ITERATION_COMPLETE); + EXPECT_NOT_EQUAL(metkit_requestiterator_request(it, request2), METKIT_SUCCESS); // Error to get request after iteration complete + + // check the date + const char* date{}; + METKIT_TEST_C(metkit_marsrequest_value(request0, "date", 0, &date)); + EXPECT_STR_EQUAL(date, std::to_string(eckit::Date(-1).yyyymmdd()).c_str()); // parser also calls expand + + METKIT_TEST_C(metkit_marsrequest_value(request1, "date", 0, &date)); + EXPECT_STR_EQUAL(date, "20200102"); + + // Check steps have been parsed + size_t count = 0; + METKIT_TEST_C(metkit_marsrequest_count_values(request1, "step", &count)); + EXPECT_EQUAL(count, 6); + for (size_t i = 0; i < count; i++) { + const char* step{}; + METKIT_TEST_C(metkit_marsrequest_value(request1, "step", i, &step)); + EXPECT_STR_EQUAL(step, std::to_string(10 + i*2).c_str()); + } + +} + +} // namespace metkit::test + +int main(int argc, char **argv) { + return run_tests ( argc, argv ); +}