11package dev .openfeature .contrib .providers .flagd .grpc ;
22
33import com .google .protobuf .Descriptors ;
4+ import com .google .protobuf .ListValue ;
45import com .google .protobuf .Message ;
56import com .google .protobuf .NullValue ;
7+ import com .google .protobuf .Struct ;
68import dev .openfeature .contrib .providers .flagd .cache .Cache ;
79import dev .openfeature .contrib .providers .flagd .strategy .ResolveStrategy ;
810import dev .openfeature .sdk .EvaluationContext ;
11+ import dev .openfeature .sdk .ImmutableMetadata ;
912import dev .openfeature .sdk .MutableStructure ;
1013import dev .openfeature .sdk .ProviderEvaluation ;
1114import dev .openfeature .sdk .ProviderState ;
2225import static dev .openfeature .contrib .providers .flagd .Config .CACHED_REASON ;
2326import static dev .openfeature .contrib .providers .flagd .Config .CONTEXT_FIELD ;
2427import static dev .openfeature .contrib .providers .flagd .Config .FLAG_KEY_FIELD ;
28+ import static dev .openfeature .contrib .providers .flagd .Config .METADATA_FIELD ;
2529import static dev .openfeature .contrib .providers .flagd .Config .REASON_FIELD ;
2630import static dev .openfeature .contrib .providers .flagd .Config .STATIC_REASON ;
2731import static dev .openfeature .contrib .providers .flagd .Config .VALUE_FIELD ;
@@ -41,7 +45,8 @@ public final class FlagResolution {
4145
4246 /**
4347 * Initialize the flag resolution.
44- * @param cache cache to use.
48+ *
49+ * @param cache cache to use.
4550 * @param strategy resolution strategy to use.
4651 * @param getState lambda to call for getting the state.
4752 */
@@ -51,96 +56,153 @@ public FlagResolution(Cache cache, ResolveStrategy strategy, Supplier<ProviderSt
5156 this .getState = getState ;
5257 }
5358
59+ /**
60+ * A generic resolve method that takes a resolverRef and an optional converter lambda to transform the result.
61+ */
62+ public <ValT , ReqT extends Message , ResT extends Message > ProviderEvaluation <ValT > resolve (
63+ String key , EvaluationContext ctx , ReqT request , Function <ReqT , ResT > resolverRef ,
64+ Convert <ValT , Object > converter ) {
65+
66+ // return from cache if available and item is present
67+ if (this .cacheAvailable ()) {
68+ ProviderEvaluation <? extends Object > fromCache = this .cache .get (key );
69+ if (fromCache != null ) {
70+ fromCache .setReason (CACHED_REASON );
71+ return (ProviderEvaluation <ValT >) fromCache ;
72+ }
73+ }
74+
75+ // build the gRPC request
76+ Message req = request .newBuilderForType ()
77+ .setField (getFieldDescriptor (request , FLAG_KEY_FIELD ), key )
78+ .setField (getFieldDescriptor (request , CONTEXT_FIELD ), this .convertContext (ctx ))
79+ .build ();
80+
81+ // run the referenced resolver method
82+ Message response = strategy .resolve (resolverRef , req , key );
83+
84+ // parse the response
85+ ValT value = converter == null ? getField (response , VALUE_FIELD )
86+ : converter .convert (getField (response , VALUE_FIELD ));
87+
88+ // Extract metadata from response
89+ ImmutableMetadata immutableMetadata = metadataFromResponse (response );
90+
91+ ProviderEvaluation <ValT > result = ProviderEvaluation .<ValT >builder ()
92+ .value (value )
93+ .variant (getField (response , VARIANT_FIELD ))
94+ .reason (getField (response , REASON_FIELD ))
95+ .flagMetadata (immutableMetadata )
96+ .build ();
97+
98+ // cache if cache enabled
99+ if (this .isEvaluationCacheable (result )) {
100+ this .cache .put (key , result );
101+ }
102+
103+ return result ;
104+ }
105+
106+ private <T > Boolean isEvaluationCacheable (ProviderEvaluation <T > evaluation ) {
107+ String reason = evaluation .getReason ();
108+
109+ return reason != null && reason .equals (STATIC_REASON ) && this .cacheAvailable ();
110+ }
111+
112+ private Boolean cacheAvailable () {
113+ return this .cache .getEnabled () && ProviderState .READY .equals (this .getState .get ());
114+ }
115+
54116 /**
55117 * Recursively convert protobuf structure to openfeature value.
56118 */
57- public Value convertObjectResponse (com . google . protobuf . Struct protobuf ) {
58- return this . convertProtobufMap (protobuf .getFieldsMap ());
119+ public static Value convertObjectResponse (Struct protobuf ) {
120+ return convertProtobufMap (protobuf .getFieldsMap ());
59121 }
60122
61123 /**
62124 * Recursively convert the Evaluation context to a protobuf structure.
63125 */
64- private com . google . protobuf . Struct convertContext (EvaluationContext ctx ) {
65- return this . convertMap (ctx .asMap ()).getStructValue ();
126+ private static Struct convertContext (EvaluationContext ctx ) {
127+ return convertMap (ctx .asMap ()).getStructValue ();
66128 }
67129
68130 /**
69131 * Convert any openfeature value to a protobuf value.
70132 */
71- private com .google .protobuf .Value convertAny (Value value ) {
133+ private static com .google .protobuf .Value convertAny (Value value ) {
72134 if (value .isList ()) {
73- return this . convertList (value .asList ());
135+ return convertList (value .asList ());
74136 } else if (value .isStructure ()) {
75- return this . convertMap (value .asStructure ().asMap ());
137+ return convertMap (value .asStructure ().asMap ());
76138 } else {
77- return this . convertPrimitive (value );
139+ return convertPrimitive (value );
78140 }
79141 }
80142
81143 /**
82- * Convert any protobuf value to an openfeature value .
144+ * Convert any protobuf value to {@link Value} .
83145 */
84- private Value convertAny (com .google .protobuf .Value protobuf ) {
146+ private static Value convertAny (com .google .protobuf .Value protobuf ) {
85147 if (protobuf .hasListValue ()) {
86- return this . convertList (protobuf .getListValue ());
148+ return convertList (protobuf .getListValue ());
87149 } else if (protobuf .hasStructValue ()) {
88- return this . convertProtobufMap (protobuf .getStructValue ().getFieldsMap ());
150+ return convertProtobufMap (protobuf .getStructValue ().getFieldsMap ());
89151 } else {
90- return this . convertPrimitive (protobuf );
152+ return convertPrimitive (protobuf );
91153 }
92154 }
93155
94156 /**
95- * Convert openfeature map to protobuf map .
157+ * Convert OpenFeature map to protobuf {@link com.google.protobuf.Value} .
96158 */
97- private com .google .protobuf .Value convertMap (Map <String , Value > map ) {
159+ private static com .google .protobuf .Value convertMap (Map <String , Value > map ) {
98160 Map <String , com .google .protobuf .Value > values = new HashMap <>();
99161
100- map .keySet ().stream (). forEach ((String key ) -> {
162+ map .keySet ().forEach ((String key ) -> {
101163 Value value = map .get (key );
102- values .put (key , this . convertAny (value ));
164+ values .put (key , convertAny (value ));
103165 });
104- com . google . protobuf . Struct struct = com . google . protobuf . Struct .newBuilder ()
166+ Struct struct = Struct .newBuilder ()
105167 .putAllFields (values ).build ();
106168 return com .google .protobuf .Value .newBuilder ().setStructValue (struct ).build ();
107169 }
108170
109171 /**
110- * Convert protobuf map to openfeature map.
172+ * Convert protobuf map with {@link com.google.protobuf.Value} to OpenFeature map.
111173 */
112- private Value convertProtobufMap (Map <String , com .google .protobuf .Value > map ) {
174+ private static Value convertProtobufMap (Map <String , com .google .protobuf .Value > map ) {
113175 Map <String , Value > values = new HashMap <>();
114176
115- map .keySet ().stream (). forEach ((String key ) -> {
177+ map .keySet ().forEach ((String key ) -> {
116178 com .google .protobuf .Value value = map .get (key );
117- values .put (key , this . convertAny (value ));
179+ values .put (key , convertAny (value ));
118180 });
119181 return new Value (new MutableStructure (values ));
120182 }
121183
122184 /**
123- * Convert openfeature list to protobuf list .
185+ * Convert OpenFeature list to protobuf {@link com.google.protobuf.Value} .
124186 */
125- private com .google .protobuf .Value convertList (List <Value > values ) {
126- com . google . protobuf . ListValue list = com . google . protobuf . ListValue .newBuilder ()
187+ private static com .google .protobuf .Value convertList (List <Value > values ) {
188+ ListValue list = ListValue .newBuilder ()
127189 .addAllValues (values .stream ()
128- .map (v -> this . convertAny (v )).collect (Collectors .toList ()))
190+ .map (v -> convertAny (v )).collect (Collectors .toList ()))
129191 .build ();
130192 return com .google .protobuf .Value .newBuilder ().setListValue (list ).build ();
131193 }
132194
133195 /**
134- * Convert protobuf list to openfeature list .
196+ * Convert protobuf list to OpenFeature {@link com.google.protobuf.Value} .
135197 */
136- private Value convertList (com . google . protobuf . ListValue protobuf ) {
137- return new Value (protobuf .getValuesList ().stream ().map (p -> this . convertAny (p )).collect (Collectors .toList ()));
198+ private static Value convertList (ListValue protobuf ) {
199+ return new Value (protobuf .getValuesList ().stream ().map (p -> convertAny (p )).collect (Collectors .toList ()));
138200 }
139201
140202 /**
141- * Convert openfeature value to protobuf value .
203+ * Convert OpenFeature {@link Value} to protobuf {@link com.google.protobuf.Value} .
142204 */
143- private com .google .protobuf .Value convertPrimitive (Value value ) {
205+ private static com .google .protobuf .Value convertPrimitive (Value value ) {
144206 com .google .protobuf .Value .Builder builder = com .google .protobuf .Value .newBuilder ();
145207
146208 if (value .isBoolean ()) {
@@ -156,10 +218,10 @@ private com.google.protobuf.Value convertPrimitive(Value value) {
156218 }
157219
158220 /**
159- * Convert protobuf value openfeature value .
221+ * Convert protobuf {@link com.google.protobuf.Value} to OpenFeature {@link Value} .
160222 */
161- private Value convertPrimitive (com .google .protobuf .Value protobuf ) {
162- Value value ;
223+ private static Value convertPrimitive (com .google .protobuf .Value protobuf ) {
224+ final Value value ;
163225 if (protobuf .hasBoolValue ()) {
164226 value = new Value (protobuf .getBoolValue ());
165227 } else if (protobuf .hasStringValue ()) {
@@ -169,66 +231,39 @@ private Value convertPrimitive(com.google.protobuf.Value protobuf) {
169231 } else {
170232 value = new Value ();
171233 }
234+
172235 return value ;
173236 }
174237
175- private <T > Boolean isEvaluationCacheable (ProviderEvaluation <T > evaluation ) {
176- String reason = evaluation .getReason ();
177-
178- return reason != null && reason .equals (STATIC_REASON ) && this .cacheAvailable ();
238+ private static <T > T getField (Message message , String name ) {
239+ return (T ) message .getField (getFieldDescriptor (message , name ));
179240 }
180241
181- private Boolean cacheAvailable ( ) {
182- return this . cache . getEnabled () && ProviderState . READY . equals ( this . getState . get () );
242+ private static Descriptors . FieldDescriptor getFieldDescriptor ( Message message , String name ) {
243+ return message . getDescriptorForType (). findFieldByName ( name );
183244 }
184245
185- /**
186- * A generic resolve method that takes a resolverRef and an optional converter lambda to transform the result.
187- */
188- public <ValT , ReqT extends Message , ResT extends Message > ProviderEvaluation <ValT > resolve (
189- String key , EvaluationContext ctx , ReqT request , Function <ReqT , ResT > resolverRef ,
190- Convert <ValT , Object > converter ) {
246+ private static ImmutableMetadata metadataFromResponse (Message response ) {
247+ final Object metadata = response .getField (getFieldDescriptor (response , METADATA_FIELD ));
191248
192- // return from cache if available and item is present
193- if (this .cacheAvailable ()) {
194- ProviderEvaluation <? extends Object > fromCache = this .cache .get (key );
195- if (fromCache != null ) {
196- fromCache .setReason (CACHED_REASON );
197- return (ProviderEvaluation <ValT >) fromCache ;
198- }
249+ if (!(metadata instanceof Struct )) {
250+ return ImmutableMetadata .builder ().build ();
199251 }
200252
201- // build the gRPC request
202- Message req = request .newBuilderForType ()
203- .setField (getFieldDescriptor (request , FLAG_KEY_FIELD ), key )
204- .setField (getFieldDescriptor (request , CONTEXT_FIELD ), this .convertContext (ctx ))
205- .build ();
206-
207- // run the referenced resolver method
208- Message response = strategy .resolve (resolverRef , req , key );
253+ final Struct struct = (Struct ) metadata ;
209254
210- // parse the response
211- ValT value = converter == null ? getField (response , VALUE_FIELD )
212- : converter .convert (getField (response , VALUE_FIELD ));
213- ProviderEvaluation <ValT > result = ProviderEvaluation .<ValT >builder ()
214- .value (value )
215- .variant (getField (response , VARIANT_FIELD ))
216- .reason (getField (response , REASON_FIELD ))
217- .build ();
255+ ImmutableMetadata .ImmutableMetadataBuilder builder = ImmutableMetadata .builder ();
218256
219- // cache if cache enabled
220- if (this .isEvaluationCacheable (result )) {
221- this .cache .put (key , result );
257+ for (Map .Entry <String , com .google .protobuf .Value > entry : struct .getFieldsMap ().entrySet ()) {
258+ if (entry .getValue ().hasStringValue ()) {
259+ builder .addString (entry .getKey (), entry .getValue ().getStringValue ());
260+ } else if (entry .getValue ().hasBoolValue ()) {
261+ builder .addBoolean (entry .getKey (), entry .getValue ().getBoolValue ());
262+ } else if (entry .getValue ().hasNumberValue ()) {
263+ builder .addDouble (entry .getKey (), entry .getValue ().getNumberValue ());
264+ }
222265 }
223266
224- return result ;
225- }
226-
227- private static <T > T getField (Message message , String name ) {
228- return (T ) message .getField (getFieldDescriptor (message , name ));
229- }
230-
231- private static Descriptors .FieldDescriptor getFieldDescriptor (Message message , String name ) {
232- return message .getDescriptorForType ().findFieldByName (name );
267+ return builder .build ();
233268 }
234269}
0 commit comments