@@ -79,31 +79,33 @@ func (p *LocalResolverProvider) BooleanEvaluation(
7979) openfeature.BoolResolutionDetail {
8080 result := p .ObjectEvaluation (ctx , flag , defaultValue , evalCtx )
8181
82+ var detail openfeature.BoolResolutionDetail
83+
8284 if result .Value == nil {
83- return openfeature.BoolResolutionDetail {
85+ detail = openfeature.BoolResolutionDetail {
8486 Value : defaultValue ,
8587 ProviderResolutionDetail : openfeature.ProviderResolutionDetail {
8688 Reason : result .Reason ,
8789 ResolutionError : result .ResolutionError ,
8890 },
8991 }
90- }
91-
92- boolVal , ok := result .Value .(bool )
93- if ! ok {
94- return openfeature.BoolResolutionDetail {
92+ } else if boolVal , ok := result .Value .(bool ); ! ok {
93+ detail = openfeature.BoolResolutionDetail {
9594 Value : defaultValue ,
9695 ProviderResolutionDetail : openfeature.ProviderResolutionDetail {
9796 Reason : openfeature .ErrorReason ,
9897 ResolutionError : openfeature .NewTypeMismatchResolutionError ("value is not a boolean" ),
9998 },
10099 }
100+ } else {
101+ detail = openfeature.BoolResolutionDetail {
102+ Value : boolVal ,
103+ ProviderResolutionDetail : result .ProviderResolutionDetail ,
104+ }
101105 }
102106
103- return openfeature.BoolResolutionDetail {
104- Value : boolVal ,
105- ProviderResolutionDetail : result .ProviderResolutionDetail ,
106- }
107+ p .logResolutionErrorIfPresent (flag , detail .ProviderResolutionDetail )
108+ return detail
107109}
108110
109111// StringEvaluation evaluates a string flag
@@ -115,31 +117,33 @@ func (p *LocalResolverProvider) StringEvaluation(
115117) openfeature.StringResolutionDetail {
116118 result := p .ObjectEvaluation (ctx , flag , defaultValue , evalCtx )
117119
120+ var detail openfeature.StringResolutionDetail
121+
118122 if result .Value == nil {
119- return openfeature.StringResolutionDetail {
123+ detail = openfeature.StringResolutionDetail {
120124 Value : defaultValue ,
121125 ProviderResolutionDetail : openfeature.ProviderResolutionDetail {
122126 Reason : result .Reason ,
123127 ResolutionError : result .ResolutionError ,
124128 },
125129 }
126- }
127-
128- strVal , ok := result .Value .(string )
129- if ! ok {
130- return openfeature.StringResolutionDetail {
130+ } else if strVal , ok := result .Value .(string ); ! ok {
131+ detail = openfeature.StringResolutionDetail {
131132 Value : defaultValue ,
132133 ProviderResolutionDetail : openfeature.ProviderResolutionDetail {
133134 Reason : openfeature .ErrorReason ,
134135 ResolutionError : openfeature .NewTypeMismatchResolutionError ("value is not a string" ),
135136 },
136137 }
138+ } else {
139+ detail = openfeature.StringResolutionDetail {
140+ Value : strVal ,
141+ ProviderResolutionDetail : result .ProviderResolutionDetail ,
142+ }
137143 }
138144
139- return openfeature.StringResolutionDetail {
140- Value : strVal ,
141- ProviderResolutionDetail : result .ProviderResolutionDetail ,
142- }
145+ p .logResolutionErrorIfPresent (flag , detail .ProviderResolutionDetail )
146+ return detail
143147}
144148
145149// FloatEvaluation evaluates a float flag
@@ -151,31 +155,33 @@ func (p *LocalResolverProvider) FloatEvaluation(
151155) openfeature.FloatResolutionDetail {
152156 result := p .ObjectEvaluation (ctx , flag , defaultValue , evalCtx )
153157
158+ var detail openfeature.FloatResolutionDetail
159+
154160 if result .Value == nil {
155- return openfeature.FloatResolutionDetail {
161+ detail = openfeature.FloatResolutionDetail {
156162 Value : defaultValue ,
157163 ProviderResolutionDetail : openfeature.ProviderResolutionDetail {
158164 Reason : result .Reason ,
159165 ResolutionError : result .ResolutionError ,
160166 },
161167 }
162- }
163-
164- floatVal , ok := result .Value .(float64 )
165- if ! ok {
166- return openfeature.FloatResolutionDetail {
168+ } else if floatVal , ok := result .Value .(float64 ); ! ok {
169+ detail = openfeature.FloatResolutionDetail {
167170 Value : defaultValue ,
168171 ProviderResolutionDetail : openfeature.ProviderResolutionDetail {
169172 Reason : openfeature .ErrorReason ,
170173 ResolutionError : openfeature .NewTypeMismatchResolutionError ("value is not a float" ),
171174 },
172175 }
176+ } else {
177+ detail = openfeature.FloatResolutionDetail {
178+ Value : floatVal ,
179+ ProviderResolutionDetail : result .ProviderResolutionDetail ,
180+ }
173181 }
174182
175- return openfeature.FloatResolutionDetail {
176- Value : floatVal ,
177- ProviderResolutionDetail : result .ProviderResolutionDetail ,
178- }
183+ p .logResolutionErrorIfPresent (flag , detail .ProviderResolutionDetail )
184+ return detail
179185}
180186
181187// IntEvaluation evaluates an int flag
@@ -187,37 +193,42 @@ func (p *LocalResolverProvider) IntEvaluation(
187193) openfeature.IntResolutionDetail {
188194 result := p .ObjectEvaluation (ctx , flag , defaultValue , evalCtx )
189195
196+ var detail openfeature.IntResolutionDetail
197+
190198 if result .Value == nil {
191- return openfeature.IntResolutionDetail {
199+ detail = openfeature.IntResolutionDetail {
192200 Value : defaultValue ,
193201 ProviderResolutionDetail : openfeature.ProviderResolutionDetail {
194202 Reason : result .Reason ,
195203 ResolutionError : result .ResolutionError ,
196204 },
197205 }
198- }
199-
200- // Handle both int64 and float64 (JSON numbers are float64)
201- switch v := result . Value .( type ) {
202- case int64 :
203- return openfeature. IntResolutionDetail {
204- Value : v ,
205- ProviderResolutionDetail : result . ProviderResolutionDetail ,
206- }
207- case float64 :
208- return openfeature. IntResolutionDetail {
209- Value : int64 ( v ) ,
210- ProviderResolutionDetail : result . ProviderResolutionDetail ,
211- }
212- default :
213- return openfeature. IntResolutionDetail {
214- Value : defaultValue ,
215- ProviderResolutionDetail : openfeature.ProviderResolutionDetail {
216- Reason : openfeature . ErrorReason ,
217- ResolutionError : openfeature . NewTypeMismatchResolutionError ( "value is not an integer" ) ,
218- },
206+ } else {
207+ // Handle both int64 and float64 (JSON numbers are float64)
208+ switch v := result . Value .( type ) {
209+ case int64 :
210+ detail = openfeature. IntResolutionDetail {
211+ Value : v ,
212+ ProviderResolutionDetail : result . ProviderResolutionDetail ,
213+ }
214+ case float64 :
215+ detail = openfeature. IntResolutionDetail {
216+ Value : int64 ( v ),
217+ ProviderResolutionDetail : result . ProviderResolutionDetail ,
218+ }
219+ default :
220+ detail = openfeature. IntResolutionDetail {
221+ Value : defaultValue ,
222+ ProviderResolutionDetail : openfeature. ProviderResolutionDetail {
223+ Reason : openfeature .ErrorReason ,
224+ ResolutionError : openfeature . NewTypeMismatchResolutionError ( "value is not an integer" ) ,
225+ } ,
226+ }
219227 }
220228 }
229+
230+ p .logResolutionErrorIfPresent (flag , detail .ProviderResolutionDetail )
231+ return detail
221232}
222233
223234// ObjectEvaluation evaluates an object flag (core implementation)
@@ -309,7 +320,6 @@ func (p *LocalResolverProvider) ObjectEvaluation(
309320
310321 // Check if flag was found
311322 if len (response .ResolvedFlags ) == 0 {
312- p .logger .Info ("No active flag was found" , "flag" , flagPath )
313323 return openfeature.InterfaceResolutionDetail {
314324 Value : defaultValue ,
315325 ProviderResolutionDetail : openfeature.ProviderResolutionDetail {
@@ -349,10 +359,21 @@ func (p *LocalResolverProvider) ObjectEvaluation(
349359
350360 // If a path was specified, extract the nested value
351361 if path != "" {
352- value = getValueForPath (path , value )
362+ var found bool
363+ value , found = getValueForPath (path , value )
364+ // If path was specified but not found, return FLAG_NOT_FOUND error
365+ if ! found {
366+ return openfeature.InterfaceResolutionDetail {
367+ Value : defaultValue ,
368+ ProviderResolutionDetail : openfeature.ProviderResolutionDetail {
369+ Reason : openfeature .ErrorReason ,
370+ ResolutionError : openfeature .NewFlagNotFoundResolutionError (fmt .Sprintf ("path '%s' not found in flag '%s'" , path , flagPath )),
371+ },
372+ }
373+ }
353374 }
354375
355- // If value is nil, use default
376+ // If value is nil (flag has no value) , use default
356377 if value == nil {
357378 value = defaultValue
358379 }
@@ -425,7 +446,7 @@ func (p *LocalResolverProvider) Shutdown() {
425446 p .cancelFunc ()
426447 p .cancelFunc = nil
427448 if p .logger != nil {
428- p .logger .Info ("Cancelled scheduled tasks" )
449+ p .logger .Debug ("Cancelled scheduled tasks" )
429450 }
430451 }
431452
@@ -438,20 +459,20 @@ func (p *LocalResolverProvider) Shutdown() {
438459 if p .resolverAPI != nil {
439460 p .resolverAPI .Close (ctx )
440461 if p .logger != nil {
441- p .logger .Info ("Closed resolver API" )
462+ p .logger .Debug ("Closed resolver API" )
442463 }
443464 }
444465
445466 // Shutdown flag logger (which waits for log sends to complete)
446467 if p .flagLogger != nil {
447468 p .flagLogger .Shutdown ()
448469 if p .logger != nil {
449- p .logger .Info ("Shut down flag logger" )
470+ p .logger .Debug ("Shut down flag logger" )
450471 }
451472 }
452473
453474 if p .logger != nil {
454- p .logger .Info ("Provider shut down" )
475+ p .logger .Info ("Provider has been shut down" )
455476 }
456477}
457478
@@ -480,7 +501,7 @@ func (p *LocalResolverProvider) startScheduledTasks(parentCtx context.Context) {
480501 }
481502
482503 if accountId == "" {
483- p .logger .Warn ("AccountID is empty, skipping state update" )
504+ p .logger .Error ("AccountID inside fetched state is empty, skipping this state update attempt " )
484505 continue
485506 }
486507
@@ -632,9 +653,10 @@ func protoValueToGo(value *structpb.Value) interface{} {
632653
633654// getValueForPath extracts a nested value from a map using dot notation
634655// e.g., "nested.value" from map{"nested": map{"value": 42}} returns 42
635- func getValueForPath (path string , value interface {}) interface {} {
656+ // Returns (value, found) where found indicates if the path was fully traversed
657+ func getValueForPath (path string , value interface {}) (interface {}, bool ) {
636658 if path == "" {
637- return value
659+ return value , true
638660 }
639661
640662 parts := strings .Split (path , "." )
@@ -643,13 +665,27 @@ func getValueForPath(path string, value interface{}) interface{} {
643665 for _ , part := range parts {
644666 switch v := current .(type ) {
645667 case map [string ]interface {}:
646- current = v [part ]
668+ var exists bool
669+ current , exists = v [part ]
670+ if ! exists {
671+ return nil , false
672+ }
647673 default :
648- return nil
674+ // Can't traverse further - path not found
675+ return nil , false
649676 }
650677 }
651678
652- return current
679+ return current , true
680+ }
681+
682+ // logResolutionErrorIfPresent logs a warning if the resolution detail contains an error
683+ func (p * LocalResolverProvider ) logResolutionErrorIfPresent (flag string , detail openfeature.ProviderResolutionDetail ) {
684+ errStr := detail .ResolutionError .Error ()
685+ // Empty ResolutionError returns ": ", so check for meaningful error
686+ if errStr != "" && errStr != ": " {
687+ p .logger .Warn ("Flag evaluation error" , "flag" , flag , "error_code" , errStr )
688+ }
653689}
654690
655691// mapResolveReasonToOpenFeature converts Confidence ResolveReason to OpenFeature Reason
0 commit comments