Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
5db0f98
init commit
gagantrivedi Mar 8, 2022
ba0d0c9
create org and project modules
gagantrivedi Mar 9, 2022
7566966
Add environments module
gagantrivedi Mar 9, 2022
eda3735
Add segments module
gagantrivedi Mar 9, 2022
8347e71
Add clone trait to features mod
gagantrivedi Mar 9, 2022
9227488
Add identities mod
gagantrivedi Mar 9, 2022
acf0a75
Add engine module
gagantrivedi Mar 9, 2022
c75d6c4
Add environment api key struct
gagantrivedi Mar 9, 2022
2aa85f7
cargo fmt
gagantrivedi Mar 9, 2022
73cb9f4
Add engine test data submodule
gagantrivedi Mar 9, 2022
a592fee
feat(environments): Add is_valid
gagantrivedi Mar 10, 2022
84aff4e
feat(org/mod): Add unique slug function
gagantrivedi Mar 12, 2022
249af41
feat(identity/mod.rs): Add composite key
gagantrivedi Mar 12, 2022
3a9f7ae
Add hashing module
gagantrivedi Mar 18, 2022
0d31481
fix hash to int conversion
gagantrivedi Mar 20, 2022
8c931c8
Add uuid
gagantrivedi Mar 21, 2022
1640d46
test(utils/hashing): Add more tests
gagantrivedi Mar 21, 2022
ac171b8
Implement engine method for enviroment
gagantrivedi Mar 23, 2022
ea8728b
test(engine): Add test for environment fs interface
gagantrivedi Mar 23, 2022
12ce022
feat(features): Add default uuid
gagantrivedi Mar 23, 2022
d6e4b2a
Fix sorting in get_mv_value
gagantrivedi Mar 24, 2022
8ddcc2d
refac(hashing/get_hashed_pc)update signature
gagantrivedi Mar 24, 2022
bb3eebe
fix segment rule model
gagantrivedi Mar 24, 2022
414fb2b
feat(segment): Add constants
gagantrivedi Mar 24, 2022
5d4d369
Add regex
gagantrivedi Mar 24, 2022
7beea78
implements segments module
gagantrivedi Mar 24, 2022
057fca0
make trait stuct members public
gagantrivedi Mar 24, 2022
cc577a8
implement identity interface of engine
gagantrivedi Mar 24, 2022
a190cb1
remove main.rs
gagantrivedi Mar 25, 2022
9c22eca
make modules public
gagantrivedi Mar 25, 2022
9d5f56d
fix(feature_state_value): use custom ser/deserialize
gagantrivedi Mar 25, 2022
c16c35c
Add github action for pull request
gagantrivedi Mar 26, 2022
b38fa60
cargo fmt .
gagantrivedi Mar 26, 2022
f25f0e7
Add pre-commit
gagantrivedi Mar 26, 2022
75adccc
fix(feature_state:value): serialization and deserialization
gagantrivedi Mar 26, 2022
a3773f8
feat(environments): Add builder module
gagantrivedi Mar 26, 2022
43582c3
fixup! fix(feature_state:value): serialization and deserialization
gagantrivedi Mar 26, 2022
9916855
fixup! feat(environments): Add builder module
gagantrivedi Mar 26, 2022
8a36088
feat(identities): Add identity builder
gagantrivedi Mar 26, 2022
7f8deed
fixup! feat(identities): Add identity builder
gagantrivedi Mar 26, 2022
92c1cfc
feat(utils): Add datetime modules
gagantrivedi Mar 28, 2022
65e4e59
fix(match_segment): Add test cases
gagantrivedi Mar 30, 2022
e74d250
Add rtest
gagantrivedi Mar 30, 2022
2b4eb73
fix(segments): make constants public
gagantrivedi Mar 30, 2022
1e8f384
tests(segment_evaluator): Add tests for evaluate_iden..in_segments
gagantrivedi Mar 30, 2022
9023822
rename modules add fixtures
gagantrivedi Mar 30, 2022
a205f6b
fix(segment): use property_
gagantrivedi Mar 30, 2022
b94bebb
Add engine tests
gagantrivedi Mar 31, 2022
26a36ce
fix(workflow): fetch submodule
gagantrivedi Mar 31, 2022
52034ff
mv featurstate value to types/mod.rs
gagantrivedi Mar 31, 2022
18b0d30
/s FeatureStateValue/FlagsmithValue
gagantrivedi Mar 31, 2022
50b30e3
fix: remove warnings
gagantrivedi Mar 31, 2022
1400ae1
update raw identifier to normal identifier
gagantrivedi Mar 31, 2022
eb12ce6
fix/remove todos
gagantrivedi Mar 31, 2022
d6c8503
update package name
gagantrivedi Mar 31, 2022
5520494
Add debug trait
gagantrivedi Apr 2, 2022
f040d76
Add clone trait
gagantrivedi Apr 4, 2022
15de30b
feat(identity): Add ::new constructor
gagantrivedi Apr 6, 2022
e568c23
fix(identity): make trait clone(able)
gagantrivedi Apr 7, 2022
5804cd4
fix mv_fs value
gagantrivedi Apr 8, 2022
e979f1e
tests(feature): Add test for get_value_mv_values
gagantrivedi Apr 8, 2022
9c79631
tests(environment/builder): Add test for api_key builder
gagantrivedi Apr 8, 2022
30c2155
tests(feature): Add tests for mv get value
gagantrivedi Apr 8, 2022
3c20c85
segment/tess: use rtest case
gagantrivedi Apr 8, 2022
c91259a
[minor]
gagantrivedi Apr 8, 2022
83ebb02
update readme
gagantrivedi Apr 8, 2022
edc64f0
tests(types/mod.rs): Add test cases
gagantrivedi Apr 8, 2022
99da0c1
Add tests for datetime module
gagantrivedi Apr 8, 2022
63b7cdf
Add public errors
gagantrivedi Apr 9, 2022
799b686
Add test for _get_feature_state
gagantrivedi Apr 9, 2022
e3372ce
fixup: Add error module
gagantrivedi Apr 9, 2022
ccfb16f
Implement default for flagsmithValue
gagantrivedi Apr 10, 2022
a5646d3
support == on flagsmithvalue
gagantrivedi Apr 12, 2022
1beaab2
[minor]
gagantrivedi Apr 13, 2022
b7974c5
Add .gitignore
gagantrivedi Apr 13, 2022
4c8f9ca
remove Cargo.lock
gagantrivedi Apr 13, 2022
bb17485
remove implemented todo
gagantrivedi Apr 25, 2022
d0e527c
use django id for segment_evaluation if present
gagantrivedi Apr 25, 2022
ff02e0a
Add semver to cargo
gagantrivedi Apr 26, 2022
b5d8ddd
feat(semVer): Add semver operations
gagantrivedi Apr 26, 2022
2ca5a30
remove comment
gagantrivedi May 24, 2022
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
30 changes: 30 additions & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Rust Flag Engine Pull Request

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- main
- release**

env:
CARGO_TERM_COLOR: always

jobs:
test:

runs-on: ubuntu-latest

steps:
- name: Cloning repo
uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: recursive
- uses: actions/checkout@v2
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- name: Check Formatting
run: cargo fmt --all -- --check
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
Cargo.lock
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "tests/engine_tests/engine-test-data"]
path = tests/engine_tests/engine-test-data
url = git@github.com:Flagsmith/engine-test-data.git
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
repos:
- repo: https://github.com/doublify/pre-commit-rust
rev: v1.0
hooks:
- id: fmt
- id: cargo-check
20 changes: 20 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "flagsmith-flag-engine"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
md-5="0.10.1"
num-bigint = "0.4"
num-traits = "0.2.14"
uuid = { version = "0.8", features = ["serde", "v4"] }
regex = "1"
semver = "1.0"

[dev-dependencies]
rstest = "0.12.0"
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,26 @@
# flagsmith-rust-flag-engine
Flag Engine implementaion in Rust
[![Feature Flag, Remote Config and A/B Testing platform, Flagsmith](https://github.com/Flagsmith/flagsmith/raw/main/static-files/hero.png)](https://www.flagsmith.com/)

[Flagsmith](https://www.flagsmith.com/) is an open source, fully featured, Feature Flag and Remote Config service. Use
our hosted API, deploy to your own private cloud, or run on-premise.

# Flagsmith Rust Flag Engine

This project is the rust clone of flagsmith flag engine [Flagsmith API](https://github.com/Flagsmith/flagsmith-engine).

## Contributing

Please read [CONTRIBUTING.md](https://docs.flagsmith.com/platform/contributing) for details on our code of conduct, and the process for submitting pull requests

## Getting Help

If you encounter a bug or feature request we would like to hear about it. Before you submit an issue please search existing issues in order to prevent duplicates.

## Get in touch

If you have any questions about our projects you can email <a href="mailto:support@flagsmith.com">support@flagsmith.com</a>.

## Useful links

[Website](https://www.flagsmith.com/)

[Documentation](https://docs.flagsmith.com/)
207 changes: 207 additions & 0 deletions src/engine.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
use super::environments;
use super::error;
use super::features;
use super::identities;
use super::segments::evaluator;
use crate::features::Feature;
use crate::features::FeatureState;
use std::collections::HashMap;

//Returns a vector of feature states for a given environment
pub fn get_environment_feature_states(
environment: environments::Environment,
) -> Vec<features::FeatureState> {
if environment.project.hide_disabled_flags {
return environment
.feature_states
.iter()
.filter(|fs| fs.enabled)
.map(|fs| fs.clone())
.collect();
}
return environment.feature_states;
}

// Returns a specific feature state for a given feature_name in a given environment
// If exists else returns a FeatureStateNotFound error
pub fn get_environment_feature_state(
environment: environments::Environment,
feature_name: &str,
) -> Result<features::FeatureState, error::Error> {
let fs = environment
.feature_states
.iter()
.filter(|fs| fs.feature.name == feature_name)
.next()
.ok_or(error::Error::new(error::ErrorKind::FeatureStateNotFound));
return Ok(fs?.clone());
}

// Returns a vector of feature state models based on the environment, any matching
// segments and any specific identity overrides
pub fn get_identity_feature_states(
environment: &environments::Environment,
identity: &identities::Identity,
override_traits: Option<&Vec<identities::Trait>>,
) -> Vec<features::FeatureState> {
let feature_states =
get_identity_feature_states_map(environment, identity, override_traits).into_values();
if environment.project.hide_disabled_flags {
return feature_states.filter(|fs| fs.enabled).collect();
}
return feature_states.collect();
}

// Returns a specific feature state based on the environment, any matching
// segments and any specific identity overrides
// If exists else returns a FeatureStateNotFound error
pub fn get_identity_feature_state(
environment: &environments::Environment,
identity: &identities::Identity,
feature_name: &str,
override_traits: Option<&Vec<identities::Trait>>,
) -> Result<features::FeatureState, error::Error> {
let feature_states =
get_identity_feature_states_map(environment, identity, override_traits).into_values();
let fs = feature_states
.filter(|fs| fs.feature.name == feature_name)
.next()
.ok_or(error::Error::new(error::ErrorKind::FeatureStateNotFound));

return Ok(fs?.clone());
}

fn get_identity_feature_states_map(
environment: &environments::Environment,
identity: &identities::Identity,
override_traits: Option<&Vec<identities::Trait>>,
) -> HashMap<Feature, FeatureState> {
let mut feature_states: HashMap<Feature, FeatureState> = HashMap::new();

// Get feature states from the environment
for fs in environment.feature_states.clone() {
feature_states.insert(fs.feature.clone(), fs);
}

// Override with any feature states defined by matching segments
let identity_segments =
evaluator::get_identity_segments(environment, identity, override_traits);
for matching_segments in identity_segments {
for feature_state in matching_segments.feature_states {
feature_states.insert(feature_state.feature.clone(), feature_state);
}
}
// Override with any feature states defined directly the identity
for feature_state in identity.identity_features.clone() {
feature_states.insert(feature_state.feature.clone(), feature_state);
}
return feature_states;
}

#[cfg(test)]
mod tests {
use super::*;
static IDENTITY_JSON: &str = r#"{
"identifier": "test_user",
"environment_api_key": "test_api_key",
"created_date": "2022-03-02T12:31:05.309861",
"identity_features": [],
"identity_traits": [],
"identity_uuid":""
}"#;
static ENVIRONMENT_JSON: &str = r#"
{
"api_key": "test_key",
"project": {
"name": "Test project",
"organisation": {
"feature_analytics": false,
"name": "Test Org",
"id": 1,
"persist_trait_data": true,
"stop_serving_flags": false
},
"id": 1,
"hide_disabled_flags": true,
"segments": []
},
"segment_overrides": [],
"id": 1,
"feature_states": [
{
"multivariate_feature_state_values": [],
"feature_state_value": true,
"django_id": 1,
"feature": {
"name": "feature1",
"type": null,
"id": 1
},
"enabled": false
},
{
"multivariate_feature_state_values": [],
"feature_state_value": null,
"django_id": 2,
"feature": {
"name": "feature_2",
"type": null,
"id": 2
},
"enabled": true
}
]
}"#;

#[test]
fn get_environment_feature_states_only_return_enabled_fs_if_hide_disabled_flags_is_true() {
let environment: environments::Environment =
serde_json::from_str(ENVIRONMENT_JSON).unwrap();

let environment_feature_states = get_environment_feature_states(environment);
assert_eq!(environment_feature_states.len(), 1);
assert_eq!(environment_feature_states[0].django_id.unwrap(), 2);
}

#[test]
fn get_environment_feature_state_returns_correct_feature_state() {
let environment: environments::Environment =
serde_json::from_str(ENVIRONMENT_JSON).unwrap();
let feature_name = "feature_2";
let feature_state = get_environment_feature_state(environment, feature_name).unwrap();
assert_eq!(feature_state.feature.name, feature_name)
}

#[test]
fn get_environment_feature_state_returns_error_if_feature_state_does_not_exists() {
let environment: environments::Environment =
serde_json::from_str(ENVIRONMENT_JSON).unwrap();
let feature_name = "feature_that_does_not_exists";
let err = get_environment_feature_state(environment, feature_name)
.err()
.unwrap();
assert_eq!(err.kind, error::ErrorKind::FeatureStateNotFound)
}

#[test]
fn get_identity_feature_state_returns_correct_feature_state() {
let environment: environments::Environment =
serde_json::from_str(ENVIRONMENT_JSON).unwrap();
let feature_name = "feature_2";
let identity: identities::Identity = serde_json::from_str(IDENTITY_JSON).unwrap();
let feature_state =
get_identity_feature_state(&environment, &identity, feature_name, None).unwrap();
assert_eq!(feature_state.feature.name, feature_name)
}
#[test]
fn get_identity_feature_state_returns_error_if_feature_state_does_not_exists() {
let environment: environments::Environment =
serde_json::from_str(ENVIRONMENT_JSON).unwrap();
let feature_name = "feature_that_does_not_exists";
let identity: identities::Identity = serde_json::from_str(IDENTITY_JSON).unwrap();
let err = get_identity_feature_state(&environment, &identity, feature_name, None)
.err()
.unwrap();
assert_eq!(err.kind, error::ErrorKind::FeatureStateNotFound)
}
}
35 changes: 35 additions & 0 deletions src/environments/builders.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use super::Environment;
use super::EnvironmentAPIKey;

pub fn build_environment_struct(value: serde_json::Value) -> Environment {
let environment: Environment = serde_json::from_value(value).unwrap();
return environment;
}

pub fn build_environment_api_key_struct(value: serde_json::Value) -> EnvironmentAPIKey {
let environment_api_key: EnvironmentAPIKey = serde_json::from_value(value).unwrap();
return environment_api_key;
}
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn build_environment_api_key_struct_returns_correct_struct() {
// Given
let key = "ser.test_key".to_string();
let api_key_json = serde_json::json!({
"key": key,
"active": true,
"created_at": "2022-03-02T12:31:05.309861+00:00",
"client_api_key": "client_key",
"id": 1,
"name": "api key 1",
"expires_at": null
});
// When
let api_key_struct = build_environment_api_key_struct(api_key_json);
// Then
assert_eq!(api_key_struct.key, key);
}
}
Loading