Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

flipt-client-cpp

flipt-client-cpp

A native, dependency-light C++17 client-side evaluation SDK for Flipt. It fetches a snapshot of your namespace's flags, segments and rollouts once, then evaluates every request in-process against that snapshot — no network round trip per flag lookup, and evaluation keeps working if Flipt becomes unreachable.

Upstream's flipt-client-sdks ship client-side SDKs backed by a Rust engine (flipt-engine-ffi) with FFI bindings per language, but has no C/C++ binding. This is a from-scratch port of the evaluation algorithm (flipt-evaluation) to plain C++, using only libcurl and nlohmann/json - no Rust toolchain, no prebuilt cdylibs.

Installation

This SDK is currently distributed as source, meant to be vendored or added as a git submodule and built with CMake:

add_subdirectory(flipt-client-cpp)
target_link_libraries(your_target PRIVATE flipt-client-cpp)

Requirements: a C++17 compiler, and nlohmann/json (resolved via find_package(nlohmann_json), falling back to a vendored copy under third_party/ if not found). libcurl is required unless you set FLIPT_WITH_CURL=OFF and supply your own flipt::IFetcher.

Usage

#include <flipt/client.hpp>

flipt::ClientOptions options;
options.url = "https://your-flipt-instance.example.com";
options.namespaceKey = "default";

flipt::FliptClient client(options);

auto variant = client.evaluateVariant("your-flag-key", "entity-id", {{"context-key", "context-value"}});
if (variant.match)
{
    // variant.variantKey, variant.variantAttachment
}

auto boolean = client.evaluateBoolean("your-boolean-flag", "entity-id", {});
if (boolean.enabled)
{
    // ...
}

auto batch = client.evaluateBatch({
    {"flag-one", "entity-id", {}},
    {"flag-two", "entity-id", {}},
});

Initialization Arguments

flipt::ClientOptions:

Field Default Description
environment "default" Flipt environment
namespaceKey "default" Flipt namespace
url "http://localhost:8080" Base URL of your Flipt instance
authentication unset See Authentication
reference unset Reference to evaluate against, if using references
updateInterval 120 Seconds between automatic snapshot refreshes. 0 disables the background refresh thread entirely - call refresh() yourself instead
errorStrategy Fail See Error Strategies
snapshot unset Seed snapshot from a prior getSnapshot() call - see Snapshotting
requestTimeoutMillis 30000 Timeout for the built-in curl fetcher; ignored when fetcher is set
tls unset TLS options for the built-in curl fetcher - see TLS; ignored when fetcher is set
fetcher unset Custom flipt::IFetcher - see Custom Fetcher

TLS Configuration

The flipt::TlsConfig supports configuring TLS settings for secure connections to Flipt servers

// Use the OS-native CA store instead of curl's compiled-in default. Needed
// when curl is linked against OpenSSL (e.g. on Windows), which does not
// consult the OS trust store unless told to.
options.tls = flipt::TlsConfig{ .useSystemCaStore = true };

// Custom CA (e.g. self-signed/internal Flipt instance)
options.tls = flipt::TlsConfig{ .caCertFile = "/path/to/ca.pem" };
// or inline PEM content instead of a path:
options.tls = flipt::TlsConfig{ .caCertData = caPemString };

// Mutual TLS
options.tls = flipt::TlsConfig{
    .clientCertFile = "/path/to/client.pem",
    .clientKeyFile  = "/path/to/client-key.pem",
};

// Development only - do not use in production
options.tls = flipt::TlsConfig::insecure(); // skip all certificate verification
options.tls = flipt::TlsConfig::skipHostnameVerify(); // verify cert, skip hostname match

*Data fields take precedence over their *File counterpart when both are set. Ignored when a custom fetcher is set - configure TLS on your own HTTP stack instead.

Authentication

options.authentication = flipt::Authentication{
    .clientToken = flipt::ClientTokenAuthentication{"your-client-token"},
};
// or:
options.authentication = flipt::Authentication{
    .jwtToken = flipt::JwtAuthentication{"your-jwt"},
};

Error Strategies

  • flipt::ErrorStrategy::Fail (default): refresh() and the constructor rethrow flipt::Error on any fetch/parse failure.
  • flipt::ErrorStrategy::Fallback: failures are swallowed and the client keeps serving the last known-good snapshot (or an empty one, if none was ever fetched). Combine with a seeded snapshot for fully offline startup.

Snapshotting

getSnapshot() returns a base64-encoded, JSON-serialized snapshot of the client's current state, interoperable with the official SDKs:

std::string snapshot = client.getSnapshot();
// persist `snapshot` to disk...

// later, on a fresh start:
flipt::ClientOptions options;
options.snapshot = snapshot; // evaluate immediately, before the first fetch completes
flipt::FliptClient client(options);

Custom Fetcher

Inject your own HTTP stack (to share a connection pool, CA bundle, proxy settings, etc.) instead of the built-in libcurl fetcher:

class MyFetcher : public flipt::IFetcher
{
  public:
    flipt::FetchResult fetch(const flipt::FetchOptions& options) override
    {
        flipt::FetchResult result;
        // ... perform the request, honoring options.etag as If-None-Match ...
        result.status = /* HTTP status code */;
        result.body = /* response body */;
        result.etag = /* ETag response header, if present */;
        return result;
    }
};

options.fetcher = std::make_shared<MyFetcher>();

State Management

The current snapshot lives behind a std::shared_mutex; evaluation calls take a shared (read) lock and never block on network I/O. Refreshing swaps in a new snapshot behind a brief unique (write) lock, so concurrent evaluations are never blocked by a refresh in progress.

Automatic background refresh is opt-in via updateInterval > 0 (default), which starts a dedicated thread. Set updateInterval = 0 to disable it and call refresh() yourself - useful when the embedding application already has its own scheduler/timer and would rather not spawn a second thread.

ETag Support

Every fetch sends If-None-Match with the last known ETag. A 304 response is not an error: refresh() returns false and the existing snapshot is kept as-is, avoiding a reparse.

Development

cmake -B build -S . -DFLIPT_BUILD_TESTS=ON
cmake --build build
ctest --test-dir build --output-on-failure

mise run lint runs clang-format --dry-run over the SDK's sources.

License

This project is licensed under the MIT License.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages