Skip to content

Commit

Permalink
Add cache generalization & Snapshots for synchronous support (#37)
Browse files Browse the repository at this point in the history
* Add cache generalization & Snapshots for synchronous support

* Update ConfigCatClientTests.swift
  • Loading branch information
z4kn4fein authored Aug 28, 2023
1 parent c4b5dc5 commit 23b9779
Show file tree
Hide file tree
Showing 26 changed files with 686 additions and 730 deletions.
2 changes: 1 addition & 1 deletion ConfigCat.podspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Pod::Spec.new do |spec|

spec.name = "ConfigCat"
spec.version = "9.4.0"
spec.version = "10.0.0"
spec.summary = "ConfigCat Swift SDK"
spec.swift_version = "4.2"

Expand Down
2 changes: 1 addition & 1 deletion ConfigCat.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ SUPPORTED_PLATFORMS = macosx iphoneos iphonesimulator watchos watchsimulator app
SWIFT_VERSION = 4.2

// ConfigCat SDK version
MARKETING_VERSION = 9.4.0
MARKETING_VERSION = 10.0.0
24 changes: 20 additions & 4 deletions ConfigCat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,13 @@
C4FA1B3E278D919A00BFA8C3 /* OverrideDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FA1B3C278D919900BFA8C3 /* OverrideDataSource.swift */; };
C4FA1B40278D953300BFA8C3 /* OverrideBehaviour.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FA1B3F278D953300BFA8C3 /* OverrideBehaviour.swift */; };
C4FA1B41278D953300BFA8C3 /* OverrideBehaviour.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FA1B3F278D953300BFA8C3 /* OverrideBehaviour.swift */; };
F10AC6D02A93B02B006FA496 /* CacheTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F10AC6CF2A93B02B006FA496 /* CacheTest.swift */; };
F10AC6D22A950197006FA496 /* FlagEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F10AC6D12A950197006FA496 /* FlagEvaluator.swift */; };
F10AC6D32A9507C4006FA496 /* FlagEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F10AC6D12A950197006FA496 /* FlagEvaluator.swift */; };
F10AC6D52A9516F5006FA496 /* ConfigCatSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = F10AC6D42A9516F5006FA496 /* ConfigCatSnapshot.swift */; };
F10AC6D62A9516F5006FA496 /* ConfigCatSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = F10AC6D42A9516F5006FA496 /* ConfigCatSnapshot.swift */; };
F11F76BC288AD6CA0097939F /* AsyncAwaitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11F76BB288AD6CA0097939F /* AsyncAwaitTests.swift */; };
F11F76BE288AE7540097939F /* SyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11F76BD288AE7540097939F /* SyncTests.swift */; };
F11F76BE288AE7540097939F /* SnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11F76BD288AE7540097939F /* SnapshotTests.swift */; };
F11F76C0288AE7650097939F /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11F76BF288AE7640097939F /* Extensions.swift */; };
F11F76C1288AE7970097939F /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11F76BF288AE7640097939F /* Extensions.swift */; };
F14961CF28FF71400095A72A /* EvaluationDetailsExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F14961CE28FF71400095A72A /* EvaluationDetailsExtensionTests.swift */; };
Expand Down Expand Up @@ -120,9 +125,12 @@
C4D34D3A249B6F2900908D76 /* testmatrix_variationId.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = testmatrix_variationId.csv; path = Resources/testmatrix_variationId.csv; sourceTree = "<group>"; };
C4FA1B3C278D919900BFA8C3 /* OverrideDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverrideDataSource.swift; sourceTree = "<group>"; };
C4FA1B3F278D953300BFA8C3 /* OverrideBehaviour.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverrideBehaviour.swift; sourceTree = "<group>"; };
F10AC6CF2A93B02B006FA496 /* CacheTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheTest.swift; sourceTree = "<group>"; };
F10AC6D12A950197006FA496 /* FlagEvaluator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlagEvaluator.swift; sourceTree = "<group>"; };
F10AC6D42A9516F5006FA496 /* ConfigCatSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigCatSnapshot.swift; sourceTree = "<group>"; };
F10F787D2528950D0021F468 /* DataGovernanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataGovernanceTests.swift; sourceTree = "<group>"; };
F11F76BB288AD6CA0097939F /* AsyncAwaitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncAwaitTests.swift; sourceTree = "<group>"; };
F11F76BD288AE7540097939F /* SyncTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncTests.swift; sourceTree = "<group>"; };
F11F76BD288AE7540097939F /* SnapshotTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotTests.swift; sourceTree = "<group>"; };
F11F76BF288AE7640097939F /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
F14961CE28FF71400095A72A /* EvaluationDetailsExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EvaluationDetailsExtensionTests.swift; sourceTree = "<group>"; };
F15F9AF62169176A00F490CD /* ConfigCatUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigCatUser.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -222,6 +230,8 @@
3F1F2C6523E10BF300AFA7D2 /* PollingModes.swift */,
F1E1180A257532D700DA245A /* Log.swift */,
3F880A41207BE91300087A6B /* Resources */,
F10AC6D12A950197006FA496 /* FlagEvaluator.swift */,
F10AC6D42A9516F5006FA496 /* ConfigCatSnapshot.swift */,
);
name = Sources;
path = Sources/ConfigCat;
Expand All @@ -232,7 +242,7 @@
children = (
F14961CE28FF71400095A72A /* EvaluationDetailsExtensionTests.swift */,
F1BC414B28E1D6C900F2230A /* Helpers.swift */,
F11F76BD288AE7540097939F /* SyncTests.swift */,
F11F76BD288AE7540097939F /* SnapshotTests.swift */,
F11F76BB288AD6CA0097939F /* AsyncAwaitTests.swift */,
F17DEE22288876AE009C3E48 /* LazyLoadingTests.swift */,
C40CF51127B5533800D9F88A /* LocalTests.swift */,
Expand All @@ -252,6 +262,7 @@
3F8EDF9F2084194700906339 /* ConfigCatClientTests.swift */,
F15F9B16216973B000F490CD /* RolloutIntegrationTests.swift */,
F10F787D2528950D0021F468 /* DataGovernanceTests.swift */,
F10AC6CF2A93B02B006FA496 /* CacheTest.swift */,
);
name = Tests;
path = Tests/ConfigCatTests;
Expand Down Expand Up @@ -380,8 +391,10 @@
F17DEE28288876F7009C3E48 /* MutableQueue.swift in Sources */,
F1BC414728E1D54800F2230A /* EvaluationDetails.swift in Sources */,
C4FA1B3D278D919A00BFA8C3 /* OverrideDataSource.swift in Sources */,
F10AC6D52A9516F5006FA496 /* ConfigCatSnapshot.swift in Sources */,
B4BD2AB6258CA6FF007371E2 /* Log.swift in Sources */,
B4BD2AB8258CA6FF007371E2 /* KeyValue.swift in Sources */,
F10AC6D32A9507C4006FA496 /* FlagEvaluator.swift in Sources */,
B4BD2AB9258CA6FF007371E2 /* Config.swift in Sources */,
B4BD2ABA258CA6FF007371E2 /* RolloutEvaluator.swift in Sources */,
B4BD2ABE258CA6FF007371E2 /* Synced.swift in Sources */,
Expand All @@ -404,13 +417,16 @@
F1BC414828E1D54800F2230A /* EvaluationDetails.swift in Sources */,
C40CF51527B557EE00D9F88A /* LocalDictionaryDataSource.swift in Sources */,
F1B1D8B628FF2C830034165E /* ConfigCatOptions.swift in Sources */,
F10AC6D62A9516F5006FA496 /* ConfigCatSnapshot.swift in Sources */,
F10AC6D22A950197006FA496 /* FlagEvaluator.swift in Sources */,
B4BD2AE6258CA7DF007371E2 /* ConfigFetcher.swift in Sources */,
B4BD2AE7258CA7DF007371E2 /* RolloutIntegrationTests.swift in Sources */,
B4BD2AE8258CA7DF007371E2 /* Config.swift in Sources */,
F11F76C0288AE7650097939F /* Extensions.swift in Sources */,
B4BD2AEC258CA7DF007371E2 /* ConfigFetcherTests.swift in Sources */,
B4BD2AED258CA7DF007371E2 /* DataGovernanceTests.swift in Sources */,
B4BD2AEE258CA7DF007371E2 /* PollingMode.swift in Sources */,
F10AC6D02A93B02B006FA496 /* CacheTest.swift in Sources */,
F17DEE2F288876F7009C3E48 /* Utils.swift in Sources */,
F11F76BC288AD6CA0097939F /* AsyncAwaitTests.swift in Sources */,
C4FA1B41278D953300BFA8C3 /* OverrideBehaviour.swift in Sources */,
Expand All @@ -432,7 +448,7 @@
B4BD2AFC258CA7DF007371E2 /* AutoPollingTests.swift in Sources */,
B4BD2AFF258CA7DF007371E2 /* VariationIdTests.swift in Sources */,
B4BD2B00258CA7DF007371E2 /* Mock.swift in Sources */,
F11F76BE288AE7540097939F /* SyncTests.swift in Sources */,
F11F76BE288AE7540097939F /* SnapshotTests.swift in Sources */,
C40CF51227B5533800D9F88A /* LocalTests.swift in Sources */,
B4BD2B02258CA7DF007371E2 /* ManualPollingTests.swift in Sources */,
F17DEE29288876F7009C3E48 /* MutableQueue.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ If you want to use ConfigCat in a [SwiftPM](https://swift.org/package-manager/)

``` swift
dependencies: [
.package(url: "https://github.com/configcat/swift-sdk", from: "9.4.0")
.package(url: "https://github.com/configcat/swift-sdk", from: "10.0.0")
]
```

Expand Down
60 changes: 42 additions & 18 deletions Sources/ConfigCat/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,68 @@ protocol JsonSerializable {
func toJsonMap() -> [String: Any]
}

class ConfigEntry: Equatable, JsonSerializable {
class ConfigEntry: Equatable {
static func ==(lhs: ConfigEntry, rhs: ConfigEntry) -> Bool {
lhs.eTag == rhs.eTag
}

let config: Config
let configJson: String
let eTag: String
let fetchTime: Date

init(config: Config = Config.empty, eTag: String = "", fetchTime: Date = .distantPast) {
init(config: Config = Config.empty, configJson: String = "", eTag: String = "", fetchTime: Date = .distantPast) {
self.config = config
self.eTag = eTag
self.fetchTime = fetchTime
self.configJson = configJson
}

func withFetchTime(time: Date) -> ConfigEntry {
ConfigEntry(config: config, eTag: eTag, fetchTime: time)
ConfigEntry(config: config, configJson: configJson, eTag: eTag, fetchTime: time)
}

func isExpired(seconds: Int) -> Bool {
return Date().subtract(seconds: seconds)! > fetchTime;
}

static func fromJson(json: [String: Any]) -> ConfigEntry {
let eTag = json["eTag"] as? String ?? ""
var config: Config = .empty
var fetchTime: Date = .distantPast
if let configFromMap = json["config"] as? [String: Any] {
config = Config.fromJson(json: configFromMap)
static func fromConfigJson(json: String, eTag: String, fetchTime: Date) -> Result<ConfigEntry, Error> {
do {
guard let data = json.data(using: .utf8) else {
return .failure(ParseError(message: "Decode to utf8 data failed."))
}
guard let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
return .failure(ParseError(message: "Convert to [String: Any] map failed."))
}

return .success(ConfigEntry(config: Config.fromJson(json: jsonObject), configJson: json, eTag: eTag, fetchTime: fetchTime))
} catch {
return .failure(error)
}
}

static func fromCached(cached: String) -> Result<ConfigEntry, Error> {
guard let timeIndex = cached.firstIndex(of: "\n") else {
return .failure(ParseError(message: "Number of values is fewer than expected."))
}
let withoutTime = String(cached.suffix(from: cached.index(timeIndex, offsetBy: 1)))
guard let eTagIndex = withoutTime.firstIndex(of: "\n") else {
return .failure(ParseError(message: "Number of values is fewer than expected."))
}
if let fetchIntervalSince1970 = json["fetchTime"] as? Double {
fetchTime = Date(timeIntervalSince1970: fetchIntervalSince1970)

let timeString = String(cached[..<timeIndex])
guard let time = Double(timeString) else {
return .failure(ParseError(message: String(format: "Invalid fetch time: %@", timeString)))
}
return ConfigEntry(config: config, eTag: eTag, fetchTime: fetchTime)

let configJson = String(withoutTime.suffix(from: withoutTime.index(eTagIndex, offsetBy: 1)))
let eTag = String(withoutTime[..<eTagIndex])

return fromConfigJson(json: configJson, eTag: eTag, fetchTime: Date(timeIntervalSince1970: time / 1000))
}

func toJsonMap() -> [String: Any] {
[
"eTag": eTag,
"fetchTime": fetchTime.timeIntervalSince1970,
"config": config.toJsonMap()
]
func serialize() -> String {
String(format: "%.0f", floor(fetchTime.timeIntervalSince1970 * 1000)) + "\n" + eTag + "\n" + configJson
}

var isEmpty: Bool {
Expand Down
Loading

0 comments on commit 23b9779

Please sign in to comment.