Skip to content

Commit fca2114

Browse files
authored
Supported to QueryBuilder for using multiple Query(_, value:) declaration (#29)
1 parent d86f131 commit fca2114

File tree

10 files changed

+302
-69
lines changed

10 files changed

+302
-69
lines changed
Binary file not shown.

README.md

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ Request {
8484
...
8585
}
8686
```
87-
- Using `Dictionary` to `httpHeader` declaration.
87+
- Using `Dictionary<String, Any>` to `httpHeader` declaration.
8888
```swift
8989
Request {
9090
...
@@ -163,7 +163,6 @@ Router {
163163
}
164164
// https://www.overrideurl.com/comments?postId=1
165165
```
166-
167166
#### URL Scheme declaration
168167
- Using `Scheme(_ scheme:)` to `Scheme` declaration.
169168
```swift
@@ -187,7 +186,50 @@ Request {
187186
...
188187
}
189188
```
190-
189+
#### URL Query declaration
190+
- Using `Dictionary<String, String?>` to `Query` declaration.
191+
```swift
192+
Request {
193+
...
194+
URL {
195+
Query(
196+
[
197+
"first": "firstQuery",
198+
"second": "secondQuery",
199+
...
200+
]
201+
)
202+
}
203+
...
204+
}
205+
```
206+
- Using `Query(_, value:)` to `Query` declaration.
207+
```swift
208+
Request {
209+
...
210+
URL {
211+
Query("test", value: "query")
212+
Query("test", value: "query")
213+
...
214+
}
215+
...
216+
}
217+
```
218+
- Using `Field(_, forKey:)` to `Query` declaration.
219+
```swift
220+
Request {
221+
...
222+
URL {
223+
Query {
224+
Field("firstQuery", forKey: "first")
225+
Field("secondQuery", forKey: "second")
226+
...
227+
}
228+
...
229+
}
230+
...
231+
}
232+
```
191233
---
192234
### How to configure and use ***APIRouter*** in a real project?
193235
- Just create APIRouter.swift in your project! Happy hacking! 😁

Sources/APIRouter/HeaderBuilder.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public extension Header {
4646
}
4747
}
4848

49-
public struct Field: HeaderProtocol, BodyProtocol {
49+
public struct Field: HeaderProtocol, BodyProtocol, QueryProtocol {
5050
private let value: Any
5151
private let key: String
5252

@@ -74,4 +74,15 @@ public struct Field: HeaderProtocol, BodyProtocol {
7474
dictionary.updateValue(value, forKey: key)
7575
body = Body(dictionary)
7676
}
77+
78+
public func build(_ query: inout Query) {
79+
var queries: [URLQueryItem] = []
80+
for item in query.queries {
81+
queries.append(item)
82+
}
83+
if let value = value as? String {
84+
queries.append(URLQueryItem(name: key, value: value))
85+
}
86+
query = Query(queries)
87+
}
7788
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import Foundation
2+
3+
public protocol QueryProtocol {
4+
func build(_ query: inout Query)
5+
}
6+
7+
@resultBuilder
8+
public struct QueryBuilder {
9+
public static func buildBlock(_ components: QueryProtocol...) -> QueryProtocol {
10+
CombinedQuery(components)
11+
}
12+
13+
public static func buildArray(_ components: [QueryProtocol]) -> QueryProtocol {
14+
CombinedQuery(components)
15+
}
16+
17+
public static func buildEither(first component: QueryProtocol) -> QueryProtocol {
18+
CombinedQuery([component])
19+
}
20+
21+
public static func buildEither(second component: QueryProtocol) -> QueryProtocol {
22+
CombinedQuery([component])
23+
}
24+
}
25+
26+
private struct CombinedQuery: QueryProtocol {
27+
private let children: [QueryProtocol]
28+
29+
init(_ children: [QueryProtocol]) {
30+
self.children = children
31+
}
32+
33+
func build(_ query: inout Query) {
34+
children.forEach {
35+
$0.build(&query)
36+
}
37+
}
38+
}
39+
40+
public extension Query {
41+
init(@QueryBuilder _ fields: () -> QueryProtocol) {
42+
let combineQuery = fields()
43+
var queries = Query([:])
44+
combineQuery.build(&queries)
45+
self = queries
46+
}
47+
}

Sources/APIRouter/RequestBuilder.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -119,17 +119,19 @@ public struct Body: RequestProtocol {
119119
}
120120

121121
public struct URL: RequestProtocol {
122-
var components: URLComponents
122+
var components: URLComponents?
123+
var queryItems: Array<URLQueryItem> = []
123124

124125
public init(_ url: String) {
125-
self.components = URLComponents(string: url) ?? URLComponents()
126-
}
127-
128-
public init(_ components: URLComponents) {
129-
self.components = components
126+
self.components = URLComponents(string: url)
130127
}
131128

132129
public func build(_ apiRequest: inout Request) {
133-
apiRequest.urlRequest?.url = self.components.url
130+
apiRequest.urlComponents = self.components
131+
apiRequest.urlRequest?.url = self.components?.url
132+
if !queryItems.isEmpty {
133+
apiRequest.urlComponents?.queryItems = self.queryItems
134+
apiRequest.urlRequest?.url = apiRequest.urlComponents?.url
135+
}
134136
}
135137
}

Sources/APIRouter/RouterBuilder.swift

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public extension Router {
5353
public struct Router: _RouterProtocol {
5454
var request: Request
5555
public var urlRequest: URLRequest?
56+
public var urlComponents: URLComponents?
5657

5758
public init(_ request: Request) {
5859
self.request = request
@@ -65,29 +66,33 @@ public struct Router: _RouterProtocol {
6566

6667
public struct Request: _RouterProtocol {
6768
var urlRequest: URLRequest?
69+
var urlComponents: URLComponents?
6870

6971
public init(_ urlRequest: URLRequest?) {
7072
self.urlRequest = urlRequest
7173
}
7274

7375
public func build(_ router: inout Router) {
74-
guard let url = buildUrl(&router) else { return }
76+
if let url = buildUrl(&router) {
77+
router.request.urlRequest = URLRequest(url: url)
78+
router.request.urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false)
79+
}
7580

76-
router.request.urlRequest = URLRequest(url: url)
7781
router.request.urlRequest?.httpBody = self.urlRequest?.httpBody
7882
router.request.urlRequest?.httpMethod = self.urlRequest?.httpMethod
7983
router.request.urlRequest?.allHTTPHeaderFields = self.urlRequest?.allHTTPHeaderFields
8084

8185
router.urlRequest = router.request.urlRequest
86+
router.urlComponents = router.request.urlComponents
8287
}
8388

8489
private func buildUrl(_ router: inout Router) -> Foundation.URL? {
8590
var url: Foundation.URL?
86-
if let defaultUrl = urlRequest?.url?.absoluteString {
87-
if defaultUrl != "CANNOT_FIND_DEFAULT_URL" {
88-
url = Foundation.URL(string: defaultUrl, relativeTo: router.request.urlRequest?.url)
89-
} else {
90-
url = Foundation.URL(string: router.request.urlRequest?.url?.absoluteString ?? "")
91+
if let urlRequestString = urlRequest?.url?.absoluteString,
92+
let urlComponentsString = urlComponents?.url?.absoluteString {
93+
if urlRequestString != "CANNOT_FIND_DEFAULT_URL" {
94+
let urlString = urlRequestString > urlComponentsString ? urlRequestString : urlComponentsString
95+
url = Foundation.URL(string: urlString, relativeTo: router.request.urlRequest?.url)
9196
}
9297
}
9398
return url

Sources/APIRouter/URLBuilder.swift

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public struct Scheme: HttpUrlProtocol {
8686
}
8787

8888
public func build(_ url: inout URL) {
89-
url.components.scheme = self.scheme.rawValue
89+
url.components?.scheme = self.scheme.rawValue
9090
}
9191
}
9292

@@ -98,7 +98,7 @@ public struct Host: HttpUrlProtocol {
9898
}
9999

100100
public func build(_ url: inout URL) {
101-
url.components.host = self.host
101+
url.components?.host = self.host
102102
}
103103
}
104104

@@ -110,12 +110,16 @@ public struct Path: HttpUrlProtocol {
110110
}
111111

112112
public func build(_ url: inout URL) {
113-
url.components.path = self.path
113+
url.components?.path = self.path
114114
}
115115
}
116116

117117
public struct Query: HttpUrlProtocol {
118-
private var queries: [URLQueryItem] = []
118+
var queries: Array<URLQueryItem> = []
119+
120+
public init(_ queries: [URLQueryItem]) {
121+
self.queries = queries
122+
}
119123

120124
public init(_ name: String, value: String?) {
121125
self.queries.append(URLQueryItem(name: name, value: value))
@@ -128,6 +132,12 @@ public struct Query: HttpUrlProtocol {
128132
}
129133

130134
public func build(_ url: inout URL) {
131-
url.components.queryItems = self.queries
135+
if self.queries.count > 1 {
136+
url.components?.queryItems = self.queries
137+
} else {
138+
if let query = self.queries.first {
139+
url.queryItems.append(URLQueryItem(name: query.name, value: query.value))
140+
}
141+
}
132142
}
133143
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import Foundation
2+
import XCTest
3+
@testable import APIRouter
4+
5+
final class QueryBUilderTests: XCTestCase {
6+
func testGeneratedMultyUrlQueryWithQueryBuilder() {
7+
let request = Request {
8+
URL {
9+
Query {
10+
Field("firstQuery", forKey: "first")
11+
Field("secondQuery", forKey: "second")
12+
}
13+
}
14+
}
15+
16+
if let queryString = request.urlRequest?.url?.absoluteString {
17+
XCTAssertEqual(queryString.first, "?")
18+
XCTAssertEqual(queryString.contains("first=firstQuery"), true)
19+
XCTAssertEqual(queryString.contains("second=secondQuery"), true)
20+
XCTAssertEqual(queryString.split(separator: "&").count, 2)
21+
}
22+
}
23+
24+
func testSwitchConditionStatementWorkingForBuildEitherInQueryBuilder() {
25+
enum QueryOptions {
26+
case one
27+
case two
28+
29+
var request: Request {
30+
Request {
31+
URL {
32+
Query {
33+
switch self {
34+
case .one:
35+
Field("firstQuery", forKey: "first")
36+
case .two:
37+
Field("secondQuery", forKey: "second")
38+
}
39+
}
40+
}
41+
}
42+
}
43+
}
44+
45+
if let optionOneQueryFields = QueryOptions.one.request.urlComponents?.queryItems,
46+
let optionTwoQueryFields = QueryOptions.two.request.urlComponents?.queryItems {
47+
XCTAssertEqual(optionOneQueryFields.first?.debugDescription, "first=firstQuery")
48+
XCTAssertEqual(optionTwoQueryFields.first?.debugDescription, "second=secondQuery")
49+
}
50+
}
51+
52+
func testIfConditionalStatementWorkingForBuildEitherInQueryBuilder() {
53+
enum QueryOptions {
54+
case one
55+
case two
56+
57+
var request: Request {
58+
Request {
59+
URL {
60+
Query {
61+
if self == .one {
62+
Field("firstQuery", forKey: "first")
63+
} else {
64+
Field("secondQuery", forKey: "second")
65+
}
66+
}
67+
}
68+
}
69+
}
70+
}
71+
72+
if let optionOneQueryFields = QueryOptions.one.request.urlComponents?.queryItems,
73+
let optionTwoQueryFields = QueryOptions.two.request.urlComponents?.queryItems {
74+
XCTAssertEqual(optionOneQueryFields.first?.debugDescription, "first=firstQuery")
75+
XCTAssertEqual(optionTwoQueryFields.first?.debugDescription, "second=secondQuery")
76+
}
77+
}
78+
79+
func testForLoopStatementWorkingForBuildEitherInQueryBuilder() {
80+
let fields = [
81+
"first": "firstQuery",
82+
"second": "secondQuery",
83+
"third": "thirdQuery"
84+
]
85+
86+
let request = Request {
87+
URL {
88+
Query {
89+
for field in fields {
90+
Field(field.value, forKey: field.key)
91+
}
92+
}
93+
}
94+
}
95+
96+
var mockQueryItems: [URLQueryItem] = []
97+
for field in fields {
98+
mockQueryItems.append(URLQueryItem(name: field.key, value: field.value))
99+
}
100+
101+
if let queryItems = request.urlComponents?.queryItems {
102+
XCTAssertEqual(queryItems, mockQueryItems)
103+
}
104+
}
105+
}

0 commit comments

Comments
 (0)