Skip to content
Open
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
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