diff --git a/Sources/YMFF/FeatureFlagResolver/Store/UserDefaultsStore.swift b/Sources/YMFF/FeatureFlagResolver/Store/UserDefaultsStore.swift index 19895c0..71e9bf3 100644 --- a/Sources/YMFF/FeatureFlagResolver/Store/UserDefaultsStore.swift +++ b/Sources/YMFF/FeatureFlagResolver/Store/UserDefaultsStore.swift @@ -39,12 +39,20 @@ final public class UserDefaultsStore { extension UserDefaultsStore: SynchronousMutableFeatureFlagStore { public func valueSync(for key: FeatureFlagKey) -> Result { + guard !(Value.self is ExpressibleByNilLiteral.Type) else { + return .failure(.otherError(Error.optionalValuesAreNotSupported)) + } + guard let anyValue = userDefaults.object(forKey: key) else { return .failure(.valueNotFound) } guard let value = anyValue as? Value else { return .failure(.typeMismatch) } return .success(value) } - public func setValueSync(_ value: Value, for key: FeatureFlagKey) { + public func setValueSync(_ value: Value, for key: FeatureFlagKey) throws { + guard !(value is ExpressibleByNilLiteral) else { + throw FeatureFlagStoreError.otherError(Error.optionalValuesAreNotSupported) + } + userDefaults.set(value, forKey: key) } @@ -54,4 +62,14 @@ extension UserDefaultsStore: SynchronousMutableFeatureFlagStore { } +// MARK: - Error + +extension UserDefaultsStore { + + enum Error: Swift.Error { + case optionalValuesAreNotSupported + } + +} + #endif diff --git a/Tests/YMFFTests/Cases/UserDefaultsStoreTests.swift b/Tests/YMFFTests/Cases/UserDefaultsStoreTests.swift index 9bff3c3..dc64020 100644 --- a/Tests/YMFFTests/Cases/UserDefaultsStoreTests.swift +++ b/Tests/YMFFTests/Cases/UserDefaultsStoreTests.swift @@ -81,6 +81,52 @@ final class UserDefaultsStoreTests: XCTestCase { } } + func test_value_optionals() async { + userDefaults.set("TEST_value1", forKey: "TEST_key1") + // No record for TEST_key2 + + do { + let _: String? = try await store.value(for: "TEST_key1").get() + XCTFail("Expected an error") + } catch FeatureFlagStoreError.otherError(UserDefaultsStore.Error.optionalValuesAreNotSupported) { + XCTAssertEqual(userDefaults.string(forKey: "TEST_key1"), "TEST_value1") + } catch { + XCTFail("Unexpected error: \(error)") + } + + do { + let _: String? = try await store.value(for: "TEST_key2").get() + XCTFail("Expected an error") + } catch FeatureFlagStoreError.otherError(UserDefaultsStore.Error.optionalValuesAreNotSupported) { + XCTAssertNil(userDefaults.string(forKey: "TEST_key2")) + } catch { + XCTFail("Unexpected error: \(error)") + } + } + + func test_valueSync_optionals() { + userDefaults.set("TEST_value1", forKey: "TEST_key1") + // No record for TEST_key2 + + do { + let _: String? = try store.valueSync(for: "TEST_key1").get() + XCTFail("Expected an error") + } catch FeatureFlagStoreError.otherError(UserDefaultsStore.Error.optionalValuesAreNotSupported) { + XCTAssertEqual(userDefaults.string(forKey: "TEST_key1"), "TEST_value1") + } catch { + XCTFail("Unexpected error: \(error)") + } + + do { + let _: String? = try store.valueSync(for: "TEST_key2").get() + XCTFail("Expected an error") + } catch FeatureFlagStoreError.otherError(UserDefaultsStore.Error.optionalValuesAreNotSupported) { + XCTAssertNil(userDefaults.string(forKey: "TEST_key2")) + } catch { + XCTFail("Unexpected error: \(error)") + } + } + func test_setValue() async throws { userDefaults.set("TEST_value1", forKey: "TEST_key1") @@ -91,16 +137,66 @@ final class UserDefaultsStoreTests: XCTestCase { XCTAssertEqual(userDefaults.string(forKey: "TEST_key2"), "TEST_newValue2") } - func test_setValueSync() { + func test_setValueSync() throws { userDefaults.set("TEST_value1", forKey: "TEST_key1") - store.setValueSync("TEST_newValue1", for: "TEST_key1") - store.setValueSync("TEST_newValue2", for: "TEST_key2") + try store.setValueSync("TEST_newValue1", for: "TEST_key1") + try store.setValueSync("TEST_newValue2", for: "TEST_key2") XCTAssertEqual(userDefaults.string(forKey: "TEST_key1"), "TEST_newValue1") XCTAssertEqual(userDefaults.string(forKey: "TEST_key2"), "TEST_newValue2") } + func test_setValue_optionals() async { + userDefaults.set("TEST_value1", forKey: "TEST_key1") + userDefaults.set("TEST_value2", forKey: "TEST_key2") + + do { + let optionalValue1: String? = "TEST_newValue1" + try await store.setValue(optionalValue1, for: "TEST_key1") + XCTFail("Expected an error") + } catch FeatureFlagStoreError.otherError(UserDefaultsStore.Error.optionalValuesAreNotSupported) { + XCTAssertEqual(userDefaults.string(forKey: "TEST_key1"), "TEST_value1") + } catch { + XCTFail("Unexpected error: \(error)") + } + + do { + let optionalValue2: String? = nil + try await store.setValue(optionalValue2, for: "TEST_key2") + XCTFail("Expected an error") + } catch FeatureFlagStoreError.otherError(UserDefaultsStore.Error.optionalValuesAreNotSupported) { + XCTAssertEqual(userDefaults.string(forKey: "TEST_key2"), "TEST_value2") + } catch { + XCTFail("Unexpected error: \(error)") + } + } + + func test_setValueSync_optionals() { + userDefaults.set("TEST_value1", forKey: "TEST_key1") + userDefaults.set("TEST_value2", forKey: "TEST_key2") + + do { + let optionalValue1: String? = "TEST_newValue1" + try store.setValueSync(optionalValue1, for: "TEST_key1") + XCTFail("Expected an error") + } catch FeatureFlagStoreError.otherError(UserDefaultsStore.Error.optionalValuesAreNotSupported) { + XCTAssertEqual(userDefaults.string(forKey: "TEST_key1"), "TEST_value1") + } catch { + XCTFail("Unexpected error: \(error)") + } + + do { + let optionalValue2: String? = nil + try store.setValueSync(optionalValue2, for: "TEST_key2") + XCTFail("Expected an error") + } catch FeatureFlagStoreError.otherError(UserDefaultsStore.Error.optionalValuesAreNotSupported) { + XCTAssertEqual(userDefaults.string(forKey: "TEST_key2"), "TEST_value2") + } catch { + XCTFail("Unexpected error: \(error)") + } + } + func test_removeValue() async throws { userDefaults.set("TEST_value1", forKey: "TEST_key1") userDefaults.set("TEST_value2", forKey: "TEST_key2")