Skip to content
This repository has been archived by the owner on Sep 15, 2024. It is now read-only.

Commit

Permalink
Merge branch 'development' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
artemkalinovsky committed Sep 22, 2020
2 parents fd47ac1 + 656306f commit e35c2a5
Show file tree
Hide file tree
Showing 19 changed files with 138 additions and 123 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,11 @@ import Legatus
final class UsersApiRequest: DeserializeableRequest {

var path: String {
return "users"
"users"
}

var deserializer: ResponseDeserializer<[User]> {
return JSONDeserializer<User>.collectionDeserializer(keyPath: "results")
JSONDeserializer<User>.collectionDeserializer(keyPath: "results")
}

}
Expand Down Expand Up @@ -244,7 +244,7 @@ While working with SwiftUI, where most of UI updates based on *Combine* mechanis
var subscriptions = Set<AnyCancellable>()

apiClient
.requestPublisher(request: UsersApiRequest())
.responsePublisher(request: UsersApiRequest())
.catch { _ in return Just([User]())}
.assign(to: \.users, on: self)
.store(in: &subscriptions)
Expand Down
26 changes: 13 additions & 13 deletions Sources/Legatus/APIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -61,11 +61,11 @@ open class APIClient {
retries: Int = 0,
uploadProgressObserver: ((Progress) -> Void)? = nil,
completion: @escaping (Swift.Result<U, Error>) -> 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() {
Expand Down Expand Up @@ -95,19 +95,19 @@ open class APIClient {
}

private func requestResponsePublisher(_ request: APIRequest) -> AnyPublisher<APIResponse, Error> {
return Deferred<DataRequestPublisher> { [weak self] in
return DataRequestPublisher(apiClient: self, apiRequest: request)
Deferred<DataResponsePublisher> { [weak self] in
DataResponsePublisher(apiClient: self, apiRequest: request)
}.eraseToAnyPublisher()
}

private func multipartRequestResponsePublisher(_ request: APIRequest,
requestInputMultipartData: [String: URL],
uploadProgressObserver: ((Progress) -> Void)? = nil) -> AnyPublisher<APIResponse, Error> {
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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ final class KeyPathWrapper<T: Decodable>: 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)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Foundation
import Combine
import Foundation

public enum JSONDeserializerError: Error {
case jsonDeserializableInitFailed(String)
Expand All @@ -13,13 +13,13 @@ open class JSONDeserializer<T>: ResponseDeserializer<T> {
"Wrong result type: \(jsonObject.self). Expected \(T.self)"
)
}

return object
}
}

public override func deserialize(data: Data) -> Future<T, Error> {
return Future { [weak self] promise in
Future { [weak self] promise in
guard let self = self else { return }
do {
let object = try self.transform(data)
Expand All @@ -32,12 +32,12 @@ open class JSONDeserializer<T>: ResponseDeserializer<T> {
}

extension JSONDeserializer where T: Decodable {

public class func singleObjectDeserializer(keyPath path: String...) -> JSONDeserializer<T> {
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: "."))
Expand All @@ -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: "."))
Expand All @@ -64,5 +64,5 @@ extension JSONDeserializer where T: Decodable {
}
}
}

}
6 changes: 3 additions & 3 deletions Sources/Legatus/Deserializers/ResponseDeserializer.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Foundation
import Combine
import Foundation

open class ResponseDeserializer<T> {
typealias Transform = ((Data) throws -> T)
Expand All @@ -17,15 +17,15 @@ open class ResponseDeserializer<T> {

open class EmptyDeserializer: ResponseDeserializer<Void> {
public override func deserialize(data: Data) -> Future<Void, Error> {
return Future { promise in
Future { promise in
promise(.success(()))
}
}
}

open class RawDataDeserializer: ResponseDeserializer<Data> {
public override func deserialize(data: Data) -> Future<Data, Error> {
return Future { promise in
Future { promise in
promise(.success(data))
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Foundation
import Combine
import Foundation
import SWXMLHash

public protocol XMLDeserializable {
Expand All @@ -16,7 +16,8 @@ open class XMLDeserializer<T>: ResponseDeserializer<T> {
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)")
}
}

Expand All @@ -33,26 +34,28 @@ open class XMLDeserializer<T>: ResponseDeserializer<T> {
}
}

public extension XMLDeserializer where T: XMLDeserializable {
extension XMLDeserializer where T: XMLDeserializable {

class func singleObjectDeserializer(keyPath path: String...) -> XMLDeserializer<T> {
return XMLDeserializer { xmlDataObject in
public class func singleObjectDeserializer(keyPath path: String...) -> XMLDeserializer<T> {
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 }
Expand Down
64 changes: 36 additions & 28 deletions Sources/Legatus/Extensions/APIClient+Extensions.swift
Original file line number Diff line number Diff line change
@@ -1,43 +1,51 @@
import Foundation
import Combine
import Foundation

public extension APIClient {
extension APIClient {

func requestPublisher<T>(_ request: APIRequest,
deserializer: ResponseDeserializer<T>,
uploadProgressObserver: ((Progress) -> Void)? = nil) -> AnyPublisher<T, Error> {
return Deferred {
return Future { [weak self] promise in
public func responsePublisher<T>(
request: APIRequest,
deserializer: ResponseDeserializer<T>,
uploadProgressObserver: ((Progress) -> Void)? = nil
) -> AnyPublisher<T, Error> {
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<T: DeserializeableRequest, U>(request: T,
uploadProgressObserver: ((Progress) -> Void)? = nil) -> AnyPublisher<U, Error> where U == T.ResponseType {
return Deferred {
return Future { [weak self] promise in
public func responsePublisher<T: DeserializeableRequest, U>(
request: T,
uploadProgressObserver: ((Progress) -> Void)? = nil
) -> AnyPublisher<U, Error> 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()
Expand Down
26 changes: 0 additions & 26 deletions Sources/Legatus/RequestPublishers/DataRequestPublisher.swift

This file was deleted.

27 changes: 27 additions & 0 deletions Sources/Legatus/ResponsePublishers/DataResponsePublisher.swift
Original file line number Diff line number Diff line change
@@ -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<S: Subscriber>(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)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -23,7 +23,7 @@ public struct MultipartRequestPublisher: Publisher {

public func receive<S: Subscriber>(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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Foundation
import Combine
import Alamofire

public final class DataRequestSubscription<S: Subscriber>: Subscription where S.Input == APIResponse, S.Failure == Error {
public final class DataResponseSubscription<S: Subscriber>: Subscription where S.Input == APIResponse, S.Failure == Error {
private let apiClient: APIClient
private let apiRequest: APIRequest
private var dataRequest: DataRequest?
Expand Down
Loading

0 comments on commit e35c2a5

Please sign in to comment.