DataLireCoder swift package
This commit is contained in:
102
Sources/DLCDecoder/Classes/KeyedContainer.swift
Normal file
102
Sources/DLCDecoder/Classes/KeyedContainer.swift
Normal file
@@ -0,0 +1,102 @@
|
||||
import Foundation
|
||||
import DataLiteCore
|
||||
|
||||
final class KeyedContainer<Decoder: RowDecoder & KeyCheckingDecoder, Key: CodingKey>: Container, KeyedDecodingContainerProtocol {
|
||||
// MARK: - Properties
|
||||
|
||||
let decoder: Decoder
|
||||
let codingPath: [any CodingKey]
|
||||
let allKeys: [Key]
|
||||
|
||||
// MARK: - Inits
|
||||
|
||||
init(
|
||||
decoder: Decoder,
|
||||
codingPath: [any CodingKey],
|
||||
allKeys: [Key]
|
||||
) {
|
||||
self.decoder = decoder
|
||||
self.codingPath = codingPath
|
||||
self.allKeys = allKeys
|
||||
}
|
||||
|
||||
// MARK: - Container Methods
|
||||
|
||||
func contains(_ key: Key) -> Bool {
|
||||
decoder.contains(key)
|
||||
}
|
||||
|
||||
func decodeNil(forKey key: Key) throws -> Bool {
|
||||
try decoder.decodeNil(for: key)
|
||||
}
|
||||
|
||||
func decode<T: Decodable>(_ type: T.Type, forKey key: Key) throws -> T {
|
||||
switch type {
|
||||
case is Date.Type:
|
||||
try decoder.decodeDate(for: key) as! T
|
||||
case let type as SQLiteRawRepresentable.Type:
|
||||
try decoder.decode(type, for: key) as! T
|
||||
default:
|
||||
try T(from: decoder.decoder(for: key))
|
||||
}
|
||||
}
|
||||
|
||||
func nestedContainer<NestedKey: CodingKey>(
|
||||
keyedBy type: NestedKey.Type,
|
||||
forKey key: Key
|
||||
) throws -> KeyedDecodingContainer<NestedKey> {
|
||||
let info = """
|
||||
Attempted to decode a nested keyed container for key '\(key.stringValue)',
|
||||
but the value cannot be represented as a keyed container.
|
||||
"""
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath + [key],
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.typeMismatch(
|
||||
KeyedDecodingContainer<NestedKey>.self,
|
||||
context
|
||||
)
|
||||
}
|
||||
|
||||
func nestedUnkeyedContainer(
|
||||
forKey key: Key
|
||||
) throws -> any UnkeyedDecodingContainer {
|
||||
let info = """
|
||||
Attempted to decode a nested unkeyed container for key '\(key.stringValue)',
|
||||
but the value cannot be represented as an unkeyed container.
|
||||
"""
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath + [key],
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.typeMismatch(
|
||||
UnkeyedDecodingContainer.self,
|
||||
context
|
||||
)
|
||||
}
|
||||
|
||||
func superDecoder() throws -> any Swift.Decoder {
|
||||
let info = """
|
||||
Attempted to get a superDecoder,
|
||||
but SQLiteRowDecoder does not support superDecoding.
|
||||
"""
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath,
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.typeMismatch(Swift.Decoder.self, context)
|
||||
}
|
||||
|
||||
func superDecoder(forKey key: Key) throws -> any Swift.Decoder {
|
||||
let info = """
|
||||
Attempted to get a superDecoder for key '\(key.stringValue)',
|
||||
but SQLiteRowDecoder does not support nested structures.
|
||||
"""
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath + [key],
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.typeMismatch(Swift.Decoder.self, context)
|
||||
}
|
||||
}
|
||||
104
Sources/DLCDecoder/Classes/MultiRowDecoder.swift
Normal file
104
Sources/DLCDecoder/Classes/MultiRowDecoder.swift
Normal file
@@ -0,0 +1,104 @@
|
||||
import Foundation
|
||||
import DataLiteCore
|
||||
|
||||
public final class MultiRowDecoder: RowDecoder {
|
||||
// MARK: - Properties
|
||||
|
||||
public let dateDecoder: any DateDecoder
|
||||
public let sqliteData: [SQLiteRow]
|
||||
public let codingPath: [any CodingKey]
|
||||
public let userInfo: [CodingUserInfoKey: Any]
|
||||
|
||||
public var count: Int? { sqliteData.count }
|
||||
|
||||
// MARK: Inits
|
||||
|
||||
public init(
|
||||
dateDecoder: any DateDecoder,
|
||||
sqliteData: [SQLiteRow],
|
||||
codingPath: [any CodingKey],
|
||||
userInfo: [CodingUserInfoKey: Any]
|
||||
) {
|
||||
self.dateDecoder = dateDecoder
|
||||
self.sqliteData = sqliteData
|
||||
self.codingPath = codingPath
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
public func decodeNil(for key: any CodingKey) throws -> Bool {
|
||||
let info = "Attempted to decode nil, but it's not supported for an array of rows."
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath + [key],
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.dataCorrupted(context)
|
||||
}
|
||||
|
||||
public func decodeDate(for key: any CodingKey) throws -> Date {
|
||||
return try decode(Date.self, for: key)
|
||||
}
|
||||
|
||||
public func decode<T: SQLiteRawRepresentable>(
|
||||
_ type: T.Type,
|
||||
for key: any CodingKey
|
||||
) throws -> T {
|
||||
let info = "Expected a type of \(type), but found an array of rows."
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath + [key],
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.typeMismatch(type, context)
|
||||
}
|
||||
|
||||
public func decoder(for key: any CodingKey) throws -> any Decoder {
|
||||
guard let index = key.intValue else {
|
||||
let info = "Expected an integer key, but found a non-integer key."
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath + [key],
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.keyNotFound(key, context)
|
||||
}
|
||||
return SingleRowDecoder(
|
||||
dateDecoder: dateDecoder,
|
||||
sqliteData: sqliteData[index],
|
||||
codingPath: codingPath + [key],
|
||||
userInfo: userInfo
|
||||
)
|
||||
}
|
||||
|
||||
public func container<Key: CodingKey>(
|
||||
keyedBy type: Key.Type
|
||||
) throws -> KeyedDecodingContainer<Key> {
|
||||
let info = "Expected a keyed container, but found an array of rows."
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath,
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.typeMismatch(
|
||||
KeyedDecodingContainer<Key>.self,
|
||||
context
|
||||
)
|
||||
}
|
||||
|
||||
public func unkeyedContainer() throws -> any UnkeyedDecodingContainer {
|
||||
UnkeyedContainer(
|
||||
decoder: self,
|
||||
codingPath: codingPath
|
||||
)
|
||||
}
|
||||
|
||||
public func singleValueContainer() throws -> any SingleValueDecodingContainer {
|
||||
let info = "Expected a single value container, but found an array of rows."
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath,
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.typeMismatch(
|
||||
SingleValueDecodingContainer.self,
|
||||
context
|
||||
)
|
||||
}
|
||||
}
|
||||
135
Sources/DLCDecoder/Classes/SingleRowDecoder.swift
Normal file
135
Sources/DLCDecoder/Classes/SingleRowDecoder.swift
Normal file
@@ -0,0 +1,135 @@
|
||||
import Foundation
|
||||
import DataLiteCore
|
||||
|
||||
private import DLCCommon
|
||||
|
||||
public final class SingleRowDecoder: RowDecoder, KeyCheckingDecoder {
|
||||
// MARK: - Properties
|
||||
|
||||
public let dateDecoder: any DateDecoder
|
||||
public let sqliteData: SQLiteRow
|
||||
public let codingPath: [any CodingKey]
|
||||
public let userInfo: [CodingUserInfoKey: Any]
|
||||
|
||||
public var count: Int? { sqliteData.count }
|
||||
|
||||
// MARK: Inits
|
||||
|
||||
public init(
|
||||
dateDecoder: any DateDecoder,
|
||||
sqliteData: SQLiteRow,
|
||||
codingPath: [any CodingKey],
|
||||
userInfo: [CodingUserInfoKey: Any]
|
||||
) {
|
||||
self.dateDecoder = dateDecoder
|
||||
self.sqliteData = sqliteData
|
||||
self.codingPath = codingPath
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
|
||||
// MARK: - Methods
|
||||
|
||||
public func contains(_ key: any CodingKey) -> Bool {
|
||||
sqliteData.contains(key)
|
||||
}
|
||||
|
||||
public func decodeNil(for key: any CodingKey) throws -> Bool {
|
||||
guard sqliteData.contains(key) else {
|
||||
let info = "No value associated with key \(key)."
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath + [key],
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.keyNotFound(key, context)
|
||||
}
|
||||
return sqliteData[key] == .null
|
||||
}
|
||||
|
||||
public func decodeDate(for key: any CodingKey) throws -> Date {
|
||||
try dateDecoder.decode(from: self, for: key)
|
||||
}
|
||||
|
||||
public func decode<T: SQLiteRawRepresentable>(
|
||||
_ type: T.Type,
|
||||
for key: any CodingKey
|
||||
) throws -> T {
|
||||
guard let value = sqliteData[key] else {
|
||||
let info = "No value associated with key \(key)."
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath + [key],
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.keyNotFound(key, context)
|
||||
}
|
||||
|
||||
guard value != .null else {
|
||||
let info = "Cannot get value of type \(T.self), found null value instead."
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath + [key],
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.valueNotFound(type, context)
|
||||
}
|
||||
|
||||
guard let result = T(value) else {
|
||||
let info = "Expected to decode \(T.self) but found an \(value) instead."
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath + [key],
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.typeMismatch(type, context)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
public func decoder(for key: any CodingKey) throws -> any Decoder {
|
||||
guard let data = sqliteData[key] else {
|
||||
let info = "No value associated with key \(key)."
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath + [key],
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.keyNotFound(key, context)
|
||||
}
|
||||
return SingleValueDecoder(
|
||||
dateDecoder: dateDecoder,
|
||||
sqliteData: data,
|
||||
codingPath: codingPath + [key],
|
||||
userInfo: userInfo
|
||||
)
|
||||
}
|
||||
|
||||
public func container<Key: CodingKey>(
|
||||
keyedBy type: Key.Type
|
||||
) throws -> KeyedDecodingContainer<Key> {
|
||||
let allKeys = sqliteData.compactMap { (column, _) in
|
||||
Key(stringValue: column)
|
||||
}
|
||||
let container = KeyedContainer(
|
||||
decoder: self,
|
||||
codingPath: codingPath,
|
||||
allKeys: allKeys
|
||||
)
|
||||
return KeyedDecodingContainer(container)
|
||||
}
|
||||
|
||||
public func unkeyedContainer() throws -> any UnkeyedDecodingContainer {
|
||||
UnkeyedContainer(
|
||||
decoder: self,
|
||||
codingPath: codingPath
|
||||
)
|
||||
}
|
||||
|
||||
public func singleValueContainer() throws -> any SingleValueDecodingContainer {
|
||||
let info = "Expected a single value container, but found a row value."
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath,
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.typeMismatch(
|
||||
SingleValueDecodingContainer.self,
|
||||
context
|
||||
)
|
||||
}
|
||||
}
|
||||
36
Sources/DLCDecoder/Classes/SingleValueContainer.swift
Normal file
36
Sources/DLCDecoder/Classes/SingleValueContainer.swift
Normal file
@@ -0,0 +1,36 @@
|
||||
import Foundation
|
||||
import DataLiteCore
|
||||
|
||||
final class SingleValueContainer<Decoder: ValueDecoder>: Container, SingleValueDecodingContainer {
|
||||
// MARK: - Properties
|
||||
|
||||
let decoder: Decoder
|
||||
let codingPath: [any CodingKey]
|
||||
|
||||
// MARK: - Inits
|
||||
|
||||
init(
|
||||
decoder: Decoder,
|
||||
codingPath: [any CodingKey]
|
||||
) {
|
||||
self.decoder = decoder
|
||||
self.codingPath = codingPath
|
||||
}
|
||||
|
||||
// MARK: - Container Methods
|
||||
|
||||
func decodeNil() -> Bool {
|
||||
decoder.decodeNil()
|
||||
}
|
||||
|
||||
func decode<T: Decodable>(_ type: T.Type) throws -> T {
|
||||
switch type {
|
||||
case is Date.Type:
|
||||
try decoder.decodeDate() as! T
|
||||
case let type as SQLiteRawRepresentable.Type:
|
||||
try decoder.decode(type) as! T
|
||||
default:
|
||||
try T(from: decoder)
|
||||
}
|
||||
}
|
||||
}
|
||||
87
Sources/DLCDecoder/Classes/SingleValueDecoder.swift
Normal file
87
Sources/DLCDecoder/Classes/SingleValueDecoder.swift
Normal file
@@ -0,0 +1,87 @@
|
||||
import Foundation
|
||||
import DataLiteCore
|
||||
|
||||
final class SingleValueDecoder: ValueDecoder {
|
||||
// MARK: - Properties
|
||||
|
||||
let dateDecoder: any DateDecoder
|
||||
let sqliteData: SQLiteRawValue
|
||||
let codingPath: [any CodingKey]
|
||||
let userInfo: [CodingUserInfoKey: Any]
|
||||
|
||||
// MARK: - Inits
|
||||
|
||||
init(
|
||||
dateDecoder: any DateDecoder,
|
||||
sqliteData: SQLiteRawValue,
|
||||
codingPath: [any CodingKey],
|
||||
userInfo: [CodingUserInfoKey: Any]
|
||||
) {
|
||||
self.dateDecoder = dateDecoder
|
||||
self.sqliteData = sqliteData
|
||||
self.codingPath = codingPath
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
|
||||
// MARK: - Methods
|
||||
|
||||
func decodeNil() -> Bool {
|
||||
sqliteData == .null
|
||||
}
|
||||
|
||||
func decodeDate() throws -> Date {
|
||||
try dateDecoder.decode(from: self)
|
||||
}
|
||||
|
||||
func decode<T: SQLiteRawRepresentable>(_ type: T.Type) throws -> T {
|
||||
guard sqliteData != .null else {
|
||||
let info = "Cannot get value of type \(T.self), found null value instead."
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath,
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.valueNotFound(type, context)
|
||||
}
|
||||
|
||||
guard let result = type.init(sqliteData) else {
|
||||
let info = "Expected to decode \(T.self) but found an \(sqliteData) instead."
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath,
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.typeMismatch(type, context)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func container<Key: CodingKey>(
|
||||
keyedBy type: Key.Type
|
||||
) throws -> KeyedDecodingContainer<Key> {
|
||||
let info = "Expected a keyed container, but found a single value."
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath,
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.typeMismatch(
|
||||
KeyedDecodingContainer<Key>.self,
|
||||
context
|
||||
)
|
||||
}
|
||||
|
||||
func unkeyedContainer() throws -> any UnkeyedDecodingContainer {
|
||||
let info = "Expected a unkeyed container, but found a single value."
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath,
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.typeMismatch(
|
||||
UnkeyedDecodingContainer.self,
|
||||
context
|
||||
)
|
||||
}
|
||||
|
||||
func singleValueContainer() throws -> any SingleValueDecodingContainer {
|
||||
SingleValueContainer(decoder: self, codingPath: codingPath)
|
||||
}
|
||||
}
|
||||
121
Sources/DLCDecoder/Classes/UnkeyedContainer.swift
Normal file
121
Sources/DLCDecoder/Classes/UnkeyedContainer.swift
Normal file
@@ -0,0 +1,121 @@
|
||||
import Foundation
|
||||
import DataLiteCore
|
||||
|
||||
private import DLCCommon
|
||||
|
||||
final class UnkeyedContainer<Decoder: RowDecoder>: Container, UnkeyedDecodingContainer {
|
||||
// MARK: - Properties
|
||||
|
||||
let decoder: Decoder
|
||||
let codingPath: [any CodingKey]
|
||||
|
||||
var count: Int? {
|
||||
decoder.count
|
||||
}
|
||||
|
||||
var isAtEnd: Bool {
|
||||
currentIndex >= count ?? 0
|
||||
}
|
||||
|
||||
private(set) var currentIndex: Int = 0
|
||||
|
||||
private var currentKey: CodingKey {
|
||||
RowCodingKey(intValue: currentIndex)
|
||||
}
|
||||
|
||||
// MARK: - Inits
|
||||
|
||||
init(
|
||||
decoder: Decoder,
|
||||
codingPath: [any CodingKey]
|
||||
) {
|
||||
self.decoder = decoder
|
||||
self.codingPath = codingPath
|
||||
}
|
||||
|
||||
// MARK: - Container Methods
|
||||
|
||||
func decodeNil() throws -> Bool {
|
||||
try checkIsAtEnd(Optional<Any>.self)
|
||||
if try decoder.decodeNil(for: currentKey) {
|
||||
currentIndex += 1
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func decode<T: Decodable>(_ type: T.Type) throws -> T {
|
||||
try checkIsAtEnd(type)
|
||||
defer { currentIndex += 1 }
|
||||
|
||||
switch type {
|
||||
case is Date.Type:
|
||||
return try decoder.decodeDate(for: currentKey) as! T
|
||||
case let type as SQLiteRawRepresentable.Type:
|
||||
return try decoder.decode(type, for: currentKey) as! T
|
||||
default:
|
||||
return try T(from: decoder.decoder(for: currentKey))
|
||||
}
|
||||
}
|
||||
|
||||
func nestedContainer<NestedKey: CodingKey>(
|
||||
keyedBy type: NestedKey.Type
|
||||
) throws -> KeyedDecodingContainer<NestedKey> {
|
||||
let info = """
|
||||
Attempted to decode a nested keyed container,
|
||||
but the value cannot be represented as a keyed container.
|
||||
"""
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath + [currentKey],
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.typeMismatch(
|
||||
KeyedDecodingContainer<NestedKey>.self,
|
||||
context
|
||||
)
|
||||
}
|
||||
|
||||
func nestedUnkeyedContainer() throws -> any UnkeyedDecodingContainer {
|
||||
let info = """
|
||||
Attempted to decode a nested unkeyed container,
|
||||
but the value cannot be represented as an unkeyed container.
|
||||
"""
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath + [currentKey],
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.typeMismatch(
|
||||
UnkeyedDecodingContainer.self,
|
||||
context
|
||||
)
|
||||
}
|
||||
|
||||
func superDecoder() throws -> any Swift.Decoder {
|
||||
let info = """
|
||||
Attempted to get a superDecoder,
|
||||
but SQLiteRowDecoder does not support superDecoding.
|
||||
"""
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath + [currentKey],
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.typeMismatch(Swift.Decoder.self, context)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
|
||||
private extension UnkeyedContainer {
|
||||
@inline(__always)
|
||||
func checkIsAtEnd<T>(_ type: T.Type) throws {
|
||||
guard !isAtEnd else {
|
||||
let info = "Unkeyed container is at end."
|
||||
let context = DecodingError.Context(
|
||||
codingPath: codingPath + [currentKey],
|
||||
debugDescription: info
|
||||
)
|
||||
throw DecodingError.valueNotFound(type, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user