Skip to content

Commit

Permalink
Fixed encoding body and query
Browse files Browse the repository at this point in the history
  • Loading branch information
Bartłomiej Świerad committed Feb 5, 2021
1 parent 028b1b5 commit 221b644
Show file tree
Hide file tree
Showing 15 changed files with 253 additions and 415 deletions.
80 changes: 4 additions & 76 deletions Sources/RestlerCore/Internal/HTTPMethod.swift
@@ -1,10 +1,10 @@
import Foundation

enum HTTPMethod: Equatable {
case get(query: [URLQueryItem])
case post(content: Data?)
case put(content: Data?)
case patch(content: Data?)
case get
case post
case put
case patch
case delete
case head

Expand All @@ -18,76 +18,4 @@ enum HTTPMethod: Equatable {
case .head: return "HEAD"
}
}

var query: [URLQueryItem]? {
switch self {
case let .get(query): return query
case .post: return nil
case .put: return nil
case .patch: return nil
case .delete: return nil
case .head: return nil
}
}

var content: Data? {
switch self {
case .get: return nil
case let .post(content): return content
case let .put(content): return content
case let .patch(content): return content
case .delete: return nil
case .head: return nil
}
}

var isQueryAvailable: Bool {
switch self {
case .get: return true
case .post: return false
case .put: return false
case .patch: return false
case .delete: return false
case .head: return false
}
}

var isBodyAvailable: Bool {
switch self {
case .get: return false
case .post: return true
case .put: return true
case .patch: return true
case .delete: return false
case .head: return false
}
}

var isMultipartAvailable: Bool {
switch self {
case .get: return false
case .post: return true
case .put: return false
case .patch: return false
case .delete: return false
case .head: return false
}
}

func combinedWith(query: [URLQueryItem]?, body: Data?) -> HTTPMethod {
switch self {
case .get:
return .get(query: query ?? [])
case .post:
return .post(content: body)
case .put:
return .put(content: body)
case .patch:
return .patch(content: body)
case .delete:
return .delete
case .head:
return .head
}
}
}
29 changes: 11 additions & 18 deletions Sources/RestlerCore/Internal/Networking.swift
Expand Up @@ -8,9 +8,7 @@ typealias DataCompletion = (DataResult) -> Void

protocol NetworkingType: class {
func buildRequest(
url: URL,
method: HTTPMethod,
header: Restler.Header,
requestData: RequestData,
customRequestModification: ((inout URLRequest) -> Void)?
) -> URLRequest?

Expand Down Expand Up @@ -67,17 +65,10 @@ final class Networking: NSObject {
// MARK: - NetworkingType
extension Networking: NetworkingType {
func buildRequest(
url: URL,
method: HTTPMethod,
header: Restler.Header,
requestData: RequestData,
customRequestModification: ((inout URLRequest) -> Void)?
) -> URLRequest? {
guard var request = self.buildURLRequest(
url: url,
httpMethod: method,
header: header) else {
return nil
}
guard var request = self.buildURLRequest(requestData: requestData) else { return nil }
customRequestModification?(&request)
return request
}
Expand Down Expand Up @@ -192,14 +183,16 @@ extension Networking: URLSessionDownloadDelegate {

// MARK: - Private
extension Networking {
private func buildURLRequest(url: URL, httpMethod: HTTPMethod, header: Restler.Header) -> URLRequest? {
guard var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return nil }
urlComponents.queryItems = httpMethod.query
private func buildURLRequest(requestData: RequestData) -> URLRequest? {
guard var urlComponents = URLComponents(url: requestData.url, resolvingAgainstBaseURL: false) else { return nil }
if !requestData.query.isEmpty {
urlComponents.queryItems = requestData.query
}
guard let url = urlComponents.url else { return nil }
var request = URLRequest(url: url)
request.allHTTPHeaderFields = header.raw
request.httpMethod = httpMethod.name
request.httpBody = httpMethod.content
request.allHTTPHeaderFields = requestData.header.raw
request.httpMethod = requestData.method.name
request.httpBody = requestData.content
return request
}

Expand Down
9 changes: 9 additions & 0 deletions Sources/RestlerCore/Internal/RequestData.swift
@@ -0,0 +1,9 @@
import Foundation

struct RequestData {
let url: URL
let method: HTTPMethod
let content: Data?
let query: [URLQueryItem]
let header: Restler.Header
}
14 changes: 7 additions & 7 deletions Sources/RestlerCore/Public/Classes/Request/RequestBuilder.swift
Expand Up @@ -90,10 +90,14 @@ extension Restler.RequestBuilder: RestlerBasicRequestBuilderType {
self.form.builderErrorsHandler?(error)
return nil
}
return self.dependencies.networking.buildRequest(
let requestData = RequestData(
url: self.dependencies.url,
method: self.dependencies.method.combinedWith(query: self.form.query, body: self.form.body),
header: self.form.header,
method: self.dependencies.method,
content: self.form.body,
query: self.form.query ?? [],
header: self.form.header)
return self.dependencies.networking.buildRequest(
requestData: requestData,
customRequestModification: self.form.customRequestModification)
}

Expand All @@ -111,10 +115,8 @@ extension Restler.RequestBuilder: RestlerBasicRequestBuilderType {
// MARK: - RestlerQueryRequestBuilderType
extension Restler.RequestBuilder: RestlerQueryRequestBuilderType {
public func query<E>(_ object: E) -> Self where E: RestlerQueryEncodable {
guard self.dependencies.method.isQueryAvailable else { return self }
do {
self.form.query = try self.dependencies.queryEncoder.encode(object)
self.form.header[.contentType] = "application/x-www-form-urlencoded"
} catch {
self.form.errors.append(Restler.Error.common(type: .invalidParameters, base: error))
}
Expand All @@ -125,7 +127,6 @@ extension Restler.RequestBuilder: RestlerQueryRequestBuilderType {
// MARK: - RestlerBodyRequestBuilderType
extension Restler.RequestBuilder: RestlerBodyRequestBuilderType {
public func body<E>(_ object: E) -> Self where E: Encodable {
guard self.dependencies.method.isBodyAvailable else { return self }
do {
self.form.body = try self.dependencies.encoder.encode(object)
self.form.header[.contentType] = "application/json"
Expand All @@ -139,7 +140,6 @@ extension Restler.RequestBuilder: RestlerBodyRequestBuilderType {
// MARK: - RestlerMultipartRequestBuilderType
extension Restler.RequestBuilder: RestlerMultipartRequestBuilderType {
public func multipart<E>(_ object: E, boundary: String? = nil) -> Self where E: RestlerMultipartEncodable {
guard self.dependencies.method.isMultipartAvailable else { return self }
do {
let unwrappedBoundary = boundary ?? "Boundary--\(UUID().uuidString)"
self.form.body = try self.dependencies.multipartEncoder.encode(object, boundary: unwrappedBoundary)
Expand Down
8 changes: 4 additions & 4 deletions Sources/RestlerCore/Public/Classes/Restler.swift
Expand Up @@ -84,19 +84,19 @@ open class Restler: RestlerType {
// MARK: - Open

open func get(_ endpoint: RestlerEndpointable) -> RestlerGetRequestBuilderType {
self.requestBuilder(for: .get(query: []), to: endpoint)
self.requestBuilder(for: .get, to: endpoint)
}

open func post(_ endpoint: RestlerEndpointable) -> RestlerPostRequestBuilderType {
self.requestBuilder(for: .post(content: nil), to: endpoint)
self.requestBuilder(for: .post, to: endpoint)
}

open func put(_ endpoint: RestlerEndpointable) -> RestlerPutRequestBuilderType {
self.requestBuilder(for: .put(content: nil), to: endpoint)
self.requestBuilder(for: .put, to: endpoint)
}

open func patch(_ endpoint: RestlerEndpointable) -> RestlerPatchRequestBuilderType {
self.requestBuilder(for: .patch(content: nil), to: endpoint)
self.requestBuilder(for: .patch, to: endpoint)
}

open func delete(_ endpoint: RestlerEndpointable) -> RestlerDeleteRequestBuilderType {
Expand Down
28 changes: 12 additions & 16 deletions Tests/RestlerTests/Mocks/NetworkingMock.swift
Expand Up @@ -19,9 +19,7 @@ final class NetworkingMock {
var buildRequestReturnValue: URLRequest?
private(set) var buildRequestParams: [BuildRequestParams] = []
struct BuildRequestParams {
let url: URL
let method: HTTPMethod
let header: Restler.Header
let requestData: RequestData
let customRequestModification: ((inout URLRequest) -> Void)?
}

Expand Down Expand Up @@ -49,25 +47,23 @@ extension NetworkingMock: NetworkingType {
eventLogger: EventLoggerLogging,
completion: @escaping DataCompletion
) -> Restler.Task {
self.makeRequestParams.append(MakeRequestParams(
urlRequest: urlRequest,
urlSession: urlSession,
eventLogger: eventLogger,
completion: completion))
self.makeRequestParams.append(
MakeRequestParams(
urlRequest: urlRequest,
urlSession: urlSession,
eventLogger: eventLogger,
completion: completion))
return self.makeRequestReturnValue
}

func buildRequest(
url: URL,
method: HTTPMethod,
header: Restler.Header,
requestData: RequestData,
customRequestModification: ((inout URLRequest) -> Void)?
) -> URLRequest? {
self.buildRequestParams.append(BuildRequestParams(
url: url,
method: method,
header: header,
customRequestModification: customRequestModification))
self.buildRequestParams.append(
BuildRequestParams(
requestData: requestData,
customRequestModification: customRequestModification))
return self.buildRequestReturnValue
}

Expand Down
Expand Up @@ -16,9 +16,11 @@ extension DeleteInterfaceIntegrationTests {
XCTAssertEqual(request, expectedRequest)
XCTAssertEqual(self.networking.buildRequestParams.count, 1)
let requestParams = try XCTUnwrap(self.networking.buildRequestParams.first)
XCTAssertEqual(requestParams.url.absoluteString, self.mockURLString)
XCTAssertEqual(requestParams.method, .delete)
XCTAssertNil(requestParams.header[.contentType])
XCTAssertEqual(requestParams.requestData.url.absoluteString, self.mockURLString)
XCTAssertEqual(requestParams.requestData.method, .delete)
XCTAssertNil(requestParams.requestData.header[.contentType])
XCTAssertNil(requestParams.requestData.content)
XCTAssertEqual(requestParams.requestData.query, [])
}

func testURLRequestBuilding_customHeader() throws {
Expand All @@ -27,15 +29,60 @@ extension DeleteInterfaceIntegrationTests {
// Act
let request = sut
.delete(self.endpoint)
.setInHeader("hello darkness", forKey: .contentType)
.setInHeader("hello_darkness", forKey: .contentType)
.urlRequest()
// Assert
XCTAssertEqual(request, expectedRequest)
XCTAssertEqual(self.networking.buildRequestParams.count, 1)
let requestParams = try XCTUnwrap(self.networking.buildRequestParams.first)
XCTAssertEqual(requestParams.url.absoluteString, self.mockURLString)
XCTAssertEqual(requestParams.method, .delete)
XCTAssertEqual(requestParams.header[.contentType], "hello darkness")
XCTAssertEqual(requestParams.requestData.url.absoluteString, self.mockURLString)
XCTAssertEqual(requestParams.requestData.method, .delete)
XCTAssertEqual(requestParams.requestData.header[.contentType], "hello_darkness")
XCTAssertNil(requestParams.requestData.content)
XCTAssertEqual(requestParams.requestData.query, [])
}

// MARK: - Encoding Body
func testURLRequestBuilding_encodingBody() throws {
// Arrange
let sut = self.buildSUT()
let object = SomeObject(id: 1, name: "name", double: 1.23)
let data = try JSONEncoder().encode(object)
// Act
let request = sut
.delete(self.endpoint)
.body(object)
.urlRequest()
// Assert
XCTAssertEqual(request, expectedRequest)
XCTAssertEqual(self.networking.buildRequestParams.count, 1)
let requestParams = try XCTUnwrap(self.networking.buildRequestParams.first)
XCTAssertEqual(requestParams.requestData.url.absoluteString, self.mockURLString)
XCTAssertEqual(requestParams.requestData.method, .delete)
XCTAssertEqual(requestParams.requestData.header[.contentType], "application/json")
XCTAssertEqual(requestParams.requestData.content, data)
XCTAssertEqual(requestParams.requestData.query, [])
}

func testURLRequestBuilding_encodingBodyFails() throws {
// Arrange
let sut = self.buildSUT()
let object = ThrowingObject()
let expectedError = TestError()
object.thrownError = expectedError
var returnedError: Error?
// Act
let request = sut
.post(self.endpoint)
.catching { returnedError = $0 }
.body(object)
.urlRequest()
// Assert
XCTAssertNil(request)
XCTAssertEqual(self.networking.buildRequestParams.count, 0)
try self.assertThrowsEncodingError(
expected: expectedError,
returnedError: returnedError)
}
}

Expand Down
Expand Up @@ -15,7 +15,7 @@ extension DownloadInterfaceIntegrationTests {
.subscribe()
// Assert
XCTAssertEqual(self.networking.buildRequestParams.count, 1)
XCTAssertEqual(self.networking.buildRequestParams.last?.url.absoluteString, self.mockURLString)
XCTAssertEqual(self.networking.buildRequestParams.last?.requestData.url.absoluteString, self.mockURLString)
XCTAssertEqual(self.networking.downloadRequestParams.count, 1)
XCTAssertEqual(self.networking.downloadRequestParams.last?.urlRequest.url?.absoluteString, expectedURL.absoluteString)
}
Expand All @@ -30,8 +30,10 @@ extension DownloadInterfaceIntegrationTests {
.subscribe()
// Assert
XCTAssertEqual(self.networking.buildRequestParams.count, 1)
XCTAssertEqual(self.networking.buildRequestParams.last?.url.absoluteString, self.mockURLString)
XCTAssertEqual(self.networking.buildRequestParams.last?.method, .get(query: [URLQueryItem(name: "user", value: "yes")]))
let requestParams = try XCTUnwrap(self.networking.buildRequestParams.last)
XCTAssertEqual(requestParams.requestData.url.absoluteString, self.mockURLString)
XCTAssertEqual(requestParams.requestData.method, .get)
XCTAssertEqual(requestParams.requestData.query, [URLQueryItem(name: "user", value: "yes")])
}

func testDownloadInterface_usingResumeData() throws {
Expand Down

0 comments on commit 221b644

Please sign in to comment.