@@ -3,6 +3,8 @@ package repository
33import (
44 "fmt"
55
6+ "golang.org/x/exp/slices"
7+
68 "github.com/harness/ff-golang-server-sdk/log"
79 "github.com/harness/ff-golang-server-sdk/rest"
810 "github.com/harness/ff-golang-server-sdk/storage"
@@ -20,7 +22,9 @@ type Repository interface {
2022 SetSegments (initialLoad bool , envID string , segment ... rest.Segment )
2123
2224 DeleteFlag (identifier string )
25+ DeleteFlags (envID string , identifier string )
2326 DeleteSegment (identifier string )
27+ DeleteSegments (envID string , identifier string )
2428
2529 Close ()
2630}
@@ -29,10 +33,12 @@ type Repository interface {
2933type Callback interface {
3034 OnFlagStored (identifier string )
3135 OnFlagsStored (envID string )
36+ OnFlagsDeleted (envID string , identifier string )
3237 OnFlagDeleted (identifier string )
3338 OnSegmentStored (identifier string )
3439 OnSegmentsStored (envID string )
3540 OnSegmentDeleted (identifier string )
41+ OnSegmentsDeleted (envID string , identifier string )
3642}
3743
3844// FFRepository holds cache and optionally offline data
@@ -77,6 +83,16 @@ func (r FFRepository) getFlags(envID string) ([]rest.FeatureConfig, error) {
7783 return []rest.FeatureConfig {}, fmt .Errorf ("%w with environment: %s" , ErrFeatureConfigNotFound , envID )
7884}
7985
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+
8096func (r FFRepository ) getFlagAndCache (identifier string , cacheable bool ) (rest.FeatureConfig , error ) {
8197 flagKey := formatFlagKey (identifier )
8298 flag , ok := r .cache .Get (flagKey )
@@ -201,7 +217,7 @@ func (r FFRepository) SetSegment(segment rest.Segment, initialLoad bool) {
201217func (r FFRepository ) SetSegments (initialLoad bool , envID string , segments ... rest.Segment ) {
202218 if ! initialLoad {
203219 // 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 ... ) {
205221 return
206222 }
207223 }
@@ -238,6 +254,42 @@ func (r FFRepository) DeleteFlag(identifier string) {
238254 }
239255}
240256
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+
241293// DeleteSegment removes a segment from the repository
242294func (r FFRepository ) DeleteSegment (identifier string ) {
243295 segmentKey := formatSegmentKey (identifier )
@@ -254,6 +306,42 @@ func (r FFRepository) DeleteSegment(identifier string) {
254306 }
255307}
256308
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+
257345func (r FFRepository ) isFlagOutdated (featureConfig rest.FeatureConfig ) bool {
258346 oldFlag , err := r .getFlagAndCache (featureConfig .Feature , false )
259347 if err != nil || oldFlag .Version == nil {
@@ -319,7 +407,32 @@ func (r FFRepository) isSegmentOutdated(segment rest.Segment) bool {
319407 return * oldSegment .Version < * segment .Version
320408}
321409
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+
323436 for _ , segment := range segments {
324437 if r .isSegmentOutdated (segment ) {
325438 return true
0 commit comments