Skip to content
Merged
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
6 changes: 6 additions & 0 deletions src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ fn get_identity_feature_states_map(
evaluator::get_identity_segments(environment, identity, override_traits);
for matching_segments in identity_segments {
for feature_state in matching_segments.feature_states {
let existing = feature_states.get(&feature_state.feature);
if existing.is_some() {
if existing.unwrap().is_higher_segment_priority(&feature_state) {
continue;
}
}
feature_states.insert(feature_state.feature.clone(), feature_state);
}
}
Expand Down
85 changes: 85 additions & 0 deletions src/features/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,20 @@ pub struct MultivariateFeatureStateValue {
pub mv_fs_value_uuid: String,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct FeatureSegment {
pub priority: u32,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct FeatureState {
pub feature: Feature,
pub enabled: bool,
pub django_id: Option<u32>,

#[serde(skip_serializing_if = "Option::is_none")]
pub feature_segment: Option<FeatureSegment>,

#[serde(default = "utils::get_uuid")]
pub featurestate_uuid: String,
pub multivariate_feature_state_values: Vec<MultivariateFeatureStateValue>,
Expand All @@ -51,6 +59,25 @@ impl FeatureState {
};
return value;
}

// Returns `true` if `self` is higher segment priority than `other`
// (i.e. has lower value for feature_segment.priority)
// NOTE:
// A segment will be considered higher priority only if:
// 1. `other` does not have a feature segment(i.e: it is an environment feature state or it's a
// feature state with feature segment but from an old document that does not have `feature_segment.priority`)
// but `self` does.

// 2. `other` have a feature segment but with lower priority
pub fn is_higher_segment_priority(&self, other: &FeatureState) -> bool {
match &other.feature_segment {
None if self.feature_segment.is_some() => true,
Some(feature_segment) if self.feature_segment.is_some() => {
self.feature_segment.as_ref().unwrap().priority < feature_segment.priority
}
_ => false,
}
}
fn get_multivariate_value(&self, identity_id: &str) -> FlagsmithValue {
let object_id = match self.django_id {
Some(django_id) => django_id.to_string(),
Expand Down Expand Up @@ -144,6 +171,64 @@ mod tests {
assert_eq!(given_json, feature_state_json)
}

#[test]
fn feature_state_is_higher_segment_priority() {
// Given
let feature_state_json = serde_json::json!(
{
"multivariate_feature_state_values": [],
"feature_state_value": 1,
"featurestate_uuid":"a6ff815f-63ed-4e72-99dc-9124c442ce4d",
"django_id": 1,
"feature": {
"name": "feature1",
"type": null,
"id": 1
},
"enabled": false
}
);
let mut feature_state_1: FeatureState =
serde_json::from_value(feature_state_json.clone()).unwrap();
let mut feature_state_2: FeatureState =
serde_json::from_value(feature_state_json.clone()).unwrap();

// Firstly, since both fs do not have feature segment this should be false
assert_eq!(
feature_state_1.is_higher_segment_priority(&feature_state_2),
false
);
assert_eq!(
feature_state_2.is_higher_segment_priority(&feature_state_1),
false
);

// Now add feature_segment to feature_state_2
feature_state_2.feature_segment = Some(FeatureSegment { priority: 1 });

// Since `feature_state_2` have a feature segment this should be false as well
assert_eq!(
feature_state_1.is_higher_segment_priority(&feature_state_2),
false
);
// And, this true
assert_eq!(
feature_state_2.is_higher_segment_priority(&feature_state_1),
true
);

// Next, let's add a feature segment with higher priority to `feature_state_1`
feature_state_1.feature_segment = Some(FeatureSegment { priority: 0 });
assert_eq!(
feature_state_1.is_higher_segment_priority(&feature_state_2),
true
);
assert_eq!(
feature_state_2.is_higher_segment_priority(&feature_state_1),
false
);
}

#[rstest]
#[case("2", "foo".to_string())] // Generated hash percentage 26
#[case("8", "bar".to_string())] // Generated hash percentage 38
Expand Down