Skip to content

Commit 746997e

Browse files
authored
Fixed query parameters without value (#2)
1 parent 3a11e69 commit 746997e

File tree

2 files changed

+44
-62
lines changed

2 files changed

+44
-62
lines changed

Sources/Decoder/URLQueryDeserializer.swift

Lines changed: 32 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,18 @@ internal final class URLQueryDeserializer {
3434
}
3535

3636
private func deserializeStringValue(
37-
_ fragmentValue: Substring,
37+
_ value: Substring?,
3838
path: [String]
39-
) throws -> URLQueryValue {
40-
guard let decodedValue = fragmentValue.removingPercentEncoding else {
39+
) throws -> URLQueryValue? {
40+
guard let value = value else {
41+
return nil
42+
}
43+
44+
guard let decodedValue = value.removingPercentEncoding else {
4145
throw DecodingError.dataCorrupted(
4246
DecodingError.Context(
4347
codingPath: [],
44-
debugDescription: "Unable to remove percent encoding for '\(fragmentValue)'."
48+
debugDescription: "Unable to remove percent encoding for '\(value)' at the '\(path)' key."
4549
)
4650
)
4751
}
@@ -50,7 +54,7 @@ internal final class URLQueryDeserializer {
5054
}
5155

5256
private func deserializeArrayValue(
53-
_ fragmentValue: Substring,
57+
_ value: Substring?,
5458
at keyIndex: Int,
5559
of path: [String],
5660
for array: [Int: URLQueryValue]
@@ -66,77 +70,65 @@ internal final class URLQueryDeserializer {
6670
throw DecodingError.dataCorrupted(context)
6771
}
6872

69-
let array = array.updatingValue(
70-
try deserializeValue(
71-
fragmentValue,
72-
at: keyIndex + 1,
73-
of: path,
74-
for: array[index]
75-
),
76-
forKey: index
77-
)
73+
guard let value = try deserializeValue(value, at: keyIndex + 1, of: path, for: array[index]) else {
74+
return .array(array)
75+
}
7876

79-
return .array(array)
77+
return .array(array.updatingValue(value, forKey: index))
8078
}
8179

8280
private func deserializeDictionaryValue(
83-
_ fragmentValue: Substring,
81+
_ value: Substring?,
8482
at keyIndex: Int,
8583
of path: [String],
8684
for dictionary: [String: URLQueryValue]
8785
) throws -> URLQueryValue {
8886
let key = path[keyIndex]
8987

90-
let dictionary = dictionary.updatingValue(
91-
try deserializeValue(
92-
fragmentValue,
93-
at: keyIndex + 1,
94-
of: path,
95-
for: dictionary[key]
96-
),
97-
forKey: key
98-
)
88+
guard let value = try deserializeValue(value, at: keyIndex + 1, of: path, for: dictionary[key]) else {
89+
return .dictionary(dictionary)
90+
}
9991

100-
return .dictionary(dictionary)
92+
return .dictionary(dictionary.updatingValue(value, forKey: key))
10193
}
10294

10395
private func deserializeValue(
104-
_ fragmentValue: Substring,
96+
_ value: Substring?,
10597
at keyIndex: Int,
10698
of path: [String],
107-
for value: URLQueryValue?
108-
) throws -> URLQueryValue {
109-
switch value {
99+
for component: URLQueryValue?
100+
) throws -> URLQueryValue? {
101+
switch component {
110102
case nil where keyIndex >= path.count:
111-
return try deserializeStringValue(fragmentValue, path: path)
103+
return try deserializeStringValue(value, path: path)
112104

113105
case nil where path[keyIndex].isEmpty || Int(path[keyIndex]) != nil:
114106
return try deserializeArrayValue(
115-
fragmentValue,
107+
value,
116108
at: keyIndex,
117109
of: path,
118110
for: [:]
119111
)
120112

121113
case nil:
122114
return try deserializeDictionaryValue(
123-
fragmentValue,
115+
value,
124116
at: keyIndex,
125117
of: path,
126118
for: [:]
127119
)
128120

129121
case .array(let array) where keyIndex < path.count:
130122
return try deserializeArrayValue(
131-
fragmentValue,
123+
value,
132124
at: keyIndex,
133125
of: path,
134126
for: array
135127
)
136128

137129
case .dictionary(let dictionary) where keyIndex < path.count:
138130
return try deserializeDictionaryValue(
139-
fragmentValue,
131+
value,
140132
at: keyIndex,
141133
of: path,
142134
for: dictionary
@@ -154,30 +146,22 @@ internal final class URLQueryDeserializer {
154146

155147
private func deserializeFragment(
156148
_ fragment: Substring,
157-
for value: URLQueryValue?
158-
) throws -> URLQueryValue {
149+
for query: URLQueryValue?
150+
) throws -> URLQueryValue? {
159151
let keyValue = fragment.split(
160152
separator: .equals,
161153
maxSplits: 1,
162154
omittingEmptySubsequences: false
163155
)
164156

165157
let path = try deserializeKey(keyValue[0])
166-
167-
guard let fragmentValue = keyValue[safe: 1] else {
168-
let context = DecodingError.Context(
169-
codingPath: [],
170-
debugDescription: "Value for key '\(path)' is missing or invalid."
171-
)
172-
173-
throw DecodingError.dataCorrupted(context)
174-
}
158+
let value = keyValue[safe: 1]
175159

176160
return try deserializeValue(
177-
fragmentValue,
161+
value,
178162
at: .zero,
179163
of: path,
180-
for: value
164+
for: query
181165
)
182166
}
183167

Tests/Decoder/URLQueryDecoderTests.swift

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,18 @@ final class URLQueryDecoderTests: XCTestCase, URLQueryDecoderTesting {
1717
)
1818
}
1919

20+
func testThatDecoderSucceedsWhenDecodingKeyWithoutValue() {
21+
let query = "foo&bar=123"
22+
23+
let expectedValue: [String: Int?] = ["bar": 123]
24+
25+
assertDecoderSucceeds(
26+
decoding: [String: Int?].self,
27+
from: query,
28+
expecting: expectedValue
29+
)
30+
}
31+
2032
func testThatDecoderSucceedsWhenDecodingStringToBoolDictionary() {
2133
let query = "foo=true&bar=false"
2234

@@ -790,20 +802,6 @@ final class URLQueryDecoderTests: XCTestCase, URLQueryDecoderTesting {
790802
}
791803
}
792804

793-
func testThatDecoderFailsWhenDecodingKeyWithoutValue() {
794-
let query = "foobar"
795-
796-
assertDecoderFails(decoding: [Int].self, from: query) { error in
797-
switch error {
798-
case DecodingError.dataCorrupted:
799-
return true
800-
801-
default:
802-
return false
803-
}
804-
}
805-
}
806-
807805
func testThatDecoderFailsWhenDecodingValuesOfDifferentTypesWithSameKey() {
808806
let query = "foo=123&foo=qwe"
809807

0 commit comments

Comments
 (0)