From 692605347dc4b7f293068d69dad1c658e802e164 Mon Sep 17 00:00:00 2001 From: Paolo Prodossimo Lopes Date: Fri, 23 May 2025 14:39:07 -0300 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20add=20dict=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/EasyPrefence/PreferenceGetter.swift | 1 + Sources/EasyPrefence/PreferenceSetter.swift | 1 + .../PreferenceGetterProvider.swift | 4 + .../PreferenceSetterProvider.swift | 4 + .../UserDefaultPreferenceAdapter.swift | 8 ++ .../Doubles/PreferenceGetterDouble.swift | 5 ++ .../Doubles/PreferenceSetterDouble.swift | 17 ++++ .../UserDefaultPreferenceAdapterTests.swift | 90 +++++++++++++++++++ ...rDefaultPreferenceGetterAdapterTests.swift | 64 ++++++++++++- ...rDefaultPreferenceSetterAdapterTests.swift | 42 +++++++++ 10 files changed, 232 insertions(+), 4 deletions(-) diff --git a/Sources/EasyPrefence/PreferenceGetter.swift b/Sources/EasyPrefence/PreferenceGetter.swift index 87e15c1..c1d90ae 100644 --- a/Sources/EasyPrefence/PreferenceGetter.swift +++ b/Sources/EasyPrefence/PreferenceGetter.swift @@ -5,4 +5,5 @@ public protocol PreferenceGetter { func getInt(_ key: String) -> Int? func getDouble(_ key: String) -> Double? func getData(_ key: String) -> Data? + func getDict(_ key: String) -> [String: Any]? } diff --git a/Sources/EasyPrefence/PreferenceSetter.swift b/Sources/EasyPrefence/PreferenceSetter.swift index 414d456..b566cbf 100644 --- a/Sources/EasyPrefence/PreferenceSetter.swift +++ b/Sources/EasyPrefence/PreferenceSetter.swift @@ -5,4 +5,5 @@ public protocol PreferenceSetter { func setInt(_ key: String, value: Int) func setDouble(_ key: String, value: Double) func setData(_ key: String, value: Data) + func setDict(_ key: String, value: [String: Any]) } diff --git a/Sources/EasyUserDefaultPreference/PreferenceGetterProvider.swift b/Sources/EasyUserDefaultPreference/PreferenceGetterProvider.swift index 0f22911..ba35d60 100644 --- a/Sources/EasyUserDefaultPreference/PreferenceGetterProvider.swift +++ b/Sources/EasyUserDefaultPreference/PreferenceGetterProvider.swift @@ -26,4 +26,8 @@ public struct UserDefaultPreferenceGetterAdapter: PreferenceGetter { public func getData(_ key: String) -> Data? { provider.value(forKey: key) as? Data } + + public func getDict(_ key: String) -> [String : Any]? { + provider.value(forKey: key) as? [String: Any] + } } diff --git a/Sources/EasyUserDefaultPreference/PreferenceSetterProvider.swift b/Sources/EasyUserDefaultPreference/PreferenceSetterProvider.swift index f15d34d..53f7c06 100644 --- a/Sources/EasyUserDefaultPreference/PreferenceSetterProvider.swift +++ b/Sources/EasyUserDefaultPreference/PreferenceSetterProvider.swift @@ -26,4 +26,8 @@ public struct UserDefaultPreferenceSetterAdapter: PreferenceSetter { public func setData(_ key: String, value: Data) { provider.set(value, forKey: key) } + + public func setDict(_ key: String, value: [String : Any]) { + provider.set(value, forKey: key) + } } diff --git a/Sources/EasyUserDefaultPreference/UserDefaultPreferenceAdapter.swift b/Sources/EasyUserDefaultPreference/UserDefaultPreferenceAdapter.swift index 4209757..621b4fc 100644 --- a/Sources/EasyUserDefaultPreference/UserDefaultPreferenceAdapter.swift +++ b/Sources/EasyUserDefaultPreference/UserDefaultPreferenceAdapter.swift @@ -30,6 +30,10 @@ public struct UserDefaultPreferenceAdapter: Preference { getter.getData(key) } + public func getDict(_ key: String) -> [String: Any]? { + getter.getDict(key) + } + public func setBool(_ key: String, value: Bool) { setter.setBool(key, value: value) } @@ -45,4 +49,8 @@ public struct UserDefaultPreferenceAdapter: Preference { public func setData(_ key: String, value: Data) { setter.setData(key, value: value) } + + public func setDict(_ key: String, value: [String: Any]) { + setter.setDict(key, value: value) + } } diff --git a/Tests/EasyUserDefaultPreferenceTests/Doubles/PreferenceGetterDouble.swift b/Tests/EasyUserDefaultPreferenceTests/Doubles/PreferenceGetterDouble.swift index 93d08a7..26df758 100644 --- a/Tests/EasyUserDefaultPreferenceTests/Doubles/PreferenceGetterDouble.swift +++ b/Tests/EasyUserDefaultPreferenceTests/Doubles/PreferenceGetterDouble.swift @@ -9,6 +9,7 @@ struct PreferenceGetterDouble: PreferenceGetter { let getIntMocked = Mock(nil) let getDoubleMocked = Mock(nil) let getDataMocked = Mock(nil) + let getDictMocked = Mock(nil) func getBool(_ key: String) -> Bool? { getBoolMocked.synchronize(key) @@ -25,4 +26,8 @@ struct PreferenceGetterDouble: PreferenceGetter { func getData(_ key: String) -> Data? { getDataMocked.synchronize(key) } + + func getDict(_ key: String) -> [String: Any]? { + getDictMocked.synchronize(key) + } } diff --git a/Tests/EasyUserDefaultPreferenceTests/Doubles/PreferenceSetterDouble.swift b/Tests/EasyUserDefaultPreferenceTests/Doubles/PreferenceSetterDouble.swift index 2bb4d17..61a5ce5 100644 --- a/Tests/EasyUserDefaultPreferenceTests/Doubles/PreferenceSetterDouble.swift +++ b/Tests/EasyUserDefaultPreferenceTests/Doubles/PreferenceSetterDouble.swift @@ -10,6 +10,7 @@ struct PreferenceSetterDouble: PreferenceSetter { let setIntMocked = Mock<(key: String, value: Int), Void>() let setDoubleMocked = Mock<(key: String, value: Double), Void>() let setDataMocked = Mock<(key: String, value: Data), Void>() + let setDictMocked = Mock<(key: String, value: [String: Any]), Void>() func setBool(_ key: String, value: Bool) { setBoolMocked.synchronize((key, value)) @@ -27,6 +28,10 @@ struct PreferenceSetterDouble: PreferenceSetter { setDataMocked.synchronize((key, value)) } + func setDict(_ key: String, value: [String: Any]) { + setDictMocked.synchronize((key, value)) + } + func expectBool(at index: Int, key: String, value: Bool, sourceLocation: SourceLocation = #_sourceLocation) { let spies = setBoolMocked.spies guard spies.indices.contains(index) else { @@ -74,4 +79,16 @@ struct PreferenceSetterDouble: PreferenceSetter { #expect(spy.key == key, sourceLocation: sourceLocation) #expect(spy.value == value, sourceLocation: sourceLocation) } + + func expectDict(at index: Int, key: String, value: [String: String], sourceLocation: SourceLocation = #_sourceLocation) { + let spies = setDictMocked.spies + guard spies.indices.contains(index) else { + return expectFail("Spy propertry not found at index \(index)", sourceLocation: sourceLocation) + } + + let spy = spies[index] + + #expect(spy.key == key, sourceLocation: sourceLocation) + #expect(spy.value as? [String: String] == value, sourceLocation: sourceLocation) + } } diff --git a/Tests/EasyUserDefaultPreferenceTests/UserDefaultPreferenceAdapterTests.swift b/Tests/EasyUserDefaultPreferenceTests/UserDefaultPreferenceAdapterTests.swift index d018030..b636402 100644 --- a/Tests/EasyUserDefaultPreferenceTests/UserDefaultPreferenceAdapterTests.swift +++ b/Tests/EasyUserDefaultPreferenceTests/UserDefaultPreferenceAdapterTests.swift @@ -203,6 +203,54 @@ struct UserDefaultPreferenceAdapterTests { #expect(env.getterDouble.getDataMocked.spies == [key, key2]) } } + + @Suite(".getDict(value:)") + struct DictTests { + private let key = "any" + private let env = makeEnv() + + @Test("should return nil when no value is stored") + func shouldReturnNilWhenNoValueIsStored() { + env.getterDouble.getDictMocked.mock(returning: nil) + + #expect(env.sut.getDict(key) == nil) + } + + @Test("should return when true is stored") + func shouldReturnTrueWhenTrueIsStored() { + let valueStub = ["key": "value"] + env.getterDouble.getDictMocked.mock(returning: valueStub) + + #expect(env.sut.getDict(key) as? [String: String] == valueStub) + } + + @Test("should return other data when false is stored") + func shouldReturnFalseWhenFalseIsStored() { + let valueStub = ["key": "value"] + env.getterDouble.getDictMocked.mock(returning: valueStub) + + #expect(env.sut.getDict(key) as? [String: String] == valueStub) + } + + @Test("should call provider with expected key") + func shouldCallProviderWithExpectedKey() { + let key = "any" + + _ = env.sut.getDict(key) + + #expect(env.getterDouble.getDictMocked.spies == [key]) + } + + @Test("should call provider twice with different keys") + func shouldCallProviderTwiceWithDifferentKeys() { + let key2 = "any-other" + _ = env.sut.getDict(key) + + _ = env.sut.getDict(key2) + + #expect(env.getterDouble.getDictMocked.spies == [key, key2]) + } + } } @Suite("Set") @@ -375,6 +423,48 @@ struct UserDefaultPreferenceAdapterTests { env.setterDouble.expectData(at: 1, key: key2, value: value2) } } + + @Suite(".setDict(_:value:)") + struct SetDictTests { + private let env = makeEnv() + + @Test("should call set once with dict value and expected key") + func shouldCallSetOnceWithDictValueAndExpectedKey() { + let key = "any-key" + let value = ["key": "value"] + + env.sut.setDict(key, value: value) + + #expect(env.setterDouble.setDictMocked.callCount == 1) + env.setterDouble.expectDict(at: 0, key: key, value: value) + } + + @Test("should call set once with same dict but different key") + func shouldCallSetOnceWithSameDictDifferentKey() { + let key = "any-other-key" + let value = ["key": "value"] + + env.sut.setDict(key, value: value) + + #expect(env.setterDouble.setDictMocked.callCount == 1) + env.setterDouble.expectDict(at: 0, key: key, value: value) + } + + @Test("should call set twice with different dict and keys") + func shouldCallSetTwiceWithDifferentDictAndKeys() { + let key1 = "any-other-key" + let value1 = ["key": "value"] + let key2 = "any-key" + let value2 = ["key2": "value2"] + env.sut.setDict(key1, value: value1) + + env.sut.setDict(key2, value: value2) + + #expect(env.setterDouble.setDictMocked.callCount == 2) + env.setterDouble.expectDict(at: 0, key: key1, value: value1) + env.setterDouble.expectDict(at: 1, key: key2, value: value2) + } + } } private struct Environment { diff --git a/Tests/EasyUserDefaultPreferenceTests/UserDefaultPreferenceGetterAdapterTests.swift b/Tests/EasyUserDefaultPreferenceTests/UserDefaultPreferenceGetterAdapterTests.swift index 20eceee..bee0736 100644 --- a/Tests/EasyUserDefaultPreferenceTests/UserDefaultPreferenceGetterAdapterTests.swift +++ b/Tests/EasyUserDefaultPreferenceTests/UserDefaultPreferenceGetterAdapterTests.swift @@ -7,7 +7,7 @@ import EasyMock @Suite("UserDefaultPreferenceGetterAdapter") struct UserDefaultPreferenceGetterAdapterTests { - @Suite(".getBool(value:)") + @Suite(".getBool(key:)") struct BoolTests { private let key = "any" private let env = makeEnv(valueType: Bool.self) @@ -63,7 +63,7 @@ struct UserDefaultPreferenceGetterAdapterTests { } } - @Suite(".getBool(value:)") + @Suite(".getBool(key:)") struct IntTests { private let key = "any" private let env = makeEnv(valueType: Int.self) @@ -119,7 +119,7 @@ struct UserDefaultPreferenceGetterAdapterTests { } } - @Suite(".getDouble(value:)") + @Suite(".getDouble(key:)") struct DoubleTests { private let key = "any" private let env = makeEnv(valueType: Double.self) @@ -175,7 +175,7 @@ struct UserDefaultPreferenceGetterAdapterTests { } } - @Suite(".getData(value:)") + @Suite(".getData(key:)") struct DataTests { private let key = "any" private let env = makeEnv(valueType: Data.self) @@ -231,6 +231,62 @@ struct UserDefaultPreferenceGetterAdapterTests { } } + @Suite(".getDict(key:)") + struct DictTests { + private let key = "any" + private let env = makeEnv(valueType: [String: String].self) + + @Test("should return nil when no value is stored") + func shouldReturnNilWhenNoValueIsStored() { + env.providerDouble.valueMocked.mock(returning: nil) + + #expect(env.sut.getData(key) == nil) + } + + @Test("should return nil when no value is stored with wrong type") + func shouldReturnNilWhenNoValueIsStoredWrongType() { + let env = makeEnv(valueType: Bool.self) + env.providerDouble.valueMocked.mock(returning: true) + + #expect(env.sut.getData(key) == nil) + } + + @Test("should return when true is stored") + func shouldReturnTrueWhenTrueIsStored() { + let valueStub = ["k": "v"] + env.providerDouble.valueMocked.mock(returning: valueStub) + + #expect(env.sut.getDict(key) as? [String: String] == valueStub) + } + + @Test("should return other data when false is stored") + func shouldReturnFalseWhenFalseIsStored() { + let valueStub = ["k": "v"] + env.providerDouble.valueMocked.mock(returning: valueStub) + + #expect(env.sut.getDict(key) as? [String: String] == valueStub) + } + + @Test("should call provider with expected key") + func shouldCallProviderWithExpectedKey() { + let key = "any" + + _ = env.sut.getData(key) + + #expect(env.providerDouble.valueMocked.spies == [key]) + } + + @Test("should call provider twice with different keys") + func shouldCallProviderTwiceWithDifferentKeys() { + let key2 = "any-other" + _ = env.sut.getData(key) + + _ = env.sut.getData(key2) + + #expect(env.providerDouble.valueMocked.spies == [key, key2]) + } + } + private struct Environment { let sut: UserDefaultPreferenceGetterAdapter let providerDouble: PreferenceGetterProviderDouble diff --git a/Tests/EasyUserDefaultPreferenceTests/UserDefaultPreferenceSetterAdapterTests.swift b/Tests/EasyUserDefaultPreferenceTests/UserDefaultPreferenceSetterAdapterTests.swift index d9aae54..ee7b6b8 100644 --- a/Tests/EasyUserDefaultPreferenceTests/UserDefaultPreferenceSetterAdapterTests.swift +++ b/Tests/EasyUserDefaultPreferenceTests/UserDefaultPreferenceSetterAdapterTests.swift @@ -173,6 +173,48 @@ struct UserDefaultPreferenceSetterAdapterTests { } } + @Suite(".setDict(_:value:)") + struct SetDictTests { + private let env = makeEnv(valueType: [String: String].self) + + @Test("should call set once with dictionary value and expected key") + func shouldCallSetOnceWithDictValueAndExpectedKey() { + let key = "any-key" + let value = ["key": "value"] + + env.sut.setDict(key, value: value) + + #expect(env.providerDouble.setMocked.callCount == 1) + env.providerDouble.expect(at: 0, key: key, value: value) + } + + @Test("should call set once with same dictionary but different key") + func shouldCallSetOnceWithSameDictDifferentKey() { + let key = "any-other-key" + let value = ["key": "value"] + + env.sut.setDict(key, value: value) + + #expect(env.providerDouble.setMocked.callCount == 1) + env.providerDouble.expect(at: 0, key: key, value: value) + } + + @Test("should call set twice with different dictionaries and keys") + func shouldCallSetTwiceWithDifferentDictionariesAndKeys() { + let key1 = "any-other-key" + let value1 = ["key": "value"] + let key2 = "any-key" + let value2 = ["key": "another-value"] + env.sut.setDict(key1, value: value1) + + env.sut.setDict(key2, value: value2) + + #expect(env.providerDouble.setMocked.callCount == 2) + env.providerDouble.expect(at: 0, key: key1, value: value1) + env.providerDouble.expect(at: 1, key: key2, value: value2) + } + } + private struct Environment { let sut: UserDefaultPreferenceSetterAdapter let providerDouble: PreferenceSetterProviderDouble From d29f00a148da2e4b9cad840f5e51bb205327f4db Mon Sep 17 00:00:00 2001 From: Paolo Prodossimo Lopes Date: Fri, 23 May 2025 14:57:27 -0300 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=93=9D=20documente=20the=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/EasyPrefence/Preference.swift | 29 +++++ Sources/EasyPrefence/PreferenceGetter.swift | 76 ++++++++++++ Sources/EasyPrefence/PreferenceSetter.swift | 75 ++++++++++++ .../PreferenceGetterProvider.swift | 95 +++++++++++++++ .../PreferenceSetterProvider.swift | 111 ++++++++++++++++++ .../UserDefaultPreferenceAdapter.swift | 111 ++++++++++++++++++ ...rDefaultPreferenceGetterAdapterTests.swift | 10 +- 7 files changed, 502 insertions(+), 5 deletions(-) diff --git a/Sources/EasyPrefence/Preference.swift b/Sources/EasyPrefence/Preference.swift index e533175..52078d4 100644 --- a/Sources/EasyPrefence/Preference.swift +++ b/Sources/EasyPrefence/Preference.swift @@ -1,3 +1,32 @@ +import Foundation + +/// +/// `Preference` is a composite type that combines the capabilities of both reading and writing preference values. +/// +/// This typealias combines two protocols: +/// - `PreferenceGetter`: Provides methods for retrieving values from a preference store +/// - `PreferenceSetter`: Provides methods for saving values to a preference store +/// +/// By combining these protocols, `Preference` represents a complete interface for interacting with +/// preference data, allowing both reading and writing operations through a single type. +/// +/// ## Usage Example: +/// ```swift +/// // Create a preference object that conforms to both getter and setter protocols +/// let preferences: Preference = UserDefaultPreferenceAdapter() +/// +/// // Store a value +/// preferences.setInt("user.age", value: 25) +/// +/// // Retrieve a value +/// if let age = preferences.getInt("user.age") { +/// print("User age is \(age)") +/// } +/// ``` +/// +/// The actual implementation of these methods depends on the concrete type that conforms to `Preference`. +/// For example, `UserDefaultPreferenceAdapter` implements these methods using UserDefaults. +/// public typealias Preference = ( PreferenceGetter & PreferenceSetter diff --git a/Sources/EasyPrefence/PreferenceGetter.swift b/Sources/EasyPrefence/PreferenceGetter.swift index c1d90ae..33dc0a4 100644 --- a/Sources/EasyPrefence/PreferenceGetter.swift +++ b/Sources/EasyPrefence/PreferenceGetter.swift @@ -1,9 +1,85 @@ import Foundation +/// +/// `PreferenceGetter` is a protocol that defines an interface for retrieving various types of values from a preference store. +/// +/// This protocol provides a standardized way to access stored preferences regardless of the underlying storage mechanism. +/// Each method follows a consistent pattern of accepting a string key and returning an optional value of the specified type. +/// The optional return type reflects the possibility that the requested key might not exist in the preference store. +/// +/// ## Supported Value Types +/// - `Bool`: Boolean values +/// - `Int`: Integer values +/// - `Double`: Double-precision floating point values +/// - `Data`: Binary data +/// - `[String: Any]`: Dictionary values (commonly used for storing complex objects) +/// +/// ## Usage Example +/// ```swift +/// let preferences: PreferenceGetter = UserDefaultPreferenceGetterAdapter() +/// +/// // Retrieve a boolean preference +/// if let isEnabled = preferences.getBool("feature.enabled") { +/// print("Feature is enabled: \(isEnabled)") +/// } else { +/// print("Feature enabled setting not found") +/// } +/// +/// // Retrieve user settings as a dictionary +/// if let settings = preferences.getDict("user.settings") { +/// print("User has \(settings.count) settings configured") +/// } +/// ``` +/// +/// ## Implementation Notes +/// When implementing this protocol, consider the following: +/// - Methods should return `nil` when a key doesn't exist +/// - Type conversion should be handled safely (return `nil` rather than crashing on type mismatch) +/// - Consider thread safety for concurrent access scenarios +/// public protocol PreferenceGetter { + /// + /// Retrieves a Boolean value associated with the specified key. + /// + /// - Parameter key: The key to look up in the preference store. + /// - Returns: The Boolean value associated with the specified key, or `nil` if the key + /// doesn't exist or the value is not a Boolean. + /// func getBool(_ key: String) -> Bool? + + /// + /// Retrieves an integer value associated with the specified key. + /// + /// - Parameter key: The key to look up in the preference store. + /// - Returns: The integer value associated with the specified key, or `nil` if the key + /// doesn't exist or the value is not an integer. + /// func getInt(_ key: String) -> Int? + + /// + /// Retrieves a double-precision floating-point value associated with the specified key. + /// + /// - Parameter key: The key to look up in the preference store. + /// - Returns: The Double value associated with the specified key, or `nil` if the key + /// doesn't exist or the value is not a Double. + /// func getDouble(_ key: String) -> Double? + + /// + /// Retrieves binary data associated with the specified key. + /// + /// - Parameter key: The key to look up in the preference store. + /// - Returns: The Data value associated with the specified key, or `nil` if the key + /// doesn't exist or the value is not Data. + /// func getData(_ key: String) -> Data? + + /// + /// Retrieves a dictionary associated with the specified key. + /// + /// - Parameter key: The key to look up in the preference store. + /// - Returns: The dictionary associated with the specified key, or `nil` if the key + /// doesn't exist or the value is not a dictionary. + /// func getDict(_ key: String) -> [String: Any]? } diff --git a/Sources/EasyPrefence/PreferenceSetter.swift b/Sources/EasyPrefence/PreferenceSetter.swift index b566cbf..df11127 100644 --- a/Sources/EasyPrefence/PreferenceSetter.swift +++ b/Sources/EasyPrefence/PreferenceSetter.swift @@ -1,9 +1,84 @@ import Foundation +/// +/// `PreferenceSetter` is a protocol that defines an interface for storing various types of values in a preference store. +/// +/// This protocol provides a standardized way to save preferences regardless of the underlying storage mechanism. +/// Each method follows a consistent pattern of accepting a string key and a value of the specified type. +/// The setter methods handle the serialization and storage of the values in the backing store. +/// +/// ## Supported Value Types +/// - `Bool`: Boolean values +/// - `Int`: Integer values +/// - `Double`: Double-precision floating point values +/// - `Data`: Binary data +/// - `[String: Any]`: Dictionary values (commonly used for storing complex objects) +/// +/// ## Usage Example +/// ```swift +/// let preferences: PreferenceSetter = UserDefaultPreferenceSetterAdapter() +/// +/// // Store a boolean preference +/// preferences.setBool("feature.enabled", value: true) +/// +/// // Store user settings as a dictionary +/// let settings: [String: Any] = ["theme": "dark", "fontSize": 14, "notifications": true] +/// preferences.setDict("user.settings", value: settings) +/// ``` +/// +/// ## Implementation Notes +/// When implementing this protocol, consider the following: +/// - Ensure values are properly persisted to the underlying storage +/// - Handle type serialization appropriately for the storage medium +/// - Consider thread safety for concurrent write operations +/// - Implement proper error handling within the implementation +/// - Consider implementing synchronization behavior if needed (e.g., immediate write to disk) +/// public protocol PreferenceSetter { + /// + /// Stores a Boolean value in the preference store for the specified key. + /// + /// - Parameters: + /// - key: The key under which to store the value. + /// - value: The Boolean value to store. + /// func setBool(_ key: String, value: Bool) + + /// + /// Stores an integer value in the preference store for the specified key. + /// + /// - Parameters: + /// - key: The key under which to store the value. + /// - value: The integer value to store. + /// func setInt(_ key: String, value: Int) + + /// + /// Stores a double-precision floating-point value in the preference store for the specified key. + /// + /// - Parameters: + /// - key: The key under which to store the value. + /// - value: The Double value to store. + /// func setDouble(_ key: String, value: Double) + + /// + /// Stores binary data in the preference store for the specified key. + /// + /// - Parameters: + /// - key: The key under which to store the value. + /// - value: The Data object to store. + /// func setData(_ key: String, value: Data) + + /// + /// Stores a dictionary in the preference store for the specified key. + /// + /// - Parameters: + /// - key: The key under which to store the value. + /// - value: The dictionary to store. + /// - Note: The dictionary must contain only property list compatible types + /// if using implementations like UserDefaults. + /// func setDict(_ key: String, value: [String: Any]) } diff --git a/Sources/EasyUserDefaultPreference/PreferenceGetterProvider.swift b/Sources/EasyUserDefaultPreference/PreferenceGetterProvider.swift index ba35d60..1449a63 100644 --- a/Sources/EasyUserDefaultPreference/PreferenceGetterProvider.swift +++ b/Sources/EasyUserDefaultPreference/PreferenceGetterProvider.swift @@ -1,32 +1,127 @@ import Foundation +/// `PreferenceGetterProvider` is a protocol that defines an abstraction for retrieving raw values from a preference store. +/// +/// This protocol is designed to decouple the retrieval logic from the underlying storage mechanism, enabling flexibility +/// and testability. It serves as the foundation for adapters like `UserDefaultPreferenceGetterAdapter` that implement +/// higher-level interfaces such as `PreferenceGetter`. +/// +/// ## Purpose +/// The primary purpose of `PreferenceGetterProvider` is to provide a low-level interface for fetching values +/// from a storage system (e.g., `UserDefaults`) without imposing type constraints. The retrieved values are +/// typically cast to the desired type by higher-level abstractions. +/// +/// ## Usage Example +/// ```swift +/// // Example of a custom provider +/// struct CustomPreferenceGetterProvider: PreferenceGetterProvider { +/// private var storage: [String: Any] = [:] +/// +/// func value(forKey key: String) -> Any? { +/// return storage[key] +/// } +/// } +/// +/// let provider = CustomPreferenceGetterProvider() +/// let value = provider.value(forKey: "example.key") +/// ``` +/// +/// ## Implementation Notes +/// - The `value(forKey:)` method should return `nil` if the key does not exist in the storage. +/// - The returned value is expected to be cast to the appropriate type by the caller. +/// - Implementations should ensure thread safety if the underlying storage is accessed concurrently. public protocol PreferenceGetterProvider { + /// Retrieves a raw value associated with the specified key from the preference store. + /// + /// - Parameter key: The key to look up in the preference store. + /// - Returns: The raw value associated with the key, or `nil` if the key does not exist. func value(forKey key: String) -> Any? } +/// +/// `UserDefaultPreferenceGetterAdapter` is a concrete implementation of the `PreferenceGetter` protocol +/// that retrieves values from the `UserDefaults` system. +/// +/// This adapter uses a `PreferenceGetterProvider` to abstract the underlying storage mechanism, +/// allowing for flexibility and testability. By default, it uses `UserDefaults.standard` as the provider. +/// +/// ## Purpose +/// The adapter provides a seamless way to access stored preferences in `UserDefaults` while adhering to the +/// `PreferenceGetter` protocol. It ensures type safety and handles type casting for various data types. +/// +/// ## Supported Value Types +/// - `Bool`: Boolean values +/// - `Int`: Integer values +/// - `Double`: Double-precision floating point values +/// - `Data`: Binary data +/// - `[String: Any]`: Dictionary values +/// +/// ## Usage Example +/// ```swift +/// let getter = UserDefaultPreferenceGetterAdapter() +/// +/// // Retrieve a boolean value +/// if let isEnabled = getter.getBool("feature.enabled") { +/// print("Feature is enabled: \(isEnabled)") +/// } +/// +/// // Retrieve a dictionary value +/// if let settings = getter.getDict("user.settings") { +/// print("User settings: \(settings)") +/// } +/// ``` +/// +/// ## Implementation Notes +/// - The `provider` is responsible for fetching raw values from the underlying storage. +/// - Type casting is performed to ensure the returned value matches the expected type. +/// - If the key does not exist or the value cannot be cast to the expected type, the methods return `nil`. +/// public struct UserDefaultPreferenceGetterAdapter: PreferenceGetter { private let provider: PreferenceGetterProvider + /// Initializes the adapter with a `PreferenceGetterProvider`. + /// + /// - Parameter provider: The provider responsible for fetching raw values. Defaults to `UserDefaults.standard`. public init(provider: PreferenceGetterProvider = UserDefaults.standard) { self.provider = provider } + /// Retrieves a Boolean value associated with the specified key. + /// + /// - Parameter key: The key to look up in the preference store. + /// - Returns: The Boolean value associated with the key, or `nil` if the key does not exist or the value is not a Boolean. public func getBool(_ key: String) -> Bool? { provider.value(forKey: key) as? Bool } + /// Retrieves an integer value associated with the specified key. + /// + /// - Parameter key: The key to look up in the preference store. + /// - Returns: The integer value associated with the key, or `nil` if the key does not exist or the value is not an integer. public func getInt(_ key: String) -> Int? { provider.value(forKey: key) as? Int } + /// Retrieves a double-precision floating-point value associated with the specified key. + /// + /// - Parameter key: The key to look up in the preference store. + /// - Returns: The Double value associated with the key, or `nil` if the key does not exist or the value is not a Double. public func getDouble(_ key: String) -> Double? { provider.value(forKey: key) as? Double } + /// Retrieves binary data associated with the specified key. + /// + /// - Parameter key: The key to look up in the preference store. + /// - Returns: The Data value associated with the key, or `nil` if the key does not exist or the value is not Data. public func getData(_ key: String) -> Data? { provider.value(forKey: key) as? Data } + /// Retrieves a dictionary associated with the specified key. + /// + /// - Parameter key: The key to look up in the preference store. + /// - Returns: The dictionary associated with the key, or `nil` if the key does not exist or the value is not a dictionary. public func getDict(_ key: String) -> [String : Any]? { provider.value(forKey: key) as? [String: Any] } diff --git a/Sources/EasyUserDefaultPreference/PreferenceSetterProvider.swift b/Sources/EasyUserDefaultPreference/PreferenceSetterProvider.swift index 53f7c06..9af31f5 100644 --- a/Sources/EasyUserDefaultPreference/PreferenceSetterProvider.swift +++ b/Sources/EasyUserDefaultPreference/PreferenceSetterProvider.swift @@ -1,32 +1,143 @@ import Foundation +/// +/// `PreferenceSetterProvider` is a protocol that defines an abstraction for storing raw values in a preference store. +/// +/// This protocol is designed to decouple the storage logic from the underlying storage mechanism, enabling flexibility +/// and testability. It serves as the foundation for adapters like `UserDefaultPreferenceSetterAdapter` that implement +/// higher-level interfaces such as `PreferenceSetter`. +/// +/// ## Purpose +/// The primary purpose of `PreferenceSetterProvider` is to provide a low-level interface for saving values +/// to a storage system (e.g., `UserDefaults`) without imposing type constraints. The values are typically +/// serialized and stored by higher-level abstractions. +/// +/// ## Usage Example +/// ```swift +/// // Example of a custom provider +/// struct CustomPreferenceSetterProvider: PreferenceSetterProvider { +/// private var storage: [String: Any] = [:] +/// +/// func set(_ value: Any?, forKey key: String) { +/// storage[key] = value +/// } +/// } +/// +/// let provider = CustomPreferenceSetterProvider() +/// provider.set("exampleValue", forKey: "example.key") +/// ``` +/// +/// ## Implementation Notes +/// - The `set(_:forKey:)` method should handle `nil` values appropriately (e.g., removing the key from storage). +/// - Implementations should ensure thread safety if the underlying storage is accessed concurrently. +/// public protocol PreferenceSetterProvider { + /// Stores a raw value in the preference store for the specified key. + /// + /// - Parameters: + /// - value: The raw value to store. Passing `nil` should remove the key from the store. + /// - key: The key under which to store the value. func set(_ value: Any?, forKey key: String) } +/// +/// `UserDefaultPreferenceSetterAdapter` is a concrete implementation of the `PreferenceSetter` protocol +/// that stores values in the `UserDefaults` system. +/// +/// This adapter uses a `PreferenceSetterProvider` to abstract the underlying storage mechanism, +/// allowing for flexibility and testability. By default, it uses `UserDefaults.standard` as the provider. +/// +/// ## Purpose +/// The adapter provides a seamless way to save preferences in `UserDefaults` while adhering to the +/// `PreferenceSetter` protocol. It ensures type safety and handles serialization for various data types. +/// +/// ## Supported Value Types +/// - `Bool`: Boolean values +/// - `Int`: Integer values +/// - `Double`: Double-precision floating point values +/// - `Data`: Binary data +/// - `[String: Any]`: Dictionary values +/// +/// ## Usage Example +/// ```swift +/// let setter = UserDefaultPreferenceSetterAdapter() +/// +/// // Store a boolean value +/// setter.setBool("feature.enabled", value: true) +/// +/// // Store a dictionary value +/// let settings: [String: Any] = ["theme": "dark", "fontSize": 14] +/// setter.setDict("user.settings", value: settings) +/// ``` +/// +/// ## Implementation Notes +/// - The `provider` is responsible for storing raw values in the underlying storage. +/// - Serialization is handled by the adapter to ensure compatibility with the storage system. +/// - If the value cannot be serialized, the method should handle the error gracefully. +/// public struct UserDefaultPreferenceSetterAdapter: PreferenceSetter { private let provider: PreferenceSetterProvider + /// + /// Initializes the adapter with a `PreferenceSetterProvider`. + /// + /// - Parameter provider: The provider responsible for storing raw values. Defaults to `UserDefaults.standard`. + /// public init(provider: PreferenceSetterProvider = UserDefaults.standard) { self.provider = provider } + /// + /// Stores a Boolean value in the preference store for the specified key. + /// + /// - Parameters: + /// - key: The key under which to store the value. + /// - value: The Boolean value to store. + /// public func setBool(_ key: String, value: Bool) { provider.set(value, forKey: key) } + /// + /// Stores an integer value in the preference store for the specified key. + /// + /// - Parameters: + /// - key: The key under which to store the value. + /// - value: The integer value to store. + /// public func setInt(_ key: String, value: Int) { provider.set(value, forKey: key) } + /// + /// Stores a double-precision floating-point value in the preference store for the specified key. + /// + /// - Parameters: + /// - key: The key under which to store the value. + /// - value: The Double value to store. + /// public func setDouble(_ key: String, value: Double) { provider.set(value, forKey: key) } + /// Stores binary data in the preference store for the specified key. + /// + /// - Parameters: + /// - key: The key under which to store the value. + /// - value: The Data object to store. public func setData(_ key: String, value: Data) { provider.set(value, forKey: key) } + /// + /// Stores a dictionary in the preference store for the specified key. + /// + /// - Parameters: + /// - key: The key under which to store the value. + /// - value: The dictionary to store. + /// - Note: The dictionary must contain only property list compatible types + /// if using implementations like UserDefaults. + /// public func setDict(_ key: String, value: [String : Any]) { provider.set(value, forKey: key) } diff --git a/Sources/EasyUserDefaultPreference/UserDefaultPreferenceAdapter.swift b/Sources/EasyUserDefaultPreference/UserDefaultPreferenceAdapter.swift index 621b4fc..269da0f 100644 --- a/Sources/EasyUserDefaultPreference/UserDefaultPreferenceAdapter.swift +++ b/Sources/EasyUserDefaultPreference/UserDefaultPreferenceAdapter.swift @@ -2,10 +2,54 @@ import EasyPrefence import Foundation +/// +/// `UserDefaultPreferenceAdapter` is a concrete implementation of the `Preference` typealias +/// that combines both `PreferenceGetter` and `PreferenceSetter` functionalities using the `UserDefaults` system. +/// +/// This adapter provides a unified interface for reading and writing preferences, leveraging the +/// `UserDefaultPreferenceGetterAdapter` and `UserDefaultPreferenceSetterAdapter` for the actual operations. +/// +/// ## Purpose +/// The primary purpose of `UserDefaultPreferenceAdapter` is to simplify preference management by +/// combining getter and setter capabilities into a single structure. It abstracts the underlying +/// storage mechanism (`UserDefaults`) and ensures type safety for various data types. +/// +/// ## Supported Value Types +/// - `Bool`: Boolean values +/// - `Int`: Integer values +/// - `Double`: Double-precision floating point values +/// - `Data`: Binary data +/// - `[String: Any]`: Dictionary values +/// +/// ## Usage Example +/// ```swift +/// let preferences = UserDefaultPreferenceAdapter() +/// +/// // Store a value +/// preferences.setBool("feature.enabled", value: true) +/// +/// // Retrieve a value +/// if let isEnabled = preferences.getBool("feature.enabled") { +/// print("Feature is enabled: \(isEnabled)") +/// } +/// ``` +/// +/// ## Implementation Notes +/// - The adapter delegates read operations to `UserDefaultPreferenceGetterAdapter`. +/// - Write operations are delegated to `UserDefaultPreferenceSetterAdapter`. +/// - The default initializer uses `UserDefaults.standard` for both getter and setter operations. +/// public struct UserDefaultPreferenceAdapter: Preference { private let getter: PreferenceGetter private let setter: PreferenceSetter + /// + /// Initializes the adapter with custom getter and setter implementations. + /// + /// - Parameters: + /// - getter: An object conforming to `PreferenceGetter`. Defaults to `UserDefaultPreferenceGetterAdapter()`. + /// - setter: An object conforming to `PreferenceSetter`. Defaults to `UserDefaultPreferenceSetterAdapter()`. + /// public init( getter: PreferenceGetter = UserDefaultPreferenceGetterAdapter(), setter: PreferenceSetter = UserDefaultPreferenceSetterAdapter() @@ -14,42 +58,109 @@ public struct UserDefaultPreferenceAdapter: Preference { self.setter = setter } + /// + /// Retrieves a Boolean value associated with the specified key. + /// + /// - Parameter key: The key to look up in the preference store. + /// - Returns: The Boolean value associated with the key, or `nil` if the key does not exist. + /// public func getBool(_ key: String) -> Bool? { getter.getBool(key) } + /// + /// Retrieves an integer value associated with the specified key. + /// + /// - Parameter key: The key to look up in the preference store. + /// - Returns: The integer value associated with the key, or `nil` if the key does not exist. + /// public func getInt(_ key: String) -> Int? { getter.getInt(key) } + /// + /// Retrieves a double-precision floating-point value associated with the specified key. + /// + /// - Parameter key: The key to look up in the preference store. + /// - Returns: The Double value associated with the key, or `nil` if the key does not exist. + /// public func getDouble(_ key: String) -> Double? { getter.getDouble(key) } + /// + /// Retrieves binary data associated with the specified key. + /// + /// - Parameter key: The key to look up in the preference store. + /// - Returns: The Data value associated with the key, or `nil` if the key does not exist. + /// public func getData(_ key: String) -> Data? { getter.getData(key) } + /// + /// Retrieves a dictionary associated with the specified key. + /// + /// - Parameter key: The key to look up in the preference store. + /// - Returns: The dictionary associated with the key, or `nil` if the key does not exist. + /// public func getDict(_ key: String) -> [String: Any]? { getter.getDict(key) } + /// + /// Stores a Boolean value in the preference store for the specified key. + /// + /// - Parameters: + /// - key: The key under which to store the value. + /// - value: The Boolean value to store. + /// public func setBool(_ key: String, value: Bool) { setter.setBool(key, value: value) } + /// + /// Stores an integer value in the preference store for the specified key. + /// + /// - Parameters: + /// - key: The key under which to store the value. + /// - value: The integer value to store. + /// public func setInt(_ key: String, value: Int) { setter.setInt(key, value: value) } + /// + /// Stores a double-precision floating-point value in the preference store for the specified key. + /// + /// - Parameters: + /// - key: The key under which to store the value. + /// - value: The Double value to store. + /// public func setDouble(_ key: String, value: Double) { setter.setDouble(key, value: value) } + /// + /// Stores binary data in the preference store for the specified key. + /// + /// - Parameters: + /// - key: The key under which to store the value. + /// - value: The Data object to store. + /// public func setData(_ key: String, value: Data) { setter.setData(key, value: value) } + /// + /// Stores a dictionary in the preference store for the specified key. + /// + /// - Parameters: + /// - key: The key under which to store the value. + /// - value: The dictionary to store. + /// - Note: The dictionary must contain only property list compatible types + /// if using implementations like UserDefaults. + /// public func setDict(_ key: String, value: [String: Any]) { setter.setDict(key, value: value) } diff --git a/Tests/EasyUserDefaultPreferenceTests/UserDefaultPreferenceGetterAdapterTests.swift b/Tests/EasyUserDefaultPreferenceTests/UserDefaultPreferenceGetterAdapterTests.swift index bee0736..31fc117 100644 --- a/Tests/EasyUserDefaultPreferenceTests/UserDefaultPreferenceGetterAdapterTests.swift +++ b/Tests/EasyUserDefaultPreferenceTests/UserDefaultPreferenceGetterAdapterTests.swift @@ -240,7 +240,7 @@ struct UserDefaultPreferenceGetterAdapterTests { func shouldReturnNilWhenNoValueIsStored() { env.providerDouble.valueMocked.mock(returning: nil) - #expect(env.sut.getData(key) == nil) + #expect(env.sut.getDict(key) == nil) } @Test("should return nil when no value is stored with wrong type") @@ -248,7 +248,7 @@ struct UserDefaultPreferenceGetterAdapterTests { let env = makeEnv(valueType: Bool.self) env.providerDouble.valueMocked.mock(returning: true) - #expect(env.sut.getData(key) == nil) + #expect(env.sut.getDict(key) == nil) } @Test("should return when true is stored") @@ -271,7 +271,7 @@ struct UserDefaultPreferenceGetterAdapterTests { func shouldCallProviderWithExpectedKey() { let key = "any" - _ = env.sut.getData(key) + _ = env.sut.getDict(key) #expect(env.providerDouble.valueMocked.spies == [key]) } @@ -279,9 +279,9 @@ struct UserDefaultPreferenceGetterAdapterTests { @Test("should call provider twice with different keys") func shouldCallProviderTwiceWithDifferentKeys() { let key2 = "any-other" - _ = env.sut.getData(key) + _ = env.sut.getDict(key) - _ = env.sut.getData(key2) + _ = env.sut.getDict(key2) #expect(env.providerDouble.valueMocked.spies == [key, key2]) } From 47f4267ce5cc3ca26b96260aeaf0f59818a746be Mon Sep 17 00:00:00 2001 From: Paolo Prodossimo Lopes Date: Fri, 23 May 2025 15:13:23 -0300 Subject: [PATCH 3/4] =?UTF-8?q?=E2=AC=86=20bump=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Package.resolved | 15 ++++++++++++--- Package.swift | 7 +++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Package.resolved b/Package.resolved index 209e74d..0f0741a 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,22 @@ { - "originHash" : "8ecf31753b0f214f302b42c285b981cbace1641683e5058adde8d0b5dc17f36b", + "originHash" : "6ac5edfe864d3b8f654348a461e89f5873e3d607e0899067f502ea1dd3e3b68a", "pins" : [ { "identity" : "easymock", "kind" : "remoteSourceControl", "location" : "https://github.com/EasyPackages/EasyMock.git", "state" : { - "revision" : "4a26d5a220414099371b53b155c11da95a1e5966", - "version" : "1.0.0" + "branch" : "main", + "revision" : "4a26d5a220414099371b53b155c11da95a1e5966" + } + }, + { + "identity" : "easytesting", + "kind" : "remoteSourceControl", + "location" : "https://github.com/EasyPackages/EasyTesting.git", + "state" : { + "branch" : "main", + "revision" : "4e7c53346ea6191a5a308fb13b1f5b849d6c5d4c" } } ], diff --git a/Package.swift b/Package.swift index 444603e..eaaca3a 100644 --- a/Package.swift +++ b/Package.swift @@ -17,9 +17,12 @@ let package = Package( dependencies: [ .package( url: "https://github.com/EasyPackages/EasyMock.git", - from: "1.0.0" + branch: "main" ), - .package(path: "../EasyTesting") + .package( + url: "https://github.com/EasyPackages/EasyTesting.git", + branch: "main" + ) ], targets: [ .target(name: "EasyPrefence"), From 3b2877fa36ef6fc2d6556de170fd2acfef2e5f08 Mon Sep 17 00:00:00 2001 From: Paolo Prodossimo Lopes Date: Fri, 23 May 2025 15:20:16 -0300 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=93=9D=20organize=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PreferenceGetterProvider.swift | 12 ++++++++++++ .../PreferenceSetterProvider.swift | 2 ++ 2 files changed, 14 insertions(+) diff --git a/Sources/EasyUserDefaultPreference/PreferenceGetterProvider.swift b/Sources/EasyUserDefaultPreference/PreferenceGetterProvider.swift index 1449a63..8a6a5f4 100644 --- a/Sources/EasyUserDefaultPreference/PreferenceGetterProvider.swift +++ b/Sources/EasyUserDefaultPreference/PreferenceGetterProvider.swift @@ -79,49 +79,61 @@ public protocol PreferenceGetterProvider { public struct UserDefaultPreferenceGetterAdapter: PreferenceGetter { private let provider: PreferenceGetterProvider + /// /// Initializes the adapter with a `PreferenceGetterProvider`. /// /// - Parameter provider: The provider responsible for fetching raw values. Defaults to `UserDefaults.standard`. + /// public init(provider: PreferenceGetterProvider = UserDefaults.standard) { self.provider = provider } + /// /// Retrieves a Boolean value associated with the specified key. /// /// - Parameter key: The key to look up in the preference store. /// - Returns: The Boolean value associated with the key, or `nil` if the key does not exist or the value is not a Boolean. + /// public func getBool(_ key: String) -> Bool? { provider.value(forKey: key) as? Bool } + /// /// Retrieves an integer value associated with the specified key. /// /// - Parameter key: The key to look up in the preference store. /// - Returns: The integer value associated with the key, or `nil` if the key does not exist or the value is not an integer. + /// public func getInt(_ key: String) -> Int? { provider.value(forKey: key) as? Int } + /// /// Retrieves a double-precision floating-point value associated with the specified key. /// /// - Parameter key: The key to look up in the preference store. /// - Returns: The Double value associated with the key, or `nil` if the key does not exist or the value is not a Double. + /// public func getDouble(_ key: String) -> Double? { provider.value(forKey: key) as? Double } + /// /// Retrieves binary data associated with the specified key. /// /// - Parameter key: The key to look up in the preference store. /// - Returns: The Data value associated with the key, or `nil` if the key does not exist or the value is not Data. + /// public func getData(_ key: String) -> Data? { provider.value(forKey: key) as? Data } + /// /// Retrieves a dictionary associated with the specified key. /// /// - Parameter key: The key to look up in the preference store. /// - Returns: The dictionary associated with the key, or `nil` if the key does not exist or the value is not a dictionary. + /// public func getDict(_ key: String) -> [String : Any]? { provider.value(forKey: key) as? [String: Any] } diff --git a/Sources/EasyUserDefaultPreference/PreferenceSetterProvider.swift b/Sources/EasyUserDefaultPreference/PreferenceSetterProvider.swift index 9af31f5..61f45a6 100644 --- a/Sources/EasyUserDefaultPreference/PreferenceSetterProvider.swift +++ b/Sources/EasyUserDefaultPreference/PreferenceSetterProvider.swift @@ -120,11 +120,13 @@ public struct UserDefaultPreferenceSetterAdapter: PreferenceSetter { provider.set(value, forKey: key) } + /// /// Stores binary data in the preference store for the specified key. /// /// - Parameters: /// - key: The key under which to store the value. /// - value: The Data object to store. + /// public func setData(_ key: String, value: Data) { provider.set(value, forKey: key) }