@@ -10,6 +10,7 @@ use flagsmith_flag_engine::segments::Segment;
1010use log:: debug;
1111use reqwest:: header:: { self , HeaderMap } ;
1212use serde_json:: json;
13+ use std:: collections:: HashMap ;
1314use std:: sync:: mpsc:: { self , SyncSender , TryRecvError } ;
1415use std:: sync:: { Arc , Mutex } ;
1516use std:: { thread, time:: Duration } ;
@@ -62,6 +63,7 @@ pub struct Flagsmith {
6263
6364struct DataStore {
6465 environment : Option < Environment > ,
66+ identities_with_overrides_by_identifier : HashMap < String , Identity > ,
6567}
6668
6769impl Flagsmith {
@@ -109,7 +111,7 @@ impl Flagsmith {
109111
110112 // Put the environment model behind mutex to
111113 // to share it safely between threads
112- let ds = Arc :: new ( Mutex :: new ( DataStore { environment : None } ) ) ;
114+ let ds = Arc :: new ( Mutex :: new ( DataStore { environment : None , identities_with_overrides_by_identifier : HashMap :: new ( ) } ) ) ;
113115 let ( tx, rx) = mpsc:: sync_channel :: < u32 > ( 1 ) ;
114116
115117 let flagsmith = Flagsmith {
@@ -141,6 +143,10 @@ impl Flagsmith {
141143 flagsmith. options . environment_refresh_interval_mills ;
142144
143145 if flagsmith. options . enable_local_evaluation {
146+ // Update environment once...
147+ update_environment ( & client, & ds, & environment_url) . unwrap ( ) ;
148+
149+ // ...and continue updating in the background
144150 let ds = Arc :: clone ( & ds) ;
145151 thread:: spawn ( move || loop {
146152 match rx. try_recv ( ) {
@@ -150,14 +156,8 @@ impl Flagsmith {
150156 }
151157 Err ( TryRecvError :: Empty ) => { }
152158 }
153-
154- let environment = Some (
155- get_environment_from_api ( & client, environment_url. clone ( ) )
156- . expect ( "updating environment document failed" ) ,
157- ) ;
158- let mut data = ds. lock ( ) . unwrap ( ) ;
159- data. environment = environment;
160159 thread:: sleep ( Duration :: from_millis ( environment_refresh_interval_mills) ) ;
160+ update_environment ( & client, & ds, & environment_url) . unwrap ( ) ;
161161 } ) ;
162162 }
163163 return flagsmith;
@@ -201,7 +201,7 @@ impl Flagsmith {
201201 let traits = traits. unwrap_or ( vec ! [ ] ) ;
202202 if data. environment . is_some ( ) {
203203 let environment = data. environment . as_ref ( ) . unwrap ( ) ;
204- return self . get_identity_flags_from_document ( environment, identifier, traits) ;
204+ return self . get_identity_flags_from_document ( environment, & data . identities_with_overrides_by_identifier , identifier, traits) ;
205205 }
206206 return self . default_handler_if_err ( self . get_identity_flags_from_api ( identifier, traits) ) ;
207207 }
@@ -219,8 +219,9 @@ impl Flagsmith {
219219 ) ) ;
220220 }
221221 let environment = data. environment . as_ref ( ) . unwrap ( ) ;
222+ let identities_with_overrides_by_identifier = & data. identities_with_overrides_by_identifier ;
222223 let identity_model =
223- self . build_identity_model ( environment, identifier, traits. clone ( ) . unwrap_or ( vec ! [ ] ) ) ?;
224+ self . get_identity_model ( & environment, & identities_with_overrides_by_identifier , identifier, traits. clone ( ) . unwrap_or ( vec ! [ ] ) ) ?;
224225 let segments = get_identity_segments ( environment, & identity_model, traits. as_ref ( ) ) ;
225226 return Ok ( segments) ;
226227 }
@@ -254,21 +255,17 @@ impl Flagsmith {
254255 ) ;
255256 }
256257 pub fn update_environment ( & mut self ) -> Result < ( ) , error:: Error > {
257- let mut data = self . datastore . lock ( ) . unwrap ( ) ;
258- data. environment = Some ( get_environment_from_api (
259- & self . client ,
260- self . environment_url . clone ( ) ,
261- ) ?) ;
262- return Ok ( ( ) ) ;
258+ return update_environment ( & self . client , & self . datastore , & self . environment_url ) ;
263259 }
264260
265261 fn get_identity_flags_from_document (
266262 & self ,
267263 environment : & Environment ,
264+ identities_with_overrides_by_identifier : & HashMap < String , Identity > ,
268265 identifier : & str ,
269266 traits : Vec < Trait > ,
270267 ) -> Result < Flags , error:: Error > {
271- let identity = self . build_identity_model ( environment, identifier, traits. clone ( ) ) ?;
268+ let identity = self . get_identity_model ( environment, identities_with_overrides_by_identifier , identifier, traits. clone ( ) ) ?;
272269 let feature_states =
273270 engine:: get_identity_feature_states ( environment, & identity, Some ( traits. as_ref ( ) ) ) ;
274271 let flags = Flags :: from_feature_states (
@@ -280,15 +277,23 @@ impl Flagsmith {
280277 return Ok ( flags) ;
281278 }
282279
283- fn build_identity_model (
280+ fn get_identity_model (
284281 & self ,
285282 environment : & Environment ,
283+ identities_with_overrides_by_identifier : & HashMap < String , Identity > ,
286284 identifier : & str ,
287285 traits : Vec < Trait > ,
288286 ) -> Result < Identity , error:: Error > {
289- let mut identity = Identity :: new ( identifier. to_string ( ) , environment. api_key . clone ( ) ) ;
287+ let mut identity: Identity ;
288+
289+ if identities_with_overrides_by_identifier. contains_key ( identifier) {
290+ identity = identities_with_overrides_by_identifier. get ( identifier) . unwrap ( ) . clone ( ) ;
291+ } else {
292+ identity = Identity :: new ( identifier. to_string ( ) , environment. api_key . clone ( ) ) ;
293+ }
294+
290295 identity. identity_traits = traits;
291- Ok ( identity)
296+ return Ok ( identity. to_owned ( ) )
292297 }
293298 fn get_identity_flags_from_api (
294299 & self ,
@@ -358,6 +363,23 @@ fn get_environment_from_api(
358363 return Ok ( environment) ;
359364}
360365
366+ fn update_environment (
367+ client : & reqwest:: blocking:: Client ,
368+ datastore : & Arc < Mutex < DataStore > > ,
369+ environment_url : & String ,
370+ ) -> Result < ( ) , error:: Error > {
371+ let mut data = datastore. lock ( ) . unwrap ( ) ;
372+ let environment = Some ( get_environment_from_api (
373+ & client,
374+ environment_url. clone ( ) ,
375+ ) ?) ;
376+ for identity in & environment. as_ref ( ) . unwrap ( ) . identity_overrides {
377+ data. identities_with_overrides_by_identifier . insert ( identity. identifier . clone ( ) , identity. clone ( ) ) ;
378+ }
379+ data. environment = environment;
380+ return Ok ( ( ) ) ;
381+ }
382+
361383fn get_json_response (
362384 client : & reqwest:: blocking:: Client ,
363385 method : reqwest:: Method ,
@@ -385,24 +407,87 @@ mod tests {
385407 use httpmock:: prelude:: * ;
386408
387409 static ENVIRONMENT_JSON : & str = r#"{
388- "api_key": "B62qaMZNwfiqT76p38ggrQ",
389- "project": {
390- "name": "Test project",
391- "organisation": {
392- "feature_analytics": false,
393- "name": "Test Org",
394- "id": 1,
395- "persist_trait_data": true,
396- "stop_serving_flags": false
410+ "api_key": "B62qaMZNwfiqT76p38ggrQ",
411+ "project": {
412+ "name": "Test project",
413+ "organisation": {
414+ "feature_analytics": false,
415+ "name": "Test Org",
416+ "id": 1,
417+ "persist_trait_data": true,
418+ "stop_serving_flags": false
419+ },
420+ "segments": [],
421+ "id": 1,
422+ "hide_disabled_flags": false
423+ },
424+ "segment_overrides": [],
425+ "id": 1,
426+ "feature_states": [
427+ {
428+ "multivariate_feature_state_values": [],
429+ "feature_state_value": "some-value",
430+ "id": 1,
431+ "featurestate_uuid": "40eb539d-3713-4720-bbd4-829dbef10d51",
432+ "feature": {
433+ "name": "some_feature",
434+ "type": "STANDARD",
435+ "id": 1
436+ },
437+ "segment_id": null,
438+ "enabled": true
439+ },
440+ {
441+ "feature": {
442+ "id": 83755,
443+ "name": "test_mv",
444+ "type": "MULTIVARIATE"
445+ },
446+ "enabled": false,
447+ "django_id": 482285,
448+ "feature_segment": null,
449+ "featurestate_uuid": "c3af5fbf-39ba-422c-a846-f2fea952b37c",
450+ "feature_state_value": "1111",
451+ "multivariate_feature_state_values": [
452+ {
453+ "multivariate_feature_option": {
454+ "value": "8888",
455+ "id": 11516
397456 },
457+ "percentage_allocation": 100.0,
458+ "id": 38451,
459+ "mv_fs_value_uuid": "a4299c73-2430-47e4-9185-42accd01686c"
460+ }
461+ ]
462+ }
463+ ],
464+ "updated_at": "2023-07-14 16:12:00.000000",
465+ "identity_overrides": [
466+ {
467+ "identifier": "overridden-id",
468+ "identity_uuid": "0f21cde8-63c5-4e50-baca-87897fa6cd01",
469+ "created_date": "2019-08-27T14:53:45.698555Z",
470+ "updated_at": "2023-07-14 16:12:00.000000",
471+ "environment_api_key": "B62qaMZNwfiqT76p38ggrQ",
472+ "identity_features": [
473+ {
398474 "id": 1,
399- "hide_disabled_flags": false,
400- "segments": []
401- },
402- "segment_overrides": [],
403- "id": 1,
404- "feature_states": []
405- }"# ;
475+ "feature": {
476+ "id": 1,
477+ "name": "some_feature",
478+ "type": "STANDARD"
479+ },
480+ "featurestate_uuid": "1bddb9a5-7e59-42c6-9be9-625fa369749f",
481+ "feature_state_value": "some-overridden-value",
482+ "enabled": false,
483+ "environment": 1,
484+ "identity": null,
485+ "feature_segment": null
486+ }
487+ ]
488+ }
489+ ]
490+ }"# ;
406491
407492 #[ test]
408493 fn client_implements_send_and_sync ( ) {
@@ -472,4 +557,37 @@ mod tests {
472557 // for each subsequent refresh
473558 api_mock. assert_hits ( 3 ) ;
474559 }
560+
561+ #[ test]
562+ fn test_local_evaluation_identity_override_evaluate_expected ( ) {
563+ // Given
564+ let environment_key = "ser.test_environment_key" ;
565+ let response_body: serde_json:: Value = serde_json:: from_str ( ENVIRONMENT_JSON ) . unwrap ( ) ;
566+
567+ let mock_server = MockServer :: start ( ) ;
568+ mock_server. mock ( |when, then| {
569+ when. method ( GET )
570+ . path ( "/api/v1/environment-document/" )
571+ . header ( "X-Environment-Key" , environment_key) ;
572+ then. status ( 200 ) . json_body ( response_body) ;
573+ } ) ;
574+
575+ let url = mock_server. url ( "/api/v1/" ) ;
576+
577+ let flagsmith_options = FlagsmithOptions {
578+ api_url : url,
579+ environment_refresh_interval_mills : 100 ,
580+ enable_local_evaluation : true ,
581+ ..Default :: default ( )
582+ } ;
583+
584+ // When
585+ let mut _flagsmith = Flagsmith :: new ( environment_key. to_string ( ) , flagsmith_options) ;
586+
587+ // Then
588+ let flags = _flagsmith. get_environment_flags ( ) ;
589+ let identity_flags = _flagsmith. get_identity_flags ( "overridden-id" , None ) ;
590+ assert_eq ! ( flags. unwrap( ) . get_feature_value_as_string( "some_feature" ) . unwrap( ) . to_owned( ) , "some-value" ) ;
591+ assert_eq ! ( identity_flags. unwrap( ) . get_feature_value_as_string( "some_feature" ) . unwrap( ) . to_owned( ) , "some-overridden-value" ) ;
592+ }
475593}
0 commit comments