this repo has no description
at main 144 lines 5.7 kB view raw
1// 2// URLQueryItemEncoder.swift 3// URLQueryItemCoder 4// 5// Created by Kyle Hughes on 1/15/23. 6// 7 8import Foundation 9 10/// An object that encodes instances of a data type as `URLQueryItem` name-value pairs. 11public struct URLQueryItemEncoder { 12 /// The strategies used by this encoder for encoding `Encodable` values. 13 public var strategies: EncodingStrategies 14 15 /// The formatting settings used on the output of the encoder. 16 public var outputFormatting: OutputFormatting 17 18 // MARK: Public Initialization 19 20 /// Creates a new, reusable `URLQueryItem` encoder. 21 /// 22 /// The default formatting settings and encoding strategies are used if none are supplied. 23 /// 24 /// - Parameter dataStrategy: The strategy to use for encoding `Data` values. 25 /// - Parameter dateStrategy: The strategy to use for encoding `Date` values. 26 /// - Parameter keyStrategy: The strategy to use for encoding keys on keyed containers. 27 /// - Parameter nonConformingFloatStrategy: The strategy to use for encoding non-`URLQueryItem`-conforming 28 /// floating-point values (IEEE 754 infinity and NaN). 29 /// - Parameter outputFormatting: The formatting settings used on the output of the encoder. 30 /// - Returns: A new, reusable `URLQueryItem` encoder. 31 @inlinable 32 public init( 33 arrayStrategy: ArrayEncodingStrategy = .default, 34 dataStrategy: DataEncodingStrategy = .default, 35 dateStrategy: DateEncodingStrategy = .default, 36 keyStrategy: KeyEncodingStrategy = .default, 37 nonConformingFloatStrategy: NonConformingFloatEncodingStrategy = .default, 38 outputFormatting: OutputFormatting = .default 39 ) { 40 self.init( 41 strategies: EncodingStrategies( 42 arrayStrategy: arrayStrategy, 43 dataStrategy: dataStrategy, 44 dateStrategy: dateStrategy, 45 keyStrategy: keyStrategy, 46 nonConformingFloatStrategy: nonConformingFloatStrategy 47 ), 48 outputFormatting: outputFormatting 49 ) 50 } 51 52 /// Creates a new, reusable `URLQueryItem` encoder. 53 /// 54 /// The default formatting settings and encoding strategies are used if none are supplied. 55 /// 56 /// - Parameter strategies: The strategies for this encoder to use. The default value is `.default`. 57 /// - Parameter outputFormatting: The formatting settings used on the output of the encoder. 58 /// - Returns: A new, reusable `URLQueryItem` encoder. 59 public init(strategies: EncodingStrategies, outputFormatting: OutputFormatting = .default) { 60 self.strategies = strategies 61 self.outputFormatting = outputFormatting 62 } 63 64 // MARK: Private Instance Interface 65 66 private func encode( 67 _ container: EncodingContainer?, 68 at key: String = String(), 69 into queryItems: inout [URLQueryItem] 70 ) { 71 guard let container else { 72 return 73 } 74 75 let separator = key.isEmpty ? "" : "." 76 77 switch container { 78 case let .keyed(keyedContainer): 79 if keyedContainer.children.isEmpty { 80 if !key.isEmpty { 81 queryItems.append(URLQueryItem(name: key, value: String())) 82 } 83 } else { 84 for (subKey, childContainer) in keyedContainer.children { 85 let nextKey = "\(key)\(separator)\(subKey)" 86 encode(childContainer, at: nextKey, into: &queryItems) 87 } 88 } 89 case let .lowLevelEncoder(lowLevelEncoder): 90 guard let childContainer = lowLevelEncoder.container else { 91 preconditionFailure("Nothing was never encoded to nested low level encoder.") 92 } 93 encode(childContainer, at: key, into: &queryItems) 94 case let .singleValue(singleValueContainer): 95 switch singleValueContainer.storage { 96 case let .container(childContainer): 97 encode(childContainer, at: key, into: &queryItems) 98 case let .primitive(value): 99 if !key.isEmpty { 100 queryItems.append(URLQueryItem(name: key, value: value.map { String(describing: $0) })) 101 } 102 case .none: 103 preconditionFailure("Value was never encoded to single value container.") 104 } 105 case let .unkeyed(unkeyedContainer): 106 for (index, childContainer) in unkeyedContainer.children.enumerated() { 107 switch strategies.arrayStrategy { 108 case .indexed: 109 let nextKey = "\(key)\(separator)\(index)" 110 encode(childContainer, at: nextKey, into: &queryItems) 111 case .repeated: 112 encode(childContainer, at: key, into: &queryItems) 113 } 114 } 115 } 116 } 117} 118 119// MARK: - TopLevelEncoder Extension 120#if canImport(Combine) 121 122import Combine 123extension URLQueryItemEncoder: TopLevelEncoder { 124 // MARK: Public Instance Interface 125 126 /// Encodes an instance of the indicated type. 127 /// 128 /// - Parameter value: The instance to encode. 129 /// - Returns: The query items that represent the given value. 130 public func encode(_ value: some Encodable) throws -> [URLQueryItem] { 131 let container = try EncodingContainer.encodeWithSpecialTreatment(value, at: [], using: strategies) 132 133 var queryItems: [URLQueryItem] = [] 134 135 encode(container, into: &queryItems) 136 137 if outputFormatting.contains(.sortedKeys) { 138 return queryItems.sorted { $0.name < $1.name } 139 } else { 140 return queryItems 141 } 142 } 143} 144#endif