From f88d9c97f6ae43224cae1b3102c60e54ba985765 Mon Sep 17 00:00:00 2001 From: matsuda Date: Mon, 9 Jan 2017 02:13:17 +0900 Subject: [PATCH] Add QueryParameters protocol to provide interface for URL query. (cherry picked from commit 263ba3f6009a35f9e19a0c6594c3ca184f603b88) --- 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 e460ab57..4b034458 100644 --- a/APIKit.xcodeproj/project.pbxproj +++ b/APIKit.xcodeproj/project.pbxproj @@ -50,6 +50,9 @@ C5725F4B28D8C36500810D7C /* Concurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5725F4A28D8C36500810D7C /* Concurrency.swift */; }; C5B144D828D8D7DC00E30ECD /* ConcurrencyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B144D728D8D7DC00E30ECD /* ConcurrencyTests.swift */; }; C5FF1DC128A80FFD0059573D /* test.json in Resources */ = {isa = PBXBuildFile; fileRef = C5FF1DC028A80FFD0059573D /* test.json */; }; + C5FF1DCF28A835600059573D /* QueryParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5FF1DCD28A835600059573D /* QueryParameters.swift */; }; + C5FF1DD028A835600059573D /* URLEncodedQueryParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5FF1DCE28A835600059573D /* URLEncodedQueryParameters.swift */; }; + C5FF1DD328A835680059573D /* URLEncodedQueryParametersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5FF1DD228A835680059573D /* URLEncodedQueryParametersTests.swift */; }; ECA831481DE4DDBF004EB1B5 /* ProtobufDataParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA831471DE4DDBF004EB1B5 /* ProtobufDataParser.swift */; }; ECA8314A1DE4DEBE004EB1B5 /* ProtobufDataParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA831491DE4DEBE004EB1B5 /* ProtobufDataParserTests.swift */; }; ECA8314C1DE4E677004EB1B5 /* ProtobufBodyParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA8314B1DE4E677004EB1B5 /* ProtobufBodyParameters.swift */; }; @@ -133,6 +136,9 @@ C5725F4A28D8C36500810D7C /* Concurrency.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Concurrency.swift; sourceTree = ""; }; C5B144D728D8D7DC00E30ECD /* ConcurrencyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConcurrencyTests.swift; sourceTree = ""; }; C5FF1DC028A80FFD0059573D /* test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = test.json; sourceTree = ""; }; + C5FF1DCD28A835600059573D /* QueryParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryParameters.swift; sourceTree = ""; }; + C5FF1DCE28A835600059573D /* URLEncodedQueryParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLEncodedQueryParameters.swift; sourceTree = ""; }; + C5FF1DD228A835680059573D /* URLEncodedQueryParametersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLEncodedQueryParametersTests.swift; sourceTree = ""; }; ECA831471DE4DDBF004EB1B5 /* ProtobufDataParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProtobufDataParser.swift; path = Sources/APIKit/DataParser/ProtobufDataParser.swift; sourceTree = SOURCE_ROOT; }; ECA831491DE4DEBE004EB1B5 /* ProtobufDataParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProtobufDataParserTests.swift; sourceTree = ""; }; ECA8314B1DE4E677004EB1B5 /* ProtobufBodyParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProtobufBodyParameters.swift; path = Sources/APIKit/BodyParameters/ProtobufBodyParameters.swift; sourceTree = SOURCE_ROOT; }; @@ -246,6 +252,7 @@ 7F698E4A1D9D680C00F1561D /* SessionTests.swift */, C5B144D628D8D7D000E30ECD /* Concurrency */, 0973EE33259E2DD000879BA2 /* Combine */, + C5FF1DD128A835680059573D /* QueryParameters */, 7F698E3B1D9D680C00F1561D /* BodyParametersType */, 7F698E401D9D680C00F1561D /* DataParserType */, 7F698E461D9D680C00F1561D /* SessionAdapterType */, @@ -318,6 +325,7 @@ C5725F4928D8C36500810D7C /* Concurrency */, 0969AE0D259DEC3C00C498AF /* Combine */, 7F85FB8B1C9D317300CEE132 /* SessionAdapter */, + C5FF1DCC28A835600059573D /* QueryParameters */, 7F18BD0D1C972C38003A31DF /* BodyParameters */, 7FA19A441C9CC9A2005D25AE /* DataParser */, 7F18BD161C9730ED003A31DF /* Serializations */, @@ -395,6 +403,24 @@ path = Resources; sourceTree = ""; }; + C5FF1DCC28A835600059573D /* QueryParameters */ = { + isa = PBXGroup; + children = ( + C5FF1DCD28A835600059573D /* QueryParameters.swift */, + C5FF1DCE28A835600059573D /* URLEncodedQueryParameters.swift */, + ); + name = QueryParameters; + path = APIKit/QueryParameters; + sourceTree = ""; + }; + C5FF1DD128A835680059573D /* QueryParameters */ = { + isa = PBXGroup; + children = ( + C5FF1DD228A835680059573D /* URLEncodedQueryParametersTests.swift */, + ); + path = QueryParameters; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -515,6 +541,7 @@ 7F7048F01D9D8A12003C99F6 /* ResponseError.swift in Sources */, 7F7048EA1D9D8A08003C99F6 /* JSONDataParser.swift in Sources */, 7F7048D21D9D89BE003C99F6 /* Session.swift in Sources */, + C5FF1DCF28A835600059573D /* QueryParameters.swift in Sources */, 7F7048E01D9D89FB003C99F6 /* Data+InputStream.swift in Sources */, 7F7048DF1D9D89FB003C99F6 /* BodyParameters.swift in Sources */, 7F7048E21D9D89FB003C99F6 /* JSONBodyParameters.swift in Sources */, @@ -523,6 +550,7 @@ 7F7048EF1D9D8A12003C99F6 /* RequestError.swift in Sources */, 7F7048E91D9D8A08003C99F6 /* FormURLEncodedDataParser.swift in Sources */, ECA8314C1DE4E677004EB1B5 /* ProtobufBodyParameters.swift in Sources */, + C5FF1DD028A835600059573D /* URLEncodedQueryParameters.swift in Sources */, 7F7048E11D9D89FB003C99F6 /* FormURLEncodedBodyParameters.swift in Sources */, 7F7048F11D9D8A12003C99F6 /* SessionTaskError.swift in Sources */, ECA831481DE4DDBF004EB1B5 /* ProtobufDataParser.swift in Sources */, @@ -548,6 +576,7 @@ C5B144D828D8D7DC00E30ECD /* ConcurrencyTests.swift in Sources */, 7F698E601D9D680C00F1561D /* TestSessionTask.swift in Sources */, 0973EE35259E2DDC00879BA2 /* CombineTests.swift in Sources */, + C5FF1DD328A835680059573D /* URLEncodedQueryParametersTests.swift in Sources */, 7FA1690D1D9D8C80006C982B /* HTTPStub.swift in Sources */, 7F698E5A1D9D680C00F1561D /* URLSessionAdapterTests.swift in Sources */, 7F698E561D9D680C00F1561D /* StringDataParserTests.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 ea6c5dae..8e7c02ae 100644 --- a/Sources/APIKit/Request.swift +++ b/Sources/APIKit/Request.swift @@ -28,7 +28,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`. @@ -65,12 +65,12 @@ public extension Request { return nil } - var queryParameters: [String: Any]? { - guard let parameters = parameters as? [String: Any], method.prefersQueryParameters else { + var queryParameters: QueryParameters? { + guard let parameters = parameters, method.prefersQueryParameters else { return nil } - return parameters + return URLEncodedQueryParameters(parameters: parameters) } var bodyParameters: BodyParameters? { @@ -110,8 +110,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")) + } +}