Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update Evaluation struct to conform to encodable. Update logger. #34

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 131 additions & 5 deletions Sources/FeaturevisorSDK/Instance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,30 @@ public enum EvaluationReason: String {
case error
}

public struct Evaluation {
public struct Evaluation: Codable {
private enum CodingKeys: String, CodingKey {
PatrykPiwowarczyk marked this conversation as resolved.
Show resolved Hide resolved
case featureKey
case reason
case bucketValue
case ruleKey
case enabled
case traffic
case sticky
case initial
case variation
case variationValue
case variableKey
case variableValue
case variableSchema
}

// required
public let featureKey: FeatureKey
public let reason: EvaluationReason

// common
public let bucketValue: BucketValue?
public let ruleKey: RuleKey?
public let error: Error?
public let enabled: Bool?
public let traffic: Traffic?
public let sticky: OverrideFeature?
Expand All @@ -55,7 +70,6 @@ public struct Evaluation {
reason: EvaluationReason,
bucketValue: BucketValue? = nil,
ruleKey: RuleKey? = nil,
error: Error? = nil,
enabled: Bool? = nil,
traffic: Traffic? = nil,
sticky: OverrideFeature? = nil,
Expand All @@ -66,11 +80,11 @@ public struct Evaluation {
variableValue: VariableValue? = nil,
variableSchema: VariableSchema? = nil
) {
PatrykPiwowarczyk marked this conversation as resolved.
Show resolved Hide resolved

self.featureKey = featureKey
self.reason = reason
self.bucketValue = bucketValue
self.ruleKey = ruleKey
self.error = error
self.enabled = enabled
self.traffic = traffic
self.sticky = sticky
Expand All @@ -81,6 +95,55 @@ public struct Evaluation {
self.variableValue = variableValue
self.variableSchema = variableSchema
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)

try container.encode(featureKey, forKey: .featureKey)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All fields which are optional should be encoded using encodeIfPresent

try container.encode(reason.rawValue, forKey: .reason)
try container.encodeIfPresent(bucketValue, forKey: .bucketValue)
try container.encodeIfPresent(ruleKey, forKey: .ruleKey)
try container.encodeIfPresent(enabled, forKey: .enabled)
try container.encodeIfPresent(traffic, forKey: .traffic)
try container.encodeIfPresent(sticky, forKey: .sticky)
try container.encodeIfPresent(initial, forKey: .initial)
try container.encodeIfPresent(variation, forKey: .variation)
try container.encodeIfPresent(variationValue, forKey: .variationValue)
try container.encodeIfPresent(variableKey, forKey: .variableKey)
try container.encodeIfPresent(variableValue, forKey: .variableValue)
try container.encodeIfPresent(variableSchema, forKey: .variableSchema)
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

featureKey = try container.decode(FeatureKey.self, forKey: .featureKey)
PatrykPiwowarczyk marked this conversation as resolved.
Show resolved Hide resolved
reason =
try EvaluationReason(rawValue: container.decode(String.self, forKey: .reason)) ?? .error
bucketValue = try container.decodeIfPresent(BucketValue.self, forKey: .bucketValue)
ruleKey = try? container.decodeIfPresent(RuleKey.self, forKey: .ruleKey)
enabled = try? container.decodeIfPresent(Bool.self, forKey: .enabled)
traffic = try? container.decodeIfPresent(Traffic.self, forKey: .traffic)
sticky = try? container.decodeIfPresent(OverrideFeature.self, forKey: .sticky)
initial = try? container.decodeIfPresent(OverrideFeature.self, forKey: .initial)
variation = try? container.decodeIfPresent(Variation.self, forKey: .variation)
variationValue = try? container.decodeIfPresent(
VariationValue.self,
forKey: .variationValue
)
variableKey = try? container.decodeIfPresent(VariableKey.self, forKey: .variableKey)
variableValue = try? container.decodeIfPresent(VariableValue.self, forKey: .variableValue)
variableSchema = try? container.decodeIfPresent(
VariableSchema.self,
forKey: .variableSchema
)
}

func toDictionary() -> [String: Any] {
guard let data = try? JSONEncoder().encode(self) else { return [:] }
return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments))
.flatMap { $0 as? [String: Any] } ?? [:]
}
}

let emptyDatafile = DatafileContent(
Expand Down Expand Up @@ -217,7 +280,70 @@ public class FeaturevisorInstance {
}

func getRevision() -> String {
return datafileReader.getRevision()
return self.datafileReader.getRevision()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need self here?

}

// MARK: - Bucketing

private func getFeature(byKey featureKey: String) -> Feature? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whydo we have changes here and for getBucketKey?

return self.datafileReader.getFeature(featureKey)
}

private func getBucketKey(feature: Feature, context: Context) -> BucketKey {
let featureKey = feature.key

var type: String
var attributeKeys: [AttributeKey]

switch feature.bucketBy {
case .single(let bucketBy):
type = "plain"
attributeKeys = [bucketBy]
case .and(let bucketBy):
type = "and"
attributeKeys = bucketBy
case .or(let bucketBy):
type = "or"
attributeKeys = bucketBy.or
}

var bucketKey: [AttributeValue] = []

attributeKeys.forEach { attributeKey in
guard let attributeValue = context[attributeKey] else {
return
}

if type == "plain" || type == "and" {
bucketKey.append(attributeValue)
}
else { // or
if bucketKey.isEmpty {
bucketKey.append(attributeValue)
}
}
}

bucketKey.append(.string(featureKey))

let result = bucketKey.map { $0.stringValue }.joined(separator: self.bucketKeySeparator)

if let configureBucketKey = self.configureBucketKey {
return configureBucketKey(feature, context, result)
}

return result
}

private func getBucketValue(feature: Feature, context: Context) -> BucketValue {
let bucketKey = getBucketKey(feature: feature, context: context)
let value = Bucket.resolveNumber(forKey: bucketKey)

if let configureBucketValue = self.configureBucketValue {
return configureBucketValue(feature, context, value)
}

return value
}
}

Expand Down
43 changes: 21 additions & 22 deletions Sources/FeaturevisorTypes/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public struct Attribute: Decodable {
}
}

public enum Operator: String, Decodable {
public enum Operator: String, Codable {
case equals = "equals"
case notEquals = "notEquals"

Expand Down Expand Up @@ -76,7 +76,7 @@ public enum Operator: String, Decodable {
case notIn = "notIn"
}

public enum ConditionValue: Decodable {
public enum ConditionValue: Codable {
case string(String)
case integer(Int)
case double(Double)
Expand Down Expand Up @@ -113,28 +113,27 @@ public enum ConditionValue: Decodable {
}
}

public struct PlainCondition: Decodable {
public struct PlainCondition: Codable {
public let attribute: AttributeKey
public let `operator`: Operator
public let value: ConditionValue
}

public struct AndCondition: Decodable {
public struct AndCondition: Codable {
public let and: [Condition]
}

public struct OrCondition: Decodable {
public struct OrCondition: Codable {
public let or: [Condition]
}

public struct NotCondition: Decodable {
public struct NotCondition: Codable {
public let not: [Condition]
}

public enum Condition: Decodable {
public enum Condition: Codable {
case plain(PlainCondition)
case multiple([Condition])

case and(AndCondition)
case or(OrCondition)
case not(NotCondition)
Expand Down Expand Up @@ -200,19 +199,19 @@ public struct Segment: Decodable {

public typealias PlainGroupSegment = SegmentKey

public struct AndGroupSegment: Decodable {
public struct AndGroupSegment: Codable {
public let and: [GroupSegment]
}

public struct OrGroupSegment: Decodable {
public struct OrGroupSegment: Codable {
public let or: [GroupSegment]
}

public struct NotGroupSegment: Decodable {
public struct NotGroupSegment: Codable {
public let not: [GroupSegment]
}

public enum GroupSegment: Decodable {
public enum GroupSegment: Codable {
case plain(PlainGroupSegment)
case multiple([GroupSegment])

Expand Down Expand Up @@ -252,7 +251,7 @@ public typealias VariationValue = String

public typealias VariableKey = String

public enum VariableType: String, Decodable {
public enum VariableType: String, Codable {
case boolean = "boolean"
case string = "string"
case integer = "integer"
Expand Down Expand Up @@ -353,7 +352,7 @@ public enum VariableValue: Codable {
}
}

public struct VariableOverride: Decodable {
public struct VariableOverride: Codable {
public let value: VariableValue

// one of the below must be present in YAML
Expand All @@ -374,20 +373,20 @@ public struct VariableOverride: Decodable {
}
}

public struct Variable: Decodable {
public struct Variable: Codable {
public let key: VariableKey
public let value: VariableValue
public let overrides: [VariableOverride]?
}

public struct Variation: Decodable {
public struct Variation: Codable {
public let description: String? // ony available in YAML
public let value: VariationValue
public let weight: Weight? // 0 to 100 (available from parsed YAML, but not in datafile)
public let variables: [Variable]?
}

public struct VariableSchema: Decodable {
public struct VariableSchema: Codable {
public let key: VariableKey
public let type: VariableType
public let defaultValue: VariableValue
Expand Down Expand Up @@ -446,7 +445,7 @@ public typealias BucketValue = Int
// 0 to 100,000
public typealias Percentage = Int

public struct Range: Decodable {
public struct Range: Codable {
public let start: Percentage
public let end: Percentage

Expand All @@ -463,12 +462,12 @@ public struct Range: Decodable {
}
}

public struct Allocation: Decodable {
public struct Allocation: Codable {
public let variation: VariationValue
public let range: Range
}

public struct Traffic: Decodable {
public struct Traffic: Codable {
public let key: RuleKey
public let segments: GroupSegment
public let percentage: Percentage
Expand All @@ -484,7 +483,7 @@ public struct Traffic: Decodable {

key = try container.decode(RuleKey.self, forKey: .key)
percentage = try container.decode(Percentage.self, forKey: .percentage)
enabled = try container.decodeIfPresent(Bool.self, forKey: .enabled)
enabled = try? container.decodeIfPresent(Bool.self, forKey: .enabled)
variation = try? container.decodeIfPresent(VariationValue.self, forKey: .variation)
variables = try? container.decodeIfPresent(VariableValues.self, forKey: .variables)
allocation = (try? container.decode([Allocation].self, forKey: .allocation)) ?? []
Expand Down Expand Up @@ -674,7 +673,7 @@ public struct DatafileContent: Decodable {
}
}

public struct OverrideFeature {
public struct OverrideFeature: Codable {
public let enabled: Bool
public let variation: VariationValue?
public let variables: VariableValues?
Expand Down
Loading