Skip to content

Commit

Permalink
Possible to receive an empty body
Browse files Browse the repository at this point in the history
As mentioned at issue ronanrodrigo#8, it should be possible to accept an empty body, like when the server sends 204 (HTTP NOT CONTENT), it could be retrieved
  • Loading branch information
amadeu01 committed Oct 3, 2018
1 parent bda57e3 commit 540fe8d
Show file tree
Hide file tree
Showing 14 changed files with 129 additions and 36 deletions.
2 changes: 1 addition & 1 deletion Sources/Frisbee/Adapters/DecodableAdapter.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Foundation

protocol DecodableAdapter {
func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T
func decode<T: Decodable>(_ type: T.Type, from data: Data?) throws -> T?
}
5 changes: 2 additions & 3 deletions Sources/Frisbee/Adapters/DecoderDataAdapter.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import Foundation

final class DecoderDataAdapter: DecodableAdapter {

func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T {
if let data = data as? T { return data }
func decode<T: Decodable>(_ type: T.Type, from data: Data?) throws -> T? {
if let data = data as? T? { return data }
throw FrisbeeError.invalidEntity
}

Expand Down
9 changes: 6 additions & 3 deletions Sources/Frisbee/Adapters/DecoderJSONAdapter.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import Foundation

final class DecoderJSONAdapter: DecodableAdapter {

let decoder: JSONDecoder

init(decoder: JSONDecoder = JSONDecoder()) {
self.decoder = decoder
}

func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T {
return try decoder.decode(type, from: data)
func decode<T: Decodable>(_ type: T.Type, from data: Data?) throws -> T? {
if let data = data {
return try decoder.decode(type, from: data)
}

return nil
}

}
11 changes: 9 additions & 2 deletions Sources/Frisbee/Entities/Result.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
public enum Result<T> {
case success(T)
case success(T?, Int?)
case fail(FrisbeeError)
}

extension Result {
public var data: T? {
guard case let .success(data) = self else {
guard case .success(let data, _) = self else {
return nil
}
return data
}

public var httpStatusCode: Int? {
guard case .success(_, let httpStatusCode) = self else {
return nil
}
return httpStatusCode
}

public var error: FrisbeeError? {
guard case let .fail(error) = self else {
return nil
Expand Down
4 changes: 2 additions & 2 deletions Sources/Frisbee/Interactors/DataTaskRunner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ final class DataTaskRunner {

static func run<T: Decodable>(with urlSession: URLSession, request: URLRequest,
onComplete: @escaping OnComplete<T>) -> Cancellable {
let task = urlSession.dataTask(with: request) { data, _, error in
onComplete(ResultGeneratorFactory.make().generate(data: data, error: error))
let task = urlSession.dataTask(with: request) { data, urlResponse, error in
onComplete(ResultGeneratorFactory.make().generate(data: data, urlResponse: urlResponse, error: error))
}
task.resume()
return URLSessionTaskAdapter(task: task)
Expand Down
10 changes: 7 additions & 3 deletions Sources/Frisbee/Interactors/ResultGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ final class ResultGenerator<T: Decodable> {
self.decoder = decoder
}

func generate(data: Data?, error: Error?) -> Result<T> {
guard let data = data else {
func generate(data: Data?, urlResponse: URLResponse?, error: Error?) -> Result<T> {
guard let urlResponse = urlResponse else {
switch error {
case .some(let error): return .fail(FrisbeeError(error))
case .none: return .fail(FrisbeeError.noData)
Expand All @@ -18,7 +18,11 @@ final class ResultGenerator<T: Decodable> {

do {
let entityDecoded = try decoder.decode(T.self, from: data)
return .success(entityDecoded)

if urlResponse is HTTPURLResponse {
return .success(entityDecoded, (urlResponse as! HTTPURLResponse).statusCode)
}
return .success(entityDecoded, nil)
} catch { return .fail(.noData) }
}

Expand Down
8 changes: 7 additions & 1 deletion Tests/FrisbeeTests/Adapters/DecoderDataAdapterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ final class DecoderDataAdapterTests: XCTestCase {

let decodedData = try DecoderDataAdapter().decode(Data.self, from: data)

XCTAssertEqual(decodedData.count, data.count)
XCTAssertEqual(decodedData!.count, data.count)
}

func testDecodeWhenEncodableGenericIsNilThenDecodeDataIsNil() throws {
let decodedData = try DecoderDataAdapter().decode(Data.self, from: nil)

XCTAssertNil(decodedData)
}

func testDecodeWhenNotDataEntityThenThrowsError() throws {
Expand Down
6 changes: 3 additions & 3 deletions Tests/FrisbeeTests/Entities/ResultTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import XCTest
class ResultTests: XCTestCase {

func testEquatableEnumCasesWhenAssociatedValueAreEquatableThenShouldBeEqual() {
let success = Result.success(SomeEquatableEntity())
let success = Result.success(SomeEquatableEntity(), nil)
let fail = Result<SomeEquatableEntity>.fail(FrisbeeError.invalidEntity)

XCTAssertEqual(success.data, success.data)
Expand All @@ -18,7 +18,7 @@ class ResultTests: XCTestCase {
}

func testEquatableEnumCasesWhenAssociatedValueArentEquatableThenShoudBeNotEqual() {
let success = Result.success(SomeEntity())
let success = Result.success(SomeEntity(), nil)
let fail = Result<SomeEntity>.fail(FrisbeeError.invalidEntity)

XCTAssertNotEqual(success, success)
Expand All @@ -28,7 +28,7 @@ class ResultTests: XCTestCase {
}

func testEqualityOperatorWhenEquatableEntityThenShoudeBeEqual() {
let success = Result.success(SomeEquatableEntity())
let success = Result.success(SomeEquatableEntity(), nil)
let fail = Result<SomeEquatableEntity>.fail(FrisbeeError.invalidEntity)

XCTAssertTrue(success == success)
Expand Down
72 changes: 56 additions & 16 deletions Tests/FrisbeeTests/Interactors/ResultGeneratorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,57 +5,91 @@ final class ResultGeneratorTests: XCTestCase {

private let someError = NSError(domain: "Some error", code: 33, userInfo: nil)
private let fakeString = "Fake Fake"
private let fakeUrl = URL(string: "www.domain.io")!

func testGenerateResultWhenEncoderTrhowAnerrorThenGenerateFailResult() {
func testGenerateResultWhenEncoderThrowAnErrorThenGenerateFailResult() {
let data = try? JSONEncoder().encode(Fake(fake: fakeString))
let resultGenerator = ResultGenerator<Fake>(decoder: DecoderThrowErrorFakeAdapter())
let fakeUrlResponse = HTTPURLResponse(url: fakeUrl, statusCode: 200, httpVersion: nil, headerFields: nil)!

let result = resultGenerator.generate(data: data, error: nil)
let result = resultGenerator.generate(data: data, urlResponse: fakeUrlResponse, error: nil)

XCTAssertEqual(result.error, .noData)
}

func testGenerateResultWhenInvalidDataThenGenerateSuccessResult() {
let noDataError = FrisbeeError.noData
let data = try? JSONEncoder().encode(Data())
let resultGenerator: ResultGenerator<Fake> = ResultGeneratorFactory.make()
let noContentUrlResponse = HTTPURLResponse(url: fakeUrl, statusCode: 204, httpVersion: nil, headerFields: nil)!

let result = resultGenerator.generate(data: data, error: nil)
let result = resultGenerator.generate(data: data, urlResponse: noContentUrlResponse, error: nil)

XCTAssertEqual(result.error, noDataError)
XCTAssertNil(result.data)
}

func testGenerateResultWhenHasDataThenGenerateSuccessResult() {
let data = try? JSONEncoder().encode(Fake(fake: fakeString))
let resultGenerator: ResultGenerator<Fake> = ResultGeneratorFactory.make()
let fakeUrlResponse = HTTPURLResponse(url: fakeUrl, statusCode: 201, httpVersion: nil, headerFields: nil)!

let result = resultGenerator.generate(data: data, error: nil)
let result = resultGenerator.generate(data: data, urlResponse: fakeUrlResponse, error: nil)

XCTAssertEqual(result.data?.fake, fakeString)
XCTAssertEqual(result.httpStatusCode, fakeUrlResponse.statusCode)
}

func testGenerateResultWhenHasDataThenResultFailErrorIsNil() {
let data = try? JSONEncoder().encode(Fake(fake: fakeString))
let resultGenerator: ResultGenerator<Fake> = ResultGeneratorFactory.make()
let fakeUrlResponse = HTTPURLResponse(url: fakeUrl, statusCode: 201, httpVersion: nil, headerFields: nil)!

let result = resultGenerator.generate(data: data, urlResponse: fakeUrlResponse, error: nil)

XCTAssertNil(result.error)
}

let result = resultGenerator.generate(data: data, error: nil)
func testGenerateResultWhenHasNoContentUrlResponseWithNilDataThenErrorIsNil() {
let resultGenerator: ResultGenerator<Data> = ResultGeneratorFactory.make()
let noContentUrlResponse = HTTPURLResponse(url: fakeUrl, statusCode: 204, httpVersion: nil, headerFields: nil)!

let result = resultGenerator.generate(data: nil, urlResponse: noContentUrlResponse, error: nil)

XCTAssertNil(result.error)
}

func testGenerateResultWhenHasNilDataThenGenerateNoDataErrorResult() {
func testGenerateResultWhenHasNoContentUrlResponseWithDataThenGenerateSuccessResult() {
let data = try? JSONEncoder().encode(Fake(fake: fakeString))
let resultGenerator: ResultGenerator<Fake> = ResultGeneratorFactory.make()
let noContentUrlResponse = HTTPURLResponse(url: fakeUrl, statusCode: 204, httpVersion: nil, headerFields: nil)!

let result = resultGenerator.generate(data: data, urlResponse: noContentUrlResponse, error: nil)

XCTAssertEqual(result.data?.fake, fakeString)
}

func testGenerateResultWhenHasNilDataAndNilUrlResponseThenGenerateNoDataErrorResult() {
let noDataError = Result<Data>.fail(FrisbeeError.noData)
let resultGenerator: ResultGenerator<Data> = ResultGeneratorFactory.make()

let result = resultGenerator.generate(data: nil, urlResponse: nil, error: nil)

XCTAssertEqual(result, noDataError)
}

func testGenerateResultWhenHasNilUrlResponseThenGenerateNoDataErrorResult() {
let data = try? JSONEncoder().encode(Fake(fake: fakeString))
let noDataError = Result<Data>.fail(FrisbeeError.noData)
let resultGenerator: ResultGenerator<Data> = ResultGeneratorFactory.make()

let result = resultGenerator.generate(data: nil, error: nil)
let result = resultGenerator.generate(data: data, urlResponse: nil, error: nil)

XCTAssertEqual(result, noDataError)
}

func testGenerateResultWhenHasNilDataThenResultSuccessDataIsNil() {
let resultGenerator: ResultGenerator<Data> = ResultGeneratorFactory.make()

let result = resultGenerator.generate(data: nil, error: nil)
let result = resultGenerator.generate(data: nil, urlResponse: nil, error: nil)

XCTAssertNil(result.data)
}
Expand All @@ -64,28 +98,34 @@ final class ResultGeneratorTests: XCTestCase {
let resultGenerator: ResultGenerator<Data> = ResultGeneratorFactory.make()
let noDataError = FrisbeeError.other(localizedDescription: someError.localizedDescription)

let result = resultGenerator.generate(data: nil, error: someError)
let result = resultGenerator.generate(data: nil, urlResponse: nil, error: someError)

XCTAssertEqual(result.error, noDataError)
}

func testGenerateResultWhenHasErrorThenResultSuccessDataIsNil() {
let resultGenerator: ResultGenerator<Data> = ResultGeneratorFactory.make()

let result = resultGenerator.generate(data: nil, error: someError)
let result = resultGenerator.generate(data: nil, urlResponse: nil, error: someError)

XCTAssertNil(result.data)
}

static var allTests = [
("testGenerateResultWhenEncoderTrhowAnerrorThenGenerateFailResult",
testGenerateResultWhenEncoderTrhowAnerrorThenGenerateFailResult),
("testGenerateResultWhenEncoderThrowAnErrorThenGenerateFailResult",
testGenerateResultWhenEncoderThrowAnErrorThenGenerateFailResult),
("testGenerateResultWhenHasDataThenGenerateSuccessResult",
testGenerateResultWhenHasDataThenGenerateSuccessResult),
("testGenerateResultWhenHasDataThenResultFailErrorIsNil",
testGenerateResultWhenHasDataThenResultFailErrorIsNil),
("testGenerateResultWhenHasNilDataThenGenerateNoDataErrorResult",
testGenerateResultWhenHasNilDataThenGenerateNoDataErrorResult),
("testGenerateResultWhenHasNilUrlResponseThenGenerateNoDataErrorResult",
testGenerateResultWhenHasNilUrlResponseThenGenerateNoDataErrorResult),
("testGenerateResultWhenHasNilDataAndNilUrlResponseThenGenerateNoDataErrorResult",
testGenerateResultWhenHasNilDataAndNilUrlResponseThenGenerateNoDataErrorResult),
("testGenerateResultWhenHasNoContentUrlResponseWithNilDataThenErrorIsNil",
testGenerateResultWhenHasNoContentUrlResponseWithNilDataThenErrorIsNil),
("testGenerateResultWhenHasNoContentUrlResponseWithDataThenGenerateSuccessResult",
testGenerateResultWhenHasNoContentUrlResponseWithDataThenGenerateSuccessResult),
("testGenerateResultWhenHasNilDataThenResultSuccessDataIsNil",
testGenerateResultWhenHasNilDataThenResultSuccessDataIsNil),
("testGenerateResultWhenHasErrorThenGenerateErrorResult",
Expand Down
11 changes: 11 additions & 0 deletions Tests/FrisbeeTests/Requestables/NetworkGetTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ final class NetworkGetTests: XCTestCase {
XCTAssertNil(generatedResult.error)
}

func testGetWhenValidURLWithoutBodyThenGenerateSuccessResult() {
let session = MockURLSession(results: [.success(nil, URLResponse())])
let getter = NetworkGet(urlSession: session)
var generatedResult: Result<Empty>!

getter.get(url: validUrlString) { generatedResult = $0 }

XCTAssertNil(generatedResult.data)
XCTAssertNil(generatedResult.error)
}

func testGetWhenInvalidURLThenGenerateFailResult() {
let session = MockURLSession(results: [])
let getter = NetworkGet(urlSession: session)
Expand Down
11 changes: 11 additions & 0 deletions Tests/FrisbeeTests/Requestables/NetworkPostTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ final class NetworkPostTests: XCTestCase {
XCTAssertNil(generatedResult.error)
}

func testPostWhenValidURLWithoutBodyThenGenerateSuccessResult() {
let session = MockURLSession(results: [.success(nil, URLResponse())])
let networkPost = NetworkPost(urlSession: session)
var generatedResult: Result<Empty>!

networkPost.post(url: validUrlString) { generatedResult = $0 }

XCTAssertNil(generatedResult.data)
XCTAssertNil(generatedResult.error)
}

func testPostWhenValidURLWithBodyThenGenerateSuccessResult() {
let session = MockURLSession(results: [.success(Empty.data, URLResponse())])
let networkPost = NetworkPost(urlSession: session)
Expand Down
12 changes: 12 additions & 0 deletions Tests/FrisbeeTests/Requestables/NetworkPutTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ class NetworkPutTests: XCTestCase {
XCTAssertNil(generatedResult.error)
}

func testPutWhenValidURLWithoutBodyThenGenerateSuccessResult() {
let session = MockURLSession(results: [.success(nil, URLResponse())])
let networkPut = NetworkPut(urlSession: session)
let body = Empty()
var generatedResult: Result<Empty>!

networkPut.put(url: validUrlString, body: body) { generatedResult = $0 }

XCTAssertNil(generatedResult.data)
XCTAssertNil(generatedResult.error)
}

func testPutWhenInvalidURLThenGenerateFailResult() {
let session = MockURLSession(results: [])
let networkPut = NetworkPut(urlSession: session)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Foundation
class DecoderThrowErrorFakeAdapter: DecodableAdapter {
private let error = FrisbeeError.invalidEntity

func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T {
func decode<T: Decodable>(_ type: T.Type, from data: Data?) throws -> T? {
throw error
}

Expand Down
2 changes: 1 addition & 1 deletion Tests/FrisbeeTests/Support/Doubles/MockURLSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class MockURLSession: URLSession {

class MockDataTask: URLSessionDataTask {
enum Result {
case success(Data, URLResponse), error(Error)
case success(Data?, URLResponse), error(Error)
}

let result: Result
Expand Down

0 comments on commit 540fe8d

Please sign in to comment.