Skip to content
This repository has been archived by the owner on Jun 27, 2019. It is now read-only.

API design conventions

Bruno Bottazzini edited this page Jun 10, 2016 · 6 revisions

Soletta™ Framework 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 in sol_vector_get_no_check() (never sol_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 a struct sol_coap_packet * handle).
  • structs and enums must be namespaced as well, as in struct sol_http_params and enum 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 use take at situations where you pass ownership of some handle to the function (e.g. sol_flow_send_string_take_packet()) and steal 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 in sol_flow_packet_is_composed_type().
  • Try not to make awkward abbreviations when creating an API (e.g. SOL_COAP_RSPCODE_OK should be SOL_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 of int32_t—not just int—type, since it may use all the 32 bits range and we can't guarantee that size for the int type on all architectures).

Versioning

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.

C++

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.

Clone this wiki locally