| | |
| | | /// - Returns: The encoded key. |
| | | func encode(_ key: String, atIndex index: Int) -> String { |
| | | switch self { |
| | | case .brackets: return "\(key)[]" |
| | | case .noBrackets: return key |
| | | case .indexInBrackets: return "\(key)[\(index)]" |
| | | case let .custom(encoding): return encoding(key, index) |
| | | case .brackets: "\(key)[]" |
| | | case .noBrackets: key |
| | | case .indexInBrackets: "\(key)[\(index)]" |
| | | case let .custom(encoding): encoding(key, index) |
| | | } |
| | | } |
| | | } |
| | |
| | | /// - Returns: The encoded `String`. |
| | | func encode(_ value: Bool) -> String { |
| | | switch self { |
| | | case .numeric: return value ? "1" : "0" |
| | | case .literal: return value ? "true" : "false" |
| | | case .numeric: value ? "1" : "0" |
| | | case .literal: value ? "true" : "false" |
| | | } |
| | | } |
| | | } |
| | |
| | | /// `Encodable` implementation. |
| | | func encode(_ data: Data) throws -> String? { |
| | | switch self { |
| | | case .deferredToData: return nil |
| | | case .base64: return data.base64EncodedString() |
| | | case let .custom(encoding): return try encoding(data) |
| | | case .deferredToData: nil |
| | | case .base64: data.base64EncodedString() |
| | | case let .custom(encoding): try encoding(data) |
| | | } |
| | | } |
| | | } |
| | |
| | | /// Encoding to use for `Date` values. |
| | | public enum DateEncoding { |
| | | /// ISO8601 and RFC3339 formatter. |
| | | private static let iso8601Formatter: ISO8601DateFormatter = { |
| | | private static let iso8601Formatter = Protected<ISO8601DateFormatter>({ |
| | | let formatter = ISO8601DateFormatter() |
| | | formatter.formatOptions = .withInternetDateTime |
| | | return formatter |
| | | }() |
| | | }()) |
| | | |
| | | /// Defers encoding to the `Date` type. This is the default encoding. |
| | | case deferredToDate |
| | |
| | | func encode(_ date: Date) throws -> String? { |
| | | switch self { |
| | | case .deferredToDate: |
| | | return nil |
| | | nil |
| | | case .secondsSince1970: |
| | | return String(date.timeIntervalSince1970) |
| | | String(date.timeIntervalSince1970) |
| | | case .millisecondsSince1970: |
| | | return String(date.timeIntervalSince1970 * 1000.0) |
| | | String(date.timeIntervalSince1970 * 1000.0) |
| | | case .iso8601: |
| | | return DateEncoding.iso8601Formatter.string(from: date) |
| | | DateEncoding.iso8601Formatter.read { $0.string(from: date) } |
| | | case let .formatted(formatter): |
| | | return formatter.string(from: date) |
| | | formatter.string(from: date) |
| | | case let .custom(closure): |
| | | return try closure(date) |
| | | try closure(date) |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | func encode(_ key: String) -> String { |
| | | switch self { |
| | | case .useDefaultKeys: return key |
| | | case .convertToSnakeCase: return convertToSnakeCase(key) |
| | | case .convertToKebabCase: return convertToKebabCase(key) |
| | | case .capitalized: return String(key.prefix(1).uppercased() + key.dropFirst()) |
| | | case .uppercased: return key.uppercased() |
| | | case .lowercased: return key.lowercased() |
| | | case let .custom(encoding): return encoding(key) |
| | | case .useDefaultKeys: key |
| | | case .convertToSnakeCase: convertToSnakeCase(key) |
| | | case .convertToKebabCase: convertToKebabCase(key) |
| | | case .capitalized: String(key.prefix(1).uppercased() + key.dropFirst()) |
| | | case .uppercased: key.uppercased() |
| | | case .lowercased: key.lowercased() |
| | | case let .custom(encoding): encoding(key) |
| | | } |
| | | } |
| | | |
| | |
| | | /// |
| | | /// This encoding affects how the `parent`, `child`, `grandchild` path is encoded. Brackets are used by default. |
| | | /// e.g. `parent[child][grandchild]=value`. |
| | | public struct KeyPathEncoding { |
| | | public struct KeyPathEncoding: Sendable { |
| | | /// Encodes key paths by wrapping each component in brackets. e.g. `parent[child][grandchild]`. |
| | | public static let brackets = KeyPathEncoding { "[\($0)]" } |
| | | /// Encodes key paths by separating each component with dots. e.g. `parent.child.grandchild`. |
| | | public static let dots = KeyPathEncoding { ".\($0)" } |
| | | |
| | | private let encoding: (_ subkey: String) -> String |
| | | private let encoding: @Sendable (_ subkey: String) -> String |
| | | |
| | | /// Creates an instance with the encoding closure called for each sub-key in a key path. |
| | | /// |
| | | /// - Parameter encoding: Closure used to perform the encoding. |
| | | public init(encoding: @escaping (_ subkey: String) -> String) { |
| | | public init(encoding: @escaping @Sendable (_ subkey: String) -> String) { |
| | | self.encoding = encoding |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | /// Encoding to use for `nil` values. |
| | | public struct NilEncoding { |
| | | public struct NilEncoding: Sendable { |
| | | /// Encodes `nil` by dropping the entire key / value pair. |
| | | public static let dropKey = NilEncoding { nil } |
| | | /// Encodes `nil` by dropping only the value. e.g. `value1=one&nilValue=&value2=two`. |
| | |
| | | /// Encodes `nil` as `null`. |
| | | public static let null = NilEncoding { "null" } |
| | | |
| | | private let encoding: () -> String? |
| | | private let encoding: @Sendable () -> String? |
| | | |
| | | /// Creates an instance with the encoding closure called for `nil` values. |
| | | /// |
| | | /// - Parameter encoding: Closure used to perform the encoding. |
| | | public init(encoding: @escaping () -> String?) { |
| | | public init(encoding: @escaping @Sendable () -> String?) { |
| | | self.encoding = encoding |
| | | } |
| | | |
| | |
| | | /// - Returns: The encoded `String`. |
| | | func encode(_ string: String) -> String { |
| | | switch self { |
| | | case .percentEscaped: return string.replacingOccurrences(of: " ", with: "%20") |
| | | case .plusReplaced: return string.replacingOccurrences(of: " ", with: "+") |
| | | case .percentEscaped: string.replacingOccurrences(of: " ", with: "%20") |
| | | case .plusReplaced: string.replacingOccurrences(of: " ", with: "+") |
| | | } |
| | | } |
| | | } |
| | |
| | | var localizedDescription: String { |
| | | switch self { |
| | | case let .invalidRootObject(object): |
| | | return "URLEncodedFormEncoder requires keyed root object. Received \(object) instead." |
| | | "URLEncodedFormEncoder requires keyed root object. Received \(object) instead." |
| | | } |
| | | } |
| | | } |
| | |
| | | self.allowedCharacters = allowedCharacters |
| | | } |
| | | |
| | | func encode(_ value: Encodable) throws -> URLEncodedFormComponent { |
| | | func encode(_ value: any Encodable) throws -> URLEncodedFormComponent { |
| | | let context = URLEncodedFormContext(.object([])) |
| | | let encoder = _URLEncodedFormEncoder(context: context, |
| | | boolEncoding: boolEncoding, |
| | |
| | | /// |
| | | /// - Returns: The encoded `String`. |
| | | /// - Throws: An `Error` or `EncodingError` instance if encoding fails. |
| | | public func encode(_ value: Encodable) throws -> String { |
| | | public func encode(_ value: any Encodable) throws -> String { |
| | | let component: URLEncodedFormComponent = try encode(value) |
| | | |
| | | guard case let .object(object) = component else { |
| | |
| | | /// - Returns: The encoded `Data`. |
| | | /// |
| | | /// - Throws: An `Error` or `EncodingError` instance if encoding fails. |
| | | public func encode(_ value: Encodable) throws -> Data { |
| | | public func encode(_ value: any Encodable) throws -> Data { |
| | | let string: String = try encode(value) |
| | | |
| | | return Data(string.utf8) |
| | |
| | | } |
| | | |
| | | final class _URLEncodedFormEncoder { |
| | | var codingPath: [CodingKey] |
| | | var codingPath: [any CodingKey] |
| | | // Returns an empty dictionary, as this encoder doesn't support userInfo. |
| | | var userInfo: [CodingUserInfoKey: Any] { [:] } |
| | | |
| | |
| | | private let nilEncoding: URLEncodedFormEncoder.NilEncoding |
| | | |
| | | init(context: URLEncodedFormContext, |
| | | codingPath: [CodingKey] = [], |
| | | codingPath: [any CodingKey] = [], |
| | | boolEncoding: URLEncodedFormEncoder.BoolEncoding, |
| | | dataEncoding: URLEncodedFormEncoder.DataEncoding, |
| | | dateEncoding: URLEncodedFormEncoder.DateEncoding, |
| | |
| | | return KeyedEncodingContainer(container) |
| | | } |
| | | |
| | | func unkeyedContainer() -> UnkeyedEncodingContainer { |
| | | func unkeyedContainer() -> any UnkeyedEncodingContainer { |
| | | _URLEncodedFormEncoder.UnkeyedContainer(context: context, |
| | | codingPath: codingPath, |
| | | boolEncoding: boolEncoding, |
| | |
| | | nilEncoding: nilEncoding) |
| | | } |
| | | |
| | | func singleValueContainer() -> SingleValueEncodingContainer { |
| | | func singleValueContainer() -> any SingleValueEncodingContainer { |
| | | _URLEncodedFormEncoder.SingleValueContainer(context: context, |
| | | codingPath: codingPath, |
| | | boolEncoding: boolEncoding, |
| | |
| | | /// Converts self to an `[URLEncodedFormData]` or returns `nil` if not convertible. |
| | | var array: [URLEncodedFormComponent]? { |
| | | switch self { |
| | | case let .array(array): return array |
| | | default: return nil |
| | | case let .array(array): array |
| | | default: nil |
| | | } |
| | | } |
| | | |
| | | /// Converts self to an `Object` or returns `nil` if not convertible. |
| | | var object: Object? { |
| | | switch self { |
| | | case let .object(object): return object |
| | | default: return nil |
| | | case let .object(object): object |
| | | default: nil |
| | | } |
| | | } |
| | | |
| | |
| | | /// - parameters: |
| | | /// - value: Value of `Self` to set at the supplied path. |
| | | /// - path: `CodingKey` path to update with the supplied value. |
| | | public mutating func set(to value: URLEncodedFormComponent, at path: [CodingKey]) { |
| | | public mutating func set(to value: URLEncodedFormComponent, at path: [any CodingKey]) { |
| | | set(&self, to: value, at: path) |
| | | } |
| | | |
| | | /// Recursive backing method to `set(to:at:)`. |
| | | private func set(_ context: inout URLEncodedFormComponent, to value: URLEncodedFormComponent, at path: [CodingKey]) { |
| | | private func set(_ context: inout URLEncodedFormComponent, to value: URLEncodedFormComponent, at path: [any CodingKey]) { |
| | | guard !path.isEmpty else { |
| | | context = value |
| | | return |
| | |
| | | |
| | | extension _URLEncodedFormEncoder { |
| | | final class KeyedContainer<Key> where Key: CodingKey { |
| | | var codingPath: [CodingKey] |
| | | var codingPath: [any CodingKey] |
| | | |
| | | private let context: URLEncodedFormContext |
| | | private let boolEncoding: URLEncodedFormEncoder.BoolEncoding |
| | |
| | | private let nilEncoding: URLEncodedFormEncoder.NilEncoding |
| | | |
| | | init(context: URLEncodedFormContext, |
| | | codingPath: [CodingKey], |
| | | codingPath: [any CodingKey], |
| | | boolEncoding: URLEncodedFormEncoder.BoolEncoding, |
| | | dataEncoding: URLEncodedFormEncoder.DataEncoding, |
| | | dateEncoding: URLEncodedFormEncoder.DateEncoding, |
| | |
| | | self.nilEncoding = nilEncoding |
| | | } |
| | | |
| | | private func nestedCodingPath(for key: CodingKey) -> [CodingKey] { |
| | | private func nestedCodingPath(for key: any CodingKey) -> [any CodingKey] { |
| | | codingPath + [key] |
| | | } |
| | | } |
| | |
| | | try container.encode(value) |
| | | } |
| | | |
| | | func nestedSingleValueEncoder(for key: Key) -> SingleValueEncodingContainer { |
| | | func nestedSingleValueEncoder(for key: Key) -> any SingleValueEncodingContainer { |
| | | let container = _URLEncodedFormEncoder.SingleValueContainer(context: context, |
| | | codingPath: nestedCodingPath(for: key), |
| | | boolEncoding: boolEncoding, |
| | |
| | | return container |
| | | } |
| | | |
| | | func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { |
| | | func nestedUnkeyedContainer(forKey key: Key) -> any UnkeyedEncodingContainer { |
| | | let container = _URLEncodedFormEncoder.UnkeyedContainer(context: context, |
| | | codingPath: nestedCodingPath(for: key), |
| | | boolEncoding: boolEncoding, |
| | |
| | | return KeyedEncodingContainer(container) |
| | | } |
| | | |
| | | func superEncoder() -> Encoder { |
| | | func superEncoder() -> any Encoder { |
| | | _URLEncodedFormEncoder(context: context, |
| | | codingPath: codingPath, |
| | | boolEncoding: boolEncoding, |
| | |
| | | nilEncoding: nilEncoding) |
| | | } |
| | | |
| | | func superEncoder(forKey key: Key) -> Encoder { |
| | | func superEncoder(forKey key: Key) -> any Encoder { |
| | | _URLEncodedFormEncoder(context: context, |
| | | codingPath: nestedCodingPath(for: key), |
| | | boolEncoding: boolEncoding, |
| | |
| | | |
| | | extension _URLEncodedFormEncoder { |
| | | final class SingleValueContainer { |
| | | var codingPath: [CodingKey] |
| | | var codingPath: [any CodingKey] |
| | | |
| | | private var canEncodeNewValue = true |
| | | |
| | |
| | | private let nilEncoding: URLEncodedFormEncoder.NilEncoding |
| | | |
| | | init(context: URLEncodedFormContext, |
| | | codingPath: [CodingKey], |
| | | codingPath: [any CodingKey], |
| | | boolEncoding: URLEncodedFormEncoder.BoolEncoding, |
| | | dataEncoding: URLEncodedFormEncoder.DataEncoding, |
| | | dateEncoding: URLEncodedFormEncoder.DateEncoding, |
| | |
| | | |
| | | extension _URLEncodedFormEncoder { |
| | | final class UnkeyedContainer { |
| | | var codingPath: [CodingKey] |
| | | var codingPath: [any CodingKey] |
| | | |
| | | var count = 0 |
| | | var nestedCodingPath: [CodingKey] { |
| | | var nestedCodingPath: [any CodingKey] { |
| | | codingPath + [AnyCodingKey(intValue: count)!] |
| | | } |
| | | |
| | |
| | | private let nilEncoding: URLEncodedFormEncoder.NilEncoding |
| | | |
| | | init(context: URLEncodedFormContext, |
| | | codingPath: [CodingKey], |
| | | codingPath: [any CodingKey], |
| | | boolEncoding: URLEncodedFormEncoder.BoolEncoding, |
| | | dataEncoding: URLEncodedFormEncoder.DataEncoding, |
| | | dateEncoding: URLEncodedFormEncoder.DateEncoding, |
| | |
| | | try container.encode(value) |
| | | } |
| | | |
| | | func nestedSingleValueContainer() -> SingleValueEncodingContainer { |
| | | func nestedSingleValueContainer() -> any SingleValueEncodingContainer { |
| | | defer { count += 1 } |
| | | |
| | | return _URLEncodedFormEncoder.SingleValueContainer(context: context, |
| | |
| | | return KeyedEncodingContainer(container) |
| | | } |
| | | |
| | | func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { |
| | | func nestedUnkeyedContainer() -> any UnkeyedEncodingContainer { |
| | | defer { count += 1 } |
| | | |
| | | return _URLEncodedFormEncoder.UnkeyedContainer(context: context, |
| | |
| | | nilEncoding: nilEncoding) |
| | | } |
| | | |
| | | func superEncoder() -> Encoder { |
| | | func superEncoder() -> any Encoder { |
| | | defer { count += 1 } |
| | | |
| | | return _URLEncodedFormEncoder(context: context, |
| | |
| | | |
| | | func serialize(_ component: URLEncodedFormComponent, forKey key: String) -> String { |
| | | switch component { |
| | | case let .string(string): return "\(escape(keyEncoding.encode(key)))=\(escape(string))" |
| | | case let .array(array): return serialize(array, forKey: key) |
| | | case let .object(object): return serialize(object, forKey: key) |
| | | case let .string(string): "\(escape(keyEncoding.encode(key)))=\(escape(string))" |
| | | case let .array(array): serialize(array, forKey: key) |
| | | case let .object(object): serialize(object, forKey: key) |
| | | } |
| | | } |
| | | |