Files
data-lite-coder/Sources/DataLiteCoder/Classes/RowEncoder.swift

236 lines
6.7 KiB
Swift

import Foundation
import DataLiteCore
private import DLCEncoder
/// Encoder that encodes instances of `Encodable` types into `SQLiteRow` or an array of `SQLiteRow`.
///
/// ## Overview
///
/// Use `RowEncoder` to convert `Encodable` objects into a single `SQLiteRow` or an array of `SQLiteRow`.
///
/// ### Encode a Single Object
///
/// Use ``encode(_:)->SQLiteRow`` to encode a single `Encodable` value into a `SQLiteRow`.
///
/// ```swift
/// struct User: Encodable {
/// let id: Int
/// let name: String
/// }
///
/// do {
/// let user = User(id: 1, name: "John Doe")
/// let encoder = RowEncoder()
/// let row = try encoder.encode(user)
/// } catch {
/// print("Encoding error: ", error)
/// }
/// ```
///
/// ### Encode an Array of Objects
///
/// Use ``encode(_:)->[SQLiteRow]`` to encode an array of `Encodable` values into an array of `SQLiteRow`.
///
/// ```swift
/// struct User: Encodable {
/// let id: Int
/// let name: String
/// }
///
/// do {
/// let users = [
/// User(id: 1, name: "John Doe"),
/// User(id: 2, name: "Jane Smith")
/// ]
/// let encoder = RowEncoder()
/// let rows = try encoder.encode(users)
/// } catch {
/// print("Encoding error: ", error)
/// }
/// ```
///
/// ### Customize Encoding Behavior
///
/// Use the ``userInfo`` property to pass additional data that can affect encoding logic.
///
/// First, define a custom key:
///
/// ```swift
/// extension CodingUserInfoKey {
/// static let lowercased = CodingUserInfoKey(
/// rawValue: "lowercased"
/// )!
/// }
/// ```
///
/// Then pass a value using this key:
///
/// ```swift
/// do {
/// let user = User(id: 1, name: "John Doe")
/// let encoder = RowEncoder()
/// encoder.userInfo[.lowercased] = true
///
/// let row = try encoder.encode(user)
/// } catch {
/// print("Encoding error: ", error)
/// }
/// ```
///
/// Access this value inside your custom `encode(to:)` method:
///
/// ```swift
/// struct User: Encodable {
/// enum CodingKeys: CodingKey {
/// case id, name
/// }
///
/// let id: Int
/// let name: String
///
/// func encode(to encoder: any Encoder) throws {
/// var container = encoder.container(keyedBy: CodingKeys.self)
/// try container.encode(self.id, forKey: .id)
///
/// if (encoder.userInfo[.lowercased] as? Bool) ?? false {
/// try container.encode(self.name.lowercased(), forKey: .name)
/// } else {
/// try container.encode(self.name.capitalized, forKey: .name)
/// }
/// }
/// }
/// ```
///
/// ### Date Encoding Strategy
///
/// Use the ``dateEncodingStrategy`` property to control how `Date` values are encoded into SQLite.
///
/// ```swift
/// struct Log: Encodable {
/// let timestamp: Date
/// }
///
/// do {
/// let encoder = RowEncoder(
/// dateEncodingStrategy: .iso8601
/// )
///
/// let log = Log(timestamp: Date())
/// let row = try encoder.encode(log)
/// } catch {
/// print("Encoding error: ", error)
/// }
/// ```
///
/// For more detailed information, see ``DateEncodingStrategy``.
///
/// ## Topics
///
/// ### Creating an Encoder
///
/// - ``init(userInfo:dateEncodingStrategy:)``
///
/// ### Encoding
///
/// - ``encode(_:)->SQLiteRow``
/// - ``encode(_:)->[SQLiteRow]``
///
/// ### Customizing Encoding
///
/// - ``userInfo``
///
/// ### Encoding Dates
///
/// - ``dateEncodingStrategy``
/// - ``DateEncodingStrategy``
public final class RowEncoder {
// MARK: - Properties
/// A dictionary you use to customize encoding behavior.
///
/// Use this dictionary to pass additional contextual information to the encoded type's `encode(to:)`
/// implementation. You can define your own keys by extending `CodingUserInfoKey`.
///
/// This can be useful when encoding needs to consider external state, such as user roles,
/// environment settings, or formatting preferences.
public var userInfo: [CodingUserInfoKey: Any]
/// The strategy to use for encoding `Date` values.
///
/// Use this property to control how `Date` values are encoded into SQLite-compatible types.
/// By default, the `.deferredToDate` strategy is used.
///
/// You can change the strategy to encode dates as timestamps, ISO 8601 strings,
/// or using a custom formatter.
///
/// For details on available strategies, see ``DateEncodingStrategy``.
public var dateEncodingStrategy: DateEncodingStrategy
// MARK: - Inits
/// Creates a new instance of `RowEncoder`.
///
/// - Parameters:
/// - userInfo: A dictionary you can use to customize the encoding process by passing
/// additional contextual information. The default value is an empty dictionary.
/// - dateEncodingStrategy: The strategy to use for encoding `Date` values.
/// The default value is `.deferredToDate`.
public init(
userInfo: [CodingUserInfoKey: Any] = [:],
dateEncodingStrategy: DateEncodingStrategy = .deferredToDate
) {
self.userInfo = userInfo
self.dateEncodingStrategy = dateEncodingStrategy
}
// MARK: - Methods
/// Encodes a single value of a type that conforms to `Encodable` into a `SQLiteRow`.
///
/// - Parameter value: The value to encode.
/// - Returns: A `SQLiteRow` containing the encoded data of the provided value.
/// - Throws: An error if any value throws an error during encoding.
public func encode<T: Encodable>(_ value: T) throws -> SQLiteRow {
let dateEncoder = DateEncoder(
strategy: dateEncodingStrategy
)
let encoder = SingleRowEncoder(
dateEncoder: dateEncoder,
codingPath: [],
userInfo: userInfo
)
try value.encode(to: encoder)
return encoder.sqliteData
}
/// Encodes an array of values conforming to `Encodable` into an array of `SQLiteRow`.
///
/// - Parameter value: The array of values to encode.
/// - Returns: An array of `SQLiteRow` objects containing the encoded data.
/// - Throws: An error if any value throws an error during encoding.
public func encode<T: Encodable>(_ value: [T]) throws -> [SQLiteRow] {
let dateEncoder = DateEncoder(
strategy: dateEncodingStrategy
)
let encoder = MultiRowEncoder(
dateEncoder: dateEncoder,
codingPath: [],
userInfo: userInfo
)
try value.encode(to: encoder)
return encoder.sqliteData
}
}
#if canImport(Combine)
import Combine
// MARK: - TopLevelEncoder
extension RowEncoder: TopLevelEncoder {
/// The output type produced by the encoder.
public typealias Output = SQLiteRow
}
#endif