-
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.
[Enhancement] Lossy codable list property wrapper (#19)
This PR contains the work done to implement the `LossyCodableList` struct and property wrapper, which is used in lossy decoding/encoding processes. Co-authored-by: Javier Cicchelli <javier@rock-n-code.com> Reviewed-on: https://repo.rock-n-code.com/rock-n-code/swift-libs/pulls/19
- Loading branch information
Showing
9 changed files
with
371 additions
and
0 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,89 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftLibs open source project | ||
// | ||
// Copyright (c) 2023 Röck+Cöde VoF. and the SwiftLibs project authors | ||
// Licensed under the EUPL 1.2 or later. | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftLibs project authors | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
@propertyWrapper | ||
/// This property wrapper provides a generic type that acts as a thin wrapper around an array of `Elements` instances to allow a lossy decoding and or encoding process. | ||
public struct LossyCodableList<Element> { | ||
|
||
// MARK: Properties | ||
|
||
private var elements: [Element] | ||
|
||
/// Provides read/write access to the array of `Element` instances. | ||
public var wrappedValue: [Element] { | ||
get { elements } | ||
set { elements = newValue } | ||
} | ||
|
||
// MARK: Initialisers | ||
|
||
/// Initialises this property wrapper. | ||
public init() { | ||
self.elements = [] | ||
} | ||
|
||
} | ||
|
||
// MARK: - Decodable | ||
|
||
extension LossyCodableList: Decodable where Element: Decodable { | ||
|
||
// MARK: Initialisers | ||
|
||
/// Initialises the struct with a lossy decoder. | ||
/// - Parameter decoder: The decoder to use for the lossy decoder process. | ||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.singleValueContainer() | ||
let wrappers = try container.decode([ElementWrapper].self) | ||
|
||
self.elements = wrappers.compactMap(\.element) | ||
} | ||
|
||
} | ||
|
||
// MARK: - Encodable | ||
|
||
extension LossyCodableList: Encodable where Element: Encodable { | ||
|
||
// MARK: Functions | ||
|
||
/// Encodes an array of `Element` instances loosely. | ||
/// - Parameter encoder: The encoder to use for the lossy encoding process. | ||
public func encode(to encoder: Encoder) throws { | ||
var container = encoder.unkeyedContainer() | ||
|
||
elements.forEach { element in | ||
try? container.encode(element) | ||
} | ||
} | ||
|
||
} | ||
|
||
// MARK: - Structs | ||
|
||
private extension LossyCodableList where Element: Decodable { | ||
struct ElementWrapper: Decodable { | ||
|
||
// MARK: Properties | ||
|
||
var element: Element? | ||
|
||
// MARK: Initialisers | ||
|
||
init(from decoder: Decoder) throws { | ||
let container = try decoder.singleValueContainer() | ||
|
||
self.element = try? container.decode(Element.self) | ||
} | ||
|
||
} | ||
} |
75 changes: 75 additions & 0 deletions
75
Tests/Core/Cases/Property Wrappers/LossyCodableList+DecodableTests.swift
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,75 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftLibs open source project | ||
// | ||
// Copyright (c) 2023 Röck+Cöde VoF. and the SwiftLibs project authors | ||
// Licensed under the EUPL 1.2 or later. | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftLibs project authors | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import Core | ||
import Foundation | ||
import XCTest | ||
|
||
final class LossyCodableList_DecodableTests: XCTestCase { | ||
|
||
// MARK: Properties | ||
|
||
private let decoder = JSONDecoder() | ||
|
||
private var dataToDecode: Data! | ||
private var decodedList: TestCodableList! | ||
|
||
// MARK: Tests | ||
|
||
func test_decode_whenAllDataIsComplete() throws { | ||
// GIVEN | ||
dataToDecode = .Seed.itemsWithAllKeysHavingIntValues | ||
|
||
// WHEN | ||
decodedList = try decoder.decode(TestCodableList.self, from: dataToDecode) | ||
|
||
// THEN | ||
XCTAssertNotNil(decodedList) | ||
XCTAssertTrue(decodedList.items.isNotEmpty) | ||
XCTAssertEqual(decodedList.items, [ | ||
.init(key: "One", value: 1), | ||
.init(key: "Two", value: 2), | ||
.init(key: "Three", value: 3), | ||
.init(key: "Four", value: 4) | ||
]) | ||
} | ||
|
||
func test_decode_whenSomeDataHasNil() throws { | ||
// GIVEN | ||
dataToDecode = .Seed.itemsWithSomeKeysAndValuesAreNil | ||
|
||
// WHEN | ||
decodedList = try decoder.decode(TestCodableList.self, from: dataToDecode) | ||
|
||
// THEN | ||
XCTAssertNotNil(decodedList) | ||
XCTAssertTrue(decodedList.items.isNotEmpty) | ||
XCTAssertEqual(decodedList.items, [ | ||
.init(key: "One", value: 1), | ||
.init(key: "Three", value: 3) | ||
]) | ||
} | ||
|
||
func test_decode_whenAllDataHasNil() throws { | ||
// GIVEN | ||
dataToDecode = .Seed.itemsWithAllKeysAndValuesAreNil | ||
|
||
// WHEN | ||
decodedList = try decoder.decode(TestCodableList.self, from: dataToDecode) | ||
|
||
// THEN | ||
XCTAssertNotNil(decodedList) | ||
XCTAssertTrue(decodedList.items.isEmpty) | ||
XCTAssertEqual(decodedList.items, []) | ||
} | ||
|
||
} |
80 changes: 80 additions & 0 deletions
80
Tests/Core/Cases/Property Wrappers/LossyCodableList+EncodableTests.swift
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,80 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftLibs open source project | ||
// | ||
// Copyright (c) 2023 Röck+Cöde VoF. and the SwiftLibs project authors | ||
// Licensed under the EUPL 1.2 or later. | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftLibs project authors | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import Foundation | ||
import XCTest | ||
|
||
@testable import Core | ||
|
||
final class LossyCodableList_EncodableTests: XCTestCase { | ||
|
||
// MARK: Properties | ||
|
||
private let decoder = JSONDecoder() | ||
private let encoder = JSONEncoder() | ||
|
||
private var dataToDecode: Data! | ||
private var encodedData: Data! | ||
private var list: TestCodableList! | ||
|
||
// MARK: Setup | ||
|
||
override func setUpWithError() throws { | ||
// This setting is used to guarantee that the properties of the model are being generated by sorted keys order. | ||
encoder.outputFormatting = .sortedKeys | ||
} | ||
|
||
// MARK: Tests | ||
|
||
func test_encode_whenAllKeysHaveIntValues() throws { | ||
// GIVEN | ||
dataToDecode = .Seed.itemsWithAllKeysHavingIntValues | ||
list = try decoder.decode(TestCodableList.self, from: dataToDecode) | ||
|
||
// WHEN | ||
encodedData = try encoder.encode(list) | ||
|
||
// THEN | ||
XCTAssertNotNil(encodedData) | ||
XCTAssertTrue(encodedData.isNotEmpty) | ||
XCTAssertEqual(encodedData, .Result.allItemsNotFilteredOut) | ||
} | ||
|
||
func test_encode_whenSomeKeysAndValuesAreNil() throws { | ||
// GIVEN | ||
dataToDecode = .Seed.itemsWithSomeKeysAndValuesAreNil | ||
list = try decoder.decode(TestCodableList.self, from: dataToDecode) | ||
|
||
// WHEN | ||
encodedData = try encoder.encode(list) | ||
|
||
// THEN | ||
XCTAssertNotNil(encodedData) | ||
XCTAssertTrue(encodedData.isNotEmpty) | ||
XCTAssertEqual(encodedData, .Result.someItemsFilteredOut) | ||
} | ||
|
||
func test_encode_whenAllKeysAndValuesAreNil() throws { | ||
// GIVEN | ||
dataToDecode = .Seed.itemsWithAllKeysAndValuesAreNil | ||
list = try decoder.decode(TestCodableList.self, from: dataToDecode) | ||
|
||
// WHEN | ||
encodedData = try encoder.encode(list) | ||
|
||
// THEN | ||
XCTAssertNotNil(encodedData) | ||
XCTAssertTrue(encodedData.isNotEmpty) | ||
XCTAssertEqual(encodedData, .Result.allItemsFilteredOut) | ||
} | ||
|
||
} |
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,21 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftLibs open source project | ||
// | ||
// Copyright (c) 2023 Röck+Cöde VoF. and the SwiftLibs project authors | ||
// Licensed under the EUPL 1.2 or later. | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftLibs project authors | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import Foundation | ||
|
||
extension Data { | ||
enum Result { | ||
static let allItemsNotFilteredOut = String.Result.allItemsNotFilteredOut.data(using: .utf8) | ||
static let someItemsFilteredOut = String.Result.someItemsFilteredOut.data(using: .utf8) | ||
static let allItemsFilteredOut = String.Result.allItemsFilteredOut.data(using: .utf8) | ||
} | ||
} |
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,21 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftLibs open source project | ||
// | ||
// Copyright (c) 2023 Röck+Cöde VoF. and the SwiftLibs project authors | ||
// Licensed under the EUPL 1.2 or later. | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftLibs project authors | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import Foundation | ||
|
||
extension Data { | ||
enum Seed { | ||
static let itemsWithAllKeysHavingIntValues = String.Seed.itemsWithAllKeysHavingIntValues.data(using: .utf8) | ||
static let itemsWithSomeKeysAndValuesAreNil = String.Seed.itemsWithSomeKeysAndValuesAreNil.data(using: .utf8) | ||
static let itemsWithAllKeysAndValuesAreNil = String.Seed.itemsWithAllKeysAndValuesAreNil.data(using: .utf8) | ||
} | ||
} |
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,19 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftLibs open source project | ||
// | ||
// Copyright (c) 2023 Röck+Cöde VoF. and the SwiftLibs project authors | ||
// Licensed under the EUPL 1.2 or later. | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftLibs project authors | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
extension String { | ||
enum Result { | ||
static let allItemsNotFilteredOut = "{\"items\":[{\"key\":\"One\",\"value\":1},{\"key\":\"Two\",\"value\":2},{\"key\":\"Three\",\"value\":3},{\"key\":\"Four\",\"value\":4}]}" | ||
static let someItemsFilteredOut = "{\"items\":[{\"key\":\"One\",\"value\":1},{\"key\":\"Three\",\"value\":3}]}" | ||
static let allItemsFilteredOut = "{\"items\":[]}" | ||
} | ||
} |
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,19 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftLibs open source project | ||
// | ||
// Copyright (c) 2023 Röck+Cöde VoF. and the SwiftLibs project authors | ||
// Licensed under the EUPL 1.2 or later. | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftLibs project authors | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
extension String { | ||
enum Seed { | ||
static let itemsWithAllKeysHavingIntValues = "{\"items\":[{\"key\":\"One\",\"value\":1},{\"key\":\"Two\",\"value\":2},{\"key\":\"Three\",\"value\":3},{\"key\":\"Four\",\"value\":4}]}" | ||
static let itemsWithSomeKeysAndValuesAreNil = "{\"items\":[{\"key\":\"One\",\"value\":1},{\"key\":\"Two\",\"value\":null},{\"key\":\"Three\",\"value\":3},{\"key\":null,\"value\":4}]}" | ||
static let itemsWithAllKeysAndValuesAreNil = "{\"items\":[{\"key\":\"One\",\"value\":null},{\"key\":null,\"value\":2},{\"key\":\"Three\",\"value\":null},{\"key\":null,\"value\":4}]}" | ||
} | ||
} |
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,30 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftLibs open source project | ||
// | ||
// Copyright (c) 2023 Röck+Cöde VoF. and the SwiftLibs project authors | ||
// Licensed under the EUPL 1.2 or later. | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftLibs project authors | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
struct TestCodable: Codable, Equatable { | ||
let key: String | ||
let value: Int | ||
} | ||
|
||
// MARK: - Initialisers | ||
|
||
extension TestCodable { | ||
init?( | ||
key: String? = nil, | ||
value: Int? = nil | ||
) { | ||
guard let key, let value else { return nil } | ||
|
||
self.key = key | ||
self.value = value | ||
} | ||
} |
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,17 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftLibs open source project | ||
// | ||
// Copyright (c) 2023 Röck+Cöde VoF. and the SwiftLibs project authors | ||
// Licensed under the EUPL 1.2 or later. | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftLibs project authors | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import Core | ||
|
||
struct TestCodableList: Codable { | ||
@LossyCodableList var items: [TestCodable] | ||
} |