33import dev .openfeature .sdk .ErrorCode ;
44import dev .openfeature .sdk .EvaluationContext ;
55import dev .openfeature .sdk .FeatureProvider ;
6+ import dev .openfeature .sdk .ImmutableMetadata ;
67import dev .openfeature .sdk .Metadata ;
78import dev .openfeature .sdk .MutableStructure ;
89import dev .openfeature .sdk .ProviderEvaluation ;
910import dev .openfeature .sdk .Reason ;
11+ import dev .openfeature .sdk .TrackingEventDetails ;
1012import dev .openfeature .sdk .Value ;
1113import dev .openfeature .sdk .exceptions .GeneralError ;
1214import dev .openfeature .sdk .exceptions .OpenFeatureError ;
1315import dev .openfeature .sdk .exceptions .ParseError ;
1416import dev .openfeature .sdk .exceptions .TargetingKeyMissingError ;
1517import io .split .client .SplitClient ;
18+ import io .split .client .api .SplitResult ;
1619import io .split .openfeature .utils .Serialization ;
1720import java .time .Instant ;
1821import java .time .format .DateTimeParseException ;
22+ import java .util .HashMap ;
1923import java .util .List ;
2024import java .util .Map ;
25+ import java .util .Optional ;
2126import java .util .stream .Collectors ;
2227
2328public class SplitProvider implements FeatureProvider {
@@ -44,126 +49,138 @@ public Metadata getMetadata() {
4449 }
4550
4651 @ Override
47- public ProviderEvaluation <Boolean > getBooleanEvaluation (String key , Boolean defaultTreatment , EvaluationContext evaluationContext ) {
48- try {
49- String evaluated = evaluateTreatment (key , evaluationContext );
50- if (noTreatment (evaluated )) {
51- return constructProviderEvaluation (defaultTreatment , evaluated , Reason .DEFAULT , ErrorCode .FLAG_NOT_FOUND );
52- }
52+ public ProviderEvaluation <Boolean > getBooleanEvaluation (
53+ String key , Boolean defaultVal , EvaluationContext ctx ) {
54+ return getEvaluation (key , defaultVal , ctx , s -> {
5355 // if treatment is "on" or "true" we treat that as true
5456 // if it is "off" or "false" we treat it as false
5557 // if it is some other value we throw an error (sdk will catch it and throw default treatment)
56- boolean value ;
57- if (Boolean .parseBoolean (evaluated ) || evaluated .equals ("on" )) {
58- value = true ;
59- } else if (evaluated .equalsIgnoreCase ("false" ) || evaluated .equals ("off" )) {
60- value = false ;
58+ if (Boolean .parseBoolean (s ) || s .equals ("on" )) {
59+ return true ;
60+ } else if (s .equalsIgnoreCase ("false" ) || s .equals ("off" )) {
61+ return false ;
6162 } else {
6263 throw new ParseError ();
6364 }
64- return constructProviderEvaluation (value , evaluated );
65- } catch (OpenFeatureError e ) {
66- throw e ;
67- } catch (Exception e ) {
68- throw new GeneralError ("Error getting boolean evaluation" , e );
69- }
65+ }, "Boolean" );
66+ }
7067
68+ @ Override
69+ public ProviderEvaluation <String > getStringEvaluation (
70+ String key , String defaultVal , EvaluationContext ctx ) {
71+ return getEvaluation (key , defaultVal , ctx , s -> s , "String" );
7172 }
7273
7374 @ Override
74- public ProviderEvaluation <String > getStringEvaluation (String key , String defaultTreatment , EvaluationContext evaluationContext ) {
75- try {
76- String evaluated = evaluateTreatment (key , evaluationContext );
77- if (noTreatment (evaluated )) {
78- return constructProviderEvaluation (defaultTreatment , evaluated , Reason .DEFAULT , ErrorCode .FLAG_NOT_FOUND );
79- }
80- return constructProviderEvaluation (evaluated , evaluated );
81- } catch (OpenFeatureError e ) {
82- throw e ;
83- } catch (Exception e ) {
84- throw new GeneralError ("Error getting String evaluation" , e );
85- }
75+ public ProviderEvaluation <Integer > getIntegerEvaluation (
76+ String key , Integer defaultVal , EvaluationContext ctx ) {
77+ return getEvaluation (key , defaultVal , ctx , Integer ::valueOf , "Integer" );
8678 }
8779
8880 @ Override
89- public ProviderEvaluation <Integer > getIntegerEvaluation (String key , Integer defaultTreatment , EvaluationContext evaluationContext ) {
90- try {
91- String evaluated = evaluateTreatment (key , evaluationContext );
92- if (noTreatment (evaluated )) {
93- return constructProviderEvaluation (defaultTreatment , evaluated , Reason .DEFAULT , ErrorCode .FLAG_NOT_FOUND );
94- }
95- Integer value = Integer .valueOf (evaluated );
96- return constructProviderEvaluation (value , evaluated );
97- } catch (OpenFeatureError e ) {
98- throw e ;
99- } catch (NumberFormatException e ) {
100- throw new ParseError ();
101- } catch (Exception e ) {
102- throw new GeneralError ("Error getting Integer evaluation" , e );
103- }
81+ public ProviderEvaluation <Double > getDoubleEvaluation (
82+ String key , Double defaultTreatment , EvaluationContext ctx ) {
83+
84+ return getEvaluation (key , defaultTreatment , ctx , s -> {
85+ if (s == null ) throw new NumberFormatException ("null" );
86+ return Double .valueOf (s .trim ());
87+ }, "Double" );
10488 }
10589
10690 @ Override
107- public ProviderEvaluation <Double > getDoubleEvaluation (String key , Double defaultTreatment , EvaluationContext evaluationContext ) {
91+ public ProviderEvaluation <Value > getObjectEvaluation (
92+ String key , Value defaultVal , EvaluationContext ctx ) {
93+ return getEvaluation (key , defaultVal , ctx , s -> {
94+ Map <String , Object > rawMap = Serialization .stringToMap (s );
95+ return mapToValue (rawMap );
96+ }, "Object" );
97+ }
98+
99+ @ FunctionalInterface
100+ interface Mapper <T > {
101+ T map (String s ) throws Exception ;
102+ }
103+
104+ private <T > ProviderEvaluation <T > getEvaluation (
105+ String key ,
106+ T defaultValue ,
107+ EvaluationContext ctx ,
108+ Mapper <T > mapper ,
109+ String typeLabel
110+ ) {
108111 try {
109- String evaluated = evaluateTreatment (key , evaluationContext );
110- if (noTreatment (evaluated )) {
111- return constructProviderEvaluation (defaultTreatment , evaluated , Reason .DEFAULT , ErrorCode .FLAG_NOT_FOUND );
112+ SplitResult evaluated = evaluateTreatment (key , ctx );
113+ String treatment = evaluated .treatment ();
114+ String config = evaluated .config ();
115+ ImmutableMetadata metadata = ImmutableMetadata .builder ().addString ("config" , config ).build ();
116+ System .out .println (metadata .getString ("config" ));
117+ if (noTreatment (treatment )) {
118+ return constructProviderEvaluation (
119+ defaultValue , treatment , Reason .DEFAULT , ErrorCode .FLAG_NOT_FOUND , metadata );
112120 }
113- Double value = Double .valueOf (evaluated );
114- return constructProviderEvaluation (value , evaluated );
121+ T mapped = mapper .map (treatment );
122+ return constructProviderEvaluation (mapped , treatment , metadata );
123+
115124 } catch (OpenFeatureError e ) {
116125 throw e ;
117- } catch (NumberFormatException e ) {
118- throw new ParseError ();
119126 } catch (Exception e ) {
120- throw new GeneralError ("Error getting Double evaluation" , e );
127+ throw new GeneralError (String . format ( "Error getting %s evaluation" , typeLabel ) , e );
121128 }
122129 }
123130
124131 @ Override
125- public ProviderEvaluation <Value > getObjectEvaluation (String key , Value defaultTreatment , EvaluationContext evaluationContext ) {
126- try {
127- String evaluated = evaluateTreatment (key , evaluationContext );
128- if (noTreatment (evaluated )) {
129- return constructProviderEvaluation (defaultTreatment , evaluated , Reason .DEFAULT , ErrorCode .FLAG_NOT_FOUND );
130- }
131- Map <String , Object > rawMap = Serialization .stringToMap (evaluated );
132- Value value = mapToValue (rawMap );
133- return constructProviderEvaluation (value , evaluated );
134- } catch (OpenFeatureError e ) {
135- throw e ;
136- } catch (Exception e ) {
137- throw new GeneralError ("Error getting Object evaluation" , e );
132+ public void track (String eventName , EvaluationContext context , TrackingEventDetails details ) {
133+
134+ // targetingKey is always required
135+ String key = context .getTargetingKey ();
136+ if (key == null || key .isEmpty ()) throw new TargetingKeyMissingError ();
137+
138+ // eventName is always required
139+ if (eventName == null || eventName .isBlank ()) throw new GeneralError ("Missing eventName, required to track" );
140+
141+ // trafficType is always required
142+ Value ttVal = context .getValue ("trafficType" );
143+ String trafficType = (ttVal != null && !ttVal .isNull () && ttVal .isString ()) ? ttVal .asString () : null ;
144+ if (trafficType == null || trafficType .isBlank ()) throw new GeneralError ("Missing trafficType variable, required to track" );
145+
146+ double value = 0 ;
147+ Map <String , Object > attributes = new HashMap <>();
148+ if (details != null ) {
149+ Optional <Number > optionalValue = details .getValue ();
150+ value = optionalValue .orElse (0 ).doubleValue ();
151+ attributes = details .asObjectMap ();
138152 }
153+
154+ client .track (key , trafficType , eventName , value , attributes );
139155 }
140156
141157 public Map <String , Object > transformContext (EvaluationContext context ) {
142158 return context .asObjectMap ();
143159 }
144160
145- private String evaluateTreatment (String key , EvaluationContext evaluationContext ) {
161+ private SplitResult evaluateTreatment (String key , EvaluationContext evaluationContext ) {
146162 String id = evaluationContext .getTargetingKey ();
147163 if (id == null || id .isEmpty ()) {
148164 // targeting key is always required
149165 throw new TargetingKeyMissingError ();
150166 }
151167 Map <String , Object > attributes = transformContext (evaluationContext );
152- return client .getTreatment (id , key , attributes );
168+ return client .getTreatmentWithConfig (id , key , attributes );
153169 }
154170
155171 private boolean noTreatment (String treatment ) {
156172 return treatment == null || treatment .isEmpty () || treatment .equals ("control" );
157173 }
158174
159- private <T > ProviderEvaluation <T > constructProviderEvaluation (T value , String variant ) {
160- return constructProviderEvaluation (value , variant , Reason .TARGETING_MATCH , null );
175+ private <T > ProviderEvaluation <T > constructProviderEvaluation (T value , String variant , ImmutableMetadata metadata ) {
176+ return constructProviderEvaluation (value , variant , Reason .TARGETING_MATCH , null , metadata );
161177 }
162178
163- private <T > ProviderEvaluation <T > constructProviderEvaluation (T value , String variant , Reason reason , ErrorCode errorCode ) {
179+ private <T > ProviderEvaluation <T > constructProviderEvaluation (T value , String variant , Reason reason , ErrorCode errorCode , ImmutableMetadata metadata ) {
164180 ProviderEvaluation .ProviderEvaluationBuilder <T > builder = ProviderEvaluation .builder ();
165181 return builder
166182 .value (value )
183+ .flagMetadata (metadata )
167184 .reason (reason .name ())
168185 .variant (variant )
169186 .errorCode (errorCode )
0 commit comments