mirror of
https://github.com/PostHog/posthog.git
synced 2024-11-21 13:39:22 +01:00
feat(flags): rust implementation of a bunch of things (#24957)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
parent
d4c86a981b
commit
c940fd5229
4
.gitignore
vendored
4
.gitignore
vendored
@ -66,4 +66,6 @@ plugin-transpiler/dist
|
||||
.dlt
|
||||
*.db
|
||||
# Ignore any log files that happen to be present
|
||||
*.log
|
||||
*.log
|
||||
# pyright config (keep this until we have a standardized one)
|
||||
pyrightconfig.json
|
33
rust/Cargo.lock
generated
33
rust/Cargo.lock
generated
@ -471,7 +471,7 @@ version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
@ -1184,6 +1184,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sha1",
|
||||
"sqlx",
|
||||
"strum",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tower",
|
||||
@ -1553,6 +1554,12 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
@ -3718,7 +3725,7 @@ dependencies = [
|
||||
"atomic-write-file",
|
||||
"dotenvy",
|
||||
"either",
|
||||
"heck",
|
||||
"heck 0.4.1",
|
||||
"hex",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
@ -3870,6 +3877,28 @@ dependencies = [
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.5.0"
|
||||
|
@ -34,6 +34,7 @@ uuid = { workspace = true }
|
||||
base64.workspace = true
|
||||
flate2.workspace = true
|
||||
common-alloc = { path = "../common/alloc" }
|
||||
strum = { version = "0.26", features = ["derive"] }
|
||||
health = { path = "../common/health" }
|
||||
common-metrics = { path = "../common/metrics" }
|
||||
tower = { workspace = true }
|
||||
|
101
rust/feature-flags/src/feature_flag_match_reason.rs
Normal file
101
rust/feature-flags/src/feature_flag_match_reason.rs
Normal file
@ -0,0 +1,101 @@
|
||||
use std::cmp::Ordering;
|
||||
use strum::EnumString;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, EnumString)]
|
||||
pub enum FeatureFlagMatchReason {
|
||||
#[strum(serialize = "super_condition_value")]
|
||||
SuperConditionValue,
|
||||
#[strum(serialize = "condition_match")]
|
||||
ConditionMatch,
|
||||
#[strum(serialize = "no_condition_match")]
|
||||
NoConditionMatch,
|
||||
#[strum(serialize = "out_of_rollout_bound")]
|
||||
OutOfRolloutBound,
|
||||
#[strum(serialize = "no_group_type")]
|
||||
NoGroupType,
|
||||
}
|
||||
|
||||
impl FeatureFlagMatchReason {
|
||||
pub fn score(&self) -> i32 {
|
||||
match self {
|
||||
FeatureFlagMatchReason::SuperConditionValue => 4,
|
||||
FeatureFlagMatchReason::ConditionMatch => 3,
|
||||
FeatureFlagMatchReason::NoGroupType => 2,
|
||||
FeatureFlagMatchReason::OutOfRolloutBound => 1,
|
||||
FeatureFlagMatchReason::NoConditionMatch => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for FeatureFlagMatchReason {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for FeatureFlagMatchReason {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.score().cmp(&other.score())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FeatureFlagMatchReason {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
FeatureFlagMatchReason::SuperConditionValue => "super_condition_value",
|
||||
FeatureFlagMatchReason::ConditionMatch => "condition_match",
|
||||
FeatureFlagMatchReason::NoConditionMatch => "no_condition_match",
|
||||
FeatureFlagMatchReason::OutOfRolloutBound => "out_of_rollout_bound",
|
||||
FeatureFlagMatchReason::NoGroupType => "no_group_type",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_ordering() {
|
||||
let reasons = vec![
|
||||
FeatureFlagMatchReason::NoConditionMatch,
|
||||
FeatureFlagMatchReason::OutOfRolloutBound,
|
||||
FeatureFlagMatchReason::NoGroupType,
|
||||
FeatureFlagMatchReason::ConditionMatch,
|
||||
FeatureFlagMatchReason::SuperConditionValue,
|
||||
];
|
||||
|
||||
let mut sorted_reasons = reasons.clone();
|
||||
sorted_reasons.sort();
|
||||
|
||||
assert_eq!(sorted_reasons, reasons);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
assert_eq!(
|
||||
FeatureFlagMatchReason::SuperConditionValue.to_string(),
|
||||
"super_condition_value"
|
||||
);
|
||||
assert_eq!(
|
||||
FeatureFlagMatchReason::ConditionMatch.to_string(),
|
||||
"condition_match"
|
||||
);
|
||||
assert_eq!(
|
||||
FeatureFlagMatchReason::NoConditionMatch.to_string(),
|
||||
"no_condition_match"
|
||||
);
|
||||
assert_eq!(
|
||||
FeatureFlagMatchReason::OutOfRolloutBound.to_string(),
|
||||
"out_of_rollout_bound"
|
||||
);
|
||||
assert_eq!(
|
||||
FeatureFlagMatchReason::NoGroupType.to_string(),
|
||||
"no_group_type"
|
||||
);
|
||||
}
|
||||
}
|
@ -110,6 +110,14 @@ impl FeatureFlag {
|
||||
.clone()
|
||||
.map_or(vec![], |m| m.variants)
|
||||
}
|
||||
|
||||
pub fn get_payload(&self, match_val: &str) -> Option<serde_json::Value> {
|
||||
self.filters.payloads.as_ref().and_then(|payloads| {
|
||||
payloads
|
||||
.as_object()
|
||||
.and_then(|obj| obj.get(match_val).cloned())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
||||
pub mod api;
|
||||
pub mod config;
|
||||
pub mod database;
|
||||
pub mod feature_flag_match_reason;
|
||||
pub mod flag_definitions;
|
||||
pub mod flag_matching;
|
||||
pub mod flag_request;
|
||||
|
@ -53,9 +53,12 @@ pub fn match_property(
|
||||
let compute_exact_match = |value: &Value, override_value: &Value| -> bool {
|
||||
if is_truthy_or_falsy_property_value(value) {
|
||||
// Do boolean handling, such that passing in "true" or "True" or "false" or "False" as matching value is equivalent
|
||||
let truthy = is_truthy_property_value(value);
|
||||
return override_value.to_string().to_lowercase()
|
||||
== truthy.to_string().to_lowercase();
|
||||
let (truthy_value, truthy_override_value) = (
|
||||
is_truthy_property_value(value),
|
||||
is_truthy_property_value(override_value),
|
||||
);
|
||||
return truthy_override_value.to_string().to_lowercase()
|
||||
== truthy_value.to_string().to_lowercase();
|
||||
}
|
||||
|
||||
if value.is_array() {
|
||||
|
@ -1,3 +1,4 @@
|
||||
use feature_flags::feature_flag_match_reason::FeatureFlagMatchReason;
|
||||
/// These tests are common between all libraries doing local evaluation of feature flags.
|
||||
/// This ensures there are no mismatches between implementations.
|
||||
use feature_flags::flag_matching::{FeatureFlagMatch, FeatureFlagMatcher};
|
||||
@ -121,6 +122,9 @@ async fn it_is_consistent_with_rollout_calculation_for_simple_flags() {
|
||||
FeatureFlagMatch {
|
||||
matches: true,
|
||||
variant: None,
|
||||
reason: FeatureFlagMatchReason::ConditionMatch,
|
||||
condition_index: Some(0),
|
||||
payload: None,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
@ -129,6 +133,9 @@ async fn it_is_consistent_with_rollout_calculation_for_simple_flags() {
|
||||
FeatureFlagMatch {
|
||||
matches: false,
|
||||
variant: None,
|
||||
reason: FeatureFlagMatchReason::OutOfRolloutBound,
|
||||
condition_index: Some(0),
|
||||
payload: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -1199,12 +1206,15 @@ async fn it_is_consistent_with_rollout_calculation_for_multivariate_flags() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if result.is_some() {
|
||||
if let Some(variant) = &result {
|
||||
assert_eq!(
|
||||
feature_flag_match,
|
||||
FeatureFlagMatch {
|
||||
matches: true,
|
||||
variant: results[i].clone(),
|
||||
variant: Some(variant.clone()),
|
||||
reason: FeatureFlagMatchReason::ConditionMatch,
|
||||
condition_index: Some(0),
|
||||
payload: None,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
@ -1213,6 +1223,9 @@ async fn it_is_consistent_with_rollout_calculation_for_multivariate_flags() {
|
||||
FeatureFlagMatch {
|
||||
matches: false,
|
||||
variant: None,
|
||||
reason: FeatureFlagMatchReason::OutOfRolloutBound,
|
||||
condition_index: Some(0),
|
||||
payload: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user