2.1. SDK core elements
This chapter outlines the common, core elements and conventions used throughout the SDK, for example errors, exceptions, and handling asynchronous operations.
2.1.1. Dependencies
The Proximie SDK minimises the use of external libraries for reasons of simplicity and mitigation of security/vulnerability risks. The C++ implementation uses various Boost libraries as they are highly respected, robust, well-tested and comprehensively assessed for vulnerabilities. The sections in this chapter will give details when they utilise Boost for features and functionality.
Note
Python users do not need to interact with Boost directly — the Python bindings handle these details internally.
2.1.2. Error handling
The two language bindings surface errors differently. In C++, a failure is returned as a value — an error code or a result type — that the caller inspects, and exceptions are reserved for truly exceptional conditions such as running out of memory. In Python, those same failures are raised as typed exceptions that the caller catches.
A function reports failure by returning an error code or a result type, or by passing an error code to a callback. The application inspects that value and decides how to proceed.
A function raises an exception derived from the common base SdkError, which
carries code, message, category and a detail dictionary.
Category-specific subclasses — MediaError, HttpError and JsonError —
let a handler catch one kind of error, and REST-backed calls raise the more
specific RestApiError (see Proximie REST services).
2.1.2.1. Error codes
Both bindings build on Boost’s extensible error reporting (see https://www.boost.org/doc/libs/release/libs/system/). An error code encapsulates an error category and a numeric value, letting domain-specific errors be defined and handled by the client application. In C++ the error code is returned, or supplied to a callback, directly; in Python the same information is carried on the raised exception, and error callbacks receive an equivalent error-code object.
Testing whether an error occurred:
void onCompleted(Proximie::error_code error) {
if (!error) {
// Successful, no error
return;
}
// Handle error...
}
def on_completed(error):
if not error:
# Successful, no error
return
# Handle error...
Identifying the category — every category defined in the SDK exposes a static method to obtain it, so a handler can test which category an error belongs to:
if (error.category() == Proximie::PxMedia::PxMediaErrorCategory::category()) {
// Handle media error...
if error.category() == media.PxMediaErrorCategory.category():
# Handle media error...
Comparing against a specific error code, type-safely:
if (error == Proximie::PxMedia::PxMediaErrorCode::InvalidSession) {
// Handle invalid media session...
if error == media.PxMediaErrorCode.InvalidSession:
# Handle invalid media session...
2.1.2.2. Results from function calls
Where a call can fail, each binding represents the outcome in the way that is idiomatic for the language:
The C++ SDK uses Boost’s Outcome library (see https://www.boost.org/doc/libs/release/libs/outcome/): a call returns a result type holding either a successful value or an error code. For example, given a result-returning function:
Proximie::outcome::result<int> getSomeIntResult();
The result is marked [[nodiscard]] so the compiler warns if the caller
forgets to check it. There are several ways to check the result:
auto result = getSomeIntResult();
// Examine an outcome result with if
if (result) {
int value = result.value();
// Do something with the value (using implicit bool conversion)...
} else {
Proximie::error_code error = result.error();
// Handle the error...
}
// Examine an outcome result with has_error
if (result.has_error()) {
Proximie::error_code error = result.error();
// Handle the error...
}
// Examine an outcome result with has_value
if (result.has_value()) {
int value = result.value();
// Do something with the value...
}
The call returns the successful value directly and raises a typed SdkError
(or a subclass such as RestApiError) on failure. For example, given a
function that may fail:
def get_some_int_result() -> int:
"""Returns an int result (may raise on failure)."""
...
Call it inside a try block and handle the exception:
try:
value = get_some_int_result()
# Do something with the value...
except SdkError as error:
# Handle the error...
pass
2.1.3. Object lifetimes
The SDK manages object lifetimes automatically. Objects such as feeds and sessions
are created using static create factory methods. In C++ these return smart
pointers (std::shared_ptr); in Python the binding layer manages the equivalent
reference counting transparently.
Due to the asynchronous nature of the SDK, objects that instigate asynchronous operations are kept alive internally until those operations complete. This means the application can release its reference without causing in-flight operations to fail — the SDK will clean up once all pending work is done.
2.1.4. Asynchronous I/O
The SDK provides a broad set of features, many of which are asynchronous - e.g. HTTP requests, WebSockets, communication with various Proximie services and so on.
Underpinning the async functionality is Boost’s Asio library (see https://live.boost.org/doc/libs/release/doc/html/boost_asio.html). For the most part SDK clients do not need to be concerned by the underlying implementation.
For more details, see Async operations & threading.