Skip to content

Commit 5a2340b

Browse files
committed
use Alamofire for encoding query string
1 parent 8f91c28 commit 5a2340b

File tree

4 files changed

+31
-94
lines changed

4 files changed

+31
-94
lines changed

Sources/Functions/Types.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,13 @@ public enum FunctionsError: Error, LocalizedError {
88
/// Error indicating a non-2xx status code returned by the Edge Function.
99
case httpError(code: Int, data: Data)
1010

11-
case unknown(any Error)
12-
1311
/// A localized description of the error.
1412
public var errorDescription: String? {
1513
switch self {
1614
case .relayError:
1715
"Relay Error invoking the Edge Function"
1816
case let .httpError(code, _):
1917
"Edge Function returned a non-2xx status code: \(code)"
20-
case let .unknown(error):
21-
"Unkown error: \(error.localizedDescription)"
2218
}
2319
}
2420
}

Sources/Helpers/AnyJSON/AnyJSON.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,10 @@ public enum AnyJSON: Sendable, Codable, Hashable {
9696
self = .string(string)
9797
} else if let bool = try? container.decode(Bool.self) {
9898
self = .bool(bool)
99-
} else if let double = try? container.decode(Double.self) {
100-
self = .double(double)
10199
} else if let int = try? container.decode(Int.self) {
102100
self = .integer(int)
101+
} else if let double = try? container.decode(Double.self) {
102+
self = .double(double)
103103
} else if container.decodeNil() {
104104
self = .null
105105
} else {

Sources/Helpers/FoundationExtensions.swift

Lines changed: 15 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Created by Guilherme Souza on 23/04/24.
66
//
77

8+
import Alamofire
89
import Foundation
910

1011
#if canImport(FoundationNetworking)
@@ -16,15 +17,15 @@ import Foundation
1617

1718
extension Result {
1819
package var value: Success? {
19-
if case let .success(value) = self {
20+
if case .success(let value) = self {
2021
value
2122
} else {
2223
nil
2324
}
2425
}
2526

2627
package var error: Failure? {
27-
if case let .failure(error) = self {
28+
if case .failure(let error) = self {
2829
error
2930
} else {
3031
nil
@@ -51,16 +52,20 @@ extension URL {
5152
return
5253
}
5354

54-
let currentQueryItems = components.percentEncodedQueryItems ?? []
55+
let encoding = URLEncoding.queryString
5556

56-
components.percentEncodedQueryItems =
57-
currentQueryItems
58-
+ queryItems.map {
59-
URLQueryItem(
60-
name: escape($0.name),
61-
value: $0.value.map(escape)
62-
)
57+
func query(_ parameters: [URLQueryItem]) -> String {
58+
var components: [(String, String)] = []
59+
60+
for param in parameters.sorted(by: { $0.name < $1.name }) {
61+
components += encoding.queryComponents(fromKey: param.name, value: param.value!)
6362
}
63+
return components.map { "\($0)=\($1)" }.joined(separator: "&")
64+
}
65+
66+
let percentEncodedQuery =
67+
(components.percentEncodedQuery.map { $0 + "&" } ?? "") + query(queryItems)
68+
components.percentEncodedQuery = percentEncodedQuery
6469

6570
if let newURL = components.url {
6671
self = newURL
@@ -72,63 +77,4 @@ extension URL {
7277
url.appendQueryItems(queryItems)
7378
return url
7479
}
75-
76-
// package mutating func appendOrUpdateQueryItems(_ queryItems: [URLQueryItem]) {
77-
// guard !queryItems.isEmpty else {
78-
// return
79-
// }
80-
81-
// guard var components = URLComponents(url: self, resolvingAgainstBaseURL: false) else {
82-
// return
83-
// }
84-
85-
// var currentQueryItems = components.percentEncodedQueryItems ?? []
86-
87-
// for var queryItem in queryItems {
88-
// queryItem.name = escape(queryItem.name)
89-
// queryItem.value = queryItem.value.map(escape)
90-
// if let index = currentQueryItems.firstIndex(where: { $0.name == queryItem.name }) {
91-
// currentQueryItems[index] = queryItem
92-
// } else {
93-
// currentQueryItems.append(queryItem)
94-
// }
95-
// }
96-
97-
// components.percentEncodedQueryItems = currentQueryItems
98-
99-
// if let newURL = components.url {
100-
// self = newURL
101-
// }
102-
// }
103-
104-
// package func appendingOrUpdatingQueryItems(_ queryItems: [URLQueryItem]) -> URL {
105-
// var url = self
106-
// url.appendOrUpdateQueryItems(queryItems)
107-
// return url
108-
// }
109-
}
110-
111-
func escape(_ string: String) -> String {
112-
string.addingPercentEncoding(withAllowedCharacters: .sbURLQueryAllowed) ?? string
113-
}
114-
115-
extension CharacterSet {
116-
/// Creates a CharacterSet from RFC 3986 allowed characters.
117-
///
118-
/// RFC 3986 states that the following characters are "reserved" characters.
119-
///
120-
/// - General Delimiters: ":", "#", "[", "]", "@", "?", "/"
121-
/// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
122-
///
123-
/// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
124-
/// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
125-
/// should be percent-escaped in the query string.
126-
static let sbURLQueryAllowed: CharacterSet = {
127-
let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
128-
let subDelimitersToEncode = "!$&'()*+,;="
129-
let encodableDelimiters = CharacterSet(
130-
charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
131-
132-
return CharacterSet.urlQueryAllowed.subtracting(encodableDelimiters)
133-
}()
13480
}

Tests/FunctionsTests/FunctionsClientTests.swift

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -156,14 +156,12 @@ final class FunctionsClientTests: XCTestCase {
156156
} catch {
157157
assertInlineSnapshot(of: error, as: .customDump) {
158158
"""
159-
FunctionsError.unknown(
160-
.keyNotFound(
161-
.CodingKeys(stringValue: "status", intValue: nil),
162-
DecodingError.Context(
163-
codingPath: [],
164-
debugDescription: #"No value associated with key CodingKeys(stringValue: "status", intValue: nil) ("status")."#,
165-
underlyingError: nil
166-
)
159+
DecodingError.keyNotFound(
160+
.CodingKeys(stringValue: "status", intValue: nil),
161+
DecodingError.Context(
162+
codingPath: [],
163+
debugDescription: #"No value associated with key CodingKeys(stringValue: "status", intValue: nil) ("status")."#,
164+
underlyingError: nil
167165
)
168166
)
169167
"""
@@ -294,7 +292,7 @@ final class FunctionsClientTests: XCTestCase {
294292
func testInvoke_shouldThrow_error() async throws {
295293
Mock(
296294
url: url.appendingPathComponent("hello_world"),
297-
statusCode: 200,
295+
statusCode: 204,
298296
data: [.post: Data()],
299297
requestError: URLError(.badServerResponse)
300298
)
@@ -312,13 +310,10 @@ final class FunctionsClientTests: XCTestCase {
312310
do {
313311
try await sut.invoke("hello_world")
314312
XCTFail("Invoke should fail.")
315-
} catch let FunctionsError.unknown(error) {
316-
guard case let AFError.sessionTaskFailed(underlyingError as URLError) = error else {
317-
XCTFail()
318-
return
319-
}
320-
313+
} catch let AFError.sessionTaskFailed(underlyingError as URLError) {
321314
XCTAssertEqual(underlyingError.code, .badServerResponse)
315+
} catch {
316+
XCTFail("Unexpected error \(error)")
322317
}
323318
}
324319

@@ -345,7 +340,7 @@ final class FunctionsClientTests: XCTestCase {
345340
} catch {
346341
assertInlineSnapshot(of: error, as: .description) {
347342
"""
348-
httpError(code: 300, data: 0 bytes)
343+
responseValidationFailed(reason: Alamofire.AFError.ResponseValidationFailureReason.customValidationFailed(error: Functions.FunctionsError.httpError(code: 300, data: 0 bytes)))
349344
"""
350345
}
351346
}
@@ -377,7 +372,7 @@ final class FunctionsClientTests: XCTestCase {
377372
} catch {
378373
assertInlineSnapshot(of: error, as: .description) {
379374
"""
380-
relayError
375+
responseValidationFailed(reason: Alamofire.AFError.ResponseValidationFailureReason.customValidationFailed(error: Functions.FunctionsError.relayError))
381376
"""
382377
}
383378
}
@@ -441,7 +436,7 @@ final class FunctionsClientTests: XCTestCase {
441436
} catch {
442437
assertInlineSnapshot(of: error, as: .description) {
443438
"""
444-
httpError(code: 300, data: 0 bytes)
439+
responseValidationFailed(reason: Alamofire.AFError.ResponseValidationFailureReason.customValidationFailed(error: Functions.FunctionsError.httpError(code: 300, data: 0 bytes)))
445440
"""
446441
}
447442
}
@@ -476,7 +471,7 @@ final class FunctionsClientTests: XCTestCase {
476471
} catch {
477472
assertInlineSnapshot(of: error, as: .description) {
478473
"""
479-
relayError
474+
responseValidationFailed(reason: Alamofire.AFError.ResponseValidationFailureReason.customValidationFailed(error: Functions.FunctionsError.relayError))
480475
"""
481476
}
482477
}

0 commit comments

Comments
 (0)