Skip to content

Commit 78c9668

Browse files
authored
feat: Add option to omit anonymous users from index and identify events (#89)
1 parent 5a52e41 commit 78c9668

File tree

6 files changed

+213
-17
lines changed

6 files changed

+213
-17
lines changed

contract-tests/src/client_entity.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ impl ClientEntity {
117117
processor_builder.private_attributes(attributes);
118118
}
119119
processor_builder.https_connector(connector.clone());
120+
processor_builder.omit_anonymous_contexts(events.omit_anonymous_contexts);
120121

121122
config_builder.event_processor(&processor_builder)
122123
} else {

contract-tests/src/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ pub struct EventParameters {
4242
pub all_attributes_private: bool,
4343
pub global_private_attributes: Option<HashSet<Reference>>,
4444
pub flush_interval_ms: Option<u64>,
45+
#[serde(default = "bool::default")]
46+
pub omit_anonymous_contexts: bool,
4547
}
4648

4749
#[derive(Deserialize, Debug)]
@@ -103,6 +105,7 @@ async fn status() -> impl Responder {
103105
"secure-mode-hash".to_string(),
104106
"inline-context".to_string(),
105107
"anonymous-redaction".to_string(),
108+
"omit-anonymous-contexts".to_string(),
106109
],
107110
})
108111
}

launchdarkly-server-sdk/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ lazy_static = "1.4.0"
2525
log = "0.4.14"
2626
lru = { version = "0.12.0", default-features = false }
2727
ring = "0.17.5"
28-
launchdarkly-server-sdk-evaluation = "1.2.0"
28+
launchdarkly-server-sdk-evaluation = "2.0.0"
2929
serde = { version = "1.0.132", features = ["derive"] }
3030
serde_json = { version = "1.0.73", features = ["float_roundtrip"] }
3131
thiserror = "1.0"

launchdarkly-server-sdk/src/events/dispatcher.rs

Lines changed: 194 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
use crossbeam_channel::{bounded, select, tick, Receiver, Sender};
2-
use std::collections::HashSet;
3-
use std::iter::FromIterator;
42
use std::time::SystemTime;
53

64
use launchdarkly_server_sdk_evaluation::Context;
75
use lru::LruCache;
86

9-
use super::event::FeatureRequestEvent;
7+
use super::event::{BaseEvent, FeatureRequestEvent, IndexEvent};
108
use super::sender::EventSenderResult;
119
use super::{
1210
event::{EventSummary, InputEvent, OutputEvent},
@@ -192,11 +190,13 @@ impl EventDispatcher {
192190
self.events_configuration.private_attributes.clone(),
193191
);
194192

195-
if self.notice_context(&fre.base.context) {
196-
self.outbox.add_event(OutputEvent::Index(fre.to_index_event(
193+
if let Some(context) = self.get_indexable_context(&fre.base) {
194+
let base = BaseEvent::new(fre.base.creation_date, context).into_inline(
197195
self.events_configuration.all_attributes_private,
198196
self.events_configuration.private_attributes.clone(),
199-
)));
197+
);
198+
self.outbox
199+
.add_event(OutputEvent::Index(IndexEvent::from(base)));
200200
}
201201

202202
let now = match SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
@@ -219,30 +219,53 @@ impl EventDispatcher {
219219
self.outbox.add_event(OutputEvent::FeatureRequest(inlined));
220220
}
221221
}
222-
InputEvent::Identify(identify) => {
222+
InputEvent::Identify(mut identify) => {
223+
if self.events_configuration.omit_anonymous_contexts {
224+
match identify.base.context.without_anonymous_contexts() {
225+
Ok(context) => identify.base.context = context,
226+
Err(_) => return,
227+
}
228+
}
229+
223230
self.notice_context(&identify.base.context);
224231
self.outbox
225232
.add_event(OutputEvent::Identify(identify.into_inline(
226233
self.events_configuration.all_attributes_private,
227-
HashSet::from_iter(
228-
self.events_configuration.private_attributes.iter().cloned(),
229-
),
234+
self.events_configuration.private_attributes.clone(),
230235
)));
231236
}
232237
InputEvent::Custom(custom) => {
233-
if self.notice_context(&custom.base.context) {
238+
if let Some(context) = self.get_indexable_context(&custom.base) {
239+
let base = BaseEvent::new(custom.base.creation_date, context).into_inline(
240+
self.events_configuration.all_attributes_private,
241+
self.events_configuration.private_attributes.clone(),
242+
);
234243
self.outbox
235-
.add_event(OutputEvent::Index(custom.to_index_event(
236-
self.events_configuration.all_attributes_private,
237-
self.events_configuration.private_attributes.clone(),
238-
)));
244+
.add_event(OutputEvent::Index(IndexEvent::from(base)));
239245
}
240246

241247
self.outbox.add_event(OutputEvent::Custom(custom));
242248
}
243249
}
244250
}
245251

252+
fn get_indexable_context(&mut self, event: &BaseEvent) -> Option<Context> {
253+
let context = match self.events_configuration.omit_anonymous_contexts {
254+
true => event.context.without_anonymous_contexts(),
255+
false => Ok(event.context.clone()),
256+
};
257+
258+
if let Ok(ctx) = context {
259+
if self.notice_context(&ctx) {
260+
return Some(ctx);
261+
}
262+
263+
return None;
264+
}
265+
266+
None
267+
}
268+
246269
fn notice_context(&mut self, context: &Context) -> bool {
247270
let key = context.canonical_key();
248271

@@ -273,7 +296,9 @@ mod tests {
273296
use crate::events::event::{EventFactory, OutputEvent};
274297
use crate::events::{create_event_sender, create_events_configuration};
275298
use crate::test_common::basic_flag;
276-
use launchdarkly_server_sdk_evaluation::{ContextBuilder, Detail, FlagValue, Reason};
299+
use launchdarkly_server_sdk_evaluation::{
300+
ContextBuilder, Detail, FlagValue, MultiContextBuilder, Reason,
301+
};
277302
use test_case::test_case;
278303

279304
#[test]
@@ -359,6 +384,48 @@ mod tests {
359384
assert_eq!(1, dispatcher.outbox.summary.features.len());
360385
}
361386

387+
#[test]
388+
fn dispatcher_strips_anonymous_contexts_from_index_for_feature_request_events() {
389+
let (event_sender, _) = create_event_sender();
390+
let mut events_configuration =
391+
create_events_configuration(event_sender, Duration::from_secs(100));
392+
events_configuration.omit_anonymous_contexts = true;
393+
let mut dispatcher = create_dispatcher(events_configuration);
394+
395+
let context = ContextBuilder::new("context")
396+
.anonymous(true)
397+
.build()
398+
.expect("Failed to create context");
399+
let mut flag = basic_flag("flag");
400+
flag.debug_events_until_date = Some(64_060_606_800_000);
401+
flag.track_events = true;
402+
403+
let detail = Detail {
404+
value: Some(FlagValue::from(false)),
405+
variation_index: Some(1),
406+
reason: Reason::Fallthrough {
407+
in_experiment: false,
408+
},
409+
};
410+
411+
let event_factory = EventFactory::new(true);
412+
let feature_request_event = event_factory.new_eval_event(
413+
&flag.key,
414+
context,
415+
&flag,
416+
detail,
417+
FlagValue::from(false),
418+
None,
419+
);
420+
421+
dispatcher.process_event(feature_request_event);
422+
assert_eq!(2, dispatcher.outbox.events.len());
423+
assert_eq!("debug", dispatcher.outbox.events[0].kind());
424+
assert_eq!("feature", dispatcher.outbox.events[1].kind());
425+
assert_eq!(0, dispatcher.context_keys.len());
426+
assert_eq!(1, dispatcher.outbox.summary.features.len());
427+
}
428+
362429
#[test_case(0, 64_060_606_800_000, vec!["debug", "index", "summary"])]
363430
#[test_case(64_060_606_800_000, 64_060_606_800_000, vec!["index", "summary"])]
364431
#[test_case(64_060_606_800_001, 64_060_606_800_000, vec!["index", "summary"])]
@@ -446,6 +513,61 @@ mod tests {
446513
assert_eq!(1, dispatcher.context_keys.len());
447514
}
448515

516+
#[test]
517+
fn dispatcher_can_ignore_identify_if_anonymous() {
518+
let (event_sender, _) = create_event_sender();
519+
let mut events_configuration =
520+
create_events_configuration(event_sender, Duration::from_secs(100));
521+
events_configuration.omit_anonymous_contexts = true;
522+
let mut dispatcher = create_dispatcher(events_configuration);
523+
524+
let context = ContextBuilder::new("context")
525+
.anonymous(true)
526+
.build()
527+
.expect("Failed to create context");
528+
let event_factory = EventFactory::new(true);
529+
530+
dispatcher.process_event(event_factory.new_identify(context));
531+
assert_eq!(0, dispatcher.outbox.events.len());
532+
assert_eq!(0, dispatcher.context_keys.len());
533+
}
534+
535+
#[test]
536+
fn dispatcher_strips_anon_contexts_from_multi_kind_identify() {
537+
let (event_sender, _) = create_event_sender();
538+
let mut events_configuration =
539+
create_events_configuration(event_sender, Duration::from_secs(100));
540+
events_configuration.omit_anonymous_contexts = true;
541+
let mut dispatcher = create_dispatcher(events_configuration);
542+
543+
let user_context = ContextBuilder::new("user")
544+
.anonymous(true)
545+
.build()
546+
.expect("Failed to create context");
547+
let org_context = ContextBuilder::new("org")
548+
.kind("org")
549+
.build()
550+
.expect("Failed to create context");
551+
let context = MultiContextBuilder::new()
552+
.add_context(user_context)
553+
.add_context(org_context)
554+
.build()
555+
.expect("Failed to create context");
556+
557+
let event_factory = EventFactory::new(true);
558+
559+
dispatcher.process_event(event_factory.new_identify(context));
560+
assert_eq!(1, dispatcher.outbox.events.len());
561+
assert_eq!("identify", dispatcher.outbox.events[0].kind());
562+
assert_eq!(1, dispatcher.context_keys.len());
563+
564+
if let OutputEvent::Identify(identify) = &dispatcher.outbox.events[0] {
565+
assert_eq!("org:org", identify.base.context.canonical_key());
566+
} else {
567+
panic!("Expected an identify event");
568+
}
569+
}
570+
449571
#[test]
450572
fn dispatcher_adds_index_on_custom_event() {
451573
let (event_sender, _) = create_event_sender();
@@ -468,6 +590,62 @@ mod tests {
468590
assert_eq!(1, dispatcher.context_keys.len());
469591
}
470592

593+
#[test]
594+
fn dispatcher_can_strip_anonymous_from_index_events() {
595+
let (event_sender, _) = create_event_sender();
596+
let mut events_configuration =
597+
create_events_configuration(event_sender, Duration::from_secs(100));
598+
events_configuration.omit_anonymous_contexts = true;
599+
let mut dispatcher = create_dispatcher(events_configuration);
600+
601+
let context = ContextBuilder::new("context")
602+
.anonymous(true)
603+
.build()
604+
.expect("Failed to create context");
605+
let event_factory = EventFactory::new(true);
606+
let custom_event = event_factory
607+
.new_custom(context, "context", None, "")
608+
.expect("failed to make new custom event");
609+
610+
dispatcher.process_event(custom_event);
611+
assert_eq!(1, dispatcher.outbox.events.len());
612+
assert_eq!("custom", dispatcher.outbox.events[0].kind());
613+
assert_eq!(0, dispatcher.context_keys.len());
614+
}
615+
616+
#[test]
617+
fn dispatcher_can_strip_anonymous_from_index_events_with_multi_kinds() {
618+
let (event_sender, _) = create_event_sender();
619+
let mut events_configuration =
620+
create_events_configuration(event_sender, Duration::from_secs(100));
621+
events_configuration.omit_anonymous_contexts = true;
622+
let mut dispatcher = create_dispatcher(events_configuration);
623+
624+
let user_context = ContextBuilder::new("user")
625+
.anonymous(true)
626+
.build()
627+
.expect("Failed to create context");
628+
let org_context = ContextBuilder::new("org")
629+
.kind("org")
630+
.build()
631+
.expect("Failed to create context");
632+
let context = MultiContextBuilder::new()
633+
.add_context(user_context)
634+
.add_context(org_context)
635+
.build()
636+
.expect("Failed to create context");
637+
let event_factory = EventFactory::new(true);
638+
let custom_event = event_factory
639+
.new_custom(context, "context", None, "")
640+
.expect("failed to make new custom event");
641+
642+
dispatcher.process_event(custom_event);
643+
assert_eq!(2, dispatcher.outbox.events.len());
644+
assert_eq!("index", dispatcher.outbox.events[0].kind());
645+
assert_eq!("custom", dispatcher.outbox.events[1].kind());
646+
assert_eq!(1, dispatcher.context_keys.len());
647+
}
648+
471649
#[test]
472650
fn can_process_events_successfully() {
473651
let (event_sender, event_rx) = create_event_sender();

launchdarkly-server-sdk/src/events/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub struct EventsConfiguration {
2020
context_keys_flush_interval: Duration,
2121
all_attributes_private: bool,
2222
private_attributes: HashSet<Reference>,
23+
omit_anonymous_contexts: bool,
2324
}
2425

2526
#[cfg(test)]
@@ -35,6 +36,7 @@ fn create_events_configuration(
3536
context_keys_flush_interval: Duration::from_secs(100),
3637
all_attributes_private: false,
3738
private_attributes: HashSet::new(),
39+
omit_anonymous_contexts: false,
3840
}
3941
}
4042

launchdarkly-server-sdk/src/events/processor_builders.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ pub struct EventProcessorBuilder<C> {
8383
all_attributes_private: bool,
8484
private_attributes: HashSet<Reference>,
8585
connector: Option<C>,
86+
omit_anonymous_contexts: bool,
8687
// diagnostic_recording_interval: Duration
8788
}
8889

@@ -149,6 +150,7 @@ where
149150
context_keys_flush_interval: self.context_keys_flush_interval,
150151
all_attributes_private: self.all_attributes_private,
151152
private_attributes: self.private_attributes.clone(),
153+
omit_anonymous_contexts: self.omit_anonymous_contexts,
152154
};
153155

154156
let events_processor =
@@ -174,6 +176,7 @@ impl<C> EventProcessorBuilder<C> {
174176
event_sender: None,
175177
all_attributes_private: false,
176178
private_attributes: HashSet::new(),
179+
omit_anonymous_contexts: false,
177180
connector: None,
178181
}
179182
}
@@ -246,6 +249,15 @@ impl<C> EventProcessorBuilder<C> {
246249
self
247250
}
248251

252+
/// Sets whether anonymous contexts should be omitted from index and identify events.
253+
///
254+
/// The default is false, meaning that anonymous contexts will be included in index and
255+
/// identify events.
256+
pub fn omit_anonymous_contexts(&mut self, omit: bool) -> &mut Self {
257+
self.omit_anonymous_contexts = omit;
258+
self
259+
}
260+
249261
#[cfg(test)]
250262
pub fn event_sender(&mut self, event_sender: Arc<dyn EventSender>) -> &mut Self {
251263
self.event_sender = Some(event_sender);

0 commit comments

Comments
 (0)