-
Notifications
You must be signed in to change notification settings - Fork 108
API design conventions
When adding new API to Soletta, we ask you to follow a few guidelines on how to design them (besides documenting all public symbols).
- We don't typedef structs, enums, function pointers. There are some exceptions around the code, but we prefer not to open space for more.
- All of our functions are namespaced with
sol_
as a prefix. - When the function is from a well-defined sub-module of Soletta, the
module's name is the next namespacing token in a function name
(e.g.
sol_util_timespec_add()
is one of the functions of the Soletta utilities set). - When defining an API symbol, separate all individual words with
"
_
", as insol_vector_get_no_check()
(neversol_vector_get_nocheck()
or similar). - Soletta handle names never get
handle
as part of their names, as it's redundant. - When the function acts on a given module's handle, its name must
compose the function's final namespace as well (e.g.
sol_coap_packet_ref()
, where we're under the CoAP module and, furthermore, the function acts over astruct sol_coap_packet *
handle). -
struct
s andenum
s must be namespaced as well, as instruct sol_http_params
andenum sol_http_method
. - Enumeration entries must be namespaced, as in
SOL_COAP_OPTION_ETAG
, that belongs to the CoAP API and accounts for the possible packet options. - Public macros should be namespaced as well, as in
SOL_COAP_CODE_EMPTY
. - When naming functions with verb and object, respect this order, as in
sol_http_encode_params()
. - Try to follow the common verbs we already use for common handle
operations, like
add
/del
for creation and deletion,ref
/unref
for reference count management,get
/set
for getter/setters. We also usetake
at situations where you pass ownership of some handle to the function (e.g.sol_flow_send_string_take_packet()
) andsteal
when you do the contrary (e.g.sol_buffer_steal()
). - When doing getters for boolean attributes, try to follow the
pattern
sol_namspace_is_attribute()
, as insol_flow_packet_is_composed_type()
. - Try not to make awkward abbreviations when creating an API (e.g.
SOL_COAP_RSPCODE_OK
should beSOL_COAP_RESPONSE_CODE_OK
). - When a parameter or return value is of a scalar type and is not
merely a numerical status value, but a meaningful piece of
information, use the specific type for the size of all the
range you expect in there (e.g.
int32_t sol_pwm_get_duty_cycle(const struct sol_pwm *pwm)
, where the duty cycle returned is explicitly set to be ofint32_t
—not justint
—type, since it may use all the 32 bits range and we can't guarantee that size for theint
type on all architectures).
Public, non-opaque handles must be versioned, so that run-time checks can be done to avoid run-time linking of Soletta modules at different, conflicting versions to crash. The pattern that we use is like:
struct sol_namespace {
#ifndef SOL_NO_API_VERSION
#define SOL_NAMESPACE_API_VERSION (1)
uint16_t api_version;
#endif
int32_t some_field;
/* more fields... */
};
Naturally, you should exchange "namespace" with your own. The important bit is the use of the
#ifndef SOL_NO_API_VERSION
#define SOL_NAMESPACE_API_VERSION (1)
uint16_t api_version;
#endif
idiom. Declare the SOL_NAMESPACE_API_VERSION
with the starting
value of 1. Don't forget to bump it whenever you have changes in that
struct. The SOL_NO_API_VERSION
thing is there for Soletta static
builds, that should ignore versioning (and not even compile the
support).
Don't forget, at the public functions receiving those handles, to check for their API, as in:
#ifndef SOL_NO_API_VERSION
if (SOL_UNLIKELY(config->api_version != SOL_NAMESPACE_API_VERSION)) {
SOL_WRN("Namespace handle version mismatch: found '%u', "
"expected '%u'",
sol_namespace->api_version, SOL_NAMESPACE_API_VERSION);
return NULL;
}
#endif
At code using your added Soletta API, don't forget to issue
SOL_SET_API_VERSION(sol_namespace.api_version = SOL_NAMESPACE_API_VERSION; )
as well. If SOL_NO_API_VERSION
is set, you must still issue the
line above as good practice, since it will be a no-op.
Soletta is a C API, but it's meant to be used by C++ code with no issues. Thus, when declaring macros that may have issues on C++, please take care of making them C++-compatible, as in
#ifdef __cplusplus
#define CREATE_BUFFER(_val) \
struct sol_buffer buf SOL_BUFFER_INIT_FLAGS(_val, \
sizeof(*(_val)), SOL_BUFFER_FLAGS_MEMORY_NOT_OWNED | SOL_BUFFER_FLAGS_NO_NUL_BYTE);
#else
#define CREATE_BUFFER(_val) \
struct sol_buffer buf = SOL_BUFFER_INIT_FLAGS(_val, \
sizeof(*(_val)), SOL_BUFFER_FLAGS_MEMORY_NOT_OWNED | SOL_BUFFER_FLAGS_NO_NUL_BYTE);
#endif /* __cplusplus */
Finally, when adding macros as new API, don't forget to add usage of
them to the headers.cc.in
file, so that our make check-pp
build rule exercises those macros under a C++ compilation and assures
we're fine.