@@ -3,6 +3,8 @@ package repository
3
3
import (
4
4
"fmt"
5
5
6
+ "golang.org/x/exp/slices"
7
+
6
8
"github.com/harness/ff-golang-server-sdk/log"
7
9
"github.com/harness/ff-golang-server-sdk/rest"
8
10
"github.com/harness/ff-golang-server-sdk/storage"
@@ -20,7 +22,9 @@ type Repository interface {
20
22
SetSegments (initialLoad bool , envID string , segment ... rest.Segment )
21
23
22
24
DeleteFlag (identifier string )
25
+ DeleteFlags (envID string , identifier string )
23
26
DeleteSegment (identifier string )
27
+ DeleteSegments (envID string , identifier string )
24
28
25
29
Close ()
26
30
}
@@ -29,10 +33,12 @@ type Repository interface {
29
33
type Callback interface {
30
34
OnFlagStored (identifier string )
31
35
OnFlagsStored (envID string )
36
+ OnFlagsDeleted (envID string , identifier string )
32
37
OnFlagDeleted (identifier string )
33
38
OnSegmentStored (identifier string )
34
39
OnSegmentsStored (envID string )
35
40
OnSegmentDeleted (identifier string )
41
+ OnSegmentsDeleted (envID string , identifier string )
36
42
}
37
43
38
44
// FFRepository holds cache and optionally offline data
@@ -77,6 +83,16 @@ func (r FFRepository) getFlags(envID string) ([]rest.FeatureConfig, error) {
77
83
return []rest.FeatureConfig {}, fmt .Errorf ("%w with environment: %s" , ErrFeatureConfigNotFound , envID )
78
84
}
79
85
86
+ func (r FFRepository ) getSegments (envID string ) ([]rest.Segment , error ) {
87
+ segmentsKey := formatSegmentsKey (envID )
88
+ flags , ok := r .cache .Get (segmentsKey )
89
+ if ok {
90
+ return flags .([]rest.Segment ), nil
91
+ }
92
+
93
+ return []rest.Segment {}, fmt .Errorf ("%w with environment: %s" , ErrFeatureConfigNotFound , envID )
94
+ }
95
+
80
96
func (r FFRepository ) getFlagAndCache (identifier string , cacheable bool ) (rest.FeatureConfig , error ) {
81
97
flagKey := formatFlagKey (identifier )
82
98
flag , ok := r .cache .Get (flagKey )
@@ -201,7 +217,7 @@ func (r FFRepository) SetSegment(segment rest.Segment, initialLoad bool) {
201
217
func (r FFRepository ) SetSegments (initialLoad bool , envID string , segments ... rest.Segment ) {
202
218
if ! initialLoad {
203
219
// If segments aren't outdated then we can exit as we don't need to refresh the cache
204
- if ! r .areSegmentsOutdated (segments ... ) {
220
+ if ! r .areSegmentsOutdated (envID , segments ... ) {
205
221
return
206
222
}
207
223
}
@@ -238,6 +254,42 @@ func (r FFRepository) DeleteFlag(identifier string) {
238
254
}
239
255
}
240
256
257
+ // DeleteFlags removes a flag from the flags key.
258
+ //
259
+ // We can't just delete the key here the way we can for a single flag because then we'd be removing flags that
260
+ // haven't been deleted. So we have to first fetch value, then remove the specific flag that has been deleted
261
+ // and update the key in the cache/storage
262
+ func (r FFRepository ) DeleteFlags (envID string , identifier string ) {
263
+ flagsKey := formatFlagsKey (envID )
264
+ if r .storage != nil {
265
+ // remove from storage
266
+ if err := r .storage .Remove (flagsKey ); err != nil {
267
+ log .Errorf ("error while removing flags %s from repository" , envID )
268
+ }
269
+ }
270
+
271
+ value , ok := r .cache .Get (flagsKey )
272
+ if ! ok {
273
+ log .Errorf ("error fetching flags from cache for env=%s" , envID )
274
+ return
275
+ }
276
+
277
+ featureConfigs , ok := value .([]rest.FeatureConfig )
278
+ if ! ok {
279
+ log .Errorf ("failed to delete flags, expected type to be []rest.FeatureConfig but got %T" , featureConfigs )
280
+ return
281
+ }
282
+
283
+ updatedFeatureConfigs := slices .DeleteFunc (featureConfigs , func (element rest.FeatureConfig ) bool {
284
+ return element .Feature == identifier
285
+ })
286
+ r .cache .Set (flagsKey , updatedFeatureConfigs )
287
+
288
+ if r .callback != nil {
289
+ r .callback .OnFlagsDeleted (envID , identifier )
290
+ }
291
+ }
292
+
241
293
// DeleteSegment removes a segment from the repository
242
294
func (r FFRepository ) DeleteSegment (identifier string ) {
243
295
segmentKey := formatSegmentKey (identifier )
@@ -254,6 +306,42 @@ func (r FFRepository) DeleteSegment(identifier string) {
254
306
}
255
307
}
256
308
309
+ // DeleteSegments removes a Segment from the segments key.
310
+ //
311
+ // We can't just delete the key here the way we can for a single flag because then we'd be removing segments that
312
+ // haven't been deleted. So we have to first fetch value, then remove the specific segment that has been deleted
313
+ // and update the key in the cache/storage
314
+ func (r FFRepository ) DeleteSegments (envID string , identifier string ) {
315
+ segmentsKey := formatSegmentsKey (envID )
316
+ if r .storage != nil {
317
+ // remove from storage
318
+ if err := r .storage .Remove (segmentsKey ); err != nil {
319
+ log .Errorf ("error while removing segments %s from repository" , envID )
320
+ }
321
+ }
322
+
323
+ value , ok := r .cache .Get (segmentsKey )
324
+ if ! ok {
325
+ log .Errorf ("error fetching segments from cache for env=%s" , envID )
326
+ return
327
+ }
328
+
329
+ segments , ok := value .([]rest.Segment )
330
+ if ! ok {
331
+ log .Errorf ("failed to delete flags, expected type to be []rest.Segment but got %T" , segments )
332
+ return
333
+ }
334
+
335
+ updatedSegments := slices .DeleteFunc (segments , func (element rest.Segment ) bool {
336
+ return element .Identifier == identifier
337
+ })
338
+ r .cache .Set (segmentsKey , updatedSegments )
339
+
340
+ if r .callback != nil {
341
+ r .callback .OnSegmentsDeleted (envID , identifier )
342
+ }
343
+ }
344
+
257
345
func (r FFRepository ) isFlagOutdated (featureConfig rest.FeatureConfig ) bool {
258
346
oldFlag , err := r .getFlagAndCache (featureConfig .Feature , false )
259
347
if err != nil || oldFlag .Version == nil {
@@ -319,7 +407,32 @@ func (r FFRepository) isSegmentOutdated(segment rest.Segment) bool {
319
407
return * oldSegment .Version < * segment .Version
320
408
}
321
409
322
- func (r FFRepository ) areSegmentsOutdated (segments ... rest.Segment ) bool {
410
+ func (r FFRepository ) areSegmentsOutdated (envID string , segments ... rest.Segment ) bool {
411
+ oldSegments , err := r .getSegments (envID )
412
+ if err != nil {
413
+ // If we get an error return true to force a cache refresh
414
+ return true
415
+ }
416
+
417
+ oldSegmentsMap := map [string ]rest.Segment {}
418
+ for _ , v := range oldSegments {
419
+ oldSegmentsMap [v .Identifier ] = v
420
+ }
421
+
422
+ for _ , seg := range segments {
423
+ os , ok := oldSegmentsMap [seg .Identifier ]
424
+ if ! ok {
425
+ // If a new flag isn't in the oldFlagMap then the list of old flags are outdated and we'll
426
+ // want to refresh the cache
427
+ return true
428
+ }
429
+
430
+ if * os .Version < * seg .Version {
431
+ return true
432
+ }
433
+ }
434
+ return false
435
+
323
436
for _ , segment := range segments {
324
437
if r .isSegmentOutdated (segment ) {
325
438
return true
0 commit comments