Skip to content

Commit

Permalink
Initial implementation (#2)
Browse files Browse the repository at this point in the history
* Initialize crate

* chore: add CI configuration

* feat: add initial API and noop implementation

* test: add Makefile to pull test data

* feat: Universal Flag Configuration parser

* feat: rules evaluation

* feat: sharder

* feat: eval

* test: integrate sdk-test-data test for eval

* feat: configuration store

* refactor: Ufc -> UniversalFlagConfig

* feat: add configuration poller

* feat: add logging and error handling
  • Loading branch information
rasendubi authored May 11, 2024
1 parent 05373c8 commit f97a2b1
Show file tree
Hide file tree
Showing 16 changed files with 1,696 additions and 2 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Cargo Build & Test

on:
push:
pull_request:

env:
CARGO_TERM_COLOR: always

jobs:
build_and_test:
name: Build & Test
runs-on: ubuntu-latest
strategy:
matrix:
toolchain:
- stable
steps:
- uses: actions/checkout@v3
- run: make test-data
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- run: cargo build --verbose
- run: cargo test --verbose
- run: cargo doc --verbose
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Generated by Cargo
# will have compiled files and executables
debug/
target/
/debug/
/target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Expand All @@ -12,3 +12,5 @@ Cargo.lock

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

/tests/data/
30 changes: 30 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "eppo"
version = "0.1.0"
edition = "2021"
description = "Eppo SDK for Rust"
homepage = "https://docs.geteppo.com/sdks/server-sdks/rust"
repository = "https://github.com/Eppo-exp/rust-sdk"
license = "MIT"
keywords = ["eppo", "feature-flags"]
categories = ["config"]

[dependencies]
chrono = { version = "0.4.38", features = ["serde"] }
derive_more = "0.99.17"
log = { version = "0.4.21", features = ["kv", "kv_serde"] }
md5 = "0.7.0"
rand = "0.8.5"
regex = "1.10.4"
reqwest = { version = "0.12.4", features = ["blocking", "json"] }
semver = { version = "1.0.22", features = ["serde"] }
serde = { version = "1.0.198", features = ["derive"] }
serde_json = "1.0.116"
thiserror = "1.0.60"
url = "2.5.0"

[[example]]
name = "simple"

[dev-dependencies]
env_logger = { version = "0.11.3", features = ["unstable-kv"] }
41 changes: 41 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Make settings - @see https://tech.davis-hansson.com/p/make/
SHELL := bash
.ONESHELL:
.SHELLFLAGS := -eu -o pipefail -c
.DELETE_ON_ERROR:
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules

# Log levels
DEBUG := $(shell printf "\e[2D\e[35m")
INFO := $(shell printf "\e[2D\e[36m🔵 ")
OK := $(shell printf "\e[2D\e[32m🟢 ")
WARN := $(shell printf "\e[2D\e[33m🟡 ")
ERROR := $(shell printf "\e[2D\e[31m🔴 ")
END := $(shell printf "\e[0m")

.PHONY: default
default: help

## help - Print help message.
.PHONY: help
help: Makefile
@echo "usage: make <target>"
@sed -n 's/^##//p' $<

## test-data
testDataDir := tests/data/
branchName := main
githubRepoLink := https://github.com/Eppo-exp/sdk-test-data.git
.PHONY: test-data
test-data:
rm -rf ${testDataDir}
git clone -b ${branchName} --depth 1 --single-branch ${githubRepoLink} ${testDataDir}

${testDataDir}:
rm -rf ${testDataDir}
git clone -b ${branchName} --depth 1 --single-branch ${githubRepoLink} ${testDataDir}

.PHONY: test
test: ${testDataDir}
cargo test
29 changes: 29 additions & 0 deletions examples/simple/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use std::collections::HashMap;

pub fn main() -> eppo::Result<()> {
env_logger::Builder::from_env(env_logger::Env::new().default_filter_or("eppo")).init();

let api_key = std::env::var("EPPO_API_KEY").unwrap();
let mut client = eppo::ClientConfig::from_api_key(api_key).to_client();

// Start a poller thread to fetch configuration from the server.
let poller = client.start_poller_thread()?;

// Block waiting for configuration. Until this call returns, the client will return None for all
// assignments.
if let Err(err) = poller.wait_for_configuration() {
println!("error requesting configuration: {:?}", err);
}

// Get assignment for test-subject.
let assignment = client
.get_assignment("a-boolean-flag", "test-subject", &HashMap::new())
.unwrap_or_default()
.and_then(|x| x.as_boolean())
// default assignment
.unwrap_or(false);

println!("Assignment: {:?}", assignment);

Ok(())
}
35 changes: 35 additions & 0 deletions src/assignment_logger.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use std::collections::HashMap;

use serde::{Deserialize, Serialize};

use crate::SubjectAttributes;

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AssignmentEvent {
pub feature_flag: String,
pub allocation: String,
pub experiment: String,
pub variation: String,
pub subject: String,
pub subject_attributes: SubjectAttributes,
pub timestamp: String,
pub meta_data: HashMap<String, String>,
#[serde(flatten)]
pub extra_logging: HashMap<String, String>,
}

pub trait AssignmentLogger {
fn log_assignment(&self, event: AssignmentEvent);
}

pub(crate) struct NoopAssignmentLogger;
impl AssignmentLogger for NoopAssignmentLogger {
fn log_assignment(&self, _event: AssignmentEvent) {}
}

impl<T: Fn(AssignmentEvent)> AssignmentLogger for T {
fn log_assignment(&self, event: AssignmentEvent) {
self(event);
}
}
Loading

0 comments on commit f97a2b1

Please sign in to comment.