diff --git a/MHNetwork.xcodeproj/project.pbxproj b/MHNetwork.xcodeproj/project.pbxproj index 8c80413..815d257 100644 --- a/MHNetwork.xcodeproj/project.pbxproj +++ b/MHNetwork.xcodeproj/project.pbxproj @@ -15,7 +15,7 @@ FD75389120FCBCF100F99D83 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD75388B20FCBCF100F99D83 /* Response.swift */; }; FD75389220FCBCF100F99D83 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD75388C20FCBCF100F99D83 /* Request.swift */; }; FD75389520FCBCF100F99D83 /* NetworkDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD75388F20FCBCF100F99D83 /* NetworkDispatcher.swift */; }; - FD7AB6232102447E00B95DBB /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7AB6222102447E00B95DBB /* NetworkError.swift */; }; + FD7AB6232102447E00B95DBB /* StatusCodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7AB6222102447E00B95DBB /* StatusCodes.swift */; }; FDA192B220FE4944001C586F /* Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA192B120FE4944001C586F /* Operations.swift */; }; FDCC0B6F20FCC9A100092F44 /* ResponseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCC0B6D20FCC9A100092F44 /* ResponseTest.swift */; }; FDCC0B7020FCC9A100092F44 /* NetworkDispatcherTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCC0B6E20FCC9A100092F44 /* NetworkDispatcherTest.swift */; }; @@ -48,7 +48,7 @@ FD75388B20FCBCF100F99D83 /* Response.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = ""; }; FD75388C20FCBCF100F99D83 /* Request.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = ""; }; FD75388F20FCBCF100F99D83 /* NetworkDispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkDispatcher.swift; sourceTree = ""; }; - FD7AB6222102447E00B95DBB /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; + FD7AB6222102447E00B95DBB /* StatusCodes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusCodes.swift; sourceTree = ""; }; FDA192B120FE4944001C586F /* Operations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operations.swift; sourceTree = ""; }; FDCC0B6D20FCC9A100092F44 /* ResponseTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResponseTest.swift; sourceTree = ""; }; FDCC0B6E20FCC9A100092F44 /* NetworkDispatcherTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkDispatcherTest.swift; sourceTree = ""; }; @@ -96,6 +96,14 @@ name = Frameworks; sourceTree = ""; }; + FD1F50ED2105D7C900313702 /* Helpers */ = { + isa = PBXGroup; + children = ( + FD7AB6222102447E00B95DBB /* StatusCodes.swift */, + ); + path = Helpers; + sourceTree = ""; + }; FD75386520FCBCC700F99D83 = { isa = PBXGroup; children = ( @@ -145,7 +153,7 @@ FD75388C20FCBCF100F99D83 /* Request.swift */, FDA192B120FE4944001C586F /* Operations.swift */, FD75388F20FCBCF100F99D83 /* NetworkDispatcher.swift */, - FD7AB6222102447E00B95DBB /* NetworkError.swift */, + FD1F50ED2105D7C900313702 /* Helpers */, ); path = Network; sourceTree = ""; @@ -325,7 +333,7 @@ FD75389520FCBCF100F99D83 /* NetworkDispatcher.swift in Sources */, FD75389020FCBCF100F99D83 /* Environment.swift in Sources */, FDA192B220FE4944001C586F /* Operations.swift in Sources */, - FD7AB6232102447E00B95DBB /* NetworkError.swift in Sources */, + FD7AB6232102447E00B95DBB /* StatusCodes.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/MHNetwork/Network/Environment.swift b/MHNetwork/Network/Environment.swift index 8ab9d55..183c367 100755 --- a/MHNetwork/Network/Environment.swift +++ b/MHNetwork/Network/Environment.swift @@ -49,6 +49,6 @@ public protocol Dispatcher { /// - Parameter request: request to execute /// - Returns: promise func execute(request: Request, completion: @escaping (Response) -> Void, - onError: @escaping (Error) -> Void) throws + onError: @escaping (ErrorItem) -> Void) throws } diff --git a/MHNetwork/Network/Helpers/StatusCodes.swift b/MHNetwork/Network/Helpers/StatusCodes.swift new file mode 100644 index 0000000..9b6e8d3 --- /dev/null +++ b/MHNetwork/Network/Helpers/StatusCodes.swift @@ -0,0 +1,109 @@ +// +// CustomError.swift +// MHNetwork +// +// Created by Mohamed Emad Abdalla Hegab on 20.07.18. +// Copyright © 2018 Mohamed Hegab. All rights reserved. +// + +import Foundation + +public enum HTTPStatusCodes: Int { + case unspecified = 0 // for testing perpuse + // 100 Informational + case `continue` = 100 + case switchingProtocols + case processing + // 200 Success + case ok = 200 + case created + case accepted + case nonAuthoritativeInformation + case noContent + case resetContent + case partialContent + case multiStatus + case alreadyReported + case imUsed = 226 + // 300 Redirection + case multipleChoices = 300 + case movedPermanently + case found + case seeOther + case notModified + case useProxy + case switchProxy + case temporaryRedirect + case permanentRedirect + // 400 Client Error + case badRequest = 400 + case unauthorized + case paymentRequired + case forbidden + case notFound + case methodNotAllowed + case notAcceptable + case proxyAuthenticationRequired + case requestTimeout + case conflict + case gone + case lengthRequired + case preconditionFailed + case payloadTooLarge + case uriTooLong + case unsupportedMediaType + case rangeNotSatisfiable + case expectationFailed + case imATeapot + case misdirectedRequest = 421 + case unprocessableEntity + case locked + case failedDependency + case upgradeRequired = 426 + case preconditionRequired = 428 + case tooManyRequests + case requestHeaderFieldsTooLarge = 431 + case unavailableForLegalReasons = 451 + // 500 Server Error + case internalServerError = 500 + case notImplemented + case badGateway + case serviceUnavailable + case gatewayTimeout + case httpVersionNotSupported + case variantAlsoNegotiates + case insufficientStorage + case loopDetected + case notExtended = 510 + case networkAuthenticationRequired + + public var description: String { + switch self { + case .badRequest: + return "Bad Request" + case .unauthorized: + return "Unauthorized" + case .forbidden: + return "Forbidden" + case .notFound: + return "Not Found" + case .internalServerError: + return "Internal Server Error" + case .notImplemented: + return "Not implemented" + case .badGateway: + return "Bad Gateway" + case .serviceUnavailable: + return "Service Unavailable" + + default: + return "Error with code: \(self.rawValue)" + } + } +} + +extension HTTPStatusCodes { + public var convertStatusCodeToErrorItem: ErrorItem { + return (self, nil, nil) + } +} diff --git a/MHNetwork/Network/NetworkDispatcher.swift b/MHNetwork/Network/NetworkDispatcher.swift index 232cc64..d5b11f5 100755 --- a/MHNetwork/Network/NetworkDispatcher.swift +++ b/MHNetwork/Network/NetworkDispatcher.swift @@ -18,7 +18,7 @@ public class NetworkDispatcher: Dispatcher { } public func execute(request: Request, completion: @escaping (Response) -> Void, - onError: @escaping (Error) -> Void) throws { + onError: @escaping (ErrorItem) -> Void) throws { try self.prepareURLRequest(for: request, onComplete: { [weak self] (rq) in guard let `self` = self else { return } @@ -35,7 +35,7 @@ public class NetworkDispatcher: Dispatcher { private func prepareURLRequest(for request: Request, onComplete: @escaping (URLRequest) -> Void, - onError: @escaping (Error) -> Void) throws { + onError: @escaping (ErrorItem) -> Void) throws { // Compose the url let fullUrl = "\(environment.host)/\(request.path)" var urlRequest: URLRequest! @@ -59,7 +59,7 @@ public class NetworkDispatcher: Dispatcher { return URLQueryItem(name: element.key, value: element.value as? String) }) guard var components = URLComponents(string: fullUrl), let url = URL(string: fullUrl) else { - onError(NetworkError.runTimeError("Bad Input")) + onError((HTTPStatusCodes.notFound, nil, nil)) return } components.queryItems = queryParams diff --git a/MHNetwork/Network/NetworkError.swift b/MHNetwork/Network/NetworkError.swift deleted file mode 100644 index 08b41c9..0000000 --- a/MHNetwork/Network/NetworkError.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// CustomError.swift -// MHNetwork -// -// Created by Mohamed Emad Abdalla Hegab on 20.07.18. -// Copyright © 2018 Mohamed Hegab. All rights reserved. -// - -import Foundation - -enum NetworkError: Error { - case runTimeError(String) -} diff --git a/MHNetwork/Network/Response.swift b/MHNetwork/Network/Response.swift index 9602010..e613b33 100755 --- a/MHNetwork/Network/Response.swift +++ b/MHNetwork/Network/Response.swift @@ -7,14 +7,13 @@ // import Foundation -public typealias ErrorItem = (code: Int?, error: Error?, data: Data?) +public typealias ErrorItem = (code: HTTPStatusCodes?, error: Error?, data: Data?) public enum Response { case data(_: Data) - case error(ErrorItem) + case error(error: ErrorItem) public init(_ response: (r: HTTPURLResponse?, data: Data?, error: Error?), for request: Request) { - guard let serverResponse = response.r else { self = Response.handleNetworkError(status: 500, error: nil, data: nil) return @@ -38,7 +37,7 @@ public enum Response { private static func handleNetworkError(status: Int, error: Error?, data: Data?) -> Response { // Handle the error and convert it to application friendly error - return .error((code: status, error: error, data: data)) + return .error(error: (code: HTTPStatusCodes(rawValue: status), error: error, data: data)) } } diff --git a/MHNetworkTests/NetworkDispatcherTest.swift b/MHNetworkTests/NetworkDispatcherTest.swift index 697c543..15814da 100644 --- a/MHNetworkTests/NetworkDispatcherTest.swift +++ b/MHNetworkTests/NetworkDispatcherTest.swift @@ -70,7 +70,7 @@ private class MockQuoteTask: Operations { return MockQuoteRequest.getRandomQuote } - func exeute(in dispatcher: Dispatcher, completed: @escaping (T) -> Void, onError: @escaping (Error) -> Void) { + func exeute(in dispatcher: Dispatcher, completed: @escaping (T) -> Void, onError: @escaping (ErrorItem) -> Void) { do { try dispatcher.execute(request: self.request, completion: { (response) in @@ -83,16 +83,16 @@ private class MockQuoteTask: Operations { let object = try decoder.decode(T.self, from: data) completed(object) } catch let error { - onError(error) + onError((nil, error, nil)) } break - case .error(let status, let error, let data): + case .error(let error): onError(error) break } }, onError: onError) } catch { - onError(error) + onError((nil, error, nil)) } } } @@ -105,9 +105,11 @@ private class MockBadTask: Operations { return MockBadRequest(body: body) } - func exeute(in dispatcher: Dispatcher, completed: @escaping (T) -> Void, onError: @escaping (Error) -> Void) { + func exeute(in dispatcher: Dispatcher, completed: @escaping (T) -> Void, onError: @escaping (ErrorItem) -> Void) { do { + + try dispatcher.execute(request: request, completion: { (response) in switch response { @@ -117,19 +119,18 @@ private class MockBadTask: Operations { let t = try decoder.decode(T.self, from: data) completed(t) } catch let error { - onError(error) + onError((nil, error, nil)) } - case .error(_, let networkError, _): - guard let error = networkError else { break } + case .error(let error): onError(error) - + break } }, onError: { (error) in onError(error) }) } catch { - onError(error) + onError((nil, error, nil)) } } } diff --git a/MHNetworkTests/ResponseTest.swift b/MHNetworkTests/ResponseTest.swift index d3b8317..64f65c9 100755 --- a/MHNetworkTests/ResponseTest.swift +++ b/MHNetworkTests/ResponseTest.swift @@ -28,17 +28,16 @@ class ResponseTest: XCTestCase { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } -// -// func testEmptyResponseWithSuccessStatus() { -// let response = Response((r: TestData.succeededHttpResponse, data: nil, error: nil), for: RequestMock()) -// switch response { -// case .error(let statusCode, let error, let data): -// expect(statusCode).to(equal(200)) -// expect(error).to(equal(NetworkErrors.noData)) -// default: -// XCTFail("Response type should be of .error instead of \(type(of: response))") -// } -// } + + func testEmptyResponseWithSuccessStatus() { + let response = Response((r: TestData.succeededHttpResponse, data: nil, error: nil), for: RequestMock()) + switch response { + case .error(let error): + expect(error.code).to(equal(HTTPStatusCodes.ok)) + default: + XCTFail("Response type should be of .error instead of \(type(of: response))") + } + } func testFailWithURLWithCanceledError() { let bodyContent = "test-body-data" let error = NSError(domain: NSURLErrorDomain.description, code: URLError.cancelled.rawValue, @@ -48,9 +47,9 @@ class ResponseTest: XCTestCase { error: error), for: RequestMock()) switch response { - case .error(let statusCode, let error, let data): - expect(statusCode).to(equal(500)) - expect(data).to(equal(bodyContent.data(using: .utf8))) + case .error(let error): + expect(error.code).to(equal(HTTPStatusCodes.internalServerError)) + expect(error.data).to(equal(bodyContent.data(using: .utf8))) default: XCTFail("Response type should be of .error instead of \(type(of: response))") } @@ -64,8 +63,8 @@ class ResponseTest: XCTestCase { error: error), for: RequestMock()) switch response { - case .error(let statusCode, let error, let _): - expect(statusCode).to(equal(500)) + case .error(let error): + expect(error.code).to(equal(HTTPStatusCodes.internalServerError)) default: XCTFail("Response type should be of .error instead of \(type(of: response))") } @@ -80,8 +79,8 @@ class ResponseTest: XCTestCase { error: error), for: RequestMock()) switch response { - case .error(let statusCode, let error, let data): - expect(statusCode).to(equal(500)) + case .error(let error): + expect(error.code).to(equal(HTTPStatusCodes.internalServerError)) default: XCTFail("Response type should be of .error instead of \(type(of: response))") } @@ -96,8 +95,8 @@ class ResponseTest: XCTestCase { error: error), for: RequestMock()) switch response { - case .error(let statusCode, let error, let data): - expect(statusCode).to(equal(500)) + case .error(let error): + expect(error.code).to(equal(HTTPStatusCodes.internalServerError)) default: XCTFail("Response type should be of .error instead of \(type(of: response))") } @@ -106,8 +105,8 @@ class ResponseTest: XCTestCase { func testUnspecifiedResponseError() { let response = Response((r: TestData.unspecifiedHttpResponse, data: nil, error: nil), for: RequestMock()) switch response { - case .error(let statusCode, let error, let data): - expect(statusCode).to(equal(0)) + case .error(let error): + expect(error.code).to(equal(HTTPStatusCodes.unspecified)) default: XCTFail("Response type should be of .error instead of \(type(of: response))") @@ -118,9 +117,9 @@ class ResponseTest: XCTestCase { let bodyContent = "test-body-data" let response = Response((r: TestData.unAuthorizedHttpResponse, data: bodyContent.data(using: .utf8), error: nil), for: RequestMock()) switch response { - case .error(let statusCode, let error, let data): - expect(statusCode).to(equal(401)) - expect(data).to(equal(bodyContent.data(using: .utf8))) + case .error(let error): + expect(error.code).to(equal(HTTPStatusCodes.unauthorized)) + expect(error.data).to(equal(bodyContent.data(using: .utf8))) default: XCTFail("Response type should be of .error instead of \(type(of: response))") } @@ -130,25 +129,25 @@ class ResponseTest: XCTestCase { let bodyContent = "test-body-data" let response = Response((r: TestData.failedHttpResponse, data: bodyContent.data(using: .utf8), error: nil), for: RequestMock()) switch response { - case .error(let statusCode, let error, let data): - expect(statusCode).to(equal(500)) - expect(data).to(equal(bodyContent.data(using: .utf8))) + case .error(let error): + expect(error.code).to(equal(HTTPStatusCodes.internalServerError)) + expect(error.data).to(equal(bodyContent.data(using: .utf8))) default: XCTFail("Response type should be of .error instead of \(type(of: response))") } } -// func testEmptyURLResponseFailure() { -// let bodyContent = "test-body-data" -// let response = Response((r: nil, data: bodyContent.data(using: .utf8), error: nil), for: RequestMock()) -// switch response { -// case .error(let statusCode, let error, let data): -// expect(statusCode).to(equal(500)) -// -// default: -// XCTFail("Response type should be of .error instead of \(type(of: response))") -// } -// } + func testEmptyURLResponseFailure() { + let bodyContent = "test-body-data" + let response = Response((r: nil, data: bodyContent.data(using: .utf8), error: nil), for: RequestMock()) + switch response { + case .error(let error): + expect(error.code).to(equal(HTTPStatusCodes.internalServerError)) + + default: + XCTFail("Response type should be of .error instead of \(type(of: response))") + } + } func testSucceededResponse() { let bodyContent = "test-body-data"