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

[#130] Support for Optional Values #143

Merged
merged 4 commits into from
May 12, 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
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ extension FeatureFlagResolver {
/// 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

}

}
24 changes: 2 additions & 22 deletions Sources/YMFF/FeatureFlagResolver/FeatureFlagResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,7 @@ final public class FeatureFlagResolver {
extension FeatureFlagResolver: FeatureFlagResolverProtocol {

public func value<Value>(for key: FeatureFlagKey) async throws -> Value {
let retrievedValue: Value = try await retrieveFirstValue(forKey: key)
try validateValue(retrievedValue)

return retrievedValue
try await retrieveFirstValue(forKey: key)
}

public func setValue<Value>(_ newValue: Value, toMutableStoreUsing key: FeatureFlagKey) async throws {
Expand All @@ -66,8 +63,6 @@ extension FeatureFlagResolver: FeatureFlagResolverProtocol {
throw Error.noStoreAvailable
}

try validateValue(newValue)

for store in getStores() {
if
case .failure(let error) = await store.value(forKey: key) as Result<Value, _>,
Expand Down Expand Up @@ -118,10 +113,7 @@ extension FeatureFlagResolver: FeatureFlagResolverProtocol {
extension FeatureFlagResolver: SynchronousFeatureFlagResolverProtocol {

public func valueSync<Value>(for key: FeatureFlagKey) throws -> Value {
let retrievedValue: Value = try retrieveFirstValueSync(forKey: key)
try validateValue(retrievedValue)

return retrievedValue
try retrieveFirstValueSync(forKey: key)
}

public func setValueSync<Value>(_ newValue: Value, toMutableStoreUsing key: FeatureFlagKey) throws {
Expand All @@ -130,8 +122,6 @@ extension FeatureFlagResolver: SynchronousFeatureFlagResolverProtocol {
throw Error.noStoreAvailable
}

try validateValue(newValue)

for store in getSyncStores() {
if
case .failure(let error) = store.valueSync(forKey: key) as Result<Value, _>,
Expand Down Expand Up @@ -253,14 +243,4 @@ extension FeatureFlagResolver {
throw Error.valueNotFoundInStores(key: key)
}

func validateValue<Value>(_ value: Value) throws {
if valueIsOptional(value) {
throw Error.optionalValuesNotAllowed
}
}

func valueIsOptional<Value>(_ value: Value) -> Bool {
value is ExpressibleByNilLiteral
}

}
228 changes: 200 additions & 28 deletions Tests/YMFFTests/Cases/FeatureFlagResolverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -244,33 +244,73 @@ final class FeatureFlagResolverTests: XCTestCase {
}
}

func test_value_optionalValue() async {
func test_value_optionalValue_nonNil() async {
let store = FeatureFlagStoreMock()
configuration.stores = [store]
store.value_result = .success((123 as Int?)!)
let optionalInt: Int? = 123
store.value_result = .success(optionalInt as Any)

do {
let _: Int? = try await resolver.value(for: "TEST_key1")
XCTFail("Expected an error")
} catch FeatureFlagResolver.Error.optionalValuesNotAllowed {
let value: Int? = try await resolver.value(for: "TEST_key1")

XCTAssertEqual(store.value_invocationCount, 1)
XCTAssertEqual(store.value_keys, ["TEST_key1"])
XCTAssert(type(of: value) == Optional<Int>.self)
XCTAssertEqual(value, 123)
} catch {
XCTFail("Unexpected error: \(error)")
}
}

func test_valueSync_optionalValue() {
func test_valueSync_optionalValue_nonNil() {
let store = SynchronousFeatureFlagStoreMock()
configuration.stores = [store]
store.valueSync_result = .success((123 as Int?)!)
let optionalInt: Int? = 123
store.valueSync_result = .success(optionalInt as Any)

do {
let _: Int? = try resolver.valueSync(for: "TEST_key1")
XCTFail("Expected an error")
} catch FeatureFlagResolver.Error.optionalValuesNotAllowed {
let value: Int? = try resolver.valueSync(for: "TEST_key1")

XCTAssertEqual(store.valueSync_invocationCount, 1)
XCTAssertEqual(store.valueSync_keys, ["TEST_key1"])
XCTAssert(type(of: value) == Optional<Int>.self)
XCTAssertEqual(value, 123)
} catch {
XCTFail("Unexpected error: \(error)")
}
}

func test_value_optionalValue_nil() async {
let store = FeatureFlagStoreMock()
configuration.stores = [store]
let optionalString: String? = nil
store.value_result = .success(optionalString as Any)

do {
let value: String? = try await resolver.value(for: "TEST_key1")

XCTAssertEqual(store.value_invocationCount, 1)
XCTAssertEqual(store.value_keys, ["TEST_key1"])
XCTAssert(type(of: value) == Optional<String>.self)
XCTAssertNil(value)
} catch {
XCTFail("Unexpected error: \(error)")
}
}

func test_valueSync_optionalValue_nil() {
let store = SynchronousFeatureFlagStoreMock()
configuration.stores = [store]
let optionalString: String? = nil
store.valueSync_result = .success(optionalString as Any)

do {
let value: String? = try resolver.valueSync(for: "TEST_key1")

XCTAssertEqual(store.valueSync_invocationCount, 1)
XCTAssertEqual(store.valueSync_keys, ["TEST_key1"])
XCTAssert(type(of: value) == Optional<String>.self)
XCTAssertNil(value)
} catch {
XCTFail("Unexpected error: \(error)")
}
Expand Down Expand Up @@ -438,37 +478,169 @@ final class FeatureFlagResolverTests: XCTestCase {
}
}

func test_setValue_singleMutableStore_existingValue_optionalValue() async {
func test_setValue_singleMutableStore_existingValue_nonNilToNonNil() async {
let store = MutableFeatureFlagStoreMock()
configuration.stores = [store]
store.value_result = .success("TEST_value1")
let optionalInt: Int? = 123
store.value_result = .success(optionalInt as Any)

do {
try await resolver.setValue(456 as Int?, toMutableStoreUsing: "TEST_key1")
XCTFail("Expected an error")
} catch FeatureFlagResolver.Error.optionalValuesNotAllowed {
XCTAssertEqual(store.value_invocationCount, 0)
XCTAssertTrue(store.value_keys.isEmpty)
XCTAssertEqual(store.setValue_invocationCount, 0)
XCTAssertTrue(store.setValue_keyValuePairs.isEmpty)
let newValue: Int? = 456
try await resolver.setValue(newValue, toMutableStoreUsing: "TEST_key1")

XCTAssertEqual(store.value_invocationCount, 1)
XCTAssertEqual(store.value_keys, ["TEST_key1"])
XCTAssertEqual(store.setValue_invocationCount, 1)
XCTAssertEqual(store.setValue_keyValuePairs.count, 1)
XCTAssertEqual(store.setValue_keyValuePairs[0].0, "TEST_key1")
XCTAssertEqual(store.setValue_keyValuePairs[0].1 as! Int?, 456)
} catch {
XCTFail("Unexpected error: \(error)")
}
}

func test_setValueSync_singleSyncMutableStore_existingValue_optionalValue() {
func test_setValueSync_singleSyncMutableStore_existingValue_nonNilToNonNil() {
let store = SynchronousMutableFeatureFlagStoreMock()
configuration.stores = [store]
store.valueSync_result = .success("TEST_value1")
let optionalInt: Int? = 123
store.valueSync_result = .success(optionalInt as Any)

do {
try resolver.setValueSync(456 as Int?, toMutableStoreUsing: "TEST_key1")
XCTFail("Expected an error")
} catch FeatureFlagResolver.Error.optionalValuesNotAllowed {
XCTAssertEqual(store.valueSync_invocationCount, 0)
XCTAssertTrue(store.valueSync_keys.isEmpty)
XCTAssertEqual(store.setValueSync_invocationCount, 0)
XCTAssertTrue(store.setValueSync_keyValuePairs.isEmpty)
let newValue: Int? = 456
try resolver.setValueSync(newValue, toMutableStoreUsing: "TEST_key1")

XCTAssertEqual(store.valueSync_invocationCount, 1)
XCTAssertEqual(store.valueSync_keys, ["TEST_key1"])
XCTAssertEqual(store.setValueSync_invocationCount, 1)
XCTAssertEqual(store.setValueSync_keyValuePairs.count, 1)
XCTAssertEqual(store.setValueSync_keyValuePairs[0].0, "TEST_key1")
XCTAssertEqual(store.setValueSync_keyValuePairs[0].1 as! Int?, 456)
} catch {
XCTFail("Unexpected error: \(error)")
}
}

func test_setValue_singleMutableStore_existingValue_nilToNonNil() async {
let store = MutableFeatureFlagStoreMock()
configuration.stores = [store]
let optionalInt: Int? = 123
store.value_result = .success(optionalInt as Any)

do {
let newValue: Int? = nil
try await resolver.setValue(newValue, toMutableStoreUsing: "TEST_key1")

XCTAssertEqual(store.value_invocationCount, 1)
XCTAssertEqual(store.value_keys, ["TEST_key1"])
XCTAssertEqual(store.setValue_invocationCount, 1)
XCTAssertEqual(store.setValue_keyValuePairs.count, 1)
XCTAssertEqual(store.setValue_keyValuePairs[0].0, "TEST_key1")
XCTAssertEqual(store.setValue_keyValuePairs[0].1 as! Int?, nil)
} catch {
XCTFail("Unexpected error: \(error)")
}
}

func test_setValueSync_singleSyncMutableStore_existingValue_nilToNonNil() {
let store = SynchronousMutableFeatureFlagStoreMock()
configuration.stores = [store]
let optionalInt: Int? = 123
store.valueSync_result = .success(optionalInt as Any)

do {
let newValue: Int? = nil
try resolver.setValueSync(newValue, toMutableStoreUsing: "TEST_key1")

XCTAssertEqual(store.valueSync_invocationCount, 1)
XCTAssertEqual(store.valueSync_keys, ["TEST_key1"])
XCTAssertEqual(store.setValueSync_invocationCount, 1)
XCTAssertEqual(store.setValueSync_keyValuePairs.count, 1)
XCTAssertEqual(store.setValueSync_keyValuePairs[0].0, "TEST_key1")
XCTAssertEqual(store.setValueSync_keyValuePairs[0].1 as! Int?, nil)
} catch {
XCTFail("Unexpected error: \(error)")
}
}

func test_setValue_singleMutableStore_existingValue_nonNilToNil() async {
let store = MutableFeatureFlagStoreMock()
configuration.stores = [store]
let optionalInt: Int? = nil
store.value_result = .success(optionalInt as Any)

do {
let newValue: Int? = 456
try await resolver.setValue(newValue, toMutableStoreUsing: "TEST_key1")

XCTAssertEqual(store.value_invocationCount, 1)
XCTAssertEqual(store.value_keys, ["TEST_key1"])
XCTAssertEqual(store.setValue_invocationCount, 1)
XCTAssertEqual(store.setValue_keyValuePairs.count, 1)
XCTAssertEqual(store.setValue_keyValuePairs[0].0, "TEST_key1")
XCTAssertEqual(store.setValue_keyValuePairs[0].1 as! Int?, 456)
} catch {
XCTFail("Unexpected error: \(error)")
}
}

func test_setValueSync_singleSyncMutableStore_existingValue_nonNilToNil() {
let store = SynchronousMutableFeatureFlagStoreMock()
configuration.stores = [store]
let optionalInt: Int? = nil
store.valueSync_result = .success(optionalInt as Any)

do {
let newValue: Int? = 456
try resolver.setValueSync(newValue, toMutableStoreUsing: "TEST_key1")

XCTAssertEqual(store.valueSync_invocationCount, 1)
XCTAssertEqual(store.valueSync_keys, ["TEST_key1"])
XCTAssertEqual(store.setValueSync_invocationCount, 1)
XCTAssertEqual(store.setValueSync_keyValuePairs.count, 1)
XCTAssertEqual(store.setValueSync_keyValuePairs[0].0, "TEST_key1")
XCTAssertEqual(store.setValueSync_keyValuePairs[0].1 as! Int?, 456)
} catch {
XCTFail("Unexpected error: \(error)")
}
}

func test_setValue_singleMutableStore_existingValue_nilToNil() async {
let store = MutableFeatureFlagStoreMock()
configuration.stores = [store]
let optionalInt: Int? = nil
store.value_result = .success(optionalInt as Any)

do {
let newValue: Int? = nil
try await resolver.setValue(newValue, toMutableStoreUsing: "TEST_key1")

XCTAssertEqual(store.value_invocationCount, 1)
XCTAssertEqual(store.value_keys, ["TEST_key1"])
XCTAssertEqual(store.setValue_invocationCount, 1)
XCTAssertEqual(store.setValue_keyValuePairs.count, 1)
XCTAssertEqual(store.setValue_keyValuePairs[0].0, "TEST_key1")
XCTAssertEqual(store.setValue_keyValuePairs[0].1 as! Int?, nil)
} catch {
XCTFail("Unexpected error: \(error)")
}
}

func test_setValueSync_singleSyncMutableStore_existingValue_nilToNil() {
let store = SynchronousMutableFeatureFlagStoreMock()
configuration.stores = [store]
let optionalInt: Int? = nil
store.valueSync_result = .success(optionalInt as Any)

do {
let newValue: Int? = nil
try resolver.setValueSync(newValue, toMutableStoreUsing: "TEST_key1")

XCTAssertEqual(store.valueSync_invocationCount, 1)
XCTAssertEqual(store.valueSync_keys, ["TEST_key1"])
XCTAssertEqual(store.setValueSync_invocationCount, 1)
XCTAssertEqual(store.setValueSync_keyValuePairs.count, 1)
XCTAssertEqual(store.setValueSync_keyValuePairs[0].0, "TEST_key1")
XCTAssertEqual(store.setValueSync_keyValuePairs[0].1 as! Int?, nil)
} catch {
XCTFail("Unexpected error: \(error)")
}
Expand Down
Loading