-
Notifications
You must be signed in to change notification settings - Fork 8
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
base: develop
Are you sure you want to change the base?
Feature/c api #57
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #57 +/- ##
===========================================
+ Coverage 60.39% 61.11% +0.71%
===========================================
Files 101 103 +2
Lines 6242 6517 +275
Branches 585 590 +5
===========================================
+ Hits 3770 3983 +213
- Misses 2472 2534 +62 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Todo: Ensure we cover all functionality currently in fdb_c so that we can replace it with this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good so far. The functionality of the pyfdb is the following:
struct fdb_request_t;
typedef struct fdb_request_t fdb_request_t;
int fdb_new_request(fdb_request_t** req);
int fdb_request_add(fdb_request_t* req, const char* param, const char* values[], int numValues);
int fdb_expand_request(fdb_request_t* req);
int fdb_delete_request(fdb_request_t* req);
Those are all contained in the request section. Do we need anything on top @ChrisspyB?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like the changes. Code and Documentation was extraordinarily clean. I was a bit nitpicky. Bear with me in case some of my suggestions are not compatible with modern C; it has been a while since I programmed in C ;)
/// @comment: (maby) | ||
/// Not sure if there is much value in having a param iterator. We could just return an array of | ||
/// strings (char**) using metkit_marsrequest_params. | ||
/// I think we should metkit_paramiterator_t. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here the comment is lacking some words. Do this need to be addressed before merging?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a todo left for me by me
|
||
} // extern "C" | ||
|
||
static thread_local std::string g_current_error_string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is read-only, isn't it? Make it const.
} while (false) | ||
|
||
// Fairly minimal test coverage | ||
CASE( "metkit_marsrequest" ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is quite long. Could you break it in individual tests for easier and faster comprehension?
*/ | ||
int metkit_marsrequest_add(metkit_marsrequest_t* request, const char* param, const char* values[], int numValues); | ||
|
||
/** Add parameter and values to request |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice to have some documentation on why the 1 appears in the function name. Or even better: renaming to function to reflect what is happening.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good so far. The functionality of the pyfdb is the following:
struct fdb_request_t;
typedef struct fdb_request_t fdb_request_t;
int fdb_new_request(fdb_request_t** req);
int fdb_request_add(fdb_request_t* req, const char* param, const char* values[], int numValues);
int fdb_expand_request(fdb_request_t* req);
int fdb_delete_request(fdb_request_t* req);
Those are all contained in the request section. Do we need anything on top @ChrisspyB?
src/metkit/api/metkit_c.h
Outdated
* @param requests Allocates RequestIterator object | ||
* @return int Error code | ||
*/ | ||
int metkit_parse_marsrequest(const char* str, metkit_requestiterator_t** requests, bool strict = false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This returns mars requests (plural), but in the function name it is only singular. That isn't cool.
Also, please make sure you run this through a C compiler --- NOT a C++ compiler. Note that C does not support default arguments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And you will need to include <stdbool.h> if you want to use bool - as bool is not a builtin type in C.
* @param params Allocates ParamIterator object for parameter names in request | ||
* @return int Error code | ||
*/ | ||
int metkit_marsrequest_params(const metkit_marsrequest_t* request, metkit_paramiterator_t** params); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be easier to return the number of params, and then access them by index? Avoid creating/disposing of the paramiterator?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree.
* @param numValues number of values for param in request | ||
* @return int Error code | ||
*/ | ||
int metkit_marsrequest_values(const metkit_marsrequest_t* request, const char* param, const char** values[], size_t* numValues); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this allocate an array of values in C and then return it? Who is responsible for cleaning up that array of values? Why is this done in a different way to accessing the parameters?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this makes an array of values that the calling code is responsible for cleaning up. Thoughts on that? I was thinking we could do the same with parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Freeing 2d allocations in C is a bit verbose. I suggest either create an opaque type and implement the required query functions or use a public structure that can be returned as handle and has a corresponding free function.
Opaque type
struct marsrequest_values_t;
typedef marsrequest_values_t* marsrequest_values_handle;
size_t marsrequest_values_size(marsrequest_values_handle h);
const char* marsrequest_values_size(size_t idx);
void marsrequest_values_free(marsrequest_values_handle h);
Public type
typedef struct {
size_t size;
const char** values
} marsrequest_values;
void marsrequest_values_free(marsrequest_values* values);
I think an opaque type would be nice as this could simply be a std::vector<string>
on the C++ side.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need an extra test where you use the API and compile the test with a C compiler
typedef enum metkit_error_values_t | ||
{ | ||
METKIT_SUCCESS = 0, /* Operation succeded. */ | ||
METKIT_ITERATION_COMPLETE = 1, /* All elements have been returned */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not an error and should be handled differently. Right now this mixes error and result states. I would advocate for a strict separation.
* @param version Version string | ||
* @return int Error code | ||
*/ | ||
int metkit_version(const char** version); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This call should not be able to fail and therefor just return the version
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One convience from every function returning an error code is that it allows us (e.g. on the python layer) to wrap every C function call with a simple error handler that just checks the return value.
* @param sha1 SHA1 version string | ||
* @return int Error code | ||
*/ | ||
int metkit_vcs_version(const char** sha1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This call should not be able to fail and therefor just return the sha1
* | ||
* @note This is ONLY required when Main() is NOT initialised, such as loading | ||
* the MetKit as shared library in Python. | ||
* @return int Error code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should document the possible error codes returned from this function and under which circumstances the errors are to be expected.
The underlying issue here is that every possible error that can happen when using this API is corralled into metkit_error_values_t
. When using such an API you will greatly thank the doc writer for stating the possible error cases, (think man pages for example) because this allows me to simplify error handling. And treat every unexpected EC as fatal error.
If the user is not given any information about the possible errors, he will always be wondering "Could this return a METKIT_ITERATION_COMPLETE?"
* @param it RequestIterator instance | ||
* @return int Error code | ||
*/ | ||
int metkit_requestiterator_next(metkit_requestiterator_t* it); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would make for a nicer API if we could use it like this:
while((res = metkit_paramiterator_next(it))) {
//...
}
Ofc this would only work if the iteration cannot create errors. But return a nullptr on end of iteration makes for a more natural api imo.
* @param numValues number of values for param in request | ||
* @return int Error code | ||
*/ | ||
int metkit_marsrequest_values(const metkit_marsrequest_t* request, const char* param, const char** values[], size_t* numValues); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Freeing 2d allocations in C is a bit verbose. I suggest either create an opaque type and implement the required query functions or use a public structure that can be returned as handle and has a corresponding free function.
Opaque type
struct marsrequest_values_t;
typedef marsrequest_values_t* marsrequest_values_handle;
size_t marsrequest_values_size(marsrequest_values_handle h);
const char* marsrequest_values_size(size_t idx);
void marsrequest_values_free(marsrequest_values_handle h);
Public type
typedef struct {
size_t size;
const char** values
} marsrequest_values;
void marsrequest_values_free(marsrequest_values* values);
I think an opaque type would be nice as this could simply be a std::vector<string>
on the C++ side.
/// Not sure if there is much value in having a param iterator. We could just return an array of | ||
/// strings (char**) using metkit_marsrequest_params. | ||
/// I think we should metkit_paramiterator_t. | ||
struct metkit_paramiterator_t { |
There was a problem hiding this comment.
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.
return METKIT_ERROR_USER; | ||
} | ||
catch (const eckit::AssertionFailed& e) { | ||
eckit::Log::error() << "Assertion Failed: " << e.what() << std::endl; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the c api wrapper should not log errors, this is imo a responsibility of metkit itself -OR- the calling code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm inclined to agree
int metkit_version(const char** version) { | ||
*version = metkit_version_str(); | ||
return METKIT_SUCCESS; | ||
} | ||
|
||
int metkit_vcs_version(const char** sha1) { | ||
*sha1 = metkit_git_sha1(); | ||
return METKIT_SUCCESS; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So since its always METKIT_SUCCESS
this should just return the const char*
Maybe we should rewrite test_metkit_c.cc in C? For the time being I'll add a simple test that at least uses a C compiler |
Re: error handling, if we're going to do similar across the stack, it might make sense to centralise this somewhere (eckit). All of the caught exceptions are in fact eckit::exceptions |
No description provided.