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: Introduce variation method with generic return types #342

Merged
merged 1 commit into from
Feb 15, 2024
Merged
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
14 changes: 14 additions & 0 deletions LaunchDarkly.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@
A31088282837DCA900184942 /* ReferenceSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = A31088252837DCA900184942 /* ReferenceSpec.swift */; };
A31088292837DCA900184942 /* KindSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = A31088262837DCA900184942 /* KindSpec.swift */; };
A33A5F7A28466D04000C29C7 /* LDContextStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33A5F7928466D04000C29C7 /* LDContextStub.swift */; };
A3470C372B7C1ACE00951CEE /* LDValueDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3470C362B7C1ACE00951CEE /* LDValueDecoder.swift */; };
A3470C382B7C1ACE00951CEE /* LDValueDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3470C362B7C1ACE00951CEE /* LDValueDecoder.swift */; };
A3470C392B7C1ACE00951CEE /* LDValueDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3470C362B7C1ACE00951CEE /* LDValueDecoder.swift */; };
A3470C3A2B7C1ACE00951CEE /* LDValueDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3470C362B7C1ACE00951CEE /* LDValueDecoder.swift */; };
A3570F5A28527B8200CF241A /* LDContextCodableSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3570F5928527B8200CF241A /* LDContextCodableSpec.swift */; };
A358D6D12A4DD48600270C60 /* EnvironmentReporterChainBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = A358D6D02A4DD48600270C60 /* EnvironmentReporterChainBase.swift */; };
A358D6D22A4DD48600270C60 /* EnvironmentReporterChainBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = A358D6D02A4DD48600270C60 /* EnvironmentReporterChainBase.swift */; };
Expand Down Expand Up @@ -256,6 +260,7 @@
A380B09A2B60178D00AB64A6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A380B0982B60178D00AB64A6 /* PrivacyInfo.xcprivacy */; };
A380B09B2B60178D00AB64A6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A380B0982B60178D00AB64A6 /* PrivacyInfo.xcprivacy */; };
A380B09C2B60178D00AB64A6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A380B0982B60178D00AB64A6 /* PrivacyInfo.xcprivacy */; };
A3FFE1132B7D4BA2009EF93F /* LDValueDecoderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3FFE1122B7D4BA2009EF93F /* LDValueDecoderSpec.swift */; };
B40B419C249ADA6B00CD0726 /* DiagnosticCacheSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40B419B249ADA6B00CD0726 /* DiagnosticCacheSpec.swift */; };
B4265EB124E7390C001CFD2C /* TestUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4265EB024E7390C001CFD2C /* TestUtil.swift */; };
B468E71024B3C3AC00E0C883 /* ObjcLDEvaluationDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = B468E70F24B3C3AC00E0C883 /* ObjcLDEvaluationDetail.swift */; };
Expand Down Expand Up @@ -455,6 +460,7 @@
A31088252837DCA900184942 /* ReferenceSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReferenceSpec.swift; sourceTree = "<group>"; };
A31088262837DCA900184942 /* KindSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KindSpec.swift; sourceTree = "<group>"; };
A33A5F7928466D04000C29C7 /* LDContextStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LDContextStub.swift; sourceTree = "<group>"; };
A3470C362B7C1ACE00951CEE /* LDValueDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LDValueDecoder.swift; sourceTree = "<group>"; };
A3570F5928527B8200CF241A /* LDContextCodableSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LDContextCodableSpec.swift; sourceTree = "<group>"; };
A358D6D02A4DD48600270C60 /* EnvironmentReporterChainBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentReporterChainBase.swift; sourceTree = "<group>"; };
A358D6D62A4DE6A500270C60 /* ApplicationInfoEnvironmentReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationInfoEnvironmentReporter.swift; sourceTree = "<group>"; };
Expand All @@ -470,6 +476,7 @@
A36EDFCC2853C50B00D91B05 /* ObjcLDContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjcLDContext.swift; sourceTree = "<group>"; };
A3799D4429033665008D4A8E /* ObjcLDApplicationInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjcLDApplicationInfo.swift; sourceTree = "<group>"; };
A380B0982B60178D00AB64A6 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
A3FFE1122B7D4BA2009EF93F /* LDValueDecoderSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LDValueDecoderSpec.swift; sourceTree = "<group>"; };
B40B419B249ADA6B00CD0726 /* DiagnosticCacheSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagnosticCacheSpec.swift; sourceTree = "<group>"; };
B4265EB024E7390C001CFD2C /* TestUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtil.swift; sourceTree = "<group>"; };
B468E70F24B3C3AC00E0C883 /* ObjcLDEvaluationDetail.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjcLDEvaluationDetail.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -641,6 +648,7 @@
83E2E2071F9FF9A0007514E9 /* Extensions */,
835E1D341F63332C00184DB4 /* ObjectiveC */,
83B6C4B71F4DE78B0055351C /* Support */,
A3470C362B7C1ACE00951CEE /* LDValueDecoder.swift */,
);
name = LaunchDarkly;
path = LaunchDarkly/LaunchDarkly;
Expand All @@ -658,6 +666,7 @@
83D17EA81FCDA16300B2823C /* Extensions */,
8354EFD21F22491C00C05156 /* Info.plist */,
B4265EB024E7390C001CFD2C /* TestUtil.swift */,
A3FFE1122B7D4BA2009EF93F /* LDValueDecoderSpec.swift */,
);
name = LaunchDarklyTests;
path = LaunchDarkly/LaunchDarklyTests;
Expand Down Expand Up @@ -1244,6 +1253,7 @@
8311886C2113AE6400D77CB5 /* ObjcLDChangedFlag.swift in Sources */,
C43C37E8238DF22D003C1624 /* LDEvaluationDetail.swift in Sources */,
8311884C2113ADDE00D77CB5 /* FlagChangeObserver.swift in Sources */,
A3470C3A2B7C1ACE00951CEE /* LDValueDecoder.swift in Sources */,
C443A41223186A4F00145710 /* ConnectionModeChangeObserver.swift in Sources */,
831188592113AE1200D77CB5 /* FlagStore.swift in Sources */,
C443A40D2315AA4D00145710 /* NetworkReporter.swift in Sources */,
Expand Down Expand Up @@ -1311,6 +1321,7 @@
A31088192837DC0400184942 /* Reference.swift in Sources */,
831EF34E20655E730001C643 /* Event.swift in Sources */,
A3799D4729033665008D4A8E /* ObjcLDApplicationInfo.swift in Sources */,
A3470C392B7C1ACE00951CEE /* LDValueDecoder.swift in Sources */,
C443A41123186A4F00145710 /* ConnectionModeChangeObserver.swift in Sources */,
831EF35020655E730001C643 /* ClientServiceFactory.swift in Sources */,
831EF35120655E730001C643 /* KeyedValueCache.swift in Sources */,
Expand Down Expand Up @@ -1377,6 +1388,7 @@
835E1D431F685AC900184DB4 /* ObjcLDChangedFlag.swift in Sources */,
8358F25E1F474E5900ECE1AF /* LDChangedFlag.swift in Sources */,
83D559741FD87CC9002D10C8 /* KeyedValueCache.swift in Sources */,
A3470C372B7C1ACE00951CEE /* LDValueDecoder.swift in Sources */,
C43C37E1236BA050003C1624 /* LDEvaluationDetail.swift in Sources */,
831AAE2C20A9E4F600B46DBA /* Throttler.swift in Sources */,
8354EFE11F26380700C05156 /* LDConfig.swift in Sources */,
Expand Down Expand Up @@ -1458,6 +1470,7 @@
830DB3AC22380A3E00D65D25 /* HTTPHeadersSpec.swift in Sources */,
831425AF206ABB5300F2EF36 /* EnvironmentReportingMock.swift in Sources */,
838AB53F1F72A7D5006F03F5 /* FlagSynchronizerSpec.swift in Sources */,
A3FFE1132B7D4BA2009EF93F /* LDValueDecoderSpec.swift in Sources */,
A3570F5A28527B8200CF241A /* LDContextCodableSpec.swift in Sources */,
837406D421F760640087B22B /* LDTimerSpec.swift in Sources */,
832307A61F7D8D720029815A /* URLRequestSpec.swift in Sources */,
Expand Down Expand Up @@ -1493,6 +1506,7 @@
83D9EC832062DEAB004D7FA6 /* KeyedValueCache.swift in Sources */,
A358D6F02A4DE9EB00270C60 /* WatchOSEnvironmentReporter.swift in Sources */,
831AAE2D20A9E4F600B46DBA /* Throttler.swift in Sources */,
A3470C382B7C1ACE00951CEE /* LDValueDecoder.swift in Sources */,
C43C37E6238DF22B003C1624 /* LDEvaluationDetail.swift in Sources */,
83D9EC872062DEAB004D7FA6 /* FlagSynchronizer.swift in Sources */,
C443A41023186A4F00145710 /* ConnectionModeChangeObserver.swift in Sources */,
Expand Down
89 changes: 50 additions & 39 deletions LaunchDarkly/LaunchDarkly/LDClientVariation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,16 +119,44 @@ extension LDClient {
variationDetailInternal(flagKey, defaultValue, needsReason: true)
}

private func variationDetailInternal<T: LDValueConvertible>(_ flagKey: LDFlagKey, _ defaultValue: T, needsReason: Bool) -> LDEvaluationDetail<T> {
/**
Returns the value of a feature flag for a given flag key, converting the raw JSON value into a type of your specification.

- parameter forKey: the unique feature key for the feature flag.
- parameter defaultValue: the default value for if the flag value is unavailable.
- returns: the variation for the selected context, or `defaultValue` if the flag is not available.
*/
public func variation<T>(forKey flagKey: LDFlagKey, defaultValue: T) -> T where T: LDValueConvertible, T: Decodable {
return variationDetailInternal(flagKey, defaultValue, needsReason: false).value
}

/**
Returns the value of a feature flag for a given flag key, converting the raw JSON value into a type
of your specifification, and including it in an object that also describes the way the value was
determined.

- parameter forKey: the unique feature key for the feature flag.
- parameter defaultValue: the default value for if the flag value is unavailable.
- returns: an `LDEvaluationDetail` object
*/
public func variationDetail<T>(forKey flagKey: LDFlagKey, defaultValue: T) -> LDEvaluationDetail<T> where T: LDValueConvertible, T: Decodable {
return variationDetailInternal(flagKey, defaultValue, needsReason: true)
}

private func variationDetailInternal<T>(_ flagKey: LDFlagKey, _ defaultValue: T, needsReason: Bool) -> LDEvaluationDetail<T> where T: Decodable, T: LDValueConvertible {
var result: LDEvaluationDetail<T>
let featureFlag = flagStore.featureFlag(for: flagKey)
if let featureFlag = featureFlag {
if featureFlag.value == .null {
result = LDEvaluationDetail(value: defaultValue, variationIndex: featureFlag.variation, reason: featureFlag.reason)
} else if let convertedValue = T(fromLDValue: featureFlag.value) {
result = LDEvaluationDetail(value: convertedValue, variationIndex: featureFlag.variation, reason: featureFlag.reason)
} else {
result = LDEvaluationDetail(value: defaultValue, variationIndex: nil, reason: ["kind": "ERROR", "errorKind": "WRONG_TYPE"])
do {
let convertedValue = try LDValueDecoder().decode(T.self, from: featureFlag.value)
result = LDEvaluationDetail(value: convertedValue, variationIndex: featureFlag.variation, reason: featureFlag.reason)
} catch let error {
os_log("%s type conversion error %s: failed converting %s to type %s", log: config.logger, type: .debug, typeName(and: #function), String(describing: error), String(describing: featureFlag.value), String(describing: T.self))
result = LDEvaluationDetail(value: defaultValue, variationIndex: nil, reason: ["kind": "ERROR", "errorKind": "WRONG_TYPE"])
}
}
} else {
os_log("%s Unknown feature flag %s; returning default value", log: config.logger, type: .debug, typeName(and: #function), flagKey.description)
Expand All @@ -144,65 +172,48 @@ extension LDClient {
}
}

private protocol LDValueConvertible {
init?(fromLDValue: LDValue)
/**
Protocol indicting a type can be converted into an LDValue.

Types used with the `LDClient.variation(forKey: defaultValue:)` or `LDClient.variationDetail(forKey: detailValue:)`
methods are required to implement this protocol. This protocol has already been implemented for Bool, Int, Double, String,
and LDValue types.

This allows custom types as evaluation result types while retaining the LDValue type throughout the event processing system.
*/
public protocol LDValueConvertible {
/**
Return an LDValue representation of this instance.
*/
func toLDValue() -> LDValue
}

extension Bool: LDValueConvertible {
init?(fromLDValue value: LDValue) {
guard case .bool(let value) = value
else { return nil }
self = value
}

func toLDValue() -> LDValue {
public func toLDValue() -> LDValue {
return .bool(self)
}
}

extension Int: LDValueConvertible {
init?(fromLDValue value: LDValue) {
guard case .number(let value) = value, let intValue = Int(exactly: value.rounded())
else { return nil }
self = intValue
}

func toLDValue() -> LDValue {
public func toLDValue() -> LDValue {
return .number(Double(self))
}
}

extension Double: LDValueConvertible {
init?(fromLDValue value: LDValue) {
guard case .number(let value) = value
else { return nil }
self = value
}

func toLDValue() -> LDValue {
public func toLDValue() -> LDValue {
return .number(self)
}
}

extension String: LDValueConvertible {
init?(fromLDValue value: LDValue) {
guard case .string(let value) = value
else { return nil }
self = value
}

func toLDValue() -> LDValue {
public func toLDValue() -> LDValue {
return .string(self)
}
}

extension LDValue: LDValueConvertible {
init?(fromLDValue value: LDValue) {
self = value
}

func toLDValue() -> LDValue {
public func toLDValue() -> LDValue {
return self
}
}
Loading