Skip to content

Commit 354d42a

Browse files
committed
feat: support prerequisite references in all_flags_detail
1 parent 08bf51b commit 354d42a

File tree

2 files changed

+102
-7
lines changed

2 files changed

+102
-7
lines changed

launchdarkly-server-sdk/src/evaluation.rs

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
use std::cell::RefCell;
12
use super::stores::store::DataStore;
23
use serde::Serialize;
34

4-
use launchdarkly_server_sdk_evaluation::{evaluate, Context, FlagValue, Reason};
5+
use launchdarkly_server_sdk_evaluation::{evaluate, Context, FlagValue, PrerequisiteEvent, PrerequisiteEventRecorder, Reason};
56
use std::collections::HashMap;
67
use std::time::SystemTime;
78

@@ -78,6 +79,9 @@ pub struct FlagState {
7879

7980
#[serde(skip_serializing_if = "Option::is_none")]
8081
debug_events_until_date: Option<u64>,
82+
83+
#[serde(skip_serializing_if = "Vec::is_empty")]
84+
prerequisites: Vec<String>,
8185
}
8286

8387
/// FlagDetail is a snapshot of the state of multiple feature flags with regard to a specific user.
@@ -97,6 +101,27 @@ pub struct FlagDetail {
97101
valid: bool,
98102
}
99103

104+
struct DirectPrerequisiteRecorder {
105+
target_flag_key: String,
106+
prerequisites: RefCell<Vec<String>>,
107+
}
108+
109+
impl DirectPrerequisiteRecorder {
110+
pub fn new(target_flag_key: impl Into<String>) -> Self {
111+
Self {
112+
target_flag_key: target_flag_key.into(),
113+
prerequisites: RefCell::new(Vec::new()),
114+
}
115+
}
116+
}
117+
impl PrerequisiteEventRecorder for DirectPrerequisiteRecorder {
118+
fn record(&self, event: PrerequisiteEvent) {
119+
if event.target_flag_key == self.target_flag_key {
120+
self.prerequisites.borrow_mut().push(event.prerequisite_flag.key)
121+
}
122+
}
123+
}
124+
100125
impl FlagDetail {
101126
/// Create a new empty instance of FlagDetail.
102127
pub fn new(valid: bool) -> Self {
@@ -118,7 +143,10 @@ impl FlagDetail {
118143
continue;
119144
}
120145

121-
let detail = evaluate(store.to_store(), &flag, context, None);
146+
let event_recorder = DirectPrerequisiteRecorder::new(key.clone());
147+
148+
let detail = evaluate(store.to_store(), &flag, context, Some(&event_recorder));
149+
122150

123151
// Here we are applying the same logic used in EventFactory.new_feature_request_event
124152
// to determine whether the evaluation involved an experiment, in which case both
@@ -171,6 +199,7 @@ impl FlagDetail {
171199
track_events,
172200
track_reason,
173201
debug_events_until_date: flag.debug_events_until_date,
202+
prerequisites: event_recorder.prerequisites.take(),
174203
},
175204
);
176205
}
@@ -182,11 +211,12 @@ impl FlagDetail {
182211

183212
#[cfg(test)]
184213
mod tests {
214+
use assert_json_diff::assert_json_eq;
185215
use crate::evaluation::FlagDetail;
186216
use crate::stores::store::DataStore;
187217
use crate::stores::store::InMemoryDataStore;
188218
use crate::stores::store_types::{PatchTarget, StorageItem};
189-
use crate::test_common::basic_flag;
219+
use crate::test_common::{basic_flag, basic_flag_with_prereqs};
190220
use crate::FlagDetailConfig;
191221
use launchdarkly_server_sdk_evaluation::ContextBuilder;
192222

@@ -410,4 +440,57 @@ mod tests {
410440
serde_json::to_string_pretty(&expected).unwrap(),
411441
);
412442
}
443+
444+
#[test]
445+
fn flag_prerequisites_should_be_exposed() {
446+
let context = ContextBuilder::new("bob")
447+
.build()
448+
.expect("Failed to create context");
449+
let mut store = InMemoryDataStore::new();
450+
451+
let prereq1 = basic_flag("prereq1");
452+
let prereq2 = basic_flag("prereq2");
453+
let toplevel = basic_flag_with_prereqs("toplevel", &["prereq1", "prereq2"]);
454+
455+
456+
store
457+
.upsert("prereq1", PatchTarget::Flag(StorageItem::Item(prereq1)))
458+
.expect("patch should apply");
459+
460+
store
461+
.upsert("prereq2", PatchTarget::Flag(StorageItem::Item(prereq2)))
462+
.expect("patch should apply");
463+
464+
store
465+
.upsert("toplevel", PatchTarget::Flag(StorageItem::Item(toplevel)))
466+
.expect("patch should apply");
467+
468+
let mut flag_detail = FlagDetail::new(true);
469+
flag_detail.populate(&store, &context, FlagDetailConfig::new());
470+
471+
let expected = json!({
472+
"prereq1": true,
473+
"prereq2": true,
474+
"toplevel": true,
475+
"$flagsState": {
476+
"toplevel": {
477+
"version": 42,
478+
"variation": 1,
479+
"prerequisites": ["prereq1", "prereq2"]
480+
},
481+
"prereq2": {
482+
"version": 42,
483+
"variation": 1
484+
},
485+
"prereq1": {
486+
"version": 42,
487+
"variation": 1,
488+
},
489+
},
490+
"$valid": true
491+
});
492+
493+
assert_json_eq!(expected, flag_detail);
494+
495+
}
413496
}

launchdarkly-server-sdk/src/test_common.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub fn basic_flag(key: &str) -> Flag {
2424
}}"#,
2525
serde_json::Value::String(key.to_string())
2626
))
27-
.unwrap()
27+
.unwrap()
2828
}
2929

3030
pub fn basic_off_flag(key: &str) -> Flag {
@@ -51,14 +51,26 @@ pub fn basic_off_flag(key: &str) -> Flag {
5151
}
5252

5353
pub fn basic_flag_with_prereq(key: &str, prereq_key: &str) -> Flag {
54+
basic_flag_with_prereqs(key, &[prereq_key])
55+
}
56+
57+
pub fn basic_flag_with_prereqs(key: &str, prereq_keys: &[&str]) -> Flag {
58+
59+
let prereqs_json: String = prereq_keys
60+
.iter()
61+
.map(|&prereq_key| format!(r#"{{"key": {}, "variation": 1}}"#, serde_json::Value::String(prereq_key.to_string())))
62+
.collect::<Vec<String>>()
63+
.join(",");
64+
65+
5466
serde_json::from_str(&format!(
5567
r#"{{
5668
"key": {},
5769
"version": 42,
5870
"on": true,
5971
"targets": [],
6072
"rules": [],
61-
"prerequisites": [{{"key": {}, "variation": 1}}],
73+
"prerequisites": [{}],
6274
"fallthrough": {{"variation": 1}},
6375
"offVariation": 0,
6476
"variations": [false, true],
@@ -69,9 +81,9 @@ pub fn basic_flag_with_prereq(key: &str, prereq_key: &str) -> Flag {
6981
"salt": "kosher"
7082
}}"#,
7183
serde_json::Value::String(key.to_string()),
72-
serde_json::Value::String(prereq_key.to_string()),
84+
prereqs_json
7385
))
74-
.unwrap()
86+
.unwrap()
7587
}
7688

7789
pub fn basic_int_flag(key: &str) -> Flag {

0 commit comments

Comments
 (0)