Skip to content

Commit

Permalink
Fridge 0.9.2
Browse files Browse the repository at this point in the history
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ć <veljko.tekelerovic@gmail.com>
  • Loading branch information
vexy committed Sep 23, 2022
1 parent 5d516d2 commit 8a84b5f
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 41 deletions.
55 changes: 48 additions & 7 deletions Sources/Fridge/BSONConverter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,53 @@
import Foundation
import BSONCoder

/// Internal helper struct that wraps generic array
fileprivate struct WrappedObject<T: Codable>: Codable, Identifiable {
var id = BSONObjectID.init()
var array: [T]

init(arrayObject: [T]) {
array = arrayObject
}
}

/// Utility class providing write/read BSON functionality
final class BSONConverter {
let _rawFilePath: URL
private let _rawFilePath: URL

init(compartment: FridgeCompartment) {
_rawFilePath = compartment.filePath
_rawFilePath = compartment.objectPath
}

/// Writes given object to a compartment storage
/// Writes given object to a local system storage
func write<T: Encodable>(object: T) throws {
// declare BSONEncoder
let rawBSONData = try BSONEncoder().encode(object).toData() //will throw further
var rawData: Data

do {
rawData = try BSONEncoder().encode(object).toData()
} catch let err {
print("<BSONConverter> Error occured. Reason:\n\(err)")
throw err
}

// now flush the data to a file
try rawBSONData.write(to: _rawFilePath)
// flush the data to a file
try rawData.write(to: _rawFilePath)
}

/// Writes given array of objects to a local system storage
func write<T: Codable>(objects: [T]) throws {
var rawData: Data
let wrappedObject = WrappedObject<T>.init(arrayObject: objects)

do {
rawData = try BSONEncoder().encode(wrappedObject).toData()
} catch let err {
print("<BSONConverter> Error occured. Reason:\n\(err)")
throw err
}

// flush the data to a file
try rawData.write(to: _rawFilePath)
}

/// Reads object from compartment data storage and returns Foundation counterpart
Expand All @@ -33,4 +65,13 @@ final class BSONConverter {
let realObject = try BSONDecoder().decode(T.self, from: rawBSONData)
return realObject
}

/// Reads array of objects from compartment data storage and returns Foundation counterpart
func read<T: Codable>() throws -> [T] {
// get raw data from the storage
let rawBSONData = try Data(contentsOf: _rawFilePath)

let wrappedObject = try BSONDecoder().decode(WrappedObject<T>.self, from: rawBSONData)
return wrappedObject.array
}
}
39 changes: 36 additions & 3 deletions Sources/Fridge/Freezer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ import Foundation
// MARK: -
final internal class Freezer {
/// Freezes an object into Fridge persistant storage. Any new object will overwrite previously stored object
func freeze<T: Encodable>(object: T, identifier: String) throws { //async ?
func freeze<T: Encodable>(object: T, identifier: String) throws {
do {
// 1. initialize fridge compartment for given key
let comp = FridgeCompartment(key: identifier)

// 2. initialize Streamer with produced compartment
// 2. initialize BSON writer from given compartment
let writer = BSONConverter(compartment: comp)

// 3. perform stream write of given object
Expand All @@ -46,13 +46,29 @@ final internal class Freezer {
}
}

/// Freezes an object into Fridge persistant storage. Any new object will overwrite previously stored object
func freeze<T: Codable>(objects: [T], identifier: String) throws {
do {
// 1. initialize fridge compartment for given key
let comp = FridgeCompartment(key: identifier)

// 2. initialize BSON writer from given compartment
let writer = BSONConverter(compartment: comp)

// 3. perform stream write of array of objects
try writer.write(objects: objects)
} catch {
throw FreezingErrors.dataStoringError
}
}

/// Unfreezes an object from Fridge persistant storage.
func unfreeze<T: Decodable>(identifier: String) throws -> T {
do {
// 1. setup compartment
let comp = FridgeCompartment(key: identifier)

// 2. initialize Streamer with created compartment
// 2. initialize BSON reader from given compartment
let reader = BSONConverter(compartment: comp)

// 3. perform stream read
Expand All @@ -63,6 +79,23 @@ final internal class Freezer {
}
}

/// Unfreezes an array of objects from Fridge persistant storage.
func unfreeze<T: Codable>(identifier: String) throws -> [T] {
do {
// 1. setup compartment
let comp = FridgeCompartment(key: identifier)

// 2. initialize BSON reader from given compartment
let reader = BSONConverter(compartment: comp)

// 3. perform stream read
let storedObject: [T] = try reader.read()
return storedObject
} catch {
throw FreezingErrors.dataReadingError
}
}

/// Returns `true` if a given object has been frozen previously.
func isAlreadyFrozen(identifier: String) -> Bool {
FridgeCompartment(key: identifier).alreadyExist
Expand Down
19 changes: 10 additions & 9 deletions Sources/Fridge/FridgeCompartment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,25 +40,26 @@ internal struct FridgeCompartment {
}

/// Returns `URL` based file path of this compartment
var filePath: URL {
//get documents directory
var objectPath: URL {
// TODO: Alter between DocumentsDirectory and CacheDirectory later
guard let documentDirectoryURL = _fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
fatalError("<Fridge.Storage> Unable to compute DocumentsDirectory path")
}

let finalName = "\(key)\(BLOB_EXTENSION)"
let finalURL = documentDirectoryURL.appendingPathComponent(finalName)
return finalURL
return documentDirectoryURL.appendingPathComponent(storageName)
}

/// Returns the compartment name formatted by key and static identifier
private var storageName: String {
key + BLOB_EXTENSION
}

/// Returns `true` if raw data already exists at this compartment, `false` otherwise
var alreadyExist: Bool {
_fileManager.fileExists(atPath: filePath.path)
_fileManager.fileExists(atPath: objectPath.path)
}


/// Removes compartment from persistent storage
func remove() {
try? _fileManager.removeItem(atPath: filePath.path)
try? _fileManager.removeItem(atPath: objectPath.path)
}
}
73 changes: 51 additions & 22 deletions Tests/FridgeTests/FreezerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,42 +48,49 @@ fileprivate struct FridgeTestObject: Codable {
string_field = "Some f🧊ncy string"
int_field = Int.max
dict_field = InnerTestObject()
arr_field = [100, 200, 300, Int.random(in: Int.min...Int.max)]
data_field = Data(repeating: 0xAE, count: 0xABCDEF)
arr_field = [0xABCDEF, 0xCAB0CAB, 0xFADE, 0xEFCAD]
data_field = Data(repeating: 0xAE, count: 0xE1FE1)
url_field = URL(fileURLWithPath: "someFilePathOfAMockObject")
}
}

fileprivate struct InnerTestObject: Codable {
var field1: Float = 1_234_567.890_001
var field2: Double = Double.pi
var field3: Set = Set([1,2,3])
var field4: String? = nil
var field1: String? = nil
var field2: Float = 1_234_567.890_001
var field3: Double = Double.pi
var field4: Date = Date.init()
var field5: Bool = !false

var field6: Set = Set([1,2,3])
var field7: Array<Int64> = Array.init()
}

extension FridgeTestObject: Equatable {
static func ==(lhs: FridgeTestObject, rhs: FridgeTestObject) -> Bool {
let equality =
(lhs.string_field == rhs.string_field) &&
(lhs.int_field == rhs.int_field) &&
(lhs.arr_field == rhs.arr_field)
(lhs.arr_field == rhs.arr_field) &&
(lhs.data_field == rhs.data_field) &&
(lhs.url_field == rhs.url_field)

return equality
}
}

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

/// Tests weather an object can be saved without throwing error
func testBasicFreezing(){
let testData1 = FridgeTestObject()
XCTAssertNoThrow(try freezer.freeze(object: testData1, identifier: FridgeTestObject.IDENTIFIER))
func testObjectFreezing(){
let testData = FridgeTestObject()
XCTAssertNoThrow(try freezer.freeze(object: testData, identifier: FridgeTestObject.IDENTIFIER))
}

/// Tests weather an object can be loaded without throwing error
func testBasicUnfreezing() {
func testObjectUnfreezing() {
//freeze an object first
let frozenObject = FridgeTestObject()

Expand All @@ -106,15 +113,37 @@ final class FreezerTests: XCTestCase {
}

/// Tests if array can be stored
// func testFreezingAnArray() throws {
// XCTAssertFalse(freezer.isAlreadyFrozen(identifier: "array.test"))
//
// let freezingArray = [1,2,3,4,5,6,7,8]
// let objectArray: Array<FridgeTestObject> = [FridgeTestObject(), FridgeTestObject()]
// try freezer.freeze(object: freezingArray, identifier: "array.test")
// try freezer.freeze(object: objectArray, identifier: "array-object.test")
//
// let unpackedObject: [Int] = try freezer.unfreeze(identifier: "array.test")
// XCTAssert(unpackedObject[0] == freezingArray[0])
// }
func testArrayFreezing() throws {
XCTAssertFalse(freezer.isAlreadyFrozen(identifier: "array.test"))

let foundationArray = [1,2,3,4,5,6,7,8]
let customStructArray: Array<FridgeTestObject> = [FridgeTestObject(), FridgeTestObject()]
XCTAssertNoThrow(try freezer.freeze(objects: foundationArray, identifier: "foundation.array"))
XCTAssertNoThrow(try freezer.freeze(objects: customStructArray, identifier: "array-object.test"))

// make sure it actually throws when passed incorrectly
XCTAssertThrowsError(try freezer.freeze(object: foundationArray, identifier: "wrong.method.array"))
XCTAssertThrowsError(try freezer.freeze(object: customStructArray, identifier: "wrong.method.custom.array"))
}

/// Tests if array can be read from the storage
func testArrayUnFreezing() throws {
let expectedFoundationArray = [1,2,3,4,5,6,7,8]
let expectedStructArray: Array<FridgeTestObject> = [FridgeTestObject(), FridgeTestObject()]
var failureMessage: String

do {
// check foundation
failureMessage = "Foundation array issue"
let unfrozenFoundation: Array<Int> = try freezer.unfreeze(identifier: "foundation.array")
XCTAssert(unfrozenFoundation == expectedFoundationArray)

failureMessage = "Array of custom struct issue"
let unfrozenCustomArray: Array<FridgeTestObject> = try freezer.unfreeze(identifier: "array-object.test")
XCTAssert(unfrozenCustomArray[0] == expectedStructArray[0])
// XCTAssert(unfrozenCustomArray[0].dict_field == expectedStructArray[0].dict_field)
} catch {
XCTFail(failureMessage)
}
}
}

0 comments on commit 8a84b5f

Please sign in to comment.