Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `ConfigurationStore(Configuration config)` - constructor accepting Configuration by value
- `setConfiguration(Configuration config)` - setter accepting Configuration by value
- Move constructor and move assignment operator for `Configuration` class
- `parseConfiguration()` convenience function for parsing both flag configuration and bandit models in a single call
- Takes flag config JSON, bandit models JSON, and error reference parameter
- Returns a fully constructed `Configuration` object with both parsed configurations
- Simplifies setup for applications using contextual bandits

### Changed

Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,17 @@ examples:
.PHONY: run-bandits
run-bandits: examples
@echo "Running bandits example..."
@$(BUILD_DIR)/bandits
@cd examples && ../$(BUILD_DIR)/bandits

.PHONY: run-flags
run-flags: examples
@echo "Running flag_assignments example..."
@$(BUILD_DIR)/flag_assignments
@cd examples && ../$(BUILD_DIR)/flag_assignments

.PHONY: run-assignment-details
run-assignment-details: examples
@echo "Running assignment_details example..."
@$(BUILD_DIR)/assignment_details
@cd examples && ../$(BUILD_DIR)/assignment_details

# Clean build artifacts
.PHONY: clean
Expand Down
122 changes: 35 additions & 87 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,8 @@ Other dependencies (nlohmann/json, semver, etc.) are vendored and require no ins
The Eppo SDK requires configuration data containing your feature flags. This SDK is designed for offline use, so you'll load configuration directly rather than using SDK keys or polling.

```cpp
#include <nlohmann/json.hpp>
#include "client.hpp"

// Parse configuration from a JSON string
eppoclient::ConfigResponse parseConfiguration(const std::string& configJson) {
nlohmann::json j = nlohmann::json::parse(configJson);
return j;
}

// Your configuration as a JSON string
std::string configJson = R"({
"flags": {
Expand All @@ -143,10 +136,17 @@ std::string configJson = R"({
}
})";

// Parse configuration from JSON string
std::string error;
eppoclient::Configuration config = eppoclient::parseConfiguration(configJson, error);
if (!error.empty()) {
std::cerr << "Configuration parsing error: " << error << std::endl;
return 1;
}

// Create and initialize the configuration store
eppoclient::ConfigurationStore configStore;
eppoclient::ConfigResponse config = parseConfiguration(configJson);
configStore.setConfiguration(eppoclient::Configuration(config));
configStore.setConfiguration(config);

// Create the client (configStore must outlive client)
eppoclient::EppoClient client(configStore);
Expand Down Expand Up @@ -267,8 +267,13 @@ int main() {
// Initialize configuration
eppoclient::ConfigurationStore configStore;
std::string configJson = "..."; // Your JSON config string
eppoclient::ConfigResponse config = parseConfiguration(configJson);
configStore.setConfiguration(eppoclient::Configuration(config));
std::string error;
eppoclient::Configuration config = eppoclient::parseConfiguration(configJson, error);
if (!error.empty()) {
std::cerr << "Configuration parsing error: " << error << std::endl;
return 1;
}
configStore.setConfiguration(config);

// Create loggers
auto assignmentLogger = std::make_shared<MyAssignmentLogger>();
Expand Down Expand Up @@ -317,25 +322,25 @@ Eppo's contextual bandits allow you to dynamically select the best variant based
To use bandits, you need to load both flag configuration and bandit models:

```cpp
#include <nlohmann/json.hpp>
#include "client.hpp"

// Parse bandit models from a JSON string
eppoclient::BanditResponse parseBanditModels(const std::string& modelsJson) {
nlohmann::json j = nlohmann::json::parse(modelsJson);
return j;
}

// Your configuration and bandit models as JSON strings
std::string flagConfigJson = "..."; // Your flag config JSON
std::string banditModelsJson = "..."; // Your bandit models JSON

// Initialize with both flags and bandit models
eppoclient::ConfigResponse flagConfig = parseConfiguration(flagConfigJson);
eppoclient::BanditResponse banditModels = parseBanditModels(banditModelsJson);
std::string error;
eppoclient::Configuration config = eppoclient::parseConfiguration(
flagConfigJson,
banditModelsJson,
error
);
if (!error.empty()) {
std::cerr << "Configuration parsing error: " << error << std::endl;
return 1;
}

eppoclient::ConfigurationStore configStore;
configStore.setConfiguration(eppoclient::Configuration(flagConfig, banditModels));
configStore.setConfiguration(config);

// Create bandit logger to track bandit actions
class MyBanditLogger : public eppoclient::BanditLogger {
Expand Down Expand Up @@ -421,69 +426,6 @@ if (result.action.has_value()) {
}
```

### Complete Bandit Example

Here's a complete example from `examples/bandits.cpp` showing bandit-powered car recommendations:

```cpp
#include <iostream>
#include <memory>
#include "client.hpp"

int main() {
// Load configuration
eppoclient::ConfigurationStore configStore;
std::string flagConfigJson = "..."; // Your flag config JSON
std::string banditModelsJson = "..."; // Your bandit models JSON
eppoclient::ConfigResponse flagConfig = parseConfiguration(flagConfigJson);
eppoclient::BanditResponse banditModels = parseBanditModels(banditModelsJson);
configStore.setConfiguration(eppoclient::Configuration(flagConfig, banditModels));

// Create loggers
auto assignmentLogger = std::make_shared<MyAssignmentLogger>();
auto banditLogger = std::make_shared<MyBanditLogger>();
auto applicationLogger = std::make_shared<MyApplicationLogger>();

// Create client
eppoclient::EppoClient client(
configStore,
assignmentLogger,
banditLogger,
applicationLogger
);

// Define subject attributes (user context)
eppoclient::ContextAttributes subjectAttributes;
// Add any relevant user attributes here

// Define available car actions with their attributes
std::map<std::string, eppoclient::ContextAttributes> actions;

eppoclient::ContextAttributes toyota;
toyota.numericAttributes["speed"] = 120.0;
actions["toyota"] = toyota;

eppoclient::ContextAttributes honda;
honda.numericAttributes["speed"] = 115.0;
actions["honda"] = honda;

// Get bandit recommendation
eppoclient::BanditResult result = client.getBanditAction(
"car_bandit_flag",
"user-abc123",
subjectAttributes,
actions,
"car_bandit"
);

if (result.action.has_value()) {
std::cout << "Recommended car: " << result.action.value() << std::endl;
}

return 0;
}
```

## Error Handling

The Eppo SDK is built with **`-fno-exceptions`** and does not use exceptions internally. When errors occur during flag evaluation (such as missing flags, invalid parameters, or type mismatches), the SDK:
Expand Down Expand Up @@ -637,8 +579,14 @@ Always ensure these preconditions are met to avoid assertion failures.
int main() {
// Initialize client with application logger
eppoclient::ConfigurationStore configStore;
eppoclient::ConfigResponse config = parseConfiguration(configJson);
configStore.setConfiguration(eppoclient::Configuration(config));
std::string configJson = "..."; // Your JSON config string
std::string error;
eppoclient::Configuration config = eppoclient::parseConfiguration(configJson, error);
if (!error.empty()) {
std::cerr << "Configuration parsing error: " << error << std::endl;
return 1;
}
configStore.setConfiguration(config);

auto applicationLogger = std::make_shared<MyApplicationLogger>();
eppoclient::EppoClient client(
Expand Down
23 changes: 16 additions & 7 deletions examples/assignment_details.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,31 +69,40 @@ class ConsoleApplicationLogger : public eppoclient::ApplicationLogger {
};

// Helper function to load flags configuration from JSON file
bool loadFlagsConfiguration(const std::string& filepath, eppoclient::ConfigResponse& response) {
bool loadFlagsConfiguration(const std::string& filepath, eppoclient::Configuration& config) {
std::ifstream file(filepath);
if (!file.is_open()) {
std::cerr << "Failed to open flags configuration file: " << filepath << std::endl;
return false;
}

nlohmann::json j;
file >> j;
// Read entire file content into a string
std::string configJson((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());

// Parse configuration using parseConfiguration
std::string error;
config = eppoclient::parseConfiguration(configJson, error);

if (!error.empty()) {
std::cerr << "Failed to parse configuration: " << error << std::endl;
return false;
}

response = j;
return true;
}

int main() {
// Load the flags configuration
std::cout << "Loading flags configuration..." << std::endl;
eppoclient::ConfigResponse ufc;
if (!loadFlagsConfiguration("config/flags-v1.json", ufc)) {
eppoclient::Configuration config;
if (!loadFlagsConfiguration("config/flags-v1.json", config)) {
return 1;
}

// Create configuration store and set the configuration
auto configStore = std::make_shared<eppoclient::ConfigurationStore>();
configStore->setConfiguration(eppoclient::Configuration(ufc));
configStore->setConfiguration(config);

// Create assignment logger and application logger
auto assignmentLogger = std::make_shared<ConsoleAssignmentLogger>();
Expand Down
51 changes: 25 additions & 26 deletions examples/bandits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,51 +118,50 @@ class ConsoleApplicationLogger : public eppoclient::ApplicationLogger {
}
};

// Helper function to load flags configuration from JSON file
bool loadFlagsConfiguration(const std::string& filepath, eppoclient::ConfigResponse& response) {
std::ifstream file(filepath);
if (!file.is_open()) {
std::cerr << "Failed to open flags configuration file: " << filepath << std::endl;
// Helper function to load complete configuration (flags + bandits) from JSON files
bool loadConfiguration(const std::string& flagsFilepath, const std::string& banditsFilepath,
eppoclient::Configuration& config) {
// Read flags configuration file
std::ifstream flagsFile(flagsFilepath);
if (!flagsFile.is_open()) {
std::cerr << "Failed to open flags configuration file: " << flagsFilepath << std::endl;
return false;
}
std::string flagsJson((std::istreambuf_iterator<char>(flagsFile)),
std::istreambuf_iterator<char>());

nlohmann::json j;
file >> j;
// Read bandit models file
std::ifstream banditsFile(banditsFilepath);
if (!banditsFile.is_open()) {
std::cerr << "Failed to open bandit models file: " << banditsFilepath << std::endl;
return false;
}
std::string banditsJson((std::istreambuf_iterator<char>(banditsFile)),
std::istreambuf_iterator<char>());

response = j;
return true;
}
// Parse both configurations at once
std::string error;
config = eppoclient::parseConfiguration(flagsJson, banditsJson, error);

// Helper function to load bandit models from JSON file
bool loadBanditModels(const std::string& filepath, eppoclient::BanditResponse& response) {
std::ifstream file(filepath);
if (!file.is_open()) {
std::cerr << "Failed to open bandit models file: " << filepath << std::endl;
if (!error.empty()) {
std::cerr << "Failed to parse configuration: " << error << std::endl;
return false;
}

nlohmann::json j;
file >> j;

response = j;
return true;
}

int main() {
// Load the bandit flags and models configuration
std::cout << "Loading bandit flags and models configuration..." << std::endl;
eppoclient::ConfigResponse banditFlags;
eppoclient::BanditResponse banditModels;
if (!loadFlagsConfiguration("config/bandit-flags-v1.json", banditFlags)) {
return 1;
}
if (!loadBanditModels("config/bandit-models-v1.json", banditModels)) {
eppoclient::Configuration config;
if (!loadConfiguration("config/bandit-flags-v1.json", "config/bandit-models-v1.json", config)) {
return 1;
}

// Create configuration store and set the configuration with both flags and bandits
auto configStore = std::make_shared<eppoclient::ConfigurationStore>();
configStore->setConfiguration(eppoclient::Configuration(banditFlags, banditModels));
configStore->setConfiguration(config);

// Create assignment logger, bandit logger, and application logger
auto assignmentLogger = std::make_shared<ConsoleAssignmentLogger>();
Expand Down
23 changes: 16 additions & 7 deletions examples/flag_assignments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,31 +67,40 @@ class ConsoleApplicationLogger : public eppoclient::ApplicationLogger {
};

// Helper function to load flags configuration from JSON file
bool loadFlagsConfiguration(const std::string& filepath, eppoclient::ConfigResponse& response) {
bool loadFlagsConfiguration(const std::string& filepath, eppoclient::Configuration& config) {
std::ifstream file(filepath);
if (!file.is_open()) {
std::cerr << "Failed to open flags configuration file: " << filepath << std::endl;
return false;
}

nlohmann::json j;
file >> j;
// Read entire file content into a string
std::string configJson((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());

// Parse configuration using parseConfiguration
std::string error;
config = eppoclient::parseConfiguration(configJson, error);

if (!error.empty()) {
std::cerr << "Failed to parse configuration: " << error << std::endl;
return false;
}

response = j;
return true;
}

int main() {
// Load the flags configuration
std::cout << "Loading flags configuration..." << std::endl;
eppoclient::ConfigResponse ufc;
if (!loadFlagsConfiguration("config/flags-v1.json", ufc)) {
eppoclient::Configuration config;
if (!loadFlagsConfiguration("config/flags-v1.json", config)) {
return 1;
}

// Create configuration store and set the configuration
auto configStore = std::make_shared<eppoclient::ConfigurationStore>();
configStore->setConfiguration(eppoclient::Configuration(ufc));
configStore->setConfiguration(config);

// Create assignment logger and application logger
auto assignmentLogger = std::make_shared<ConsoleAssignmentLogger>();
Expand Down
Loading
Loading