Skip to content

Commit

Permalink
feat(request): Handle array query parameters (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
pjechris authored May 21, 2022
1 parent 6ec589c commit 06b560c
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 12 deletions.
2 changes: 1 addition & 1 deletion Sources/SimpleHTTP/Foundation/URL+Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ extension URL {
throw URLComponents.Error.invalid(path: request.path)
}

let queryItems = (components.queryItems ?? []) + request.parameters.queryItems
let queryItems = (components.queryItems ?? []) + request.query.queryItems

components.queryItems = queryItems.isEmpty ? nil : queryItems

Expand Down
18 changes: 18 additions & 0 deletions Sources/SimpleHTTP/Request/Dictionary+QueryParam.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Foundation

extension Dictionary where Key == String, Value == QueryParam {
/// transform query params into URLQueryItem`
var queryItems: [URLQueryItem] {
self.flatMap { key, value -> [URLQueryItem] in
switch value.queryValue {
case .single(let value):
return [URLQueryItem(name: key, value: value)]
case .collection(let values):
return values.map { URLQueryItem(name: "\(key)[]", value: $0) }
case .none:
return []
}
}
}

}
56 changes: 56 additions & 0 deletions Sources/SimpleHTTP/Request/QueryParam.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Foundation

/// Protocol allowing to use a type as a query parameter
public protocol QueryParam {
/// the parameter value
var queryValue: QueryValue? { get }
}

/// Query parameter value
public enum QueryValue: Equatable {
case single(String)
case collection([String])
}

extension QueryParam where Self: RawRepresentable, RawValue: QueryParam {
public var queryValue: QueryValue? { rawValue.queryValue }
}

extension Int: QueryParam {
public var queryValue: QueryValue? { .single(String(self)) }
}

extension String: QueryParam {
public var queryValue: QueryValue? { .single(self) }
}

extension Bool: QueryParam {
public var queryValue: QueryValue? { .single(self ? "true" : "false") }
}

extension Data: QueryParam {
public var queryValue: QueryValue? { String(data: self, encoding: .utf8).map(QueryValue.single) }
}

extension Optional: QueryParam where Wrapped: QueryParam {
public var queryValue: QueryValue? {
self.flatMap { $0.queryValue }
}
}

extension Array: QueryParam where Element: QueryParam {
public var queryValue: QueryValue? {
let values = self
.compactMap { $0.queryValue }
.flatMap { queryValue -> [String] in
switch queryValue {
case .single(let value):
return [value]
case .collection(let values):
return values
}
}

return .collection(values)
}
}
22 changes: 11 additions & 11 deletions Sources/SimpleHTTP/Request/Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,32 @@ public struct Request<Output> {
public let path: String
public let method: Method
public let body: Encodable?
public let parameters: [String: String]
public let query: [String: QueryParam]
public private(set) var headers: HTTPHeaderFields = [:]

public static func get(_ path: Path, parameters: [String: String] = [:]) -> Self {
self.init(path: path, method: .get, parameters: parameters, body: nil)
public static func get(_ path: Path, query: [String: QueryParam] = [:]) -> Self {
self.init(path: path, method: .get, query: query, body: nil)
}

public static func post(_ path: Path, body: Encodable?, parameters: [String: String] = [:])
public static func post(_ path: Path, body: Encodable?, query: [String: QueryParam] = [:])
-> Self {
self.init(path: path, method: .post, parameters: parameters, body: body)
self.init(path: path, method: .post, query: query, body: body)
}

public static func put(_ path: Path, body: Encodable, parameters: [String: String] = [:])
public static func put(_ path: Path, body: Encodable, query: [String: QueryParam] = [:])
-> Self {
self.init(path: path, method: .put, parameters: parameters, body: body)
self.init(path: path, method: .put, query: query, body: body)
}

public static func delete(_ path: Path, parameters: [String: String] = [:]) -> Self {
self.init(path: path, method: .delete, parameters: parameters, body: nil)
public static func delete(_ path: Path, query: [String: QueryParam] = [:]) -> Self {
self.init(path: path, method: .delete, query: query, body: nil)
}

private init(path: Path, method: Method, parameters: [String: String] = [:], body: Encodable?) {
private init(path: Path, method: Method, query: [String: QueryParam], body: Encodable?) {
self.path = path.path
self.method = method
self.body = body
self.parameters = parameters
self.query = query
}

/// add headers to the request
Expand Down
12 changes: 12 additions & 0 deletions Tests/SimpleHTTPTests/Request/QueryParamTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import XCTest
import SimpleHTTP

class QueryParamTests: XCTestCase {

func test_queryValue_multidimenstionalArray_returnFlattenCollection() {
let array: [[Int]] = [[1, 2, 3],[4,5,6]]

XCTAssertEqual(array.queryValue, .collection(["1", "2", "3", "4", "5", "6"]))
}

}

0 comments on commit 06b560c

Please sign in to comment.