forked from jtouzy/swift-spyder
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[feat] Add cache + better error throwing
- Loading branch information
Showing
10 changed files
with
283 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import Foundation | ||
#if canImport(FoundationNetworking) | ||
import FoundationNetworking | ||
#endif | ||
|
||
public enum CachePolicy: Equatable { | ||
case none | ||
case inMemory(duration: TimeInterval) | ||
} | ||
|
||
public struct CacheManager { | ||
var policy: CachePolicy | ||
var entries: [URLRequest: Entry] | ||
|
||
public init(policy: CachePolicy) { | ||
self.policy = policy | ||
self.entries = [:] | ||
} | ||
} | ||
|
||
extension CacheManager { | ||
struct Entry: Equatable { | ||
let successfulResult: Data | ||
let expirationDate: Date | ||
} | ||
} | ||
|
||
extension CacheManager { | ||
public mutating func registerEntryIfNeeded(_ dataEntry: Data, for request: URLRequest, storageDate: Date = .init()) { | ||
guard case .inMemory(let inMemoryDuration) = policy else { | ||
return | ||
} | ||
let expirationDate = storageDate.addingTimeInterval(inMemoryDuration) | ||
entries[request] = .init(successfulResult: dataEntry, expirationDate: expirationDate) | ||
} | ||
} | ||
|
||
extension CacheManager { | ||
mutating func findNonExpiredEntry(for request: URLRequest, comparisonDate: Date = .init()) -> Data? { | ||
guard policy != .none else { | ||
return .none | ||
} | ||
guard let expectedEntry = entries[request] else { | ||
return .none | ||
} | ||
guard expectedEntry.expirationDate > comparisonDate else { | ||
entries.removeValue(forKey: request) | ||
return .none | ||
} | ||
return expectedEntry.successfulResult | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
extension API { | ||
public enum Error: Swift.Error, Equatable { | ||
case invalidStatusCodeInResponse(response: HTTPResponse) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
@testable import Spyder | ||
import XCTest | ||
|
||
final class CacheManagerTests: XCTestCase { | ||
} | ||
|
||
// ======================================================================== | ||
// MARK: Test builders | ||
// ======================================================================== | ||
|
||
private func createSUT(policy: CachePolicy) -> CacheManager { | ||
.init(policy: policy) | ||
} | ||
|
||
// ======================================================================== | ||
// MARK: Tests | ||
// ======================================================================== | ||
|
||
extension CacheManagerTests { | ||
func test_registerEntry_withNoCachePolicy() throws { | ||
// Given | ||
let request: URLRequest = .init(url: try .safe(from: "https://www.google.fr")) | ||
let successfulData: Data = try .safe(from: "{}") | ||
var sut = createSUT(policy: .none) | ||
// When | ||
sut.registerEntryIfNeeded(successfulData, for: request) | ||
// Then | ||
XCTAssertEqual(sut.entries, [:]) | ||
} | ||
func test_registerEntry_withCachePolicy() throws { | ||
// Given | ||
let request: URLRequest = .init(url: try .safe(from: "https://www.google.fr")) | ||
let successfulData: Data = try .safe(from: "{}") | ||
let cacheDuration: TimeInterval = 30 | ||
var sut = createSUT(policy: .inMemory(duration: cacheDuration)) | ||
// When | ||
let storageDate = Date() | ||
sut.registerEntryIfNeeded(successfulData, for: request, storageDate: storageDate) | ||
// Then | ||
XCTAssertEqual(sut.entries, [ | ||
request: .init(successfulResult: successfulData, expirationDate: storageDate.addingTimeInterval(cacheDuration)) | ||
]) | ||
} | ||
} | ||
|
||
extension CacheManagerTests { | ||
func test_findNonExpiredEntry_withNoCachePolicy() throws { | ||
// Given | ||
let request: URLRequest = .init(url: try .safe(from: "https://www.google.fr")) | ||
var sut = createSUT(policy: .none) | ||
// When | ||
let result = sut.findNonExpiredEntry(for: request) | ||
// Then | ||
XCTAssertNil(result) | ||
} | ||
func test_findNonExpiredEntry_withNonExistingCacheEntry() throws { | ||
// Given | ||
let request: URLRequest = .init(url: try .safe(from: "https://www.google.fr")) | ||
var sut = createSUT(policy: .inMemory(duration: 20)) | ||
// When | ||
let result = sut.findNonExpiredEntry(for: request) | ||
// Then | ||
XCTAssertNil(result) | ||
} | ||
func test_findNonExpiredEntry_withNonExpiredCacheEntry() throws { | ||
// Given | ||
let request: URLRequest = .init(url: try .safe(from: "https://www.google.fr")) | ||
let successfulData: Data = try .safe(from: "{}") | ||
let baseStorageDate = Date() | ||
var sut = createSUT(policy: .inMemory(duration: 30)) | ||
sut.registerEntryIfNeeded(successfulData, for: request, storageDate: baseStorageDate) | ||
// When | ||
let result = sut.findNonExpiredEntry(for: request, comparisonDate: baseStorageDate.addingTimeInterval(20)) | ||
// Then | ||
XCTAssertEqual(result, successfulData) | ||
} | ||
func test_findNonExpiredEntry_withExpiredCacheEntry() throws { | ||
// Given | ||
let request: URLRequest = .init(url: try .safe(from: "https://www.google.fr")) | ||
let successfulData: Data = try .safe(from: "{}") | ||
let baseStorageDate = Date() | ||
var sut = createSUT(policy: .inMemory(duration: 10)) | ||
sut.registerEntryIfNeeded(successfulData, for: request, storageDate: baseStorageDate) | ||
// When | ||
let result = sut.findNonExpiredEntry(for: request, comparisonDate: baseStorageDate.addingTimeInterval(20)) | ||
// Then | ||
XCTAssertNil(result) | ||
XCTAssertEqual(sut.entries, [:]) | ||
} | ||
} |
Oops, something went wrong.