-
Notifications
You must be signed in to change notification settings - Fork 0
Implement flag engine v0.1.0 #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
81 commits
Select commit
Hold shift + click to select a range
5db0f98
init commit
gagantrivedi ba0d0c9
create org and project modules
gagantrivedi 7566966
Add environments module
gagantrivedi eda3735
Add segments module
gagantrivedi 8347e71
Add clone trait to features mod
gagantrivedi 9227488
Add identities mod
gagantrivedi acf0a75
Add engine module
gagantrivedi c75d6c4
Add environment api key struct
gagantrivedi 2aa85f7
cargo fmt
gagantrivedi 73cb9f4
Add engine test data submodule
gagantrivedi a592fee
feat(environments): Add is_valid
gagantrivedi 84aff4e
feat(org/mod): Add unique slug function
gagantrivedi 249af41
feat(identity/mod.rs): Add composite key
gagantrivedi 3a9f7ae
Add hashing module
gagantrivedi 0d31481
fix hash to int conversion
gagantrivedi 8c931c8
Add uuid
gagantrivedi 1640d46
test(utils/hashing): Add more tests
gagantrivedi ac171b8
Implement engine method for enviroment
gagantrivedi ea8728b
test(engine): Add test for environment fs interface
gagantrivedi 12ce022
feat(features): Add default uuid
gagantrivedi d6e4b2a
Fix sorting in get_mv_value
gagantrivedi 8ddcc2d
refac(hashing/get_hashed_pc)update signature
gagantrivedi bb3eebe
fix segment rule model
gagantrivedi 414fb2b
feat(segment): Add constants
gagantrivedi 5d4d369
Add regex
gagantrivedi 7beea78
implements segments module
gagantrivedi 057fca0
make trait stuct members public
gagantrivedi cc577a8
implement identity interface of engine
gagantrivedi a190cb1
remove main.rs
gagantrivedi 9c22eca
make modules public
gagantrivedi 9d5f56d
fix(feature_state_value): use custom ser/deserialize
gagantrivedi c16c35c
Add github action for pull request
gagantrivedi b38fa60
cargo fmt .
gagantrivedi f25f0e7
Add pre-commit
gagantrivedi 75adccc
fix(feature_state:value): serialization and deserialization
gagantrivedi a3773f8
feat(environments): Add builder module
gagantrivedi 43582c3
fixup! fix(feature_state:value): serialization and deserialization
gagantrivedi 9916855
fixup! feat(environments): Add builder module
gagantrivedi 8a36088
feat(identities): Add identity builder
gagantrivedi 7f8deed
fixup! feat(identities): Add identity builder
gagantrivedi 92c1cfc
feat(utils): Add datetime modules
gagantrivedi 65e4e59
fix(match_segment): Add test cases
gagantrivedi e74d250
Add rtest
gagantrivedi 2b4eb73
fix(segments): make constants public
gagantrivedi 1e8f384
tests(segment_evaluator): Add tests for evaluate_iden..in_segments
gagantrivedi 9023822
rename modules add fixtures
gagantrivedi a205f6b
fix(segment): use property_
gagantrivedi b94bebb
Add engine tests
gagantrivedi 26a36ce
fix(workflow): fetch submodule
gagantrivedi 52034ff
mv featurstate value to types/mod.rs
gagantrivedi 18b0d30
/s FeatureStateValue/FlagsmithValue
gagantrivedi 50b30e3
fix: remove warnings
gagantrivedi 1400ae1
update raw identifier to normal identifier
gagantrivedi eb12ce6
fix/remove todos
gagantrivedi d6c8503
update package name
gagantrivedi 5520494
Add debug trait
gagantrivedi f040d76
Add clone trait
gagantrivedi 15de30b
feat(identity): Add ::new constructor
gagantrivedi e568c23
fix(identity): make trait clone(able)
gagantrivedi 5804cd4
fix mv_fs value
gagantrivedi e979f1e
tests(feature): Add test for get_value_mv_values
gagantrivedi 9c79631
tests(environment/builder): Add test for api_key builder
gagantrivedi 30c2155
tests(feature): Add tests for mv get value
gagantrivedi 3c20c85
segment/tess: use rtest case
gagantrivedi c91259a
[minor]
gagantrivedi 83ebb02
update readme
gagantrivedi edc64f0
tests(types/mod.rs): Add test cases
gagantrivedi 99da0c1
Add tests for datetime module
gagantrivedi 63b7cdf
Add public errors
gagantrivedi 799b686
Add test for _get_feature_state
gagantrivedi e3372ce
fixup: Add error module
gagantrivedi ccfb16f
Implement default for flagsmithValue
gagantrivedi a5646d3
support == on flagsmithvalue
gagantrivedi 1beaab2
[minor]
gagantrivedi b7974c5
Add .gitignore
gagantrivedi 4c8f9ca
remove Cargo.lock
gagantrivedi bb17485
remove implemented todo
gagantrivedi d0e527c
use django id for segment_evaluation if present
gagantrivedi ff02e0a
Add semver to cargo
gagantrivedi b5d8ddd
feat(semVer): Add semver operations
gagantrivedi 2ca5a30
remove comment
gagantrivedi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| /target | ||
| Cargo.lock |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,26 @@ | ||
| # flagsmith-rust-flag-engine | ||
| Flag Engine implementaion in Rust | ||
| [](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/) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.