1
+ using System ;
2
+ using System . Collections . Generic ;
3
+ using System . Linq ;
4
+ using System . Text ;
5
+ using JsonLogic . Net ;
6
+ using Microsoft . Extensions . Logging ;
7
+ using Murmur ;
8
+ using Newtonsoft . Json . Linq ;
9
+ using Semver ;
10
+
11
+ namespace OpenFeature . Contrib . Providers . Flagd . Resolver . InProcess . CustomEvaluators
12
+ {
13
+ /// <inheritdoc/>
14
+ public class FractionalEvaluator
15
+ {
16
+
17
+ internal ILogger Logger { get ; set ; }
18
+
19
+ internal FractionalEvaluator ( )
20
+ {
21
+ var loggerFactory = LoggerFactory . Create (
22
+ builder => builder
23
+ // add console as logging target
24
+ . AddConsole ( )
25
+ // add debug output as logging target
26
+ . AddDebug ( )
27
+ // set minimum level to log
28
+ . SetMinimumLevel ( LogLevel . Debug )
29
+ ) ;
30
+ Logger = loggerFactory . CreateLogger < FractionalEvaluator > ( ) ;
31
+ }
32
+
33
+ class FractionalEvaluationDistribution
34
+ {
35
+ public string variant ;
36
+ public int percentage ;
37
+ }
38
+
39
+ internal object Evaluate ( IProcessJsonLogic p , JToken [ ] args , object data )
40
+ {
41
+ // check if we have at least two arguments:
42
+ // 1. the property value
43
+ // 2. the array containing the buckets
44
+
45
+ if ( args . Length == 0 )
46
+ {
47
+ return null ;
48
+ }
49
+
50
+ var flagdProperties = new FlagdProperties ( data ) ;
51
+
52
+ // check if the first argument is a string (i.e. the property to base the distribution on
53
+ var propertyValue = flagdProperties . TargetingKey ;
54
+ var bucketStartIndex = 0 ;
55
+
56
+ var arg0 = p . Apply ( args [ 0 ] , data ) ;
57
+
58
+ if ( arg0 is string stringValue )
59
+ {
60
+ propertyValue = stringValue ;
61
+ bucketStartIndex = 1 ;
62
+ }
63
+
64
+ var distributions = new List < FractionalEvaluationDistribution > ( ) ;
65
+ var distributionSum = 0 ;
66
+
67
+ for ( var i = bucketStartIndex ; i < args . Length ; i ++ )
68
+ {
69
+ var bucket = p . Apply ( args [ i ] , data ) ;
70
+
71
+ if ( ! bucket . IsEnumerable ( ) )
72
+ {
73
+ continue ;
74
+ }
75
+
76
+ var bucketArr = bucket . MakeEnumerable ( ) . ToArray ( ) ;
77
+
78
+ if ( bucketArr . Count ( ) < 2 )
79
+ {
80
+ continue ;
81
+ }
82
+
83
+ if ( ! bucketArr . ElementAt ( 1 ) . IsNumeric ( ) )
84
+ {
85
+ continue ;
86
+ }
87
+
88
+
89
+ var percentage = Convert . ToInt32 ( bucketArr . ElementAt ( 1 ) ) ;
90
+ distributions . Add ( new FractionalEvaluationDistribution
91
+ {
92
+ variant = bucketArr . ElementAt ( 0 ) . ToString ( ) ,
93
+ percentage = percentage
94
+ } ) ;
95
+
96
+ distributionSum += percentage ;
97
+ }
98
+
99
+ if ( distributionSum != 100 )
100
+ {
101
+ Logger . LogDebug ( "Sum of distribution values is not eqyal to 100" ) ;
102
+ return null ;
103
+ }
104
+
105
+ var valueToDistribute = flagdProperties . FlagKey + propertyValue ;
106
+ var murmur32 = MurmurHash . Create32 ( ) ;
107
+ var bytes = Encoding . ASCII . GetBytes ( valueToDistribute ) ;
108
+ var hashBytes = murmur32 . ComputeHash ( bytes ) ;
109
+ var hash = BitConverter . ToInt32 ( hashBytes , 0 ) ;
110
+
111
+ var bucketValue = ( int ) ( Math . Abs ( ( float ) hash ) / Int32 . MaxValue * 100 ) ;
112
+
113
+ var rangeEnd = 0 ;
114
+
115
+ foreach ( var dist in distributions )
116
+ {
117
+ rangeEnd += dist . percentage ;
118
+ if ( bucketValue < rangeEnd )
119
+ {
120
+ return dist . variant ;
121
+ }
122
+ }
123
+
124
+ Logger . LogDebug ( "No matching bucket found" ) ;
125
+ return "" ;
126
+ }
127
+ }
128
+ }
0 commit comments