From a4c4cd00ae03497e0ecb6a438ec92d2b03e9fe68 Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Fri, 27 Oct 2017 11:32:02 +0300 Subject: [PATCH] group request errors by type --- LeadKit.xcodeproj/project.pbxproj | 30 ++++++--- Sources/Enums/RequestError.swift | 29 ++++++++ .../AlamofireRequest+Extensions.swift | 66 +++++++++++++++---- ...lProtocol.swift => ConfigurableView.swift} | 8 +-- 4 files changed, 108 insertions(+), 25 deletions(-) create mode 100644 Sources/Enums/RequestError.swift rename Sources/Protocols/{AbstractViewModelProtocol.swift => ConfigurableView.swift} (83%) diff --git a/LeadKit.xcodeproj/project.pbxproj b/LeadKit.xcodeproj/project.pbxproj index 11b17aca..2432f93a 100644 --- a/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit.xcodeproj/project.pbxproj @@ -203,10 +203,10 @@ 671463651EB3396E00EAB194 /* ViewHeightProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462321EB3396E00EAB194 /* ViewHeightProtocol.swift */; }; 671463661EB3396E00EAB194 /* ViewHeightProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462321EB3396E00EAB194 /* ViewHeightProtocol.swift */; }; 671463671EB3396E00EAB194 /* ViewHeightProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462321EB3396E00EAB194 /* ViewHeightProtocol.swift */; }; - 671463681EB3396E00EAB194 /* AbstractViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462331EB3396E00EAB194 /* AbstractViewModelProtocol.swift */; }; - 671463691EB3396E00EAB194 /* AbstractViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462331EB3396E00EAB194 /* AbstractViewModelProtocol.swift */; }; - 6714636A1EB3396E00EAB194 /* AbstractViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462331EB3396E00EAB194 /* AbstractViewModelProtocol.swift */; }; - 6714636B1EB3396E00EAB194 /* AbstractViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462331EB3396E00EAB194 /* AbstractViewModelProtocol.swift */; }; + 671463681EB3396E00EAB194 /* ConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462331EB3396E00EAB194 /* ConfigurableView.swift */; }; + 671463691EB3396E00EAB194 /* ConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462331EB3396E00EAB194 /* ConfigurableView.swift */; }; + 6714636A1EB3396E00EAB194 /* ConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462331EB3396E00EAB194 /* ConfigurableView.swift */; }; + 6714636B1EB3396E00EAB194 /* ConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462331EB3396E00EAB194 /* ConfigurableView.swift */; }; 6714636C1EB3396E00EAB194 /* XibNameProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462341EB3396E00EAB194 /* XibNameProtocol.swift */; }; 6714636D1EB3396E00EAB194 /* XibNameProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462341EB3396E00EAB194 /* XibNameProtocol.swift */; }; 6714636E1EB3396E00EAB194 /* XibNameProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462341EB3396E00EAB194 /* XibNameProtocol.swift */; }; @@ -318,6 +318,10 @@ 67E6C2361EBB32F5007842A6 /* SingleLoadCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E6C2341EBB32F5007842A6 /* SingleLoadCursor.swift */; }; 67E6C2371EBB32F5007842A6 /* SingleLoadCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E6C2341EBB32F5007842A6 /* SingleLoadCursor.swift */; }; 67E6C2381EBB32F5007842A6 /* SingleLoadCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E6C2341EBB32F5007842A6 /* SingleLoadCursor.swift */; }; + 67FDC25F1FA310EA00C76A77 /* RequestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67FDC25E1FA310EA00C76A77 /* RequestError.swift */; }; + 67FDC2601FA310EA00C76A77 /* RequestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67FDC25E1FA310EA00C76A77 /* RequestError.swift */; }; + 67FDC2611FA310EA00C76A77 /* RequestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67FDC25E1FA310EA00C76A77 /* RequestError.swift */; }; + 67FDC2621FA310EA00C76A77 /* RequestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67FDC25E1FA310EA00C76A77 /* RequestError.swift */; }; 82F8BB181F5DDED100C1061B /* Single+DeferredJust.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82F8BB171F5DDED100C1061B /* Single+DeferredJust.swift */; }; A658E54D1F8CD7790093527A /* TableRow+SeparatorsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A658E54C1F8CD7790093527A /* TableRow+SeparatorsExtensions.swift */; }; A658E54E1F8CD7790093527A /* TableRow+SeparatorsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A658E54C1F8CD7790093527A /* TableRow+SeparatorsExtensions.swift */; }; @@ -464,7 +468,7 @@ 6714622E1EB3396E00EAB194 /* StaticViewHeightProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticViewHeightProtocol.swift; sourceTree = ""; }; 671462311EB3396E00EAB194 /* SupportProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SupportProtocol.swift; sourceTree = ""; }; 671462321EB3396E00EAB194 /* ViewHeightProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewHeightProtocol.swift; sourceTree = ""; }; - 671462331EB3396E00EAB194 /* AbstractViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AbstractViewModelProtocol.swift; sourceTree = ""; }; + 671462331EB3396E00EAB194 /* ConfigurableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurableView.swift; sourceTree = ""; }; 671462341EB3396E00EAB194 /* XibNameProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XibNameProtocol.swift; sourceTree = ""; }; 671462371EB3396E00EAB194 /* ApiRequestParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiRequestParameters.swift; sourceTree = ""; }; 671462391EB3396E00EAB194 /* BorderDrawingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BorderDrawingOperation.swift; sourceTree = ""; }; @@ -512,6 +516,7 @@ 67A1FF8E1EBCA09B00D6C89F /* UIImage+Spinner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Spinner.swift"; sourceTree = ""; }; 67A1FF931EBCA65E00D6C89F /* CABasicAnimation+Rotation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CABasicAnimation+Rotation.swift"; sourceTree = ""; }; 67E6C2341EBB32F5007842A6 /* SingleLoadCursor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleLoadCursor.swift; sourceTree = ""; }; + 67FDC25E1FA310EA00C76A77 /* RequestError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestError.swift; sourceTree = ""; }; 78405D3B3D3C3E17456877FF /* Pods_LeadKit_iOSTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LeadKit_iOSTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 82F8BB171F5DDED100C1061B /* Single+DeferredJust.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Single+DeferredJust.swift"; sourceTree = ""; }; 8590CA7831555C295C5DC572 /* Pods_LeadKit_LeadKit_watchOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LeadKit_LeadKit_watchOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -676,6 +681,7 @@ 671461D71EB3396E00EAB194 /* CursorError.swift */, 671461D81EB3396E00EAB194 /* LeadKitError.swift */, 671461D91EB3396E00EAB194 /* ResizeMode.swift */, + 67FDC25E1FA310EA00C76A77 /* RequestError.swift */, ); path = Enums; sourceTree = ""; @@ -895,7 +901,7 @@ 671462221EB3396E00EAB194 /* Protocols */ = { isa = PBXGroup; children = ( - 671462331EB3396E00EAB194 /* AbstractViewModelProtocol.swift */, + 671462331EB3396E00EAB194 /* ConfigurableView.swift */, 679C77D41F98F78E0094BE10 /* AlertRepresentable.swift */, 671463A11EB33FF600EAB194 /* Animatable.swift */, 40F118461F8FEF97004AADAF /* AppearanceConfigurable.swift */, @@ -1959,6 +1965,7 @@ 67A1FF941EBCA65E00D6C89F /* CABasicAnimation+Rotation.swift in Sources */, 82F8BB181F5DDED100C1061B /* Single+DeferredJust.swift in Sources */, 671463301EB3396E00EAB194 /* CursorType.swift in Sources */, + 67FDC25F1FA310EA00C76A77 /* RequestError.swift in Sources */, 6714624C1EB3396E00EAB194 /* MapCursor.swift in Sources */, A6C9A4FA1F8BBCF2009311CC /* EmptyCell.swift in Sources */, 671463241EB3396E00EAB194 /* Any+TypeName.swift in Sources */, @@ -2020,7 +2027,7 @@ 671463A21EB33FF600EAB194 /* Animatable.swift in Sources */, 671462501EB3396E00EAB194 /* StaticCursor.swift in Sources */, 6714629C1EB3396E00EAB194 /* CursorType+Slice.swift in Sources */, - 671463681EB3396E00EAB194 /* AbstractViewModelProtocol.swift in Sources */, + 671463681EB3396E00EAB194 /* ConfigurableView.swift in Sources */, 6771DFE41EE9A00A002DCDAE /* DateFormattingArguments+DateFormatter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2106,9 +2113,10 @@ 671462761EB3396E00EAB194 /* LeadKitError.swift in Sources */, 671462DA1EB3396E00EAB194 /* TimeInterval+DateComponents.swift in Sources */, 6714638E1EB3396E00EAB194 /* SolidFillDrawingOperation.swift in Sources */, + 67FDC2611FA310EA00C76A77 /* RequestError.swift in Sources */, 671462521EB3396E00EAB194 /* StaticCursor.swift in Sources */, 6714629E1EB3396E00EAB194 /* CursorType+Slice.swift in Sources */, - 6714636A1EB3396E00EAB194 /* AbstractViewModelProtocol.swift in Sources */, + 6714636A1EB3396E00EAB194 /* ConfigurableView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2132,6 +2140,7 @@ 671462671EB3396E00EAB194 /* PaginationViewModel.swift in Sources */, 671462931EB3396E00EAB194 /* CGImage+Crop.swift in Sources */, 671462FF1EB3396E00EAB194 /* UIView+XibNameProtocol.swift in Sources */, + 67FDC2621FA310EA00C76A77 /* RequestError.swift in Sources */, 671463871EB3396E00EAB194 /* ResizeDrawingOperation.swift in Sources */, 671463931EB3396E00EAB194 /* TemplateDrawingOperation.swift in Sources */, 674AF55E1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift in Sources */, @@ -2194,7 +2203,7 @@ 67E6C2381EBB32F5007842A6 /* SingleLoadCursor.swift in Sources */, 671462531EB3396E00EAB194 /* StaticCursor.swift in Sources */, 6714629F1EB3396E00EAB194 /* CursorType+Slice.swift in Sources */, - 6714636B1EB3396E00EAB194 /* AbstractViewModelProtocol.swift in Sources */, + 6714636B1EB3396E00EAB194 /* ConfigurableView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2264,6 +2273,7 @@ 6714626D1EB3396E00EAB194 /* XibView.swift in Sources */, 6714637D1EB3396E00EAB194 /* ImageDrawingOperation.swift in Sources */, 671463351EB3396E00EAB194 /* DrawingOperation.swift in Sources */, + 67FDC2601FA310EA00C76A77 /* RequestError.swift in Sources */, 671462711EB3396E00EAB194 /* CursorError.swift in Sources */, 671463991EB3396E00EAB194 /* AnyLoadingIndicator.swift in Sources */, 671463A81EB340C000EAB194 /* UIViewController+ConfigurableController.swift in Sources */, @@ -2296,7 +2306,7 @@ 6771DFE51EE9A00A002DCDAE /* DateFormattingArguments+DateFormatter.swift in Sources */, 671462511EB3396E00EAB194 /* StaticCursor.swift in Sources */, 6714629D1EB3396E00EAB194 /* CursorType+Slice.swift in Sources */, - 671463691EB3396E00EAB194 /* AbstractViewModelProtocol.swift in Sources */, + 671463691EB3396E00EAB194 /* ConfigurableView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/Enums/RequestError.swift b/Sources/Enums/RequestError.swift new file mode 100644 index 00000000..cdb37173 --- /dev/null +++ b/Sources/Enums/RequestError.swift @@ -0,0 +1,29 @@ +// +// Copyright (c) 2017 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public enum RequestError: Error { + + case noConnection // no connection to the server + case network(error: Error) + case mapping(error: Error, response: Any) + +} diff --git a/Sources/Extensions/Alamofire/AlamofireRequest+Extensions.swift b/Sources/Extensions/Alamofire/AlamofireRequest+Extensions.swift index 88416dfa..75b4d42d 100644 --- a/Sources/Extensions/Alamofire/AlamofireRequest+Extensions.swift +++ b/Sources/Extensions/Alamofire/AlamofireRequest+Extensions.swift @@ -25,6 +25,8 @@ import RxSwift import ObjectMapper import RxAlamofire +typealias ServerResponse = (HTTPURLResponse, Any) + public extension Reactive where Base: DataRequest { /// Method which serializes response into target object @@ -35,7 +37,7 @@ public extension Reactive where Base: DataRequest { -> Observable<(response: HTTPURLResponse, model: T)> { return responseJSONOnQueue(mappingQueue) - .map { resp, value in + .tryMapResult { resp, value in let json = try cast(value) as [String: Any] return (resp, try T(JSON: json)) @@ -50,11 +52,11 @@ public extension Reactive where Base: DataRequest { -> Observable<(response: HTTPURLResponse, models: [T])> { return responseJSONOnQueue(mappingQueue) - .map { resp, value in + .tryMapResult { resp, value in let jsonArray = try cast(value) as [[String: Any]] return (resp, try Mapper().mapArray(JSONArray: jsonArray)) - } + } } /// Method which serializes response into target object @@ -65,12 +67,12 @@ public extension Reactive where Base: DataRequest { -> Observable<(response: HTTPURLResponse, model: T)> where T.ModelType == T { return responseJSONOnQueue(mappingQueue) - .flatMap { resp, value -> Observable<(response: HTTPURLResponse, model: T)> in - let json = try cast(value) as [String: Any] + .tryMapObservableResult { resp, value in + let json = try cast(value) as [String: Any] - return T.createFrom(map: Map(mappingType: .fromJSON, JSON: json)) - .map { (resp, $0) } - } + return T.createFrom(map: Map(mappingType: .fromJSON, JSON: json)) + .map { (resp, $0) } + } } /// Method which serializes response into array of target objects @@ -81,7 +83,7 @@ public extension Reactive where Base: DataRequest { -> Observable<(response: HTTPURLResponse, models: [T])> where T.ModelType == T { return responseJSONOnQueue(mappingQueue) - .flatMap { resp, value -> Observable<(response: HTTPURLResponse, models: [T])> in + .tryMapObservableResult { resp, value in let jsonArray = try cast(value) as [[String: Any]] let createFromList = jsonArray.map { @@ -90,11 +92,53 @@ public extension Reactive where Base: DataRequest { return Observable.zip(createFromList) { $0 } .map { (resp, $0) } + } + } + + internal func responseJSONOnQueue(_ queue: DispatchQueue) -> Observable { + let responseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments) + + return responseResult(queue: queue, responseSerializer: responseSerializer) + .catchError { + switch $0 { + case let urlError as URLError: + switch urlError.code { + case .notConnectedToInternet, .timedOut: + throw RequestError.noConnection + default: + throw RequestError.network(error: $0) + } + default: + throw RequestError.network(error: $0) + } } } - internal func responseJSONOnQueue(_ queue: DispatchQueue) -> Observable<(HTTPURLResponse, Any)> { - return responseResult(queue: queue, responseSerializer: DataRequest.jsonResponseSerializer(options: .allowFragments)) +} + +private extension ObservableType where E == ServerResponse { + + func tryMapResult(_ transform: @escaping (E) throws -> R) -> Observable { + return map { + do { + return try transform($0) + } catch { + throw RequestError.mapping(error: error, response: $0.1) + } + } + } + + func tryMapObservableResult(_ transform: @escaping (E) throws -> Observable) -> Observable { + return flatMap { response -> Observable in + do { + return try transform(response) + .catchError { + throw RequestError.mapping(error: $0, response: response.1) + } + } catch { + throw RequestError.mapping(error: error, response: response.1) + } + } } } diff --git a/Sources/Protocols/AbstractViewModelProtocol.swift b/Sources/Protocols/ConfigurableView.swift similarity index 83% rename from Sources/Protocols/AbstractViewModelProtocol.swift rename to Sources/Protocols/ConfigurableView.swift index 89e59963..4b83c067 100644 --- a/Sources/Protocols/AbstractViewModelProtocol.swift +++ b/Sources/Protocols/ConfigurableView.swift @@ -23,17 +23,17 @@ import Foundation /** - * protocol which ensures that specific type can create view model and can apply new view state with view model + * Protocol that ensures that specific type can create view model and can apply new view state with view model */ -public protocol AbstractViewModelProtocol { +public protocol ConfigurableView { associatedtype ViewModelType /** - method which applies new view state with view model object + Applies new view state with view model object - parameter viewModel: view model to apply new view state - returns: nothing */ - func set(viewModel: ViewModelType) + func configure(with _: ViewModelType) }