GitHub Repository
Features
- An easy to use interface
- Making use of C++17 features (is this a feature?)
- Fully asynchronous (Callbacks/Futures/Coroutines supported via Boost.Asio completion tokens)
- No RTTI is being used in the library (Templates!)
- No expcetions are being used
Getting started
Creating an API connection:
First, you need to add an error handling function to your class/struct
Code: Select all
class my_app {
///...
public:
void on_error(const boost::system::error_code& err) {
// handler error
}
///...
};
Then create an tikpp::io_context (An alias for boost::asio::io_context) instance and an tikpp::api object.
Code: Select all
#include "tikpp/io_context.hpp"
#include "tikpp/api.hpp"
///...
tikpp::io_context io{};
auto api = api = tikpp::api<my_app>::create(io, app_object);
// Connect and login to the router
api->async_open("1.2.3.4", 8728, [&api](const auto& err) {
if (err) {
// handle erros
}
api->async_login("name", "password", [](const auto& err) {
if (err) {
// handle erros
}
// Logged in
});
});
Then start the tikpp::io_context object by calling io.run() . Remember that the call to io.run() will block the calling thread, so you must call it after initiating at least one asynchronous operation, or in another thread.
Examples
Sending a request
First, you need to create a request object
Code: Select all
auto req = api->make_request("/path/to/command");
Then, add parameters and attributes to the request object
Code: Select all
req->add_param("foo", "value");
req->add_attribute("bar", "value");
{fmt}/Python formats can be used to specify values
Code: Select all
req->add_param("param-key", "Hello, {}{}", "World", '!');
Finally, send the request
Code: Select all
// Sending a request and handling the response with a callback
api->async_send(std::move(req), [](const auto& err, auto&& resp) {
// Check if the request succeeded
if (err) {
// handle error
}
// Check response type
if (resp.type() == tikpp::response_type::...) {
// Do something
}
// Get response fields
const auto& value1 = resp["resp-key-1"];
const auto& value2 = resp["resp-key-2"];
// ...
// Return false to remove this handler from handlers map
// If true is returned, this handler will remain in the completion handlers map (Used for
// commands that return multiple responses, such as: getall, listen, etc...)
return false;
});
Futures can be used to handle the response (Note that futures are generally slower, and will throw an exception if the request fails)
Code: Select all
#include "tikpp/tokens.hpp"
auto retf = api->async_send(std::move(api), tikpp::use_future);
//
// Do some work...
//
try {
auto value = retf.get();
} catch (const boost::system::system_error& e) {
// handle error
}
Using the data repository
You can use a higher level repository-pattern object to manage data of the router.
Create a repository with a data model type as a template parameter
Code: Select all
`
// Include the data model header
#include "tikpp/data/ip/hotspot/user.hpp"
#include "tikpp/data/repository.hpp"
///...
// Create a repository object with the API object
auto repo = tikpp::data::make_repository<tikpp::data::ip::hotspot::user>(api);
Code: Select all
// Using completion callbacks
repo.async_load([](const auto& err, auto&& users) {
if (err) {
// handler errors
}
// Use users
for (const auto& user : user) {
// ...
}
});
// Using futures
auto users = repo.async_load(tikpp::use_futures);
// Use users
for (const auto& user : user) {
// ...
}
Queries can be used to filter the results
Code: Select all
// To use queries we need to create query tokens with the name of the query fields
auto [foo, bar] = tikpp::data::make_tokens("foo", "bar");
// Or
auto foo = "foo"_t, bar = "bar"_t;
repo.async_load(foo == "some_text" && bar <= 0xABCD, [](const auto& err, auto&& users) {
// ...
});
// The query will be evaulated to
// ?=foo=some_text
// ?<bar=43981
// ?#|
// ?#&
// and appended to the request
Objects can be loaded one at time using async_stream
Code: Select all
repo.async_stream([](const auto& err, auto&& user) {
// ...
});
// Queries can also be used here
repo.async_stream("foo"_t == "some_text" ...);
Add objects
Code: Select all
tikpp::data::ip::hotspot::user u{};
u.name = "name";
u.password = "pa$$w0rd";
u.limit_uptime = 10h;
u.limit_bytes_out = 10_gb;
//...
repo.async_add(std::move(u), [](const auto& err) { /* ... */ });
Code: Select all
repo.async_remove("*ABCD", [](const auto& err) { /* ... */ });
// Or
repo.async_remove(0xABCD, [](const auto& err) { /* ... */ });
Update objects
Code: Select all
// Get an object to be updated
auto user = repo.async_load("name"_t == "foo", tikpp::use_futures).get()[0];
// Update properties
// Only changed read_write properties will be sent back to the router
user.profile = "other-profile";
user.limit_bytes_out += 1_gb;
// ...
repo.async_update(user, [](const auto& err) { /* ... */ });
Sometimes you only want some of the data model properties (mostly for efficiency reasons). You can add your own types.
Only the specified properties will be used when interacting the router
Code: Select all
struct my_model : tikpp::data::model {
read_only<std::string> read_only_data;
read_write<tikpp::data::types::bytes> bytes_data;
read_write<std::uint32_t> other_data;
template<typename Converter>
void convert(Converter& c) {
// Call base convert function
model::convert(c);
c["read-only-data"] % read_only_data;
c["bytes-data"] % bytes_data;
c["other-data"] % other_data;
}
};