Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Add support for custom URL parameters percent encoding #56

Merged
merged 8 commits into from
Feb 26, 2024
80 changes: 56 additions & 24 deletions Sources/Networking/Core/Requestable+Convenience.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public extension Requestable {

// encode url parameters
if let urlParameters {
urlComponents.queryItems = buildQueryItems(urlParameters: urlParameters)
urlComponents.percentEncodedQueryItems = buildPercentEncodedQueryItems(urlParameters: urlParameters)
cejanen marked this conversation as resolved.
Show resolved Hide resolved
}

return urlComponents
Expand Down Expand Up @@ -108,36 +108,68 @@ public extension Requestable {

// MARK: Build Query Items
private extension Requestable {
func buildQueryItems(urlParameters: [String: Any]) -> [URLQueryItem] {
func buildPercentEncodedQueryItems(urlParameters: [String: Any]) -> [URLQueryItem] {
urlParameters
.map { key, value -> [URLQueryItem] in
buildQueryItems(key: key, value: value)
buildPercentEncodedQueryItems(key: key, value: value)
}
.flatMap { $0 }
}

func buildQueryItems(key: String, value: Any) -> [URLQueryItem] {
if let arrayType = value as? ArrayParameter {
var queryItems: [URLQueryItem] = []

switch arrayType.arrayEncoding {
case .commaSeparated:
queryItems = [URLQueryItem(

func buildPercentEncodedQueryItems(key: String, value: Any) -> [URLQueryItem] {
switch value {
case let parameter as ArrayParameter:
return buildArrayParameter(
key: key,
parameter: parameter
)
.map { $0.percentEncoded() }
case let parameter as PercentEncodedParameter:
return [
URLQueryItem(name: key, value: parameter.value)
]
default:
return [
URLQueryItem(
name: key,
value: arrayType.values.map { String(describing: $0) }.joined(separator: ",")
)]

case .individual:
for parameter in arrayType.values {
queryItems.append(URLQueryItem(
name: key,
value: String(describing: parameter)
))
}
value: String(describing: value)
)
.percentEncoded()
]
}
}

func buildArrayParameter(
key: String,
parameter: ArrayParameter
) -> [URLQueryItem] {
var queryItems: [URLQueryItem] = []

switch parameter.arrayEncoding {
case .commaSeparated:
queryItems = [URLQueryItem(
name: key,
value: parameter.values.map { String(describing: $0) }.joined(separator: ",")
)]

case .individual:
for parameter in parameter.values {
queryItems.append(URLQueryItem(
name: key,
value: String(describing: parameter)
))
}
return queryItems
}

return [URLQueryItem(name: key, value: String(describing: value))]
return queryItems
}
}

// MARK: - URLQueryItem convenience
private extension URLQueryItem {
func percentEncoded() -> URLQueryItem {
var newQueryItem = self
newQueryItem.value = value?.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)

return newQueryItem
}
}
34 changes: 34 additions & 0 deletions Sources/Networking/Misc/PercentEncodedParameter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// PercentEncodedParameter.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 PercentEncodedParameter {
let value: String

public init(_ value: String) {
self.value = value
}
}