@@ -72,59 +72,59 @@ public class Utils {
7272 return true
7373 }
7474 }
75-
75+
7676 /// This checks if a userId is within an experiment namespace or not.
7777 static func inNamespace( userId: String , namespace: NameSpace ) -> Bool {
7878 guard let hash = hash ( seed: namespace. 0 , value: userId + " __ " , version: 1.0 ) else { return false }
7979 return inRange ( n: hash, range: BucketRange ( number1: namespace. 1 , number2: namespace. 2 ) )
8080 }
81-
81+
8282 /// Returns an array of floats with numVariations items that are all equal and sum to 1. For example, getEqualWeights(2) would return [0.5, 0.5].
8383 static func getEqualWeights( numVariations: Int ) -> [ Float ] {
8484 if numVariations <= 0 { return [ ] }
8585 return Array ( repeating: 1.0 / Float( numVariations) , count: numVariations)
8686 }
87-
87+
8888 /// This converts and experiment's coverage and variation weights into an array of bucket ranges.
8989 static func getBucketRanges( numVariations: Int , coverage: Float , weights: [ Float ] ? ) -> [ BucketRange ] {
9090 var bucketRange : [ BucketRange ]
91-
91+
9292 var targetCoverage = coverage
93-
93+
9494 // Clamp the value of coverage to between 0 and 1 inclusive.
9595 if coverage < 0 { targetCoverage = 0 }
9696 if coverage > 1 { targetCoverage = 1 }
97-
97+
9898 // Default to equal weights if the weights don't match the number of variations.
9999 let equal = getEqualWeights ( numVariations: numVariations)
100100 var targetWeights = weights ?? equal
101101 if targetWeights. count != numVariations {
102102 targetWeights = equal
103103 }
104-
104+
105105 // Default to equal weights if the sum is not equal 1 (or close enough when rounding errors are factored in):
106106 let weightsSum = targetWeights. sum ( )
107107 if weightsSum < 0.99 || weightsSum > 1.01 {
108108 targetWeights = equal
109109 }
110-
110+
111111 // Convert weights to ranges and return
112112 var cumulative : Float = 0
113-
113+
114114 bucketRange = targetWeights. map { weight in
115115 let start = cumulative
116116 cumulative += weight
117-
117+
118118 return BucketRange ( number1: start. roundTo ( numFractionDigits: 4 ) , number2: ( start + ( targetCoverage * weight) ) . roundTo ( numFractionDigits: 4 ) )
119119 }
120-
120+
121121 return bucketRange
122122 }
123123
124124 static func inRange( n: Float , range: BucketRange ) -> Bool {
125125 return n >= range. number1 && n < range. number2
126126 }
127-
127+
128128 /// Choose Variation from List of ranges which matches particular number
129129 static func chooseVariation( n: Float , ranges: [ BucketRange ] ) -> Int {
130130 for (index, range) in ranges. enumerated ( ) {
@@ -134,23 +134,23 @@ public class Utils {
134134 }
135135 return - 1
136136 }
137-
137+
138138 /// Convert JsonArray to NameSpace
139139 static func getGBNameSpace( namespace: [ JSON ] ) -> NameSpace ? {
140140 if namespace. count >= 3 {
141-
141+
142142 let title = namespace [ 0 ] . string
143143 let start = namespace [ 1 ] . float
144144 let end = namespace [ 2 ] . float
145-
145+
146146 if let title = title, let start = start, let end = end {
147147 return NameSpace ( title, start, end)
148148 }
149-
149+
150150 }
151151 return nil
152152 }
153-
153+
154154 static func paddedVersionString( input: String ) -> String {
155155 var parts = input. replacingOccurrences ( of: " [v] " , with: " " , options: . regularExpression)
156156
@@ -184,7 +184,7 @@ public class Utils {
184184 }
185185 return nil
186186 }
187-
187+
188188 static private func digest( _ string: String ) -> UInt32 {
189189 return Common . fnv1a ( Array ( string. utf8) , offsetBasis: Common . offsetBasis32, prime: Common . prime32)
190190 }
@@ -223,7 +223,7 @@ public class Utils {
223223 return [ : ]
224224 }
225225
226- let ( hashAttribute, hashValue) = getHashAttribute (
226+ let ( hashAttribute, hashValue) = getHashAttribute (
227227 attr: expHashAttribute,
228228 fallback: nil ,
229229 attributes: context. userContext. attributes
@@ -250,7 +250,7 @@ public class Utils {
250250 }
251251 }
252252 }
253-
253+
254254 var mergedAssignments : [ String : String ] = [ : ]
255255
256256 if let fallbackKey = fallbackKey, let fallbackAssignments = stickyBucketAssignmentDocs [ fallbackKey] {
@@ -296,7 +296,7 @@ public class Utils {
296296
297297 let features = data? . features ?? context. globalContext. features
298298 let experiments = data? . experiments ?? context. globalContext. experiments
299-
299+
300300 features. keys. forEach ( { id in
301301 let feature = features [ id]
302302 if let rules = feature? . rules {
@@ -364,123 +364,117 @@ public class Utils {
364364
365365 // Create assignment document
366366 static func generateStickyBucketAssignmentDoc( context: EvalContext , attributeName: String ,
367- attributeValue: String ,
368- assignments: [ String : String ] ) -> ( key: String , doc: StickyAssignmentsDocument , changed: Bool ) {
367+ attributeValue: String ,
368+ assignments: [ String : String ] ) -> ( key: String , doc: StickyAssignmentsDocument , changed: Bool ) {
369369 let key = " \( attributeName) || \( attributeValue) "
370370 let existingAssignments : [ String : String ] = ( context. userContext. stickyBucketAssignmentDocs ? [ key] ? . assignments) ?? [ : ]
371- var newAssignments = existingAssignments
372- assignments. forEach { newAssignments [ $0] = $1 }
371+ var newAssignments = existingAssignments
372+ assignments. forEach { newAssignments [ $0] = $1 }
373373
374374 let changed = NSDictionary ( dictionary: existingAssignments) . isEqual ( to: newAssignments) == false
375375
376376 return (
377- key: key,
378- doc: StickyAssignmentsDocument (
379- attributeName: attributeName,
380- attributeValue: attributeValue,
381- assignments: newAssignments
382- ) ,
383- changed: changed
384- )
377+ key: key,
378+ doc: StickyAssignmentsDocument (
379+ attributeName: attributeName,
380+ attributeValue: attributeValue,
381+ assignments: newAssignments
382+ ) ,
383+ changed: changed
384+ )
385385 }
386386
387387 static func parseQueryString( _ queryString: String ? ) -> [ String : String ] {
388- var map : [ String : String ] = [ : ]
388+ var map : [ String : String ] = [ : ]
389+
390+ guard let queryString = queryString, !queryString. isEmpty else {
391+ return map
392+ }
393+
394+ let params = queryString. split ( separator: " & " )
395+ for param in params {
396+ let keyValuePair = param. split ( separator: " = " , maxSplits: 1 , omittingEmptySubsequences: false )
389397
390- guard let queryString = queryString, !queryString. isEmpty else {
391- return map
398+ guard let name = keyValuePair. first? . removingPercentEncoding,
399+ !name. isEmpty else {
400+ continue
392401 }
393402
394- let params = queryString. split ( separator: " & " )
395- for param in params {
396- let keyValuePair = param. split ( separator: " = " , maxSplits: 1 , omittingEmptySubsequences: false )
397-
398- guard let name = keyValuePair. first? . removingPercentEncoding,
399- !name. isEmpty else {
400- continue
401- }
402-
403- let value = keyValuePair. count > 1
404- ? keyValuePair [ 1 ] . removingPercentEncoding ?? " "
405- : " "
406-
407- map [ name] = value
408- }
403+ let value = keyValuePair. count > 1
404+ ? keyValuePair [ 1 ] . removingPercentEncoding ?? " "
405+ : " "
409406
410- return map
407+ map [ name ] = value
411408 }
409+
410+ return map
411+ }
412412
413413 static func getQueryStringOverride( id: String , url: URL , numberOfVariations: Int ) -> Int ? {
414- let queryMap = parseQueryString ( url. query)
415-
416- guard let possibleValue = queryMap [ id] else {
417- return nil
418- }
419-
420- if let variationValue = Int ( possibleValue) ,
421- variationValue >= 0 && variationValue < numberOfVariations {
422- return variationValue
423- } else {
424- return nil
425- }
414+ let queryMap = parseQueryString ( url. query)
415+
416+ guard let possibleValue = queryMap [ id] else {
417+ return nil
426418 }
427419
428- static func getQueryStringOverride( id: String , urlString: String ? , numberOfVariations: Int ) -> Int ? {
429- guard let urlString = urlString, !urlString. isEmpty else {
430- return nil
431- }
432-
433- guard let url = URL ( string: urlString) else {
434- return nil
435- }
436-
437- return getQueryStringOverride ( id: id, url: url, numberOfVariations: numberOfVariations)
420+ if let variationValue = Int ( possibleValue) ,
421+ variationValue >= 0 && variationValue < numberOfVariations {
422+ return variationValue
423+ } else {
424+ return nil
425+ }
426+ }
427+
428+ static func getQueryStringOverride( id: String , urlString: String ? , numberOfVariations: Int ) -> Int ? {
429+ guard let urlString = urlString, !urlString. isEmpty else {
430+ return nil
438431 }
432+
433+ guard let url = URL ( string: urlString) else {
434+ return nil
435+ }
436+
437+ return getQueryStringOverride ( id: id, url: url, numberOfVariations: numberOfVariations)
438+ }
439439
440- static func initializeEvalContext( context: Context ) -> EvalContext {
441- let options = ClientOptions ( isEnabled: context. isEnabled,
440+ static func initializeEvalContext( context: Context ) -> EvalContext {
441+ let options = ClientOptions ( isEnabled: context. isEnabled,
442442 stickyBucketAssignmentDocs: context. stickyBucketAssignmentDocs,
443443 stickyBucketIdentifierAttributes: context. stickyBucketIdentifierAttributes,
444444 stickyBucketService: context. stickyBucketService,
445445 isQaMode: context. isQaMode,
446446 url: context. url,
447447 trackingClosure: context. trackingClosure)
448448
449- let globalContext = GlobalContext ( features: context. features, savedGroups: context. savedGroups)
449+ let globalContext = GlobalContext ( features: context. features, savedGroups: context. savedGroups)
450450
451451 // should create manual force features
452452 let userContext = UserContext ( attributes: context. attributes, stickyBucketAssignmentDocs: context. stickyBucketAssignmentDocs, forcedVariations: context. forcedVariations, forcedFeatureValues: context. forcedFeatureValues)
453453
454454 let evalContext = EvalContext ( globalContext: globalContext, userContext: userContext, stackContext: StackContext ( ) , options: options)
455455 return evalContext
456456 }
457-
457+
458458 /// Propagates sticky bucket assignments from child evaluation context to parent context
459459 static func propagateStickyAssignments( from childContext: EvalContext , to parentContext: EvalContext ) {
460- if let childAssignments = childContext. userContext. stickyBucketAssignmentDocs,
461- !childAssignments. isEmpty {
462- // Merge child assignments into parent context
463- if parentContext. userContext. stickyBucketAssignmentDocs == nil {
464- parentContext. userContext. stickyBucketAssignmentDocs = [ : ]
465- }
466-
467- for (key, doc) in childAssignments {
468- if let existingDoc = parentContext. userContext. stickyBucketAssignmentDocs ? [ key] {
469- // Merge assignments from both documents
470- var mergedAssignments = existingDoc. assignments
471- for (expKey, assignment) in doc. assignments {
472- mergedAssignments [ expKey] = assignment
473- }
474- let mergedDoc = StickyAssignmentsDocument (
475- attributeName: doc. attributeName,
476- attributeValue: doc. attributeValue,
477- assignments: mergedAssignments
478- )
479- parentContext. userContext. stickyBucketAssignmentDocs ? [ key] = mergedDoc
480- } else {
481- // Add new document
482- parentContext. userContext. stickyBucketAssignmentDocs ? [ key] = doc
483- }
460+ guard let childAssignments = childContext. userContext. stickyBucketAssignmentDocs,
461+ !childAssignments. isEmpty else { return }
462+
463+ if parentContext. userContext. stickyBucketAssignmentDocs == nil {
464+ parentContext. userContext. stickyBucketAssignmentDocs = [ : ]
465+ }
466+
467+ for (key, childDoc) in childAssignments {
468+ if let existingDoc = parentContext. userContext. stickyBucketAssignmentDocs ? [ key] {
469+ let mergedAssignments = existingDoc. assignments. merging ( childDoc. assignments) { _, new in new }
470+ let mergedDoc = StickyAssignmentsDocument (
471+ attributeName: childDoc. attributeName,
472+ attributeValue: childDoc. attributeValue,
473+ assignments: mergedAssignments
474+ )
475+ parentContext. userContext. stickyBucketAssignmentDocs ? [ key] = mergedDoc
476+ } else {
477+ parentContext. userContext. stickyBucketAssignmentDocs ? [ key] = childDoc
484478 }
485479 }
486480 }
0 commit comments