-
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 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()
- 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
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 */