Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions APIKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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, ); }; };
Expand Down Expand Up @@ -80,6 +83,9 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
0ED17E881E229EC700EC1114 /* QueryParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryParameters.swift; sourceTree = "<group>"; };
0ED17E8A1E22A22900EC1114 /* URLEncodedQueryParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLEncodedQueryParameters.swift; sourceTree = "<group>"; };
0ED17E8D1E22AC8300EC1114 /* URLEncodedQueryParametersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLEncodedQueryParametersTests.swift; sourceTree = "<group>"; };
141F120F1C1C96820026D415 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Base.xcconfig; path = Configurations/Base.xcconfig; sourceTree = "<group>"; };
141F12101C1C96820026D415 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Configurations/Debug.xcconfig; sourceTree = "<group>"; };
141F12111C1C96820026D415 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Configurations/Release.xcconfig; sourceTree = "<group>"; };
Expand Down Expand Up @@ -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 = "<group>";
};
0ED17E8C1E22AC5D00EC1114 /* QueryParameters */ = {
isa = PBXGroup;
children = (
0ED17E8D1E22AC8300EC1114 /* URLEncodedQueryParametersTests.swift */,
);
path = QueryParameters;
sourceTree = "<group>";
};
141F120E1C1C96690026D415 /* Configurations */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -224,6 +248,7 @@
7F698E451D9D680C00F1561D /* RequestTests.swift */,
7F698E491D9D680C00F1561D /* SessionCallbackQueueTests.swift */,
7F698E4A1D9D680C00F1561D /* SessionTests.swift */,
0ED17E8C1E22AC5D00EC1114 /* QueryParameters */,
7F698E3B1D9D680C00F1561D /* BodyParametersType */,
7F698E401D9D680C00F1561D /* DataParserType */,
7F698E461D9D680C00F1561D /* SessionAdapterType */,
Expand Down Expand Up @@ -294,6 +319,7 @@
7F7048CB1D9D89BE003C99F6 /* Session.swift */,
7F7048CC1D9D89BE003C99F6 /* Unavailable.swift */,
7F85FB8B1C9D317300CEE132 /* SessionAdapter */,
0ED17E871E229EAC00EC1114 /* QueryParameters */,
7F18BD0D1C972C38003A31DF /* BodyParameters */,
7FA19A441C9CC9A2005D25AE /* DataParser */,
7F18BD161C9730ED003A31DF /* Serializations */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down
2 changes: 1 addition & 1 deletion Sources/APIKit/BodyParameters/BodyParameters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ public protocol BodyParameters {
var contentType: String { get }

/// Builds `RequestBodyEntity`.
/// Throws: `ErrorType`
/// Throws: `Error`
func buildEntity() throws -> RequestBodyEntity
}
2 changes: 1 addition & 1 deletion Sources/APIKit/DataParser/DataParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
7 changes: 7 additions & 0 deletions Sources/APIKit/QueryParameters/QueryParameters.swift
Original file line number Diff line number Diff line change
@@ -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?
}
20 changes: 20 additions & 0 deletions Sources/APIKit/QueryParameters/URLEncodedQueryParameters.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
22 changes: 11 additions & 11 deletions Sources/APIKit/Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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
}

Expand All @@ -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? {
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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"))
}
}