diff --git a/README.md b/README.md index 513cd76..2f2ba94 100644 --- a/README.md +++ b/README.md @@ -140,11 +140,11 @@ import Legatus final class UsersApiRequest: DeserializeableRequest { var path: String { - return "users" + "users" } var deserializer: ResponseDeserializer<[User]> { - return JSONDeserializer.collectionDeserializer(keyPath: "results") + JSONDeserializer.collectionDeserializer(keyPath: "results") } } @@ -244,7 +244,7 @@ While working with SwiftUI, where most of UI updates based on *Combine* mechanis var subscriptions = Set() apiClient - .requestPublisher(request: UsersApiRequest()) + .responsePublisher(request: UsersApiRequest()) .catch { _ in return Just([User]())} .assign(to: \.users, on: self) .store(in: &subscriptions) diff --git a/Sources/Legatus/APIClient.swift b/Sources/Legatus/APIClient.swift index 22f973c..76e3412 100644 --- a/Sources/Legatus/APIClient.swift +++ b/Sources/Legatus/APIClient.swift @@ -38,7 +38,7 @@ open class APIClient { uploadProgressObserver: uploadProgressObserver)) .retry(retries) .handleEvents(receiveCancel: { - completion(.failure(APIClientError.requestCancelled)) + completion(.failure(APIClientError.requestCancelled)) }) .flatMap { self.handle(apiResponse: $0) } .subscribe(on: deserializationQueue) @@ -61,11 +61,11 @@ open class APIClient { retries: Int = 0, uploadProgressObserver: ((Progress) -> Void)? = nil, completion: @escaping (Swift.Result) -> Void) -> AnyCancellable where U == T.ResponseType { - return executeRequest(request, - retries: retries, - deserializer: request.deserializer, - uploadProgressObserver: uploadProgressObserver, - completion: completion) + executeRequest(request, + retries: retries, + deserializer: request.deserializer, + uploadProgressObserver: uploadProgressObserver, + completion: completion) } public func cancelAllRequests() { @@ -95,19 +95,19 @@ open class APIClient { } private func requestResponsePublisher(_ request: APIRequest) -> AnyPublisher { - return Deferred { [weak self] in - return DataRequestPublisher(apiClient: self, apiRequest: request) + Deferred { [weak self] in + DataResponsePublisher(apiClient: self, apiRequest: request) }.eraseToAnyPublisher() } private func multipartRequestResponsePublisher(_ request: APIRequest, requestInputMultipartData: [String: URL], uploadProgressObserver: ((Progress) -> Void)? = nil) -> AnyPublisher { - return Deferred { [weak self] in - return MultipartRequestPublisher(apiClient: self, - apiRequest: request, - requestInputMultipartData: requestInputMultipartData, - uploadProgressObserver: uploadProgressObserver) + Deferred { [weak self] in + MultipartResponsePublisher(apiClient: self, + apiRequest: request, + requestInputMultipartData: requestInputMultipartData, + uploadProgressObserver: uploadProgressObserver) }.eraseToAnyPublisher() } } diff --git a/Sources/Legatus/Deserializers/JSONDeserializer/JSONDecoderKeypath /KeyPathWrapper.swift b/Sources/Legatus/Deserializers/JSONDeserializer/JSONDecoderKeypath /KeyPathWrapper.swift index fdfefab..8b24a3a 100644 --- a/Sources/Legatus/Deserializers/JSONDeserializer/JSONDecoderKeypath /KeyPathWrapper.swift +++ b/Sources/Legatus/Deserializers/JSONDeserializer/JSONDecoderKeypath /KeyPathWrapper.swift @@ -37,9 +37,11 @@ final class KeyPathWrapper: Decodable { } /// Finds nested container and returns it and the key for object - func objectContainer(for keyPath: [String], - in currentContainer: KeyedContainer, - key currentKey: Key) throws -> (KeyedContainer, Key) { + func objectContainer( + for keyPath: [String], + in currentContainer: KeyedContainer, + key currentKey: Key + ) throws -> (KeyedContainer, Key) { guard !keyPath.isEmpty else { return (currentContainer, currentKey) } let container = try currentContainer.nestedContainer(keyedBy: Key.self, forKey: currentKey) let key = try getKey(from: keyPath) diff --git a/Sources/Legatus/Deserializers/JSONDeserializer/JSONDeserializer.swift b/Sources/Legatus/Deserializers/JSONDeserializer/JSONDeserializer.swift index 23ec725..52d1585 100644 --- a/Sources/Legatus/Deserializers/JSONDeserializer/JSONDeserializer.swift +++ b/Sources/Legatus/Deserializers/JSONDeserializer/JSONDeserializer.swift @@ -1,5 +1,5 @@ -import Foundation import Combine +import Foundation public enum JSONDeserializerError: Error { case jsonDeserializableInitFailed(String) @@ -13,13 +13,13 @@ open class JSONDeserializer: ResponseDeserializer { "Wrong result type: \(jsonObject.self). Expected \(T.self)" ) } - + return object } } - + public override func deserialize(data: Data) -> Future { - return Future { [weak self] promise in + Future { [weak self] promise in guard let self = self else { return } do { let object = try self.transform(data) @@ -32,12 +32,12 @@ open class JSONDeserializer: ResponseDeserializer { } extension JSONDeserializer where T: Decodable { - + public class func singleObjectDeserializer(keyPath path: String...) -> JSONDeserializer { - return JSONDeserializer { jsonDataObject in + JSONDeserializer { jsonDataObject in do { let jsonDecoder = JSONDecoder() - + return try path.isEmpty ? jsonDecoder.decode(T.self, from: jsonDataObject) : jsonDecoder.decode(T.self, from: jsonDataObject, keyPath: path.joined(separator: ".")) @@ -48,12 +48,12 @@ extension JSONDeserializer where T: Decodable { } } } - + public class func collectionDeserializer(keyPath path: String...) -> JSONDeserializer<[T]> { - return JSONDeserializer<[T]> { jsonDataObject in + JSONDeserializer<[T]> { jsonDataObject in do { let jsonDecoder = JSONDecoder() - + return try path.isEmpty ? jsonDecoder.decode([T].self, from: jsonDataObject) : jsonDecoder.decode([T].self, from: jsonDataObject, keyPath: path.joined(separator: ".")) @@ -64,5 +64,5 @@ extension JSONDeserializer where T: Decodable { } } } - + } diff --git a/Sources/Legatus/Deserializers/ResponseDeserializer.swift b/Sources/Legatus/Deserializers/ResponseDeserializer.swift index 67c7e6c..6f365b4 100644 --- a/Sources/Legatus/Deserializers/ResponseDeserializer.swift +++ b/Sources/Legatus/Deserializers/ResponseDeserializer.swift @@ -1,5 +1,5 @@ -import Foundation import Combine +import Foundation open class ResponseDeserializer { typealias Transform = ((Data) throws -> T) @@ -17,7 +17,7 @@ open class ResponseDeserializer { open class EmptyDeserializer: ResponseDeserializer { public override func deserialize(data: Data) -> Future { - return Future { promise in + Future { promise in promise(.success(())) } } @@ -25,7 +25,7 @@ open class EmptyDeserializer: ResponseDeserializer { open class RawDataDeserializer: ResponseDeserializer { public override func deserialize(data: Data) -> Future { - return Future { promise in + Future { promise in promise(.success(data)) } } diff --git a/Sources/Legatus/Deserializers/XMLDeserializer/XMLDeserializer.swift b/Sources/Legatus/Deserializers/XMLDeserializer/XMLDeserializer.swift index c780124..c949aa6 100644 --- a/Sources/Legatus/Deserializers/XMLDeserializer/XMLDeserializer.swift +++ b/Sources/Legatus/Deserializers/XMLDeserializer/XMLDeserializer.swift @@ -1,5 +1,5 @@ -import Foundation import Combine +import Foundation import SWXMLHash public protocol XMLDeserializable { @@ -16,7 +16,8 @@ open class XMLDeserializer: ResponseDeserializer { if let xmlObject = xmlObject as? T { return xmlObject } - throw XMLDeserializerError.jsonDeserializableInitFailed("Wrong result type: \(xmlObject.self). Expected \(T.self)") + throw XMLDeserializerError.jsonDeserializableInitFailed( + "Wrong result type: \(xmlObject.self). Expected \(T.self)") } } @@ -33,26 +34,28 @@ open class XMLDeserializer: ResponseDeserializer { } } -public extension XMLDeserializer where T: XMLDeserializable { +extension XMLDeserializer where T: XMLDeserializable { - class func singleObjectDeserializer(keyPath path: String...) -> XMLDeserializer { - return XMLDeserializer { xmlDataObject in + public class func singleObjectDeserializer(keyPath path: String...) -> XMLDeserializer { + XMLDeserializer { xmlDataObject in let xml = SWXMLHash.lazy(xmlDataObject) guard let deserializedObject = T(xmlIndexer: xml[path]) else { - throw XMLDeserializerError.jsonDeserializableInitFailed("Failed to create \(T.self) object.") + throw XMLDeserializerError.jsonDeserializableInitFailed( + "Failed to create \(T.self) object.") } return deserializedObject } } - class func collectionDeserializer(keyPath path: String...) -> XMLDeserializer<[T]> { - return XMLDeserializer<[T]>(transform: { xmlDataObject in + public class func collectionDeserializer(keyPath path: String...) -> XMLDeserializer<[T]> { + XMLDeserializer<[T]>(transform: { xmlDataObject in let xml = SWXMLHash.lazy(xmlDataObject) let deserializedObjects = xml[path].all.map { T(xmlIndexer: $0) } if deserializedObjects.contains(where: { $0 == nil }) { - throw XMLDeserializerError.jsonDeserializableInitFailed("Failed to create array of \(T.self) objects.") + throw XMLDeserializerError.jsonDeserializableInitFailed( + "Failed to create array of \(T.self) objects.") } return deserializedObjects.compactMap { $0 } diff --git a/Sources/Legatus/Extensions/APIClient+Extensions.swift b/Sources/Legatus/Extensions/APIClient+Extensions.swift index 78be039..2300670 100644 --- a/Sources/Legatus/Extensions/APIClient+Extensions.swift +++ b/Sources/Legatus/Extensions/APIClient+Extensions.swift @@ -1,43 +1,51 @@ -import Foundation import Combine +import Foundation -public extension APIClient { +extension APIClient { - func requestPublisher(_ request: APIRequest, - deserializer: ResponseDeserializer, - uploadProgressObserver: ((Progress) -> Void)? = nil) -> AnyPublisher { - return Deferred { - return Future { [weak self] promise in + public func responsePublisher( + request: APIRequest, + deserializer: ResponseDeserializer, + uploadProgressObserver: ((Progress) -> Void)? = nil + ) -> AnyPublisher { + Deferred { + Future { [weak self] promise in guard let self = self else { return } - self.executeRequest(request, - deserializer: deserializer, - uploadProgressObserver: uploadProgressObserver) { result in - switch result { - case .success(let deserializedObjects): - promise(.success(deserializedObjects)) - case .failure(let error): - promise(.failure(error)) - } + self.executeRequest( + request, + deserializer: deserializer, + uploadProgressObserver: uploadProgressObserver + ) { result in + switch result { + case .success(let deserializedObjects): + promise(.success(deserializedObjects)) + case .failure(let error): + promise(.failure(error)) + } } } }.eraseToAnyPublisher() } - func requestPublisher(request: T, - uploadProgressObserver: ((Progress) -> Void)? = nil) -> AnyPublisher where U == T.ResponseType { - return Deferred { - return Future { [weak self] promise in + public func responsePublisher( + request: T, + uploadProgressObserver: ((Progress) -> Void)? = nil + ) -> AnyPublisher where U == T.ResponseType { + Deferred { + Future { [weak self] promise in guard let self = self else { return } - self.executeRequest(request: request, - uploadProgressObserver: uploadProgressObserver) { result in - switch result { - case .success(let deserializedObjects): - promise(.success(deserializedObjects)) - case .failure(let error): - promise(.failure(error)) - } + self.executeRequest( + request: request, + uploadProgressObserver: uploadProgressObserver + ) { result in + switch result { + case .success(let deserializedObjects): + promise(.success(deserializedObjects)) + case .failure(let error): + promise(.failure(error)) + } } } }.eraseToAnyPublisher() diff --git a/Sources/Legatus/RequestPublishers/DataRequestPublisher.swift b/Sources/Legatus/RequestPublishers/DataRequestPublisher.swift deleted file mode 100644 index d24ae45..0000000 --- a/Sources/Legatus/RequestPublishers/DataRequestPublisher.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Foundation -import Combine -import Alamofire - -public struct DataRequestPublisher: Publisher { - public typealias Output = APIResponse - public typealias Failure = Error - - private let apiClient: APIClient? - private let apiRequest: APIRequest - - init(apiClient: APIClient?, apiRequest: APIRequest) { - self.apiClient = apiClient - self.apiRequest = apiRequest - } - - public func receive(subscriber: S) where Failure == S.Failure, Output == S.Input { - guard let apiClient = apiClient else { return } - let dataRequestSubscription = DataRequestSubscription(subscriber: subscriber, - apiClient: apiClient, - apiRequest: apiRequest) - - subscriber.receive(subscription: dataRequestSubscription) - } - -} diff --git a/Sources/Legatus/ResponsePublishers/DataResponsePublisher.swift b/Sources/Legatus/ResponsePublishers/DataResponsePublisher.swift new file mode 100644 index 0000000..c2dfb83 --- /dev/null +++ b/Sources/Legatus/ResponsePublishers/DataResponsePublisher.swift @@ -0,0 +1,27 @@ +import Alamofire +import Combine +import Foundation + +public struct DataResponsePublisher: Publisher { + public typealias Output = APIResponse + public typealias Failure = Error + + private let apiClient: APIClient? + private let apiRequest: APIRequest + + init(apiClient: APIClient?, apiRequest: APIRequest) { + self.apiClient = apiClient + self.apiRequest = apiRequest + } + + public func receive(subscriber: S) where Failure == S.Failure, Output == S.Input { + guard let apiClient = apiClient else { return } + let dataRequestSubscription = DataResponseSubscription( + subscriber: subscriber, + apiClient: apiClient, + apiRequest: apiRequest) + + subscriber.receive(subscription: dataRequestSubscription) + } + +} diff --git a/Sources/Legatus/RequestPublishers/MultipartRequestPublisher.swift b/Sources/Legatus/ResponsePublishers/MultipartResponsePublisher.swift similarity index 90% rename from Sources/Legatus/RequestPublishers/MultipartRequestPublisher.swift rename to Sources/Legatus/ResponsePublishers/MultipartResponsePublisher.swift index 2e979e6..371dc64 100644 --- a/Sources/Legatus/RequestPublishers/MultipartRequestPublisher.swift +++ b/Sources/Legatus/ResponsePublishers/MultipartResponsePublisher.swift @@ -2,7 +2,7 @@ import Foundation import Combine import Alamofire -public struct MultipartRequestPublisher: Publisher { +public struct MultipartResponsePublisher: Publisher { public typealias Output = APIResponse public typealias Failure = Error @@ -23,7 +23,7 @@ public struct MultipartRequestPublisher: Publisher { public func receive(subscriber: S) where Failure == S.Failure, Output == S.Input { guard let apiClient = apiClient else { return } - let multipartRequestSubsription = MultipartRequestSubscription(subscriber: subscriber, + let multipartRequestSubsription = MultipartResponseSubscription(subscriber: subscriber, apiClient: apiClient, apiRequest: apiRequest, requestInputMultipartData: requestInputMultipartData, diff --git a/Sources/Legatus/RequestSubscriptions/DataRequestSubscription.swift b/Sources/Legatus/ResponseSubscriptions/DataResponseSubscription.swift similarity index 94% rename from Sources/Legatus/RequestSubscriptions/DataRequestSubscription.swift rename to Sources/Legatus/ResponseSubscriptions/DataResponseSubscription.swift index c4b3ac9..2fdfa97 100644 --- a/Sources/Legatus/RequestSubscriptions/DataRequestSubscription.swift +++ b/Sources/Legatus/ResponseSubscriptions/DataResponseSubscription.swift @@ -2,7 +2,7 @@ import Foundation import Combine import Alamofire -public final class DataRequestSubscription: Subscription where S.Input == APIResponse, S.Failure == Error { +public final class DataResponseSubscription: Subscription where S.Input == APIResponse, S.Failure == Error { private let apiClient: APIClient private let apiRequest: APIRequest private var dataRequest: DataRequest? diff --git a/Sources/Legatus/RequestSubscriptions/MultipartRequestSubscription.swift b/Sources/Legatus/ResponseSubscriptions/MultipartResponseSubscription.swift similarity index 95% rename from Sources/Legatus/RequestSubscriptions/MultipartRequestSubscription.swift rename to Sources/Legatus/ResponseSubscriptions/MultipartResponseSubscription.swift index 1d2ecff..a96fbe1 100644 --- a/Sources/Legatus/RequestSubscriptions/MultipartRequestSubscription.swift +++ b/Sources/Legatus/ResponseSubscriptions/MultipartResponseSubscription.swift @@ -2,7 +2,7 @@ import Foundation import Combine import Alamofire -public final class MultipartRequestSubscription: Subscription where S.Input == APIResponse, S.Failure == Error { +public final class MultipartResponseSubscription: Subscription where S.Input == APIResponse, S.Failure == Error { private let apiClient: APIClient private let apiRequest: APIRequest private let requestInputMultipartData: [String: URL] diff --git a/Tests/LegatusTests/ApiClientTests.swift b/Tests/LegatusTests/ApiClientTests.swift index fa5f4af..90e7eb4 100644 --- a/Tests/LegatusTests/ApiClientTests.swift +++ b/Tests/LegatusTests/ApiClientTests.swift @@ -52,12 +52,12 @@ final class ApiClientTests: XCTestCase { if case let .success(apiVersion) = result { XCTAssertEqual(apiVersion.value, expectedApiVersionValue) } else if case let .failure(error) = result { - XCTAssertTrue(false, "Unexpected response: \(error).") + XCTAssertTrue(false, "Unexpected response: \(error).") } randomUserApiRequestExpectation.fulfill() } - wait(for: [randomUserApiRequestExpectation], timeout: 20.0) + wait(for: [randomUserApiRequestExpectation], timeout: 20.0) } func testParallelRequests() { @@ -98,12 +98,12 @@ final class ApiClientTests: XCTestCase { XCTAssertFalse(fetchedUsers.isEmpty) XCTAssertTrue(fetchedUsers.count == expectedResultsCount) } else { - XCTAssertTrue(false, "Unexpected response.") + XCTAssertTrue(false, "Unexpected response.") } randomUserApiRequestExpectation.fulfill() } - wait(for: [randomUserApiRequestExpectation], timeout: 20.0) + wait(for: [randomUserApiRequestExpectation], timeout: 20.0) } func testErrorResponse() { @@ -183,13 +183,13 @@ final class ApiClientTests: XCTestCase { wait(for: [httpBinGetRequestExpectation], timeout: 10.0) } - func testRequestPublisherCancelation() { + func testResponsePublisherCancelation() { let httpBinApiClient = APIClient(baseURL: URL(string: "https://webservice.com/api/")!) let httpBinGetRequest = HttpBinGetRequest() let httpBinGetRequestExpectation = XCTestExpectation(description: "Execute api request.") let cancelationToken = httpBinApiClient - .requestPublisher(request: httpBinGetRequest) + .responsePublisher(request: httpBinGetRequest) .handleEvents(receiveCancel: { XCTAssertTrue(true) httpBinGetRequestExpectation.fulfill() @@ -251,7 +251,7 @@ final class ApiClientTests: XCTestCase { ("testAuthRequest", testAuthRequest), ("testMissedAccessToken", testMissedAccessToken), ("testRequestCancelation", testRequestCancelation), - ("testRequestPublisherCancelation", testRequestPublisherCancelation), + ("testResponsePublisherCancelation", testResponsePublisherCancelation), ("testCancelAllRequests", testCancelAllRequests) ] } diff --git a/Tests/LegatusTests/Requests/GetRandomUserApiVersion.swift b/Tests/LegatusTests/Requests/GetRandomUserApiVersion.swift index 6302f17..e8a383e 100644 --- a/Tests/LegatusTests/Requests/GetRandomUserApiVersion.swift +++ b/Tests/LegatusTests/Requests/GetRandomUserApiVersion.swift @@ -4,11 +4,11 @@ import Foundation final class GetRandomUserApiVersionRequest: DeserializeableRequest { var parameters: [String: Any]? { - return ["format": "xml"] + ["format": "xml"] } var deserializer: ResponseDeserializer { - return XMLDeserializer.singleObjectDeserializer(keyPath: "user", "info", "version") + XMLDeserializer.singleObjectDeserializer(keyPath: "user", "info", "version") } } diff --git a/Tests/LegatusTests/Requests/HttpBinBearerAuthRequest.swift b/Tests/LegatusTests/Requests/HttpBinBearerAuthRequest.swift index 01962a6..c54576e 100644 --- a/Tests/LegatusTests/Requests/HttpBinBearerAuthRequest.swift +++ b/Tests/LegatusTests/Requests/HttpBinBearerAuthRequest.swift @@ -6,10 +6,10 @@ final class HttpBinBearerAuthRequest: AuthRequest, DeserializeableRequest { var accessToken: String? var path: String { - return "bearer" + "bearer" } var deserializer: ResponseDeserializer { - return JSONDeserializer.singleObjectDeserializer() + JSONDeserializer.singleObjectDeserializer() } } diff --git a/Tests/LegatusTests/Requests/HttpBinGetRequest.swift b/Tests/LegatusTests/Requests/HttpBinGetRequest.swift index 3421135..0bae959 100644 --- a/Tests/LegatusTests/Requests/HttpBinGetRequest.swift +++ b/Tests/LegatusTests/Requests/HttpBinGetRequest.swift @@ -4,11 +4,11 @@ import Foundation final class HttpBinGetRequest: DeserializeableRequest { var path: String { - return "get" + "get" } var deserializer: ResponseDeserializer { - return JSONDeserializer.singleObjectDeserializer() + JSONDeserializer.singleObjectDeserializer() } } diff --git a/Tests/LegatusTests/Requests/HttpBinGetXmlRequest.swift b/Tests/LegatusTests/Requests/HttpBinGetXmlRequest.swift index 51b9aef..90c2d00 100644 --- a/Tests/LegatusTests/Requests/HttpBinGetXmlRequest.swift +++ b/Tests/LegatusTests/Requests/HttpBinGetXmlRequest.swift @@ -4,11 +4,11 @@ import Foundation final class HttpBinGetXmlRequest: DeserializeableRequest { var path: String { - return "xml" + "xml" } var deserializer: ResponseDeserializer { - return XMLDeserializer.singleObjectDeserializer() + XMLDeserializer.singleObjectDeserializer() } } diff --git a/Tests/LegatusTests/Requests/RandomUserApiRequest.swift b/Tests/LegatusTests/Requests/RandomUserApiRequest.swift index 1c2361d..1056aac 100644 --- a/Tests/LegatusTests/Requests/RandomUserApiRequest.swift +++ b/Tests/LegatusTests/Requests/RandomUserApiRequest.swift @@ -11,7 +11,7 @@ final class RandomUserApiRequest: DeserializeableRequest { } var deserializer: ResponseDeserializer<[RandomUser]> { - return JSONDeserializer.collectionDeserializer(keyPath: "results") + JSONDeserializer.collectionDeserializer(keyPath: "results") } private let results: Int? diff --git a/Tests/LegatusTests/XCTestManifests.swift b/Tests/LegatusTests/XCTestManifests.swift index 893cc78..74c2b77 100644 --- a/Tests/LegatusTests/XCTestManifests.swift +++ b/Tests/LegatusTests/XCTestManifests.swift @@ -1,11 +1,12 @@ import XCTest #if !canImport(ObjectiveC) -public func allTests() -> [XCTestCaseEntry] { - return [ - testCase(JSONDeserializerTests.allTests), - testCase(ApiClientTests.allTests), - testCase(APIReachabilityManagerTests.allTests) - ].flatMap { $0 } -} + public func allTests() -> [XCTestCaseEntry] { + [ + testCase(JSONDeserializerTests.allTests), + testCase(ApiClientTests.allTests), + testCase(APIReachabilityManagerTests.allTests) + ] + .flatMap { $0 } + } #endif