Skip to content

Commit

Permalink
[#132] Error Throwing in Feature-Flag Stores (#139)
Browse files Browse the repository at this point in the history
* Extended the protocols for feature-flag stores to support error throwing in reading, writing, and removing values, and saving changes
* Updated the built-in stores
* Updated `FeatureFlagResolver`
* Updated tests
  • Loading branch information
yakovmanshin authored May 10, 2024
1 parent 8624a18 commit ab7fff1
Show file tree
Hide file tree
Showing 19 changed files with 417 additions and 150 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ extension FeatureFlagResolver {
/// No feature-flag store contains a value for the given key.
case valueNotFoundInStores(key: String)

/// The feature-flag store has thrown an error.
case storeError(any Swift.Error)

/// Currently, optional values are not supported by the `FeatureFlagResolver`.
///
/// - Note: Support for optional values will be added in [#130](https://github.com/yakovmanshin/YMFF/issues/130).
case optionalValuesNotAllowed

/// The types of the old and new values don’t match.
case typeMismatch

}

}
46 changes: 33 additions & 13 deletions Sources/YMFF/FeatureFlagResolver/FeatureFlagResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ final public class FeatureFlagResolver {
let mutableStores = getMutableStores()
Task { [mutableStores] in
for store in mutableStores {
await store.saveChanges()
try? await store.saveChanges()
}
}
}
Expand All @@ -68,7 +68,11 @@ extension FeatureFlagResolver: FeatureFlagResolverProtocol {

try await validateOverrideValue(newValue, forKey: key)

await mutableStores[0].setValue(newValue, forKey: key)
do {
try await mutableStores[0].setValue(newValue, forKey: key)
} catch {
throw Error.storeError(error)
}
}

public func removeValueFromMutableStore(using key: FeatureFlagKey) async throws {
Expand All @@ -77,7 +81,11 @@ extension FeatureFlagResolver: FeatureFlagResolverProtocol {
throw Error.noStoreAvailable
}

await mutableStores[0].removeValue(forKey: key)
do {
try await mutableStores[0].removeValue(forKey: key)
} catch {
throw Error.storeError(error)
}
}

}
Expand All @@ -101,7 +109,11 @@ extension FeatureFlagResolver: SynchronousFeatureFlagResolverProtocol {

try validateOverrideValueSync(newValue, forKey: key)

syncMutableStores[0].setValueSync(newValue, forKey: key)
do {
try syncMutableStores[0].setValueSync(newValue, forKey: key)
} catch {
throw Error.storeError(error)
}
}

public func removeValueFromMutableStoreSync(using key: FeatureFlagKey) throws {
Expand All @@ -110,7 +122,11 @@ extension FeatureFlagResolver: SynchronousFeatureFlagResolverProtocol {
throw Error.noStoreAvailable
}

syncMutableStores[0].removeValueSync(forKey: key)
do {
try syncMutableStores[0].removeValueSync(forKey: key)
} catch {
throw Error.storeError(error)
}
}

}
Expand Down Expand Up @@ -149,10 +165,12 @@ extension FeatureFlagResolver {

for store in matchingStores {
if await store.containsValue(forKey: key) {
guard let value: Value = await store.value(forKey: key)
else { throw Error.typeMismatch }

return value
do {
let value: Value = try await store.value(forKey: key)
return value
} catch {
throw Error.storeError(error)
}
}
}

Expand All @@ -167,10 +185,12 @@ extension FeatureFlagResolver {

for store in matchingStores {
if store.containsValueSync(forKey: key) {
guard let value: Value = store.valueSync(forKey: key)
else { throw Error.typeMismatch }

return value
do {
let value: Value = try store.valueSync(forKey: key)
return value
} catch {
throw Error.storeError(error)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// CommonFeatureFlagStoreError.swift
// YMFF
//
// Created by Yakov Manshin on 5/7/24.
// Copyright © 2024 Yakov Manshin. See the LICENSE file for license info.
//

enum CommonFeatureFlagStoreError: Error {
case valueNotFound(key: String)
case typeMismatch
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ extension RuntimeOverridesStore: SynchronousMutableFeatureFlagStore {
store[key] != nil
}

public func valueSync<Value>(forKey key: String) -> Value? {
store[key] as? Value
public func valueSync<Value>(forKey key: String) throws -> Value {
guard let anyValue = store[key] else { throw CommonFeatureFlagStoreError.valueNotFound(key: key) }
guard let value = anyValue as? Value else { throw CommonFeatureFlagStoreError.typeMismatch }
return value
}

public func setValueSync<Value>(_ value: Value, forKey key: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ extension TransparentFeatureFlagStore: SynchronousFeatureFlagStore, FeatureFlagS
self[key] != nil
}

public func valueSync<V>(forKey key: String) -> V? {
self[key] as? V
public func valueSync<V>(forKey key: String) throws -> V {
guard let anyValue = self[key] else { throw CommonFeatureFlagStoreError.valueNotFound(key: key) }
guard let value = anyValue as? V else { throw CommonFeatureFlagStoreError.typeMismatch }
return value
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,12 @@ extension UserDefaultsStore: SynchronousMutableFeatureFlagStore {
userDefaults.object(forKey: key) != nil
}

public func valueSync<Value>(forKey key: String) -> Value? {
userDefaults.object(forKey: key) as? Value
public func valueSync<Value>(forKey key: String) throws -> Value {
guard let anyValue = userDefaults.object(forKey: key) else {
throw CommonFeatureFlagStoreError.valueNotFound(key: key)
}
guard let value = anyValue as? Value else { throw CommonFeatureFlagStoreError.typeMismatch }
return value
}

public func setValueSync<Value>(_ value: Value, forKey key: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ public protocol FeatureFlagStore {
/// Retrieves a feature flag value by its key.
///
/// - Parameter key: *Required.* The key that points to a feature flag value in the store.
func value<Value>(forKey key: String) async -> Value?
func value<Value>(forKey key: String) async throws -> Value

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ public protocol MutableFeatureFlagStore: AnyObject, FeatureFlagStore {
/// - Parameters:
/// - value: *Required.* The value to record.
/// - key: *Required.* The key used to address the value.
func setValue<Value>(_ value: Value, forKey key: String) async
func setValue<Value>(_ value: Value, forKey key: String) async throws

/// Removes the value from the store.
///
/// - Parameter key: *Required.* The key used to address the value.
func removeValue(forKey key: String) async
func removeValue(forKey key: String) async throws

/// Immediately saves changed values so they’re not lost.
///
/// + This method can be called when work with the feature flag store is finished.
func saveChanges() async
func saveChanges() async throws

}

Expand All @@ -33,6 +33,6 @@ public protocol MutableFeatureFlagStore: AnyObject, FeatureFlagStore {
extension MutableFeatureFlagStore {

// Not all kinds of feature flag stores need this method, so it’s optional to implement.
public func saveChanges() async { }
public func saveChanges() async throws { }

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public protocol SynchronousFeatureFlagStore: FeatureFlagStore {
/// Retrieves a feature flag value by its key.
///
/// - Parameter key: *Required.* The key that points to a feature flag value in the store.
func valueSync<Value>(forKey key: String) -> Value?
func valueSync<Value>(forKey key: String) throws -> Value

}

Expand All @@ -28,8 +28,8 @@ extension SynchronousFeatureFlagStore {
containsValueSync(forKey: key)
}

public func value<Value>(forKey key: String) async -> Value? {
valueSync(forKey: key)
public func value<Value>(forKey key: String) async throws -> Value {
try valueSync(forKey: key)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,34 @@ public protocol SynchronousMutableFeatureFlagStore: SynchronousFeatureFlagStore,
/// - Parameters:
/// - value: *Required.* The value to record.
/// - key: *Required.* The key used to address the value.
func setValueSync<Value>(_ value: Value, forKey key: String)
func setValueSync<Value>(_ value: Value, forKey key: String) throws

/// Removes the value from the store.
///
/// - Parameter key: *Required.* The key used to address the value.
func removeValueSync(forKey key: String)
func removeValueSync(forKey key: String) throws

/// Immediately saves changed values so they’re not lost.
///
/// + This method can be called when work with the feature flag store is finished.
func saveChangesSync()
func saveChangesSync() throws

}

// MARK: - Async Requirements

extension SynchronousMutableFeatureFlagStore {

public func setValue<Value>(_ value: Value, forKey key: String) async {
setValueSync(value, forKey: key)
public func setValue<Value>(_ value: Value, forKey key: String) async throws {
try setValueSync(value, forKey: key)
}

public func removeValue(forKey key: String) async {
removeValueSync(forKey: key)
public func removeValue(forKey key: String) async throws {
try removeValueSync(forKey: key)
}

public func saveChanges() async {
saveChangesSync()
public func saveChanges() async throws {
try saveChangesSync()
}

}
Expand All @@ -49,6 +49,6 @@ extension SynchronousMutableFeatureFlagStore {

extension SynchronousMutableFeatureFlagStore {

public func saveChangesSync() { }
public func saveChangesSync() throws { }

}
Loading

0 comments on commit ab7fff1

Please sign in to comment.