this repo has no description
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