Skip to content

Commit 758f24f

Browse files
authored
Merge pull request #229 from matsuda/feature/queryParameters
Add QueryParameters protocol to provide interface for URL query.
2 parents b0da296 + 263ba3f commit 758f24f

File tree

8 files changed

+89
-14
lines changed

8 files changed

+89
-14
lines changed

APIKit.xcodeproj/project.pbxproj

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
0ED17E891E229EC700EC1114 /* QueryParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED17E881E229EC700EC1114 /* QueryParameters.swift */; };
11+
0ED17E8B1E22A22900EC1114 /* URLEncodedQueryParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED17E8A1E22A22900EC1114 /* URLEncodedQueryParameters.swift */; };
12+
0ED17E8E1E22AC8300EC1114 /* URLEncodedQueryParametersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED17E8D1E22AC8300EC1114 /* URLEncodedQueryParametersTests.swift */; };
1013
141F12201C1C9ABE0026D415 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD5115241B1FFBA900514240 /* Result.framework */; };
1114
141F12311C1C9AC70026D415 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD5115241B1FFBA900514240 /* Result.framework */; };
1215
141F12361C1C9AC70026D415 /* Result.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = CD5115241B1FFBA900514240 /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -80,6 +83,9 @@
8083
/* End PBXCopyFilesBuildPhase section */
8184

8285
/* Begin PBXFileReference section */
86+
0ED17E881E229EC700EC1114 /* QueryParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryParameters.swift; sourceTree = "<group>"; };
87+
0ED17E8A1E22A22900EC1114 /* URLEncodedQueryParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLEncodedQueryParameters.swift; sourceTree = "<group>"; };
88+
0ED17E8D1E22AC8300EC1114 /* URLEncodedQueryParametersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLEncodedQueryParametersTests.swift; sourceTree = "<group>"; };
8389
141F120F1C1C96820026D415 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Base.xcconfig; path = Configurations/Base.xcconfig; sourceTree = "<group>"; };
8490
141F12101C1C96820026D415 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Configurations/Debug.xcconfig; sourceTree = "<group>"; };
8591
141F12111C1C96820026D415 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Configurations/Release.xcconfig; sourceTree = "<group>"; };
@@ -156,6 +162,24 @@
156162
/* End PBXFrameworksBuildPhase section */
157163

158164
/* Begin PBXGroup section */
165+
0ED17E871E229EAC00EC1114 /* QueryParameters */ = {
166+
isa = PBXGroup;
167+
children = (
168+
0ED17E881E229EC700EC1114 /* QueryParameters.swift */,
169+
0ED17E8A1E22A22900EC1114 /* URLEncodedQueryParameters.swift */,
170+
);
171+
name = QueryParameters;
172+
path = APIKit/QueryParameters;
173+
sourceTree = "<group>";
174+
};
175+
0ED17E8C1E22AC5D00EC1114 /* QueryParameters */ = {
176+
isa = PBXGroup;
177+
children = (
178+
0ED17E8D1E22AC8300EC1114 /* URLEncodedQueryParametersTests.swift */,
179+
);
180+
path = QueryParameters;
181+
sourceTree = "<group>";
182+
};
159183
141F120E1C1C96690026D415 /* Configurations */ = {
160184
isa = PBXGroup;
161185
children = (
@@ -224,6 +248,7 @@
224248
7F698E451D9D680C00F1561D /* RequestTests.swift */,
225249
7F698E491D9D680C00F1561D /* SessionCallbackQueueTests.swift */,
226250
7F698E4A1D9D680C00F1561D /* SessionTests.swift */,
251+
0ED17E8C1E22AC5D00EC1114 /* QueryParameters */,
227252
7F698E3B1D9D680C00F1561D /* BodyParametersType */,
228253
7F698E401D9D680C00F1561D /* DataParserType */,
229254
7F698E461D9D680C00F1561D /* SessionAdapterType */,
@@ -294,6 +319,7 @@
294319
7F7048CB1D9D89BE003C99F6 /* Session.swift */,
295320
7F7048CC1D9D89BE003C99F6 /* Unavailable.swift */,
296321
7F85FB8B1C9D317300CEE132 /* SessionAdapter */,
322+
0ED17E871E229EAC00EC1114 /* QueryParameters */,
297323
7F18BD0D1C972C38003A31DF /* BodyParameters */,
298324
7FA19A441C9CC9A2005D25AE /* DataParser */,
299325
7F18BD161C9730ED003A31DF /* Serializations */,
@@ -457,12 +483,14 @@
457483
files = (
458484
7F7048D31D9D89BE003C99F6 /* Unavailable.swift in Sources */,
459485
7F7048D11D9D89BE003C99F6 /* Request.swift in Sources */,
486+
0ED17E891E229EC700EC1114 /* QueryParameters.swift in Sources */,
460487
7F7048E81D9D8A08003C99F6 /* DataParser.swift in Sources */,
461488
7F7048CE1D9D89BE003C99F6 /* CallbackQueue.swift in Sources */,
462489
7F7048DE1D9D89FB003C99F6 /* AbstractInputStream.m in Sources */,
463490
7F7048E31D9D89FB003C99F6 /* MultipartFormDataBodyParameters.swift in Sources */,
464491
7F7048F01D9D8A12003C99F6 /* ResponseError.swift in Sources */,
465492
7F7048EA1D9D8A08003C99F6 /* JSONDataParser.swift in Sources */,
493+
0ED17E8B1E22A22900EC1114 /* URLEncodedQueryParameters.swift in Sources */,
466494
7F7048D21D9D89BE003C99F6 /* Session.swift in Sources */,
467495
7F7048E01D9D89FB003C99F6 /* Data+InputStream.swift in Sources */,
468496
7F7048DF1D9D89FB003C99F6 /* BodyParameters.swift in Sources */,
@@ -491,6 +519,7 @@
491519
7F698E501D9D680C00F1561D /* FormURLEncodedBodyParametersTests.swift in Sources */,
492520
7F698E581D9D680C00F1561D /* RequestTests.swift in Sources */,
493521
ECA8314A1DE4DEBE004EB1B5 /* ProtobufDataParserTests.swift in Sources */,
522+
0ED17E8E1E22AC8300EC1114 /* URLEncodedQueryParametersTests.swift in Sources */,
494523
7F698E5E1D9D680C00F1561D /* TestRequest.swift in Sources */,
495524
7F698E601D9D680C00F1561D /* TestSessionTask.swift in Sources */,
496525
7FA1690D1D9D8C80006C982B /* HTTPStub.swift in Sources */,

Sources/APIKit/BodyParameters/BodyParameters.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ public protocol BodyParameters {
1515
var contentType: String { get }
1616

1717
/// Builds `RequestBodyEntity`.
18-
/// Throws: `ErrorType`
18+
/// Throws: `Error`
1919
func buildEntity() throws -> RequestBodyEntity
2020
}

Sources/APIKit/DataParser/DataParser.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ public protocol DataParser {
66
var contentType: String? { get }
77

88
/// Return `Any` that expresses structure of response such as JSON and XML.
9-
/// - Throws: `ErrorType` when parser encountered invalid format data.
9+
/// - Throws: `Error` when parser encountered invalid format data.
1010
func parse(data: Data) throws -> Any
1111
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Foundation
2+
3+
/// `QueryParameters` provides interface to generate HTTP URL query strings.
4+
public protocol QueryParameters {
5+
/// Generate URL query strings.
6+
func encode() -> String?
7+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import Foundation
2+
3+
/// `URLEncodedQueryParameters` serializes form object for HTTP URL query.
4+
public struct URLEncodedQueryParameters: QueryParameters {
5+
/// The parameters to be url encoded.
6+
public let parameters: Any
7+
8+
/// Returns `URLEncodedQueryParameters` that is initialized with parameters.
9+
public init(parameters: Any) {
10+
self.parameters = parameters
11+
}
12+
13+
/// Generate url encoded `String`.
14+
public func encode() -> String? {
15+
guard let parameters = parameters as? [String: Any], !parameters.isEmpty else {
16+
return nil
17+
}
18+
return URLEncodedSerialization.string(from: parameters)
19+
}
20+
}

Sources/APIKit/Request.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public protocol Request {
2929
/// The actual parameters for the URL query. The values of this property will be escaped using `URLEncodedSerialization`.
3030
/// If this property is not implemented and `method.prefersQueryParameter` is `true`, the value of this property
3131
/// will be computed from `parameters`.
32-
var queryParameters: [String: Any]? { get }
32+
var queryParameters: QueryParameters? { get }
3333

3434
/// The actual parameters for the HTTP body. If this property is not implemented and `method.prefersQueryParameter` is `false`,
3535
/// the value of this property will be computed from `parameters` using `JSONBodyParameters`.
@@ -45,19 +45,19 @@ public protocol Request {
4545

4646
/// Intercepts `URLRequest` which is created by `Request.buildURLRequest()`. If an error is
4747
/// thrown in this method, the result of `Session.send()` turns `.failure(.requestError(error))`.
48-
/// - Throws: `ErrorType`
48+
/// - Throws: `Error`
4949
func intercept(urlRequest: URLRequest) throws -> URLRequest
5050

5151
/// Intercepts response `Any` and `HTTPURLResponse`. If an error is thrown in this method,
5252
/// the result of `Session.send()` turns `.failure(.responseError(error))`.
5353
/// The default implementation of this method is provided to throw `RequestError.unacceptableStatusCode`
5454
/// if the HTTP status code is not in `200..<300`.
55-
/// - Throws: `ErrorType`
55+
/// - Throws: `Error`
5656
func intercept(object: Any, urlResponse: HTTPURLResponse) throws -> Any
5757

5858
/// Build `Response` instance from raw response object. This method is called after
5959
/// `intercept(object:urlResponse:)` if it does not throw any error.
60-
/// - Throws: `ErrorType`
60+
/// - Throws: `Error`
6161
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response
6262
}
6363

@@ -66,12 +66,12 @@ public extension Request {
6666
return nil
6767
}
6868

69-
public var queryParameters: [String: Any]? {
70-
guard let parameters = parameters as? [String: Any], method.prefersQueryParameters else {
69+
public var queryParameters: QueryParameters? {
70+
guard let parameters = parameters, method.prefersQueryParameters else {
7171
return nil
7272
}
7373

74-
return parameters
74+
return URLEncodedQueryParameters(parameters: parameters)
7575
}
7676

7777
public var bodyParameters: BodyParameters? {
@@ -102,7 +102,7 @@ public extension Request {
102102
}
103103

104104
/// Builds `URLRequest` from properties of `self`.
105-
/// - Throws: `RequestError`, `ErrorType`
105+
/// - Throws: `RequestError`, `Error`
106106
public func buildURLRequest() throws -> URLRequest {
107107
let url = path.isEmpty ? baseURL : baseURL.appendingPathComponent(path)
108108
guard var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
@@ -111,8 +111,8 @@ public extension Request {
111111

112112
var urlRequest = URLRequest(url: url)
113113

114-
if let queryParameters = queryParameters, !queryParameters.isEmpty {
115-
components.percentEncodedQuery = URLEncodedSerialization.string(from: queryParameters)
114+
if let queryString = queryParameters?.encode(), !queryString.isEmpty {
115+
components.percentEncodedQuery = queryString
116116
}
117117

118118
if let bodyParameters = bodyParameters {
@@ -139,7 +139,7 @@ public extension Request {
139139
}
140140

141141
/// Builds `Response` from response `Data`.
142-
/// - Throws: `ResponseError`, `ErrorType`
142+
/// - Throws: `ResponseError`, `Error`
143143
public func parse(data: Data, urlResponse: HTTPURLResponse) throws -> Response {
144144
let parsedObject = try dataParser.parse(data: data)
145145
let passedObject = try intercept(object: parsedObject, urlResponse: urlResponse)

Sources/APIKit/Serializations/URLEncodedSerialization.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public final class URLEncodedSerialization {
8787
return data
8888
}
8989

90-
/// Returns urlencoded `Data` from the string.
90+
/// Returns urlencoded `String` from the dictionary.
9191
public static func string(from dictionary: [String: Any]) -> String {
9292
let pairs = dictionary.map { key, value -> String in
9393
if value is NSNull {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import XCTest
2+
import APIKit
3+
4+
class URLEncodedQueryParametersTests: XCTestCase {
5+
func testURLEncodedSuccess() {
6+
let object: [String: Any] = ["foo": "string", "bar": 1, "q": "こんにちは"]
7+
let parameters = URLEncodedQueryParameters(parameters: object)
8+
guard let query = parameters.encode() else {
9+
XCTFail()
10+
return
11+
}
12+
13+
let items = query.components(separatedBy: "&")
14+
XCTAssertEqual(items.count, 3)
15+
XCTAssertTrue(items.contains("foo=string"))
16+
XCTAssertTrue(items.contains("bar=1"))
17+
XCTAssertTrue(items.contains("q=%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF"))
18+
}
19+
}

0 commit comments

Comments
 (0)