Skip to content

Commit 8a84b5f

Browse files
committed
Fridge 0.9.2
Added support for array types (#37) Updated package dependecies to their latest versions (#42) Squashed commits: commit 3845176d36189541c893608893cafd6e394a8690 commit 114f7e35b6e317862f13088043f1f601f5abb088 Closes #37 Closes #42 Signed-off-by: Veljko Tekelerović <[email protected]>
1 parent 5d516d2 commit 8a84b5f

File tree

4 files changed

+145
-41
lines changed

4 files changed

+145
-41
lines changed

Sources/Fridge/BSONConverter.swift

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,53 @@
88
import Foundation
99
import BSONCoder
1010

11+
/// Internal helper struct that wraps generic array
12+
fileprivate struct WrappedObject<T: Codable>: Codable, Identifiable {
13+
var id = BSONObjectID.init()
14+
var array: [T]
15+
16+
init(arrayObject: [T]) {
17+
array = arrayObject
18+
}
19+
}
20+
1121
/// Utility class providing write/read BSON functionality
1222
final class BSONConverter {
13-
let _rawFilePath: URL
23+
private let _rawFilePath: URL
1424

1525
init(compartment: FridgeCompartment) {
16-
_rawFilePath = compartment.filePath
26+
_rawFilePath = compartment.objectPath
1727
}
1828

19-
/// Writes given object to a compartment storage
29+
/// Writes given object to a local system storage
2030
func write<T: Encodable>(object: T) throws {
21-
// declare BSONEncoder
22-
let rawBSONData = try BSONEncoder().encode(object).toData() //will throw further
31+
var rawData: Data
32+
33+
do {
34+
rawData = try BSONEncoder().encode(object).toData()
35+
} catch let err {
36+
print("<BSONConverter> Error occured. Reason:\n\(err)")
37+
throw err
38+
}
2339

24-
// now flush the data to a file
25-
try rawBSONData.write(to: _rawFilePath)
40+
// flush the data to a file
41+
try rawData.write(to: _rawFilePath)
42+
}
43+
44+
/// Writes given array of objects to a local system storage
45+
func write<T: Codable>(objects: [T]) throws {
46+
var rawData: Data
47+
let wrappedObject = WrappedObject<T>.init(arrayObject: objects)
48+
49+
do {
50+
rawData = try BSONEncoder().encode(wrappedObject).toData()
51+
} catch let err {
52+
print("<BSONConverter> Error occured. Reason:\n\(err)")
53+
throw err
54+
}
55+
56+
// flush the data to a file
57+
try rawData.write(to: _rawFilePath)
2658
}
2759

2860
/// Reads object from compartment data storage and returns Foundation counterpart
@@ -33,4 +65,13 @@ final class BSONConverter {
3365
let realObject = try BSONDecoder().decode(T.self, from: rawBSONData)
3466
return realObject
3567
}
68+
69+
/// Reads array of objects from compartment data storage and returns Foundation counterpart
70+
func read<T: Codable>() throws -> [T] {
71+
// get raw data from the storage
72+
let rawBSONData = try Data(contentsOf: _rawFilePath)
73+
74+
let wrappedObject = try BSONDecoder().decode(WrappedObject<T>.self, from: rawBSONData)
75+
return wrappedObject.array
76+
}
3677
}

Sources/Fridge/Freezer.swift

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ import Foundation
3131
// MARK: -
3232
final internal class Freezer {
3333
/// Freezes an object into Fridge persistant storage. Any new object will overwrite previously stored object
34-
func freeze<T: Encodable>(object: T, identifier: String) throws { //async ?
34+
func freeze<T: Encodable>(object: T, identifier: String) throws {
3535
do {
3636
// 1. initialize fridge compartment for given key
3737
let comp = FridgeCompartment(key: identifier)
3838

39-
// 2. initialize Streamer with produced compartment
39+
// 2. initialize BSON writer from given compartment
4040
let writer = BSONConverter(compartment: comp)
4141

4242
// 3. perform stream write of given object
@@ -46,13 +46,29 @@ final internal class Freezer {
4646
}
4747
}
4848

49+
/// Freezes an object into Fridge persistant storage. Any new object will overwrite previously stored object
50+
func freeze<T: Codable>(objects: [T], identifier: String) throws {
51+
do {
52+
// 1. initialize fridge compartment for given key
53+
let comp = FridgeCompartment(key: identifier)
54+
55+
// 2. initialize BSON writer from given compartment
56+
let writer = BSONConverter(compartment: comp)
57+
58+
// 3. perform stream write of array of objects
59+
try writer.write(objects: objects)
60+
} catch {
61+
throw FreezingErrors.dataStoringError
62+
}
63+
}
64+
4965
/// Unfreezes an object from Fridge persistant storage.
5066
func unfreeze<T: Decodable>(identifier: String) throws -> T {
5167
do {
5268
// 1. setup compartment
5369
let comp = FridgeCompartment(key: identifier)
5470

55-
// 2. initialize Streamer with created compartment
71+
// 2. initialize BSON reader from given compartment
5672
let reader = BSONConverter(compartment: comp)
5773

5874
// 3. perform stream read
@@ -63,6 +79,23 @@ final internal class Freezer {
6379
}
6480
}
6581

82+
/// Unfreezes an array of objects from Fridge persistant storage.
83+
func unfreeze<T: Codable>(identifier: String) throws -> [T] {
84+
do {
85+
// 1. setup compartment
86+
let comp = FridgeCompartment(key: identifier)
87+
88+
// 2. initialize BSON reader from given compartment
89+
let reader = BSONConverter(compartment: comp)
90+
91+
// 3. perform stream read
92+
let storedObject: [T] = try reader.read()
93+
return storedObject
94+
} catch {
95+
throw FreezingErrors.dataReadingError
96+
}
97+
}
98+
6699
/// Returns `true` if a given object has been frozen previously.
67100
func isAlreadyFrozen(identifier: String) -> Bool {
68101
FridgeCompartment(key: identifier).alreadyExist

Sources/Fridge/FridgeCompartment.swift

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,25 +40,26 @@ internal struct FridgeCompartment {
4040
}
4141

4242
/// Returns `URL` based file path of this compartment
43-
var filePath: URL {
44-
//get documents directory
43+
var objectPath: URL {
44+
// TODO: Alter between DocumentsDirectory and CacheDirectory later
4545
guard let documentDirectoryURL = _fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
4646
fatalError("<Fridge.Storage> Unable to compute DocumentsDirectory path")
4747
}
48-
49-
let finalName = "\(key)\(BLOB_EXTENSION)"
50-
let finalURL = documentDirectoryURL.appendingPathComponent(finalName)
51-
return finalURL
48+
return documentDirectoryURL.appendingPathComponent(storageName)
49+
}
50+
51+
/// Returns the compartment name formatted by key and static identifier
52+
private var storageName: String {
53+
key + BLOB_EXTENSION
5254
}
5355

5456
/// Returns `true` if raw data already exists at this compartment, `false` otherwise
5557
var alreadyExist: Bool {
56-
_fileManager.fileExists(atPath: filePath.path)
58+
_fileManager.fileExists(atPath: objectPath.path)
5759
}
5860

59-
6061
/// Removes compartment from persistent storage
6162
func remove() {
62-
try? _fileManager.removeItem(atPath: filePath.path)
63+
try? _fileManager.removeItem(atPath: objectPath.path)
6364
}
6465
}

Tests/FridgeTests/FreezerTests.swift

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -48,42 +48,49 @@ fileprivate struct FridgeTestObject: Codable {
4848
string_field = "Some f🧊ncy string"
4949
int_field = Int.max
5050
dict_field = InnerTestObject()
51-
arr_field = [100, 200, 300, Int.random(in: Int.min...Int.max)]
52-
data_field = Data(repeating: 0xAE, count: 0xABCDEF)
51+
arr_field = [0xABCDEF, 0xCAB0CAB, 0xFADE, 0xEFCAD]
52+
data_field = Data(repeating: 0xAE, count: 0xE1FE1)
5353
url_field = URL(fileURLWithPath: "someFilePathOfAMockObject")
5454
}
5555
}
5656

5757
fileprivate struct InnerTestObject: Codable {
58-
var field1: Float = 1_234_567.890_001
59-
var field2: Double = Double.pi
60-
var field3: Set = Set([1,2,3])
61-
var field4: String? = nil
58+
var field1: String? = nil
59+
var field2: Float = 1_234_567.890_001
60+
var field3: Double = Double.pi
61+
var field4: Date = Date.init()
62+
var field5: Bool = !false
63+
64+
var field6: Set = Set([1,2,3])
65+
var field7: Array<Int64> = Array.init()
6266
}
6367

6468
extension FridgeTestObject: Equatable {
6569
static func ==(lhs: FridgeTestObject, rhs: FridgeTestObject) -> Bool {
6670
let equality =
6771
(lhs.string_field == rhs.string_field) &&
6872
(lhs.int_field == rhs.int_field) &&
69-
(lhs.arr_field == rhs.arr_field)
73+
(lhs.arr_field == rhs.arr_field) &&
74+
(lhs.data_field == rhs.data_field) &&
75+
(lhs.url_field == rhs.url_field)
7076

7177
return equality
7278
}
7379
}
7480

81+
// !! LET THE HUNT BEGIN !! 🕵️‍♂️🥷
7582
final class FreezerTests: XCTestCase {
7683
// SHARED TESTING OBJECT
7784
let freezer = Freezer()
7885

7986
/// Tests weather an object can be saved without throwing error
80-
func testBasicFreezing(){
81-
let testData1 = FridgeTestObject()
82-
XCTAssertNoThrow(try freezer.freeze(object: testData1, identifier: FridgeTestObject.IDENTIFIER))
87+
func testObjectFreezing(){
88+
let testData = FridgeTestObject()
89+
XCTAssertNoThrow(try freezer.freeze(object: testData, identifier: FridgeTestObject.IDENTIFIER))
8390
}
8491

8592
/// Tests weather an object can be loaded without throwing error
86-
func testBasicUnfreezing() {
93+
func testObjectUnfreezing() {
8794
//freeze an object first
8895
let frozenObject = FridgeTestObject()
8996

@@ -106,15 +113,37 @@ final class FreezerTests: XCTestCase {
106113
}
107114

108115
/// Tests if array can be stored
109-
// func testFreezingAnArray() throws {
110-
// XCTAssertFalse(freezer.isAlreadyFrozen(identifier: "array.test"))
111-
//
112-
// let freezingArray = [1,2,3,4,5,6,7,8]
113-
// let objectArray: Array<FridgeTestObject> = [FridgeTestObject(), FridgeTestObject()]
114-
// try freezer.freeze(object: freezingArray, identifier: "array.test")
115-
// try freezer.freeze(object: objectArray, identifier: "array-object.test")
116-
//
117-
// let unpackedObject: [Int] = try freezer.unfreeze(identifier: "array.test")
118-
// XCTAssert(unpackedObject[0] == freezingArray[0])
119-
// }
116+
func testArrayFreezing() throws {
117+
XCTAssertFalse(freezer.isAlreadyFrozen(identifier: "array.test"))
118+
119+
let foundationArray = [1,2,3,4,5,6,7,8]
120+
let customStructArray: Array<FridgeTestObject> = [FridgeTestObject(), FridgeTestObject()]
121+
XCTAssertNoThrow(try freezer.freeze(objects: foundationArray, identifier: "foundation.array"))
122+
XCTAssertNoThrow(try freezer.freeze(objects: customStructArray, identifier: "array-object.test"))
123+
124+
// make sure it actually throws when passed incorrectly
125+
XCTAssertThrowsError(try freezer.freeze(object: foundationArray, identifier: "wrong.method.array"))
126+
XCTAssertThrowsError(try freezer.freeze(object: customStructArray, identifier: "wrong.method.custom.array"))
127+
}
128+
129+
/// Tests if array can be read from the storage
130+
func testArrayUnFreezing() throws {
131+
let expectedFoundationArray = [1,2,3,4,5,6,7,8]
132+
let expectedStructArray: Array<FridgeTestObject> = [FridgeTestObject(), FridgeTestObject()]
133+
var failureMessage: String
134+
135+
do {
136+
// check foundation
137+
failureMessage = "Foundation array issue"
138+
let unfrozenFoundation: Array<Int> = try freezer.unfreeze(identifier: "foundation.array")
139+
XCTAssert(unfrozenFoundation == expectedFoundationArray)
140+
141+
failureMessage = "Array of custom struct issue"
142+
let unfrozenCustomArray: Array<FridgeTestObject> = try freezer.unfreeze(identifier: "array-object.test")
143+
XCTAssert(unfrozenCustomArray[0] == expectedStructArray[0])
144+
// XCTAssert(unfrozenCustomArray[0].dict_field == expectedStructArray[0].dict_field)
145+
} catch {
146+
XCTFail(failureMessage)
147+
}
148+
}
120149
}

0 commit comments

Comments
 (0)