Add CustomCodablePreferenceKey

This commit is contained in:
Shadowfacts 2024-04-15 10:50:08 -04:00
parent 70227a7fa1
commit e5c4fceacd
3 changed files with 54 additions and 12 deletions

View File

@ -27,15 +27,24 @@ final class Preference<Key: PreferenceKey>: Codable {
}
init(from decoder: any Decoder) throws {
if let container = try? decoder.singleValueContainer() {
if let keyType = Key.self as? any CustomCodablePreferenceKey.Type {
self.storedValue = try keyType.decode(from: decoder) as! Key.Value?
} else if let container = try? decoder.singleValueContainer() {
self.storedValue = try? container.decode(Key.Value.self)
}
}
func encode(to encoder: any Encoder) throws {
if let storedValue {
var container = encoder.singleValueContainer()
try container.encode(storedValue)
if let keyType = Key.self as? any CustomCodablePreferenceKey.Type {
func encode<K: CustomCodablePreferenceKey>(_: K.Type) throws {
try K.encode(value: storedValue as! K.Value, to: encoder)
}
return try _openExistential(keyType, do: encode)
} else {
var container = encoder.singleValueContainer()
try container.encode(storedValue)
}
}
}

View File

@ -28,3 +28,8 @@ extension MigratablePreferenceKey {
oldValue != defaultValue
}
}
protocol CustomCodablePreferenceKey: PreferenceKey {
static func encode(value: Value, to encoder: any Encoder) throws
static func decode(from decoder: any Decoder) throws -> Value?
}

View File

@ -15,11 +15,11 @@ final class PreferenceStoreTests: XCTestCase {
static let defaultValue = false
}
final class TestStore: Codable, ObservableObject {
private var _test = Preference<TestKey>()
final class TestStore<Key: PreferenceKey>: Codable, ObservableObject {
private var _test = Preference<Key>()
// the acutal subscript expects the enclosingInstance to be a PreferenceStore, so do it manually
var test: Bool {
var test: Key.Value {
get {
Preference.get(enclosingInstance: self, storage: \._test)
}
@ -28,7 +28,7 @@ final class PreferenceStoreTests: XCTestCase {
}
}
var testPublisher: some Publisher<TestKey.Value, Never> {
var testPublisher: some Publisher<Key.Value, Never> {
_test.projectedValue
}
@ -37,7 +37,7 @@ final class PreferenceStoreTests: XCTestCase {
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self._test = try container.decode(Preference<TestKey>.self, forKey: .test)
self._test = try container.decode(Preference<Key>.self, forKey: .test)
}
enum CodingKeys: CodingKey {
@ -52,18 +52,18 @@ final class PreferenceStoreTests: XCTestCase {
func testDecoding() throws {
let decoder = JSONDecoder()
let present = try decoder.decode(PreferenceCoding<TestStore>.self, from: Data("""
let present = try decoder.decode(PreferenceCoding<TestStore<TestKey>>.self, from: Data("""
{"test": true}
""".utf8)).wrapped
XCTAssertEqual(present.test, true)
let absent = try decoder.decode(PreferenceCoding<TestStore>.self, from: Data("""
let absent = try decoder.decode(PreferenceCoding<TestStore<TestKey>>.self, from: Data("""
{}
""".utf8)).wrapped
XCTAssertEqual(absent.test, false)
}
func testEncoding() throws {
let store = TestStore()
let store = TestStore<TestKey>()
let encoder = JSONEncoder()
XCTAssertEqual(String(data: try encoder.encode(PreferenceCoding(wrapped: store)), encoding: .utf8)!, """
{}
@ -83,7 +83,7 @@ final class PreferenceStoreTests: XCTestCase {
let specificPref = expectation(description: "preference publisher")
// initial and on change
specificPref.expectedFulfillmentCount = 2
let store = TestStore()
let store = TestStore<TestKey>()
var cancellables = Set<AnyCancellable>()
store.objectWillChange.sink {
topLevel.fulfill()
@ -97,4 +97,32 @@ final class PreferenceStoreTests: XCTestCase {
wait(for: [topLevel, specificPref])
}
func testCustomCodable() throws {
struct Key: CustomCodablePreferenceKey {
static let defaultValue = 1
static func encode(value: Int, to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(2)
}
static func decode(from decoder: any Decoder) throws -> Int? {
3
}
}
let store = TestStore<Key>()
store.test = 123
let encoder = JSONEncoder()
XCTAssertEqual(String(data: try encoder.encode(PreferenceCoding(wrapped: store)), encoding: .utf8)!, """
{"test":2}
""")
let decoder = JSONDecoder()
let present = try decoder.decode(PreferenceCoding<TestStore<Key>>.self, from: Data("""
{"test":2}
""".utf8)).wrapped
XCTAssertEqual(present.test, 3)
let absent = try decoder.decode(PreferenceCoding<TestStore<Key>>.self, from: Data("""
{}
""".utf8)).wrapped
XCTAssertEqual(absent.test, 1)
}
}