launchdarkly-server-sdk-evaluation 2.1.3

LaunchDarkly feature flag evaluation engine
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
#![cfg(test)]

use crate::flag::Flag;
use crate::segment::Segment;
use crate::store::Store;
use crate::test_data::FlagBuilder;
use crate::AttributeValue;
use crate::FlagValue;
use crate::PrerequisiteEvent;
use crate::PrerequisiteEventRecorder;
use maplit::hashmap;
use std::cell::RefCell;
use std::collections::HashMap;

pub struct TestStore {
    flags: HashMap<String, Flag>,
    segments: HashMap<String, Segment>,
}

impl TestStore {
    pub fn new() -> Self {
        Self {
            flags: hashmap! {
                "flag".to_string() => FlagBuilder::new("flag")
                    .on(false)
                    .variations(vec![FlagValue::Bool(false), FlagValue::Bool(true)])
                    .fallthrough_variation_index(1)
                    .off_variation_index(0)
                    .build(),
                "flagWithRuleExclusion".to_string() => serde_json::from_str(r#"{
                        "key": "flag",
                        "version": 42,
                        "on": false,
                        "targets": [],
                        "rules": [
                            {
                                "variation": 0,
                                "id": "6a7755ac-e47a-40ea-9579-a09dd5f061bd",
                                "clauses": [
                                    {
                                        "attribute": "platform",
                                        "op": "in",
                                        "values": [
                                            "web",
                                            "aem",
                                            "ios"
                                        ],
                                        "negate": false
                                    }
                                ],
                                "trackEvents": true
                            }
                        ],
                        "prerequisites": [],
                        "fallthrough": {"variation": 1},
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty",
                        "trackEvents": false,
                        "trackEventsFallthrough": true,
                        "debugEventsUntilDate": 1500000000
                    }"#).unwrap(),
                "flagWithMatchesOpOnGroups".to_string() => serde_json::from_str(r#"{
                        "key": "flagWithMatchesOpOnGroups",
                        "version": 42,
                        "on": true,
                        "targets": [],
                        "rules": [
                            {
                                "variation": 0,
                                "id": "6a7755ac-e47a-40ea-9579-a09dd5f061bd",
                                "clauses": [
                                    {
                                        "attribute": "groups",
                                        "op": "matches",
                                        "values": [
                                            "^\\w+"
                                        ],
                                        "negate": false
                                    }
                                ],
                                "trackEvents": true
                            }
                        ],
                        "prerequisites": [],
                        "fallthrough": {"variation": 1},
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty",
                        "trackEvents": false,
                        "trackEventsFallthrough": true,
                        "debugEventsUntilDate": 1500000000
                    }"#).unwrap(),
                "flagWithMatchesOpOnKinds".to_string() => serde_json::from_str(r#"{
                        "key": "flagWithMatchesOpOnKinds",
                        "version": 42,
                        "on": true,
                        "targets": [],
                        "rules": [
                            {
                                "variation": 0,
                                "id": "6a7755ac-e47a-40ea-9579-a09dd5f061bd",
                                "clauses": [
                                    {
                                        "attribute": "kind",
                                        "op": "matches",
                                        "values": [
                                            "^[ou]"
                                        ],
                                        "negate": false
                                    }
                                ],
                                "trackEvents": true
                            }
                        ],
                        "prerequisites": [],
                        "fallthrough": {"variation": 1},
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty",
                        "trackEvents": false,
                        "trackEventsFallthrough": true,
                        "debugEventsUntilDate": 1500000000
                    }"#).unwrap(),
                "flagWithMatchesOpOnKindsAttributeReference".to_string() => serde_json::from_str(r#"{
                        "key": "flagWithMatchesOpOnKindsAttributeReference",
                        "version": 42,
                        "on": true,
                        "targets": [],
                        "rules": [
                            {
                                "variation": 0,
                                "id": "6a7755ac-e47a-40ea-9579-a09dd5f061bd",
                                "clauses": [
                                    {
                                        "attribute": "/kind",
                                        "op": "matches",
                                        "values": [
                                            "^[ou]"
                                        ],
                                        "negate": false,
                                        "contextKind" : "arbitrary"
                                    }
                                ],
                                "trackEvents": true
                            }
                        ],
                        "prerequisites": [],
                        "fallthrough": {"variation": 1},
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty",
                        "trackEvents": false,
                        "trackEventsFallthrough": true,
                        "debugEventsUntilDate": 1500000000
                    }"#).unwrap(),
                "flagWithMatchesOpOnKindsPlainAttributeReference".to_string() => serde_json::from_str(r#"{
                        "key": "flagWithMatchesOpOnKindsPlainAttributeReference",
                        "version": 42,
                        "on": true,
                        "targets": [],
                        "rules": [
                            {
                                "variation": 0,
                                "id": "6a7755ac-e47a-40ea-9579-a09dd5f061bd",
                                "clauses": [
                                    {
                                        "attribute": "kind",
                                        "op": "matches",
                                        "values": [
                                            "^[ou]"
                                        ],
                                        "negate": false,
                                        "contextKind" : "arbitrary"
                                    }
                                ],
                                "trackEvents": true
                            }
                        ],
                        "prerequisites": [],
                        "fallthrough": {"variation": 1},
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty",
                        "trackEvents": false,
                        "trackEventsFallthrough": true,
                        "debugEventsUntilDate": 1500000000
                    }"#).unwrap(),
                "flagWithTrackAndDebugEvents".to_string() => serde_json::from_str(r#"{
                        "key": "flag",
                        "version": 42,
                        "on": false,
                        "targets": [],
                        "rules": [],
                        "prerequisites": [],
                        "fallthrough": {"variation": 1},
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty",
                        "trackEvents": true,
                        "trackEventsFallthrough": true,
                        "debugEventsUntilDate": 1500000000
                    }"#).unwrap(),
                "flagWithExperiment".to_string() => serde_json::from_str(r#"{
                        "key": "flagWithExperiment",
                        "version": 42,
                        "on": true,
                        "targets": [],
                        "rules": [],
                        "prerequisites": [],
                        "fallthrough": {
                          "rollout": {
                            "kind": "experiment",
                            "seed": 61,
                            "variations": [
                              {"variation": 0, "weight": 10000, "untracked": false},
                              {"variation": 1, "weight": 20000, "untracked": false},
                              {"variation": 0, "weight": 70000, "untracked": true}
                            ]
                          }
                        },
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty",
                        "trackEvents": false,
                        "trackEventsFallthrough": false,
                        "debugEventsUntilDate": 1500000000
                    }"#).unwrap(),
                "flagWithExperimentTargetingContext".to_string() => serde_json::from_str(r#"{
                        "key": "flagWithExperimentTargetingContext",
                        "version": 42,
                        "on": true,
                        "targets": [],
                        "rules": [],
                        "prerequisites": [],
                        "fallthrough": {
                          "rollout": {
                            "kind": "experiment",
                            "contextKind": "org",
                            "seed": 61,
                            "variations": [
                              {"variation": 0, "weight": 10000, "untracked": false},
                              {"variation": 1, "weight": 20000, "untracked": false},
                              {"variation": 0, "weight": 70000, "untracked": true}
                            ]
                          }
                        },
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty",
                        "trackEvents": false,
                        "trackEventsFallthrough": false,
                        "debugEventsUntilDate": 1500000000
                    }"#).unwrap(),
                "flagWithRolloutBucketBy".to_string() => serde_json::from_str(r#"{
                        "key": "rollout",
                        "on": true,
                        "prerequisites": [],
                        "targets": [],
                        "rules": [
                            {
                                "rollout": {
                                    "variations": [
                                        {
                                            "variation": 0,
                                            "weight": 50000
                                        },
                                        {
                                            "variation": 1,
                                            "weight": 50000
                                        },
                                        {
                                            "variation": 2,
                                            "weight": 0
                                        }
                                    ],
                                    "bucketBy": "ld_quid"
                                },
                                "id": "6a7755ac-e47a-40ea-9579-a09dd5f061bd",
                                "clauses": [
                                    {
                                        "attribute": "platform",
                                        "op": "in",
                                        "values": [
                                            "web",
                                            "aem",
                                            "ios"
                                        ],
                                        "negate": false
                                    }
                                ],
                                "trackEvents": false
                            }
                        ],
                        "fallthrough": {
                            "variation": 2
                        },
                        "offVariation": 1,
                        "variations": [
                            "rollout1",
                            "rollout2",
                            "rollout3"
                        ],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingMobileKey": true,
                            "usingEnvironmentId": true
                        },
                        "salt": "ce2634f116d741a7ad1b7ef363f6f9bc",
                        "trackEvents": false,
                        "trackEventsFallthrough": false,
                        "debugEventsUntilDate": null,
                        "version": 7
                    }"#).unwrap(),
                "flagWithTarget".to_string() => FlagBuilder::new("flagWithTarget")
                    .on(false)
                    .variations(vec![FlagValue::Bool(false), FlagValue::Bool(true)])
                    .variation_index_for_user("bob", 0)
                    .fallthrough_variation_index(1)
                    .off_variation_index(0)
                    .build(),
                "flagWithContextTarget".to_string() => serde_json::from_str(r#"{
                        "key": "flagWithContextTarget",
                        "version": 42,
                        "on": true,
                        "targets": [{
                            "values": ["bob"],
                            "variation": 1
                        }],
                        "contextTargets": [{
                            "contextKind": "org",
                            "values": ["LaunchDarkly"],
                            "variation": 1
                        }, {
                            "contextKind": "user",
                            "values": [],
                            "variation": 1
                        }],
                        "rules": [],
                        "prerequisites": [],
                        "fallthrough": {"variation": 0},
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty"
                    }"#).unwrap(),
                "flagWithMissingPrereq".to_string() => serde_json::from_str(r#"{
                        "key": "flagWithMissingPrereq",
                        "version": 42,
                        "on": true,
                        "targets": [],
                        "rules": [],
                        "prerequisites": [{
                            "key": "badPrereq",
                            "variation": 1
                        }],
                        "fallthrough": {"variation": 1},
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty"
                    }"#).unwrap(),
                "flagWithOffPrereq".to_string() => serde_json::from_str(r#"{
                        "key": "flagWithOffPrereq",
                        "version": 42,
                        "on": true,
                        "targets": [],
                        "rules": [],
                        "prerequisites": [{
                            "key": "offPrereq",
                            "variation": 1
                        }],
                        "fallthrough": {"variation": 1},
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty"
                    }"#).unwrap(),
                "flagWithFirstPrereqAsPrereqToSecondPrereq".to_string() => serde_json::from_str(r#"{
                        "key": "flagWithFirstPrereqAsPrereqToSecondPrereq",
                        "version": 42,
                        "on": true,
                        "targets": [],
                        "rules": [],
                        "prerequisites": [{
                            "key": "prereq",
                            "variation": 1
                        },
                        {
                            "key": "flagWithSatisfiedPrereq",
                            "variation": 1
                        }],
                        "fallthrough": {"variation": 1},
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty"
                    }"#).unwrap(),
                "flagWithNestedPrereq".to_string() => serde_json::from_str(r#"{
                        "key": "flagWithNestedPrereq",
                        "version": 42,
                        "on": true,
                        "targets": [],
                        "rules": [],
                        "prerequisites": [{
                            "key": "flagWithSatisfiedPrereq",
                            "variation": 1
                        }],
                        "fallthrough": {"variation": 1},
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty"
                    }"#).unwrap(),
                "flagWithSatisfiedPrereq".to_string() => serde_json::from_str(r#"{
                        "key": "flagWithSatisfiedPrereq",
                        "version": 42,
                        "on": true,
                        "targets": [],
                        "rules": [],
                        "prerequisites": [{
                            "key": "prereq",
                            "variation": 1
                        }],
                        "fallthrough": {"variation": 1},
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty"
                    }"#).unwrap(),
                "prereq".to_string() => FlagBuilder::new("prereq")
                    .on(true)
                    .variations(vec![FlagValue::Bool(false), FlagValue::Bool(true)])
                    .variation_index_for_user("bob", 0)
                    .fallthrough_variation_index(1)
                    .off_variation_index(0)
                    .exclude_from_summaries(true)
                    .build(),
                "offPrereq".to_string() => FlagBuilder::new("offPrereq")
                    .on(false)
                    .variations(vec![FlagValue::Bool(false), FlagValue::Bool(true)])
                    .fallthrough_variation_index(1)
                    .off_variation_index(1)
                    .build(),
                "flagWithInRule".to_string() => FlagBuilder::new("flagWithInRule")
                    .on(false)
                    .variations(vec![FlagValue::Bool(false), FlagValue::Bool(true)])
                    .if_match("team", vec![AttributeValue::String("Avengers".to_string())])
                    .with_id("in-rule")
                    .then_return_index(0)
                    .fallthrough_variation_index(1)
                    .off_variation_index(0)
                    .build(),
                "flagWithSegmentMatchRule".to_string() => serde_json::from_str(r#"{
                        "key": "flagWithSegmentMatchRule",
                        "version": 42,
                        "on": true,
                        "targets": [],
                        "rules": [{
                            "id": "match-rule",
                            "clauses": [{
                                "contextKind": "user",
                                "attribute": "key",
                                "negate": false,
                                "op": "segmentMatch",
                                "values": ["segment"]
                            }],
                            "variation": 0,
                            "trackEvents": false
                        }],
                        "prerequisites": [],
                        "fallthrough": {"variation": 1},
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty"
                    }"#).unwrap(),
                "flagWithContextSegmentMatchRule".to_string() => serde_json::from_str(r#"{
                        "key": "flagWithContextSegmentMatchRule",
                        "version": 42,
                        "on": true,
                        "targets": [],
                        "rules": [{
                            "id": "match-rule",
                            "clauses": [{
                                "contextKind": "user",
                                "attribute": "key",
                                "negate": false,
                                "op": "segmentMatch",
                                "values": ["segmentB"]
                            }],
                            "variation": 0,
                            "trackEvents": false
                        }],
                        "prerequisites": [],
                        "fallthrough": {"variation": 1},
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty"
                    }"#).unwrap(),
                    "flagWithContextExcludeSegmentMatchRule".to_string() => serde_json::from_str(r#"{
                        "key": "flagWithContextSegmentMatchRule",
                        "version": 42,
                        "on": true,
                        "targets": [],
                        "rules": [{
                            "id": "match-rule",
                            "clauses": [
                            {
                                "contextKind": "user",
                                "attribute": "key",
                                "negate": false,
                                "op": "segmentMatch",
                                "values": ["segment"]
                            }, {
                                "contextKind": "user",
                                "attribute": "key",
                                "negate": false,
                                "op": "segmentMatch",
                                "values": ["segmentB"]
                            }],
                            "variation": 0,
                            "trackEvents": false
                        }],
                        "prerequisites": [],
                        "fallthrough": {"variation": 1},
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty"
                    }"#).unwrap(),
                "flagWithPrereqWhichDuplicatesSegmentRuleCheck".to_string() => serde_json::from_str(r#"{
                        "key": "flagWithPrereqWhichDuplicatesSegmentRuleCheck",
                        "version": 42,
                        "on": true,
                        "targets": [],
                        "rules": [{
                            "id": "match-rule",
                            "clauses": [{
                                "contextKind": "user",
                                "attribute": "key",
                                "negate": false,
                                "op": "segmentMatch",
                                "values": ["segment"]
                            }],
                            "variation": 0,
                            "trackEvents": false
                        }],
                        "prerequisites": [{
                            "key": "flagWithSegmentMatchRule",
                            "variation": 0
                        }],
                        "fallthrough": {"variation": 1},
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty"
                    }"#).unwrap(),
                "flagWithMalformedRule".to_string() => serde_json::from_str(r#"{
                        "key": "flagWithMalformedRule",
                        "version": 42,
                        "on": false,
                        "targets": [],
                        "rules": [{
                            "id": "in-rule",
                            "clauses": [{
                                "attribute": "key",
                                "negate": false,
                                "op": "in",
                                "values": ["yes"]
                            }],
                            "trackEvents": false
                        }],
                        "prerequisites": [],
                        "fallthrough": {"variation": 1},
                        "offVariation": 0,
                        "variations": [false, true],
                        "clientSide": true,
                        "clientSideAvailability": {
                            "usingEnvironmentId": true,
                            "usingMobileKey": true
                        },
                        "salt": "salty"
                    }"#).unwrap(),
            },
            segments: hashmap! {
                "segment".to_string() => serde_json::from_str(r#"{
                        "key": "segment",
                        "included": ["alice"],
                        "excluded": [],
                        "rules": [],
                        "salt": "salty",
                        "version": 1
                    }"#).unwrap(),
                "segmentB".to_string() => serde_json::from_str(r#"{
                    "key": "segmentB",
                    "included": [],
                    "excluded": [],
                    "includedContexts": [{"contextKind": "franchise", "values": ["macdonwalds"]}],
                    "excludedContexts": [{"contextKind": "franchise", "values": ["tacochime"]}],
                    "rules": [],
                    "salt": "salty",
                    "version": 1
                }"#).unwrap()
            },
        }
    }

    pub fn new_from_json_str(flag_json: &str, segment_json: &str) -> Self {
        let flags = serde_json::from_str(flag_json).unwrap();
        let segments = serde_json::from_str(segment_json).unwrap();

        Self { flags, segments }
    }

    pub fn update_flag(&mut self, flag_key: &str, fun: fn(&mut Flag) -> ()) {
        let flag = self.flags.get_mut(flag_key).unwrap();
        fun(flag);
    }
}

impl Store for TestStore {
    fn flag(&self, flag_key: &str) -> Option<Flag> {
        self.flags.get(flag_key).cloned()
    }

    fn segment(&self, segment_key: &str) -> Option<Segment> {
        self.segments.get(segment_key).cloned()
    }
}

pub struct InMemoryPrerequisiteEventRecorder {
    pub events: RefCell<Vec<PrerequisiteEvent>>,
}

impl PrerequisiteEventRecorder for InMemoryPrerequisiteEventRecorder {
    fn record(&self, event: PrerequisiteEvent) {
        self.events.borrow_mut().push(event);
    }
}