Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/c api #57

Merged
merged 21 commits into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions src/metkit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ 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 )

install(FILES
${CMAKE_CURRENT_BINARY_DIR}/metkit_config.h
Expand All @@ -15,7 +14,7 @@ install(FILES
### metkit sources

list( APPEND metkit_srcs
${CMAKE_CURRENT_BINARY_DIR}/metkit_version.cc
metkit_version.c
config/LibMetkit.cc
config/LibMetkit.h
mars/BaseProtocol.cc
Expand Down Expand Up @@ -100,6 +99,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
Expand Down Expand Up @@ -198,6 +199,7 @@ ecbuild_add_library(
PUBLIC_INCLUDES
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/src>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}> # for metkit_version.h

PRIVATE_LIBS
"${odc_libs}"
Expand Down
346 changes: 346 additions & 0 deletions src/metkit/api/metkit_c.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
#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 <functional>

// ---------------------------------------------------------------------------------------------------------------------

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<metkit::mars::MarsRequest>&& vec) :
vector_(std::move(vec)), current_(vector_.begin()) {}

metkit_iterator_status_t next() {
if (first_) {
first_ = false;

if (current_ != vector_.end()) {
return METKIT_ITERATOR_SUCCESS;
}
}

if (current_ == vector_.end()) {
return METKIT_ITERATOR_COMPLETE;
}

++current_;
return current_ == vector_.end() ? METKIT_ITERATOR_COMPLETE : METKIT_ITERATOR_SUCCESS;
}

// Note: expected to call next() before calling current()
metkit_iterator_status_t current(metkit_marsrequest_t* out) {
if (first_ || current_ == vector_.end()) {
return METKIT_ITERATOR_ERROR;
}

*out = std::move(*current_);
return METKIT_ITERATOR_SUCCESS;
}

private:
bool first_ = true;
std::vector<metkit::mars::MarsRequest> vector_;
std::vector<metkit::mars::MarsRequest>::iterator current_;
};

// ---------------------------------------------------------------------------------------------------------------------
/// @note: Unlike metkit_requestiterator_t, lifetime of the char* returned from this iterator are have the same as the iterator's lifetime.
struct metkit_paramiterator_t {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lifetimes of returned values in this iterator differ from the metkit_requestiterator_t iterator. For itself the lifetime is ok (if documented) but a subtle difference in lifetime handling with both types imply symmetrical behavior is a nasty trap. metkit_requestiterator_t result lifetimes exceed the iterator due to the move out of the iterator, while the char* from this iterator dangle as soon as the iterator is freed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I initially resolved this by simply getting rid of the paramiterator. But it turns out it's not so straight forward as the C++ marsrequest object does not provide a nice way to iterate over the params so I need to revisit this.

metkit_paramiterator_t(std::vector<std::string> vec) :
vector_(vec), current_(vector_.begin()) {}

metkit_iterator_status_t next() {
if (first_) {
first_ = false;

if (current_ != vector_.end()) {
return METKIT_ITERATOR_SUCCESS;
}
}

if (current_ == vector_.end()) {
return METKIT_ITERATOR_COMPLETE;
}

++current_;
return current_ == vector_.end() ? METKIT_ITERATOR_COMPLETE : METKIT_ITERATOR_SUCCESS;
}

// Note: expected to call next() before calling current()
metkit_iterator_status_t current(const char** out) const {
if (first_ || current_ == vector_.end()) {
return METKIT_ITERATOR_ERROR;
}

*out = current_->c_str();
return METKIT_ITERATOR_SUCCESS;
}

bool first_ = true;
std::vector<std::string> vector_;
std::vector<std::string>::const_iterator current_;
};

// ---------------------------------------------------------------------------------------------------------------------
// 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_ERROR:
case METKIT_ERROR_USER:
case METKIT_ERROR_ASSERT:
return g_current_error_string.c_str();
default:
return "<unknown>";
}
}

metkit_error_t innerWrapFn(std::function<metkit_error_t()> f) {
return f();
}

metkit_error_t innerWrapFn(std::function<void()> f) {
f();
return METKIT_SUCCESS;
}

template <typename FN>
[[nodiscard]] metkit_error_t tryCatch(FN&& fn) {
try {
return innerWrapFn(std::forward<FN>(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 tryCatch([] {
static bool initialised = false;

if (initialised) {
eckit::Log::warning()
<< "Initialising Metkit library twice" << std::endl;
}

if (!initialised) {
const char* argv[2] = {"metkit-api", nullptr};
eckit::Main::initialise(1, const_cast<char**>(argv));
initialised = true;
}
});
}

// -----------------------------------------------------------------------------
// PARSING
// -----------------------------------------------------------------------------

metkit_error_t metkit_parse_marsrequests(const char* str, metkit_requestiterator_t** requests, bool strict) {
return tryCatch([requests, str, strict] {
ASSERT(requests);
ASSERT(str);
std::istringstream in(str);
*requests = new metkit_requestiterator_t(metkit::mars::MarsRequest::parse(in, strict));
});
}

metkit_error_t metkit_parse_marsrequest(const char* str, metkit_marsrequest_t* request, bool strict) {
return tryCatch([request, str, strict] {
ASSERT(request);
ASSERT(str);
*request = metkit::mars::MarsRequest::parse(str, strict);
});
}

// -----------------------------------------------------------------------------
// REQUEST
// -----------------------------------------------------------------------------

metkit_error_t metkit_marsrequest_new(metkit_marsrequest_t** request) {
return tryCatch([request] {
ASSERT(request);
*request = new metkit_marsrequest_t();
});
}

metkit_error_t metkit_marsrequest_delete(const metkit_marsrequest_t* request) {
return tryCatch([request] {
delete request;
});
}

metkit_error_t metkit_marsrequest_set(metkit_marsrequest_t* request, const char* param, const char* values[], int numValues) {
return tryCatch([request, param, values, numValues] {
ASSERT(request);
ASSERT(param);
ASSERT(values);
std::string param_str(param);
std::vector<std::string> 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 tryCatch([request, verb] {
ASSERT(request);
ASSERT(verb);
request->verb(verb);
});
}

metkit_error_t metkit_marsrequest_verb(const metkit_marsrequest_t* request, const char** verb) {
return tryCatch([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 tryCatch([request, param, has] {
ASSERT(request);
ASSERT(param);
ASSERT(has);
*has = request->has(param);
});
}

metkit_error_t metkit_marsrequest_params(const metkit_marsrequest_t* request, metkit_paramiterator_t** params) {
return tryCatch([request, params] {
ASSERT(request);
ASSERT(params);
*params = new metkit_paramiterator_t(request->params());
});
}

metkit_error_t metkit_marsrequest_count_values(const metkit_marsrequest_t* request, const char* param, size_t* count) {
return tryCatch([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 tryCatch([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 tryCatch([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 tryCatch([request, otherRequest] {
ASSERT(request);
ASSERT(otherRequest);
request->merge(*otherRequest);
});
}

// -----------------------------------------------------------------------------
// REQUEST ITERATOR
// -----------------------------------------------------------------------------

metkit_error_t metkit_delete_requestiterator(const metkit_requestiterator_t* it) {
return tryCatch([it] {
delete it;
});
}

metkit_iterator_status_t metkit_requestiterator_next(metkit_requestiterator_t* it) {
if (!it) {
return METKIT_ITERATOR_ERROR;
}

return it->next();
}

metkit_iterator_status_t metkit_requestiterator_current(metkit_requestiterator_t* it, metkit_marsrequest_t* request) {
if (!it || !request || !request->empty()) {
return METKIT_ITERATOR_ERROR;
}

return it->current(request);
}

// -----------------------------------------------------------------------------
// PARAM ITERATOR
// -----------------------------------------------------------------------------

metkit_error_t metkit_paramiterator_delete(const metkit_paramiterator_t* it) {
return tryCatch([it] {
delete it;
});
}

metkit_iterator_status_t metkit_paramiterator_next(metkit_paramiterator_t* it) {
if (!it) {
return METKIT_ITERATOR_ERROR;
}
return it->next();
}

metkit_iterator_status_t metkit_paramiterator_current(const metkit_paramiterator_t* it, const char** param) {
if (!it || !param) {
return METKIT_ITERATOR_ERROR;
}
return it->current(param);
}

// ---------------------------------------------------------------------------------------------------------------------
// Bridge between C and C++
const metkit::mars::MarsRequest& metkit::mars::MarsRequest::fromOpaque(const metkit_marsrequest_t* request) {
return *static_cast<const metkit::mars::MarsRequest*>(request);
}

// ---------------------------------------------------------------------------------------------------------------------
Loading
Loading