// // 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. // import Alamofire import RxSwift import RxAlamofire typealias ServerResponse = (HTTPURLResponse, Data) public extension Reactive where Base: DataRequest { /// Method that serializes response into target object /// /// - Parameter mappingQueue: The dispatch queue to use for mapping /// - Parameter decoder: JSONDecoder used to decode a decodable type /// - Returns: Observable with HTTP URL Response and target object func apiResponse(mappingQueue: DispatchQueue = .global(), decoder: JSONDecoder) -> Observable> { response(onQueue: mappingQueue) .tryMapResult { response, data in (response, try decoder.decode(T.self, from: data)) } .catchAsRequestError(with: self.base) } /// Method that serializes response into target object /// /// - Parameter mappingQueue: The dispatch queue to use for mapping /// - Returns: Observable with HTTP URL Response and target object func observableApiResponse(mappingQueue: DispatchQueue = .global(), decoder: JSONDecoder) -> Observable> { response(onQueue: mappingQueue) .tryMapObservableResult { response, value in let json = try JSONSerialization.jsonObject(with: value, options: []) return T.create(from: json, with: decoder) .map { (response, $0) } } .catchAsRequestError(with: self.base) } /// Method that serializes response into data /// /// - Parameter mappingQueue: The dispatch queue to use for mapping /// - Returns: Observable with HTTP URL Response and data func dataApiResponse(mappingQueue: DispatchQueue) -> Observable { response(onQueue: mappingQueue) .map { $0 as SessionManager.DataResponse } .catchAsRequestError(with: self.base) } private func response(onQueue queue: DispatchQueue) -> Observable<(HTTPURLResponse, Data)> { responseResult(queue: queue, responseSerializer: DataResponseSerializer()) } } public extension ObservableType where Element == DataRequest { /// Method that validates status codes and catch network errors /// /// - Parameter statusCodes: set of status codes to validate /// - Returns: Observable on self func validate(statusCodes: Set, url: String? = nil) -> Observable { map { $0.validate(statusCode: statusCodes) } .catchAsRequestError(url: url) } } private extension ObservableType where Element == ServerResponse { func tryMapResult(_ transform: @escaping (Element) throws -> R) -> Observable { map { do { return try transform($0) } catch { throw RequestError.mapping(error: error, response: $0.1, url: $0.0.url?.absoluteString) } } } func tryMapObservableResult(_ transform: @escaping (Element) throws -> Observable) -> Observable { flatMap { response, result -> Observable in do { return try transform((response, result)) .catch { throw RequestError.mapping(error: $0, response: result, url: response.url?.absoluteString) } } catch { throw RequestError.mapping(error: error, response: result, url: response.url?.absoluteString) } } } } private extension ObservableType { func catchAsRequestError(with request: DataRequest? = nil, url: String? = nil) -> Observable { self.catch { error in let resultError: RequestError let response = request?.data switch error { case let requestError as RequestError: resultError = requestError case let urlError as URLError: switch urlError.code { case .notConnectedToInternet: resultError = .noConnection(url: url) default: resultError = .network(error: urlError, response: response, url: url) } case let afError as AFError: switch afError { case let .sessionTaskFailed(error): switch error { case let urlError as URLError where urlError.code == .notConnectedToInternet: resultError = .noConnection(url: url) default: resultError = .network(error: error, response: response, url: url) } case .responseSerializationFailed, .responseValidationFailed: resultError = .invalidResponse(error: afError, response: response, url: url) default: resultError = .network(error: afError, response: response, url: url) } default: resultError = .network(error: error, response: response, url: url) } throw resultError } } }