-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #56 from strvcom/fix/55-url-encoding-and-the-+-sign
[feat] Add support for custom URL parameters percent encoding
- Loading branch information
Showing
5 changed files
with
298 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// | ||
// CustomEncodedParameter.swift | ||
// | ||
// | ||
// Created by Matej Molnár on 01.01.2024. | ||
// | ||
|
||
import Foundation | ||
|
||
/// URL request query parameter that represents a value which will not be subjected to default percent encoding during URLRequest construction. | ||
/// | ||
/// This type is useful in case you want to override the default percent encoding of some special characters with accordance to RFC3986. | ||
/// | ||
/// Usage example: | ||
/// | ||
/// var urlParameters: [String: Any]? { | ||
/// ["specialCharacter": ">"] | ||
/// } | ||
/// | ||
/// // Request URL "https://test.com?specialCharacter=%3E" | ||
/// | ||
/// var urlParameters: [String: Any]? { | ||
/// ["specialCharacter": PercentEncodedParameter(">")] | ||
/// } | ||
/// | ||
/// // Request URL "https://test.com?specialCharacter=>" | ||
/// | ||
|
||
public struct CustomEncodedParameter { | ||
let encodedValue: String | ||
|
||
public init(_ encodedValue: String) { | ||
self.encodedValue = encodedValue | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
Sources/Networking/Misc/URLQueryItem+PercentEncoding.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// | ||
// URLQueryItem+PercentEncoding.swift | ||
// | ||
// | ||
// Created by Tomas Cejka on 02.01.2024. | ||
// | ||
|
||
import Foundation | ||
|
||
/// Convenience methods to provide custom percent encoding for URLQueryItem | ||
extension URLQueryItem { | ||
|
||
func percentEncoded() -> URLQueryItem { | ||
var newQueryItem = self | ||
newQueryItem.value = value? | ||
.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) | ||
|
||
return newQueryItem | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// | ||
// String+PlusSignEncoded.swift | ||
// | ||
// | ||
// Created by Tomas Cejka on 17.02.2024. | ||
// | ||
|
||
import Foundation | ||
|
||
public extension String { | ||
/// Help method to allow custom + sign encoding, more in ```CustomEncodedParameter``` | ||
func plusSignEncoded() -> Self? { | ||
self | ||
.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)? | ||
.replacingOccurrences(of: "+", with: "%2B") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
// | ||
// URLParametersTests.swift | ||
// | ||
// | ||
// Created by Matej Molnár on 02.01.2024. | ||
// | ||
|
||
import Networking | ||
import XCTest | ||
|
||
private let baseURLString = "https://requestable.tests" | ||
|
||
final class URLParametersTests: XCTestCase { | ||
enum Router: Requestable { | ||
case urlParameters([String: Any]) | ||
|
||
var baseURL: URL { | ||
// swiftlint:disable:next force_unwrapping | ||
URL(string: baseURLString)! | ||
} | ||
|
||
var path: String { | ||
"" | ||
} | ||
|
||
var urlParameters: [String: Any]? { | ||
switch self { | ||
case let .urlParameters(parameters): | ||
parameters | ||
} | ||
} | ||
} | ||
|
||
func testDefaultEncoding() async throws { | ||
let nameString = "name]surname" | ||
let namePercentEncodedString = "name%5Dsurname" | ||
|
||
let router = Router.urlParameters(["name": nameString]) | ||
let request = try router.asRequest() | ||
|
||
guard let url = request.url else { | ||
XCTFail("Can't create url from router") | ||
return | ||
} | ||
|
||
let queryItems = percentEncodedQueryItems(from: url) | ||
XCTAssertEqual( | ||
queryItems.first(where: { $0.name == "name" })?.value, | ||
namePercentEncodedString | ||
) | ||
} | ||
|
||
func testPlusSignDefaultEncoding() async throws { | ||
let dateString = "2023-11-29T12:13:04.598+0100" | ||
let router = Router.urlParameters(["date": dateString]) | ||
let request = try router.asRequest() | ||
|
||
guard let url = request.url else { | ||
XCTFail("Can't create url from router") | ||
return | ||
} | ||
|
||
let queryItems = percentEncodedQueryItems(from: url) | ||
XCTAssertEqual( | ||
queryItems.first(where: { $0.name == "date" })?.value, | ||
dateString | ||
) | ||
} | ||
|
||
func testPlusSignPercentEncodedParameter() async throws { | ||
let dateString = "2023-11-29T12:13:04.598+0100" | ||
let datePlusSignPercentEncodedString = "2023-11-29T12:13:04.598%2B0100" | ||
let router = Router.urlParameters(["date": CustomEncodedParameter(dateString.plusSignEncoded() ?? "")]) | ||
let request = try router.asRequest() | ||
|
||
guard let url = request.url else { | ||
XCTFail("Can't create url from router") | ||
return | ||
} | ||
|
||
let queryItems = percentEncodedQueryItems(from: url) | ||
XCTAssertEqual( | ||
queryItems.first(where: { $0.name == "date" })?.value, | ||
datePlusSignPercentEncodedString | ||
) | ||
} | ||
|
||
func testMixedPlusSignPercentEncodedParameter() async throws { | ||
let dateString = "2023-11-29T12:13:04.598+0100" | ||
let datePlusSignPercentEncodedString = "2023-11-29T12:13:04.598%2B0100" | ||
let searchString = "name+surname" | ||
|
||
let router = Router.urlParameters([ | ||
"date": CustomEncodedParameter(dateString.plusSignEncoded() ?? ""), | ||
"search": searchString | ||
]) | ||
let request = try router.asRequest() | ||
|
||
guard let url = request.url else { | ||
XCTFail("Can't create url from router") | ||
return | ||
} | ||
|
||
let queryItems = percentEncodedQueryItems(from: url) | ||
XCTAssertEqual( | ||
queryItems.first(where: { $0.name == "date" })?.value, | ||
datePlusSignPercentEncodedString | ||
) | ||
|
||
XCTAssertEqual( | ||
queryItems.first(where: { $0.name == "search" })?.value, | ||
searchString | ||
) | ||
} | ||
|
||
func testMixedPercentEncodedParameter() async throws { | ||
let dateString = "2023-11-29T12:13:04.598+0100" | ||
let datePlusSignPercentEncodedString = "2023-11-29T12:13:04.598%2B0100" | ||
let searchString = "name+surnam]e" | ||
let searchPercentEncodedString = "name+surnam%5De" | ||
|
||
let router = Router.urlParameters([ | ||
"date": CustomEncodedParameter(dateString.plusSignEncoded() ?? ""), | ||
"search": searchString | ||
]) | ||
let request = try router.asRequest() | ||
|
||
guard let url = request.url else { | ||
XCTFail("Can't create url from router") | ||
return | ||
} | ||
|
||
let queryItems = percentEncodedQueryItems(from: url) | ||
XCTAssertEqual( | ||
queryItems.first(where: { $0.name == "date" })?.value, | ||
datePlusSignPercentEncodedString | ||
) | ||
|
||
XCTAssertEqual( | ||
queryItems.first(where: { $0.name == "search" })?.value, | ||
searchPercentEncodedString | ||
) | ||
} | ||
|
||
func testCustomPercentEncodedParameter() async throws { | ||
let customPercentEncodedString = "2023-11-29T12:13:04.598%2B+%0100" | ||
let router = Router.urlParameters([ | ||
"date": CustomEncodedParameter(customPercentEncodedString) | ||
]) | ||
let request = try router.asRequest() | ||
|
||
guard let url = request.url else { | ||
XCTFail("Can't create url from router") | ||
return | ||
} | ||
|
||
let queryItems = percentEncodedQueryItems(from: url) | ||
XCTAssertEqual( | ||
queryItems.first(where: { $0.name == "date" })?.value, | ||
customPercentEncodedString | ||
) | ||
} | ||
} | ||
|
||
private extension URLParametersTests { | ||
// Helper method to create query items from URL to compare it with expected percent encoding | ||
func percentEncodedQueryItems(from: URL) -> [URLQueryItem] { | ||
let urlComponents = URLComponents(url: from, resolvingAgainstBaseURL: true) | ||
return urlComponents?.percentEncodedQueryItems ?? [] | ||
} | ||
} |