diff --git a/README.md b/README.md index 264d177..eb3e28d 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Add the following line to your `Package.swift` file in the dependencies array: ```swift dependencies: [ - .package(url: "https://github.com/0xLeif/Cache.git", from: "1.0.0") + .package(url: "https://github.com/0xLeif/Cache.git", from: "2.0.0") ] ``` diff --git a/Sources/Cache/Cache/AnyCacheable.swift b/Sources/Cache/Cache/AnyCacheable.swift new file mode 100644 index 0000000..2cd509d --- /dev/null +++ b/Sources/Cache/Cache/AnyCacheable.swift @@ -0,0 +1,153 @@ +#if !os(Windows) +public class AnyCacheable: Cacheable { + public typealias Key = AnyHashable + public typealias Value = Any + + private var cache: any Cacheable + + private var cacheGet: ((AnyHashable) -> Any?)! + private var cacheResolve: ((AnyHashable) throws -> Any)! + private var cacheSet: ((Any, AnyHashable) -> Void)! + private var cacheRemove: ((AnyHashable) -> Void)! + private var cacheContains: ((AnyHashable) -> Bool)! + private var cacheRequireKeys: ((Set) throws -> Void)! + private var cacheRequireKey: ((AnyHashable) throws -> Void)! + private var cacheValues: (() -> [AnyHashable: Any])! + + public init(_ cache: InnerCache) { + self.cache = cache + + self.cacheGet = { key in + guard let key = key as? InnerCache.Key else { return nil } + + return cache.get(key, as: Any.self) + } + + self.cacheResolve = { key in + guard + let key = key as? InnerCache.Key + else { throw MissingRequiredKeysError(keys: [key]) } + + return try cache.resolve(key, as: Any.self) + } + + self.cacheSet = { value, key in + guard + let value = value as? InnerCache.Value, + let key = key as? InnerCache.Key + else { return } + + var mutableCache = cache + + mutableCache.set(value: value, forKey: key) + + self.cache = mutableCache + } + + self.cacheRemove = { key in + guard let key = key as? InnerCache.Key else { return } + + var mutableCache = cache + + mutableCache.remove(key) + + self.cache = mutableCache + } + + self.cacheContains = { key in + guard + let key = key as? InnerCache.Key + else { return false } + + return cache.contains(key) + } + + self.cacheRequireKeys = { keys in + let validKeys: Set = Set(keys.compactMap { $0 as? InnerCache.Key }) + + guard + validKeys.count == keys.count + else { throw MissingRequiredKeysError(keys: keys.subtracting(validKeys)) } + + _ = try cache.require(keys: validKeys) + } + + self.cacheRequireKey = { key in + guard + let key = key as? InnerCache.Key + else { throw MissingRequiredKeysError(keys: [key]) } + + _ = try cache.require(key) + } + + self.cacheValues = { + cache.values(ofType: Any.self) + } + } + + required public convenience init(initialValues: [AnyHashable: Any]) { + self.init(Cache(initialValues: initialValues)) + } + + public func get( + _ key: AnyHashable, + as: Output.Type = Output.self + ) -> Output? { + guard let value = cacheGet(key) else { + return nil + } + + guard let output = value as? Output else { + return nil + } + + return output + } + + public func resolve( + _ key: AnyHashable, + as: Output.Type = Output.self + ) throws -> Output { + let resolvedValue = try cacheResolve(key) + + guard let output = resolvedValue as? Output else { + throw InvalidTypeError( + expectedType: Output.self, + actualType: type(of: get(key, as: Any.self)) + ) + } + + return output + } + + public func set(value: Value, forKey key: AnyHashable) { + cacheSet(value, key) + } + + public func remove(_ key: AnyHashable) { + cacheRemove(key) + } + + public func contains(_ key: AnyHashable) -> Bool { + cacheContains(key) + } + + public func require(keys: Set) throws -> Self { + try cacheRequireKeys(keys) + + return self + } + + public func require(_ key: AnyHashable) throws -> Self { + try cacheRequireKey(key) + + return self + } + + public func values(ofType: Output.Type) -> [AnyHashable: Output] { + cacheValues().compactMapValues { value in + value as? Output + } + } +} +#endif diff --git a/Sources/Cache/Cache/Cache.swift b/Sources/Cache/Cache/Cache.swift index d351aef..0c8f242 100644 --- a/Sources/Cache/Cache/Cache.swift +++ b/Sources/Cache/Cache/Cache.swift @@ -162,3 +162,28 @@ open class Cache: Cacheable { #if !os(Linux) && !os(Windows) extension Cache: ObservableObject { } #endif + +extension Cache { + /** + Gets a value from the cache for a given key. + + - Parameters: + - key: The key to retrieve the value for. + - Returns: The value stored in cache for the given key, or `nil` if it doesn't exist. + */ + public func get(_ key: Key) -> Value? { + get(key, as: Value.self) + } + + /** + Resolves a value from the cache for a given key. + + - Parameters: + - key: The key to retrieve the value for. + - Returns: The value stored in cache for the given key. + - Throws: `MissingRequiredKeysError` if the key is missing, or `InvalidTypeError` if the value type is not compatible with the expected type. + */ + public func resolve(_ key: Key) throws -> Value { + try resolve(key, as: Value.self) + } +} diff --git a/Sources/Cache/Cache/ComposableCache.swift b/Sources/Cache/Cache/ComposableCache.swift new file mode 100644 index 0000000..826e0ee --- /dev/null +++ b/Sources/Cache/Cache/ComposableCache.swift @@ -0,0 +1,99 @@ +#if !os(Windows) +public struct ComposableCache: Cacheable { + private let caches: [AnyCacheable] + + public init(caches: [any Cacheable]) { + self.caches = caches.map { AnyCacheable($0) } + } + + public init(initialValues: [Key: Any]) { + self.init(caches: [Cache(initialValues: initialValues)]) + } + + public func get( + _ key: Key, + as: Output.Type = Output.self + ) -> Output? { + for cache in caches { + guard + let output = cache.get(key, as: Output.self) + else { + continue + } + + return output + } + + return nil + } + + public func resolve( + _ key: Key, + as: Output.Type = Output.self + ) throws -> Output { + for cache in caches { + guard + let output = try? cache.resolve(key, as: Output.self) + else { + continue + } + + return output + } + + throw MissingRequiredKeysError(keys: [key]) + } + + public func set(value: Any, forKey key: Key) { + for cache in caches { + cache.set(value: value, forKey: key) + } + } + + public func remove(_ key: Key) { + for cache in caches { + cache.remove(key) + } + } + + public func contains(_ key: Key) -> Bool { + for cache in caches { + if cache.contains(key) { + return true + } + } + + return false + } + + public func require(keys: Set) throws -> ComposableCache { + for cache in caches { + _ = try cache.require(keys: keys) + } + + return self + } + + public func require(_ key: Key) throws -> ComposableCache { + for cache in caches { + _ = try cache.require(key) + } + + return self + } + + public func values(ofType: Output.Type) -> [Key: Output] { + for cache in caches { + let values = cache.values(ofType: Output.self).compactMapKeys { $0 as? Key } + + guard values.keys.count != 0 else { + continue + } + + return values + } + + return [:] + } +} +#endif diff --git a/Sources/Cache/Cache/RequiredKeysCache.swift b/Sources/Cache/Cache/RequiredKeysCache.swift index 5370050..de8c00f 100644 --- a/Sources/Cache/Cache/RequiredKeysCache.swift +++ b/Sources/Cache/Cache/RequiredKeysCache.swift @@ -65,14 +65,9 @@ public class RequiredKeysCache: Cache { - Throws: A runtime error if the required key is not present in the cache or if the expected value type is incorrect. */ public func resolve(requiredKey: Key, as: Output.Type = Output.self) -> Output { - guard - requiredKeys.contains(requiredKey) - else { fatalError("The key '\(requiredKey)' is not a Required Key.") } - - guard - contains(requiredKey) - else { fatalError("Required Key Missing: '\(requiredKey)'") } - + precondition(requiredKeys.contains(requiredKey), "The key '\(requiredKey)' is not a Required Key.") + precondition(contains(requiredKey), "Required Key Missing: '\(requiredKey)'") + do { return try resolve(requiredKey, as: Output.self) } diff --git a/Sources/Cache/CacheInitializable.swift b/Sources/Cache/CacheInitializable.swift new file mode 100644 index 0000000..90d7c05 --- /dev/null +++ b/Sources/Cache/CacheInitializable.swift @@ -0,0 +1,5 @@ +public protocol CacheInitializable { + associatedtype OriginCache: Cacheable + + init(cache: OriginCache) +} diff --git a/Sources/Cache/Cacheable.swift b/Sources/Cache/Cacheable.swift index 50a7fd8..ca079f9 100644 --- a/Sources/Cache/Cacheable.swift +++ b/Sources/Cache/Cacheable.swift @@ -70,27 +70,4 @@ public extension Cacheable { var allValues: [Key: Value] { values(ofType: Value.self) } - - /** - Gets a value from the cache for a given key. - - - Parameters: - - key: The key to retrieve the value for. - - Returns: The value stored in cache for the given key, or `nil` if it doesn't exist. - */ - func get(_ key: Key) -> Value? { - get(key, as: Value.self) - } - - /** - Resolves a value from the cache for a given key. - - - Parameters: - - key: The key to retrieve the value for. - - Returns: The value stored in cache for the given key. - - Throws: `MissingRequiredKeysError` if the key is missing, or `InvalidTypeError` if the value type is not compatible with the expected type. - */ - func resolve(_ key: Key) throws -> Value { - try resolve(key, as: Value.self) - } } diff --git a/Sources/Cache/PropertyWrappers/Cached.swift b/Sources/Cache/PropertyWrappers/Cached.swift index 6ea062b..c6d29f6 100644 --- a/Sources/Cache/PropertyWrappers/Cached.swift +++ b/Sources/Cache/PropertyWrappers/Cached.swift @@ -22,12 +22,12 @@ - Note: The `Cached` property wrapper relies on a cache instance that conforms to the `Cache` protocol, in order to retrieve and store the values efficiently. */ -@propertyWrapper public struct Cached { +@propertyWrapper public struct Cached where CacheSource.Key == Key, CacheSource.Value == Any { /// The key associated with the value in the cache. public let key: Key /// The cache instance to retrieve the value from. - public let cache: Cache + public var cache: CacheSource /// The default value to be used if the value is not present in the cache. public let defaultValue: Value @@ -42,42 +42,37 @@ } } - - #if !os(Windows) /** Initializes a new instance of the `Cached` property wrapper. - Parameters: - key: The key associated with the value in the cache. - - cache: The cache instance to retrieve the value from. The default is `Global.cache`. + - cache: The cache instance to retrieve the value from. - defaultValue: The default value to be used if the value is not present in the cache. */ public init( key: Key, - using cache: Cache = Global.cache, + using cache: CacheSource, defaultValue: Value ) { self.key = key self.cache = cache self.defaultValue = defaultValue } - #else + /** - Initializes a new instance of the `Cached` property wrapper. + Initializes a new instance of the `Cached` property wrapper using the`Global.cache`. - Parameters: - key: The key associated with the value in the cache. - - cache: The cache instance to retrieve the value from. - defaultValue: The default value to be used if the value is not present in the cache. */ public init( key: Key, - using cache: Cache, defaultValue: Value - ) { + ) where CacheSource == Cache { self.key = key - self.cache = cache + self.cache = Global.cache self.defaultValue = defaultValue } - #endif } diff --git a/Sources/Cache/PropertyWrappers/OptionallyCached.swift b/Sources/Cache/PropertyWrappers/OptionallyCached.swift index d53893e..de7b9a0 100644 --- a/Sources/Cache/PropertyWrappers/OptionallyCached.swift +++ b/Sources/Cache/PropertyWrappers/OptionallyCached.swift @@ -25,13 +25,13 @@ - Note: The `OptionallyCached` property wrapper relies on a cache instance that conforms to the `Cache` protocol, in order to retrieve and store the values efficiently. */ -@propertyWrapper public struct OptionallyCached { +@propertyWrapper public struct OptionallyCached where CacheSource.Key == Key, CacheSource.Value == Any { /// The key associated with the value in the cache. public let key: Key /// The cache instance to retrieve the value from. - public let cache: Cache - + public var cache: CacheSource + /// The wrapped value that can be accessed and mutated by the property wrapper. public var wrappedValue: Value? { get { @@ -45,36 +45,32 @@ cache.set(value: newValue, forKey: key) } } - - #if !os(Windows) + /** Initializes a new instance of the `OptionallyCached` property wrapper. - Parameters: - key: The key associated with the value in the cache. - - cache: The cache instance to retrieve the value from. The default is `Global.cache`. + - cache: The cache instance to retrieve the value from. */ public init( key: Key, - using cache: Cache = Global.cache + using cache: CacheSource ) { self.key = key self.cache = cache } - #else + /** - Initializes a new instance of the `OptionallyCached` property wrapper. + Initializes a new instance of the `OptionallyCached` property wrapper using the`Global.cache` - Parameters: - key: The key associated with the value in the cache. - - cache: The cache instance to retrieve the value from. */ public init( - key: Key, - using cache: Cache - ) { + key: Key + ) where CacheSource == Cache { self.key = key - self.cache = cache + self.cache = Global.cache } - #endif } diff --git a/Tests/CacheTests/AnyCacheableTests.swift b/Tests/CacheTests/AnyCacheableTests.swift new file mode 100644 index 0000000..5d40f6e --- /dev/null +++ b/Tests/CacheTests/AnyCacheableTests.swift @@ -0,0 +1,236 @@ +#if !os(Windows) +import XCTest +@testable import Cache + +final class AnyCacheableTests: XCTestCase { + func testAllValues() { + enum Key { + case text + } + + let cacheable: AnyCacheable = AnyCacheable( + initialValues: [ + Key.text: "Hello, World!" + ] + ) + + XCTAssertEqual(cacheable.allValues.count, 1) + } + + func testGet_Success() { + enum Key { + case text + } + + let cache: Cache = Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + + let cacheable: AnyCacheable = AnyCacheable(cache) + + XCTAssertEqual(cacheable.get(Key.text, as: String.self), "Hello, World!") + } + + func testGet_MissingKey() { + enum Key { + case text + case missingKey + } + + let cache: Cache = Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + + let cacheable: AnyCacheable = AnyCacheable(cache) + + let missingKeyValue: Any? = cacheable.get(Key.missingKey) + + XCTAssertNil(missingKeyValue) + } + + func testGet_InvalidType() { + enum Key { + case text + } + + let cache: Cache = Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + + let cacheable: AnyCacheable = AnyCacheable(cache) + + XCTAssertNil(cacheable.get(Key.text, as: Int.self)) + } + + func testResolve_Success() throws { + enum Key: String { + case text + } + + let cache: Cache = Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + + let cacheable: AnyCacheable = AnyCacheable(cache) + + XCTAssertEqual(try cacheable.resolve(Key.text), "Hello, World!") + } + + func testResolve_MissingKey() throws { + enum Key { + case text + case missingKey + } + + let cache: Cache = Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + + let cacheable: AnyCacheable = AnyCacheable(cache) + + XCTAssertThrowsError(try cacheable.resolve(Key.missingKey, as: Any.self)) + } + + func testResolve_InvalidType() throws { + enum Key { + case text + } + + let cache: Cache = Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + + let cacheable: AnyCacheable = AnyCacheable(cache) + + XCTAssertThrowsError(try cacheable.resolve(Key.text, as: Int.self)) + } + + func testSet() { + enum Key { + case text + } + + let cache: Cache = Cache() + + let cacheable: AnyCacheable = AnyCacheable(cache) + + cacheable.set(value: "Hello, World!", forKey: Key.text) + + XCTAssertEqual(cacheable.get(Key.text), "Hello, World!") + } + + func testRemove() { + enum Key { + case text + } + + let cache: Cache = Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + + let cacheable: AnyCacheable = AnyCacheable(cache) + + XCTAssertEqual(cacheable.get(Key.text), "Hello, World!") + + cacheable.remove(Key.text) + + XCTAssertNil(cacheable.get(Key.text)) + } + + func testContains() { + enum Key { + case text + } + + let cache: Cache = Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + + let cacheable: AnyCacheable = AnyCacheable(cache) + + XCTAssert(cacheable.contains(Key.text)) + } + + func testRequire_Success() throws { + enum Key { + case text + } + + let cache: Cache = Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + + let cacheable: AnyCacheable = AnyCacheable(cache) + + XCTAssertNoThrow(try cacheable.require(Key.text)) + } + + func testRequire_Missing() throws { + enum Key { + case text + case missingKey + } + + let cache: Cache = Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + + let cacheable: AnyCacheable = AnyCacheable(cache) + + XCTAssertThrowsError(try cacheable.require(Key.missingKey)) + } + + func testRequireSet_Success() throws { + enum Key { + case text + } + + let cache: Cache = Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + + let cacheable: AnyCacheable = AnyCacheable(cache) + + XCTAssertNoThrow(try cacheable.require(keys: [Key.text])) + } + + func testRequireSet_Missing() throws { + enum Key { + case text + case missingKey + } + + let cache: Cache = Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + + let cacheable: AnyCacheable = AnyCacheable(cache) + + XCTAssertThrowsError(try cacheable.require(keys: [Key.text, Key.missingKey])) + } +} +#endif diff --git a/Tests/CacheTests/CacheInitializableTests.swift b/Tests/CacheTests/CacheInitializableTests.swift new file mode 100644 index 0000000..a0df885 --- /dev/null +++ b/Tests/CacheTests/CacheInitializableTests.swift @@ -0,0 +1,38 @@ +import XCTest +@testable import Cache + +final class CacheInitializableTests: XCTestCase { + class TestCache: Cache { + enum Key { + case text + } + } + + struct CacheInitializableObject: CacheInitializable { + let text: String? + + init(cache: TestCache) { + text = cache.get(.text) + } + } + + func testCacheInitializableEmpty() { + let cache = TestCache() + + let object = CacheInitializableObject(cache: cache) + + XCTAssertNil(object.text) + } + + func testCacheInitializable() { + let cache = TestCache( + initialValues: [ + .text: "Hello, World!" + ] + ) + + let object = CacheInitializableObject(cache: cache) + + XCTAssertNotNil(object.text) + } +} diff --git a/Tests/CacheTests/ComposableCacheTests.swift b/Tests/CacheTests/ComposableCacheTests.swift new file mode 100644 index 0000000..51895b9 --- /dev/null +++ b/Tests/CacheTests/ComposableCacheTests.swift @@ -0,0 +1,349 @@ +#if !os(Windows) +import XCTest +@testable import Cache + +final class ComposableCacheTests: XCTestCase { + func testComplexComposableCache() { + enum Key { + case a + case b + case c + case d + } + + let lruCache: LRUCache = LRUCache(capacity: 3) + let expiringCache: ExpiringCache = ExpiringCache(duration: .seconds(1)) + + let cache: ComposableCache = ComposableCache( + caches: [ + lruCache, // First Cache + // ... // Other Caches + expiringCache // Last Cache + ] + ) + + XCTAssertNil(cache.get(.a)) + + cache.set(value: "Hello, A!", forKey: .a) + + XCTAssertNotNil(cache.get(.a)) + XCTAssertNotNil(lruCache.get(.a)) + XCTAssertNotNil(expiringCache.get(.a)) + + cache.set(value: "Hello, B!", forKey: .b) + cache.set(value: "Hello, C!", forKey: .c) + cache.set(value: "Hello, D!", forKey: .d) + + XCTAssertNil(lruCache.get(.a)) + XCTAssertNotNil(cache.get(.a)) + XCTAssertNotNil(expiringCache.get(.a)) + + XCTAssertNotNil(cache.get(.b)) + XCTAssertNotNil(cache.get(.c)) + XCTAssertNotNil(cache.get(.d)) + + sleep(1) + + // Check ComposableCache + + XCTAssertNil(cache.get(.a)) + XCTAssertNotNil(cache.get(.b)) + XCTAssertNotNil(cache.get(.c)) + XCTAssertNotNil(cache.get(.d)) + + // Check LRUCache + + XCTAssertNil(lruCache.get(.a)) + XCTAssertNotNil(lruCache.get(.b)) + XCTAssertNotNil(lruCache.get(.c)) + XCTAssertNotNil(lruCache.get(.d)) + + // Check ExpiringCache + + XCTAssertNil(expiringCache.get(.a)) + XCTAssertNil(expiringCache.get(.b)) + XCTAssertNil(expiringCache.get(.c)) + XCTAssertNil(expiringCache.get(.d)) + } + + func testAllValues() { + enum Key { + case text + } + + let cache: ComposableCache = ComposableCache( + initialValues: [ + .text: "Hello, World!" + ] + ) + + XCTAssertEqual(cache.allValues.count, 1) + } + + func testAllValues_None() { + enum Key { + case text + } + + let cache: ComposableCache = ComposableCache( + initialValues: [:] + ) + + XCTAssertEqual(cache.allValues.count, 0) + } + + func testGet_Success() { + enum Key { + case text + } + + let cache: ComposableCache = ComposableCache( + caches: [ + Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + ] + ) + + XCTAssertEqual(cache.get(.text), "Hello, World!") + } + + func testGet_MissingKey() { + enum Key { + case text + case missingKey + } + + let cache: ComposableCache = ComposableCache( + caches: [ + Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + ] + ) + + XCTAssertNil(cache.get(.missingKey)) + } + + func testGet_InvalidType() { + enum Key { + case text + } + + let cache: ComposableCache = ComposableCache( + caches: [ + Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + ] + ) + + XCTAssertNil(cache.get(.text, as: Int.self)) + } + + func testResolve_Success() throws { + enum Key { + case text + } + + let cache: ComposableCache = ComposableCache( + caches: [ + Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + ] + ) + + XCTAssertEqual(try cache.resolve(.text), "Hello, World!") + } + + func testResolve_MissingKey() throws { + enum Key { + case text + case missingKey + } + + let cache: ComposableCache = ComposableCache( + caches: [ + Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + ] + ) + + XCTAssertThrowsError(try cache.resolve(.missingKey, as: Any.self)) + } + + func testResolve_InvalidType() throws { + enum Key { + case text + } + + let cache: ComposableCache = ComposableCache( + caches: [ + Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + ] + ) + + XCTAssertThrowsError(try cache.resolve(.text, as: Int.self)) + } + + func testSet() { + enum Key { + case text + } + + let cache: ComposableCache = ComposableCache( + caches: [ + Cache() + ] + ) + + cache.set(value: "Hello, World!", forKey: .text) + + XCTAssertEqual(cache.get(.text), "Hello, World!") + } + + func testRemove() { + enum Key { + case text + } + + let cache: ComposableCache = ComposableCache( + caches: [ + Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + ] + ) + + XCTAssertEqual(cache.get(.text), "Hello, World!") + + cache.remove(.text) + + XCTAssertNil(cache.get(.text)) + } + + func testContains_Success() { + enum Key { + case text + } + + let cache: ComposableCache = ComposableCache( + caches: [ + Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + ] + ) + + XCTAssert(cache.contains(.text)) + } + + func testContains_Failure() { + enum Key { + case text + } + + let cache: ComposableCache = ComposableCache( + caches: [ + Cache() + ] + ) + + XCTAssertFalse(cache.contains(.text)) + } + + func testRequire_Success() throws { + enum Key { + case text + } + + let cache: ComposableCache = ComposableCache( + caches: [ + Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + ] + ) + + XCTAssertNoThrow(try cache.require(.text)) + } + + func testRequire_Missing() throws { + enum Key { + case text + case missingKey + } + + let cache: ComposableCache = ComposableCache( + caches: [ + Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + ] + ) + + XCTAssertThrowsError(try cache.require(.missingKey)) + } + + func testRequireSet_Success() throws { + enum Key { + case text + } + + let cache: ComposableCache = ComposableCache( + caches: [ + Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + ] + ) + + XCTAssertNoThrow(try cache.require(keys: [.text])) + } + + func testRequireSet_Missing() throws { + enum Key { + case text + case missingKey + } + + let cache: ComposableCache = ComposableCache( + caches: [ + Cache( + initialValues: [ + .text: "Hello, World!" + ] + ) + ] + ) + + XCTAssertThrowsError(try cache.require(keys: [.text, .missingKey])) + } +} +#endif diff --git a/Tests/CacheTests/PropertyWrappersTests.swift b/Tests/CacheTests/PropertyWrappersTests.swift index 7487922..bf407e8 100644 --- a/Tests/CacheTests/PropertyWrappersTests.swift +++ b/Tests/CacheTests/PropertyWrappersTests.swift @@ -8,9 +8,9 @@ final class PropertyWrappersTests: XCTestCase { case value } - static let cache = Cache() + static let cache = ExpiringCache(duration: .minutes(2)) - @Cached(key: Key.value, using: cache, defaultValue: "no value") + @Cached(key: .value, using: cache, defaultValue: "no value") var someValue: String } @@ -34,7 +34,7 @@ final class PropertyWrappersTests: XCTestCase { static let cache = Cache() - @OptionallyCached(key: Key.value, using: cache) + @OptionallyCached(key: .value, using: cache) var someValue: String? } @@ -64,7 +64,7 @@ final class PropertyWrappersTests: XCTestCase { static let cache = RequiredKeysCache() - @Resolved(key: Key.value, using: cache) + @Resolved(key: .value, using: cache) var someValue: String } @@ -81,4 +81,78 @@ final class PropertyWrappersTests: XCTestCase { XCTAssertEqual(object.someValue, "Hello, World") XCTAssertEqual(cachedObject.someValue, "Hello, World") } + + func testGlobalCached() { + struct CachedValueObject { + enum Key { + case value + } + + @Cached(key: Key.value, defaultValue: "no value") + var someValue: String + } + + var object = CachedValueObject() + + XCTAssertEqual(object.someValue, "no value") + + object.someValue = "Hello, World" + + let cachedObject = CachedValueObject() + + XCTAssertEqual(object.someValue, "Hello, World") + XCTAssertEqual(cachedObject.someValue, "Hello, World") + } + + func testGloballyOptionallyCached() { + struct CachedValueObject { + enum Key { + case value + } + + @OptionallyCached(key: Key.value) + var someValue: String? + } + + var object = CachedValueObject() + + XCTAssertNil(object.someValue) + + object.someValue = "Hello, World" + + let cachedObject = CachedValueObject() + + XCTAssertEqual(object.someValue, "Hello, World") + XCTAssertEqual(cachedObject.someValue, "Hello, World") + + object.someValue = nil + + XCTAssertNil(object.someValue) + XCTAssertNil(cachedObject.someValue) + } + + func testGloballyResolved() { + struct CachedValueObject { + enum Key { + case pi + case value + } + + @Resolved(key: Key.value, using: Global.dependencies) + var someValue: String + } + + Global.dependencies.set(value: "init", forKey: CachedValueObject.Key.value) + + var object = CachedValueObject() + + XCTAssertEqual(object.someValue, "init") + + object.someValue = "Hello, World" + + let cachedObject = CachedValueObject() + + XCTAssertEqual(object.someValue, "Hello, World") + XCTAssertEqual(cachedObject.someValue, "Hello, World") + } }