forked from vapor/sqlite-kit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSQLiteDataDecoder.swift
More file actions
132 lines (115 loc) · 5.34 KB
/
Copy pathSQLiteDataDecoder.swift
File metadata and controls
132 lines (115 loc) · 5.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#if hasFeature(Embedded)
import SQLiteNIO
/// Placeholder ``SQLiteDataDecoder`` for the Embedded Swift build.
///
/// `Decodable`-based row decoding (the only thing this type does on normal platforms) is unavailable in
/// Embedded Swift, where ``SQLKit/SQLRow`` exposes no generic `decode(column:as:)`. Typed access on
/// embedded is performed directly against the concrete `SQLiteRow` via `SQLiteDataConvertible`. This
/// stub exists only so the `SQLiteDataDecoder` API surface (e.g. `sql(decoder:)`) remains uniform.
public struct SQLiteDataDecoder: Sendable {
/// Initialize a ``SQLiteDataDecoder``.
public init() {}
}
#else
import Foundation
import SQLiteNIO
@_spi(CodableUtilities) import SQLKit
#if !NativeConcurrency
import NIOFoundationCompat
#endif
/// Translates `SQLiteData` values received from the database into `Decodable` values.
///
/// Types which conform to `SQLiteDataConvertible` are converted directly to the requested type. For other types,
/// an attempt is made to interpret the database value as JSON and decode the type from it.
public struct SQLiteDataDecoder: Sendable {
/// A wrapper to silence `Sendable` warnings for `JSONDecoder` when not on macOS.
struct FakeSendable<T>: @unchecked Sendable { let value: T }
/// The `JSONDecoder` used for decoding values that can't be directly converted.
let json: FakeSendable<JSONDecoder>
/// Initialize a ``SQLiteDataDecoder`` with an unconfigured JSON decoder.
public init() {
self.init(json: .init())
}
/// Initialize a ``SQLiteDataDecoder`` with a JSON decoder.
///
/// - Parameter json: A `JSONDecoder` to use for decoding types that can't be directly converted.
public init(json: JSONDecoder) {
self.json = .init(value: json)
}
/// Convert the given `SQLiteData` into a value of type `T`, if possible.
///
/// - Parameters:
/// - type: The desired result type.
/// - data: The data to decode.
/// - Returns: The decoded value, if successful.
public func decode<T: Decodable>(_ type: T.Type, from data: SQLiteData) throws -> T {
// If `T` can be converted directly, just do so.
if let type = type as? any SQLiteDataConvertible.Type {
guard let value = type.init(sqliteData: data) else {
throw DecodingError.typeMismatch(T.self, .init(
codingPath: [],
debugDescription: "Could not convert SQLite data to \(T.self): \(data)."
))
}
return value as! T
} else {
do {
return try T.init(from: NestedSingleValueUnwrappingDecoder(decoder: self, data: data))
} catch is SQLCodingError {
// Couldn't unwrap it either. Fall back to attempting a JSON decode.
let buf: Data
switch data {
case .text(let str): buf = .init(str.utf8)
#if NativeConcurrency
case .blob(let blob): buf = .init(blob)
#else
case .blob(let blob): buf = .init(buffer: blob, byteTransferStrategy: .noCopy)
#endif
// The remaining cases should never happen, but we implement them anyway just in case.
case .integer(let n): buf = .init(String(n).utf8)
case .float(let n): buf = .init(String(n).utf8)
case .null: buf = .init()
}
return try self.json.value.decode(T.self, from: buf)
}
}
}
/// A trivial decoder for unwrapping types which decode as trivial single-value containers. This allows for
/// correct handling of types such as `Optional` when they do not conform to `SQLiteDataCovnertible`.
private final class NestedSingleValueUnwrappingDecoder: Decoder, SingleValueDecodingContainer {
// See `Decoder.codingPath` and `SingleValueDecodingContainer.codingPath`.
var codingPath: [any CodingKey] { [] }
// See `Decoder.userInfo`.
var userInfo: [CodingUserInfoKey: Any] { [:] }
/// The parent ``SQLiteDataDecoder``.
let dataDecoder: SQLiteDataDecoder
/// The data to decode.
let data: SQLiteData
/// Create a new decoder with an ``SQLiteDataDecoder`` and the data to decode.
init(decoder: SQLiteDataDecoder, data: SQLiteData) {
self.dataDecoder = decoder
self.data = data
}
// See `Decoder.container(keyedBy:)`.
func container<Key: CodingKey>(keyedBy: Key.Type) throws -> KeyedDecodingContainer<Key> {
throw .invalid(at: self.codingPath)
}
// See `Decoder.unkeyedContainer()`.
func unkeyedContainer() throws -> any UnkeyedDecodingContainer {
throw .invalid(at: self.codingPath)
}
// See `Decoder.singleValueContainer()`.
func singleValueContainer() throws -> any SingleValueDecodingContainer {
self
}
// See `SingleValueDecodingContainer.decodeNil()`.
func decodeNil() -> Bool {
self.data.isNull
}
// See `SingleValueDecodingContainer.decode(_:)`.
func decode<T: Decodable>(_: T.Type) throws -> T {
try self.dataDecoder.decode(T.self, from: self.data)
}
}
}
#endif // hasFeature(Embedded)