From 74e985db4b53fcb13a509c483684c335adcf3b5d Mon Sep 17 00:00:00 2001 From: matsuda Date: Sun, 15 Jan 2017 00:57:50 +0900 Subject: [PATCH 1/3] fix word `ErrorType` --- Sources/APIKit/BodyParameters/BodyParameters.swift | 2 +- Sources/APIKit/DataParser/DataParser.swift | 2 +- Sources/APIKit/Request.swift | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/APIKit/BodyParameters/BodyParameters.swift b/Sources/APIKit/BodyParameters/BodyParameters.swift index 6670ca56..d90a856d 100644 --- a/Sources/APIKit/BodyParameters/BodyParameters.swift +++ b/Sources/APIKit/BodyParameters/BodyParameters.swift @@ -15,6 +15,6 @@ public protocol BodyParameters { var contentType: String { get } /// Builds `RequestBodyEntity`. - /// Throws: `ErrorType` + /// Throws: `Error` func buildEntity() throws -> RequestBodyEntity } diff --git a/Sources/APIKit/DataParser/DataParser.swift b/Sources/APIKit/DataParser/DataParser.swift index 1722bb51..671f465f 100644 --- a/Sources/APIKit/DataParser/DataParser.swift +++ b/Sources/APIKit/DataParser/DataParser.swift @@ -6,6 +6,6 @@ public protocol DataParser { var contentType: String? { get } /// Return `Any` that expresses structure of response such as JSON and XML. - /// - Throws: `ErrorType` when parser encountered invalid format data. + /// - Throws: `Error` when parser encountered invalid format data. func parse(data: Data) throws -> Any } diff --git a/Sources/APIKit/Request.swift b/Sources/APIKit/Request.swift index 4f31bb24..b14b29ab 100644 --- a/Sources/APIKit/Request.swift +++ b/Sources/APIKit/Request.swift @@ -45,19 +45,19 @@ public protocol Request { /// Intercepts `URLRequest` which is created by `Request.buildURLRequest()`. If an error is /// thrown in this method, the result of `Session.send()` turns `.failure(.requestError(error))`. - /// - Throws: `ErrorType` + /// - Throws: `Error` func intercept(urlRequest: URLRequest) throws -> URLRequest /// Intercepts response `Any` and `HTTPURLResponse`. If an error is thrown in this method, /// the result of `Session.send()` turns `.failure(.responseError(error))`. /// The default implementation of this method is provided to throw `RequestError.unacceptableStatusCode` /// if the HTTP status code is not in `200..<300`. - /// - Throws: `ErrorType` + /// - Throws: `Error` func intercept(object: Any, urlResponse: HTTPURLResponse) throws -> Any /// Build `Response` instance from raw response object. This method is called after /// `intercept(object:urlResponse:)` if it does not throw any error. - /// - Throws: `ErrorType` + /// - Throws: `Error` func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response } @@ -102,7 +102,7 @@ public extension Request { } /// Builds `URLRequest` from properties of `self`. - /// - Throws: `RequestError`, `ErrorType` + /// - Throws: `RequestError`, `Error` public func buildURLRequest() throws -> URLRequest { let url = path.isEmpty ? baseURL : baseURL.appendingPathComponent(path) guard var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { @@ -139,7 +139,7 @@ public extension Request { } /// Builds `Response` from response `Data`. - /// - Throws: `ResponseError`, `ErrorType` + /// - Throws: `ResponseError`, `Error` public func parse(data: Data, urlResponse: HTTPURLResponse) throws -> Response { let parsedObject = try dataParser.parse(data: data) let passedObject = try intercept(object: parsedObject, urlResponse: urlResponse) From a0d4636d78b1471240942825e129a946e0772700 Mon Sep 17 00:00:00 2001 From: matsuda Date: Sun, 15 Jan 2017 00:59:51 +0900 Subject: [PATCH 2/3] fix method's comment --- Sources/APIKit/Serializations/URLEncodedSerialization.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/APIKit/Serializations/URLEncodedSerialization.swift b/Sources/APIKit/Serializations/URLEncodedSerialization.swift index 48d000d8..cb996b39 100644 --- a/Sources/APIKit/Serializations/URLEncodedSerialization.swift +++ b/Sources/APIKit/Serializations/URLEncodedSerialization.swift @@ -87,7 +87,7 @@ public final class URLEncodedSerialization { return data } - /// Returns urlencoded `Data` from the string. + /// Returns urlencoded `String` from the dictionary. public static func string(from dictionary: [String: Any]) -> String { let pairs = dictionary.map { key, value -> String in if value is NSNull { From 263ba3f6009a35f9e19a0c6594c3ca184f603b88 Mon Sep 17 00:00:00 2001 From: matsuda Date: Mon, 9 Jan 2017 02:13:17 +0900 Subject: [PATCH 3/3] Add QueryParameters protocol to provide interface for URL query. --- APIKit.xcodeproj/project.pbxproj | 29 +++++++++++++++++++ .../QueryParameters/QueryParameters.swift | 7 +++++ .../URLEncodedQueryParameters.swift | 20 +++++++++++++ Sources/APIKit/Request.swift | 12 ++++---- .../URLEncodedQueryParametersTests.swift | 19 ++++++++++++ 5 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 Sources/APIKit/QueryParameters/QueryParameters.swift create mode 100644 Sources/APIKit/QueryParameters/URLEncodedQueryParameters.swift create mode 100644 Tests/APIKitTests/QueryParameters/URLEncodedQueryParametersTests.swift diff --git a/APIKit.xcodeproj/project.pbxproj b/APIKit.xcodeproj/project.pbxproj index 1dc7c5ba..143f12c4 100644 --- a/APIKit.xcodeproj/project.pbxproj +++ b/APIKit.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 0ED17E891E229EC700EC1114 /* QueryParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED17E881E229EC700EC1114 /* QueryParameters.swift */; }; + 0ED17E8B1E22A22900EC1114 /* URLEncodedQueryParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED17E8A1E22A22900EC1114 /* URLEncodedQueryParameters.swift */; }; + 0ED17E8E1E22AC8300EC1114 /* URLEncodedQueryParametersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED17E8D1E22AC8300EC1114 /* URLEncodedQueryParametersTests.swift */; }; 141F12201C1C9ABE0026D415 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD5115241B1FFBA900514240 /* Result.framework */; }; 141F12311C1C9AC70026D415 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD5115241B1FFBA900514240 /* Result.framework */; }; 141F12361C1C9AC70026D415 /* Result.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = CD5115241B1FFBA900514240 /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -80,6 +83,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0ED17E881E229EC700EC1114 /* QueryParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryParameters.swift; sourceTree = ""; }; + 0ED17E8A1E22A22900EC1114 /* URLEncodedQueryParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLEncodedQueryParameters.swift; sourceTree = ""; }; + 0ED17E8D1E22AC8300EC1114 /* URLEncodedQueryParametersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLEncodedQueryParametersTests.swift; sourceTree = ""; }; 141F120F1C1C96820026D415 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Base.xcconfig; path = Configurations/Base.xcconfig; sourceTree = ""; }; 141F12101C1C96820026D415 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Configurations/Debug.xcconfig; sourceTree = ""; }; 141F12111C1C96820026D415 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Configurations/Release.xcconfig; sourceTree = ""; }; @@ -156,6 +162,24 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0ED17E871E229EAC00EC1114 /* QueryParameters */ = { + isa = PBXGroup; + children = ( + 0ED17E881E229EC700EC1114 /* QueryParameters.swift */, + 0ED17E8A1E22A22900EC1114 /* URLEncodedQueryParameters.swift */, + ); + name = QueryParameters; + path = APIKit/QueryParameters; + sourceTree = ""; + }; + 0ED17E8C1E22AC5D00EC1114 /* QueryParameters */ = { + isa = PBXGroup; + children = ( + 0ED17E8D1E22AC8300EC1114 /* URLEncodedQueryParametersTests.swift */, + ); + path = QueryParameters; + sourceTree = ""; + }; 141F120E1C1C96690026D415 /* Configurations */ = { isa = PBXGroup; children = ( @@ -224,6 +248,7 @@ 7F698E451D9D680C00F1561D /* RequestTests.swift */, 7F698E491D9D680C00F1561D /* SessionCallbackQueueTests.swift */, 7F698E4A1D9D680C00F1561D /* SessionTests.swift */, + 0ED17E8C1E22AC5D00EC1114 /* QueryParameters */, 7F698E3B1D9D680C00F1561D /* BodyParametersType */, 7F698E401D9D680C00F1561D /* DataParserType */, 7F698E461D9D680C00F1561D /* SessionAdapterType */, @@ -294,6 +319,7 @@ 7F7048CB1D9D89BE003C99F6 /* Session.swift */, 7F7048CC1D9D89BE003C99F6 /* Unavailable.swift */, 7F85FB8B1C9D317300CEE132 /* SessionAdapter */, + 0ED17E871E229EAC00EC1114 /* QueryParameters */, 7F18BD0D1C972C38003A31DF /* BodyParameters */, 7FA19A441C9CC9A2005D25AE /* DataParser */, 7F18BD161C9730ED003A31DF /* Serializations */, @@ -457,12 +483,14 @@ files = ( 7F7048D31D9D89BE003C99F6 /* Unavailable.swift in Sources */, 7F7048D11D9D89BE003C99F6 /* Request.swift in Sources */, + 0ED17E891E229EC700EC1114 /* QueryParameters.swift in Sources */, 7F7048E81D9D8A08003C99F6 /* DataParser.swift in Sources */, 7F7048CE1D9D89BE003C99F6 /* CallbackQueue.swift in Sources */, 7F7048DE1D9D89FB003C99F6 /* AbstractInputStream.m in Sources */, 7F7048E31D9D89FB003C99F6 /* MultipartFormDataBodyParameters.swift in Sources */, 7F7048F01D9D8A12003C99F6 /* ResponseError.swift in Sources */, 7F7048EA1D9D8A08003C99F6 /* JSONDataParser.swift in Sources */, + 0ED17E8B1E22A22900EC1114 /* URLEncodedQueryParameters.swift in Sources */, 7F7048D21D9D89BE003C99F6 /* Session.swift in Sources */, 7F7048E01D9D89FB003C99F6 /* Data+InputStream.swift in Sources */, 7F7048DF1D9D89FB003C99F6 /* BodyParameters.swift in Sources */, @@ -491,6 +519,7 @@ 7F698E501D9D680C00F1561D /* FormURLEncodedBodyParametersTests.swift in Sources */, 7F698E581D9D680C00F1561D /* RequestTests.swift in Sources */, ECA8314A1DE4DEBE004EB1B5 /* ProtobufDataParserTests.swift in Sources */, + 0ED17E8E1E22AC8300EC1114 /* URLEncodedQueryParametersTests.swift in Sources */, 7F698E5E1D9D680C00F1561D /* TestRequest.swift in Sources */, 7F698E601D9D680C00F1561D /* TestSessionTask.swift in Sources */, 7FA1690D1D9D8C80006C982B /* HTTPStub.swift in Sources */, diff --git a/Sources/APIKit/QueryParameters/QueryParameters.swift b/Sources/APIKit/QueryParameters/QueryParameters.swift new file mode 100644 index 00000000..6e9f16a5 --- /dev/null +++ b/Sources/APIKit/QueryParameters/QueryParameters.swift @@ -0,0 +1,7 @@ +import Foundation + +/// `QueryParameters` provides interface to generate HTTP URL query strings. +public protocol QueryParameters { + /// Generate URL query strings. + func encode() -> String? +} diff --git a/Sources/APIKit/QueryParameters/URLEncodedQueryParameters.swift b/Sources/APIKit/QueryParameters/URLEncodedQueryParameters.swift new file mode 100644 index 00000000..e4c0f476 --- /dev/null +++ b/Sources/APIKit/QueryParameters/URLEncodedQueryParameters.swift @@ -0,0 +1,20 @@ +import Foundation + +/// `URLEncodedQueryParameters` serializes form object for HTTP URL query. +public struct URLEncodedQueryParameters: QueryParameters { + /// The parameters to be url encoded. + public let parameters: Any + + /// Returns `URLEncodedQueryParameters` that is initialized with parameters. + public init(parameters: Any) { + self.parameters = parameters + } + + /// Generate url encoded `String`. + public func encode() -> String? { + guard let parameters = parameters as? [String: Any], !parameters.isEmpty else { + return nil + } + return URLEncodedSerialization.string(from: parameters) + } +} diff --git a/Sources/APIKit/Request.swift b/Sources/APIKit/Request.swift index b14b29ab..18bcb80e 100644 --- a/Sources/APIKit/Request.swift +++ b/Sources/APIKit/Request.swift @@ -29,7 +29,7 @@ public protocol Request { /// The actual parameters for the URL query. The values of this property will be escaped using `URLEncodedSerialization`. /// If this property is not implemented and `method.prefersQueryParameter` is `true`, the value of this property /// will be computed from `parameters`. - var queryParameters: [String: Any]? { get } + var queryParameters: QueryParameters? { get } /// The actual parameters for the HTTP body. If this property is not implemented and `method.prefersQueryParameter` is `false`, /// the value of this property will be computed from `parameters` using `JSONBodyParameters`. @@ -66,12 +66,12 @@ public extension Request { return nil } - public var queryParameters: [String: Any]? { - guard let parameters = parameters as? [String: Any], method.prefersQueryParameters else { + public var queryParameters: QueryParameters? { + guard let parameters = parameters, method.prefersQueryParameters else { return nil } - return parameters + return URLEncodedQueryParameters(parameters: parameters) } public var bodyParameters: BodyParameters? { @@ -111,8 +111,8 @@ public extension Request { var urlRequest = URLRequest(url: url) - if let queryParameters = queryParameters, !queryParameters.isEmpty { - components.percentEncodedQuery = URLEncodedSerialization.string(from: queryParameters) + if let queryString = queryParameters?.encode(), !queryString.isEmpty { + components.percentEncodedQuery = queryString } if let bodyParameters = bodyParameters { diff --git a/Tests/APIKitTests/QueryParameters/URLEncodedQueryParametersTests.swift b/Tests/APIKitTests/QueryParameters/URLEncodedQueryParametersTests.swift new file mode 100644 index 00000000..a764e00c --- /dev/null +++ b/Tests/APIKitTests/QueryParameters/URLEncodedQueryParametersTests.swift @@ -0,0 +1,19 @@ +import XCTest +import APIKit + +class URLEncodedQueryParametersTests: XCTestCase { + func testURLEncodedSuccess() { + let object: [String: Any] = ["foo": "string", "bar": 1, "q": "こんにちは"] + let parameters = URLEncodedQueryParameters(parameters: object) + guard let query = parameters.encode() else { + XCTFail() + return + } + + let items = query.components(separatedBy: "&") + XCTAssertEqual(items.count, 3) + XCTAssertTrue(items.contains("foo=string")) + XCTAssertTrue(items.contains("bar=1")) + XCTAssertTrue(items.contains("q=%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF")) + } +}