Retry after an error

This commit is contained in:
Djorkaeff Alexandre 2024-02-15 17:38:37 -03:00
parent cdbab6ec07
commit fd8c099e23
7 changed files with 70 additions and 31 deletions

View File

@ -1,3 +1,5 @@
import Foundation
protocol ErrorActionHandling { protocol ErrorActionHandling {
func handle(error: RocketChatError) func handle(error: RocketChatError)
} }
@ -12,18 +14,26 @@ final class ErrorActionHandler {
init(server: Server) { init(server: Server) {
self.server = server self.server = server
} }
}
extension ErrorActionHandler: ErrorActionHandling { private func handleOnMain(error: RocketChatError) {
func handle(error: RocketChatError) {
switch error { switch error {
case .server(let response):
router.present(error: response)
case .unauthorized: case .unauthorized:
router.route(to: .serverList) router.route(to: .serverList)
database.remove() database.remove()
serversDB.remove(server) serversDB.remove(server)
default: case .unknown:
break print("Unexpected error on Client.")
}
}
}
extension ErrorActionHandler: ErrorActionHandling {
func handle(error: RocketChatError) {
DispatchQueue.main.async {
self.handleOnMain(error: error)
} }
} }
} }

View File

@ -2,16 +2,27 @@ import Foundation
protocol AppRouting { protocol AppRouting {
func route(to route: Route) func route(to route: Route)
func present(error: ErrorResponse)
} }
final class AppRouter: ObservableObject { final class AppRouter: ObservableObject {
@Published private(set) var route: Route = .loading @Published private(set) var route: Route = .loading
@Published var error: ErrorResponse?
} }
extension AppRouter: AppRouting { extension AppRouter: AppRouting {
func route(to route: Route) { func route(to route: Route) {
self.route = route self.route = route
} }
func present(error: ErrorResponse) {
guard self.error == nil else {
return
}
self.error = error
}
} }
enum Route: Equatable { enum Route: Equatable {

View File

@ -26,6 +26,11 @@ struct AppView: View {
.onAppear { .onAppear {
loadRoute() loadRoute()
} }
.sheet(item: $router.error) { error in
Text(error.error)
.multilineTextAlignment(.center)
.padding()
}
} }
private func loadRoute() { private func loadRoute() {

View File

@ -12,6 +12,8 @@ protocol RocketChatClientProtocol {
} }
final class RocketChatClient: NSObject { final class RocketChatClient: NSObject {
@Dependency private var errorActionHandler: ErrorActionHandling
private let server: Server private let server: Server
init(server: Server) { init(server: Server) {
@ -42,22 +44,27 @@ final class RocketChatClient: NSObject {
urlRequest.httpBody = request.body urlRequest.httpBody = request.body
return session.dataTaskPublisher(for: urlRequest) return session.dataTaskPublisher(for: urlRequest)
.tryMap { (data, response) in .tryMap { data, response in
guard let httpResponse = response as? HTTPURLResponse, 200...299 ~= httpResponse.statusCode else { if let response = response as? HTTPURLResponse, response.statusCode == 401 {
throw RocketChatError.unauthorized throw RocketChatError.unauthorized
} }
return try data.decode(T.Response.self) let decoder = JSONDecoder()
}
.mapError { error in if let response = try? decoder.decode(T.Response.self, from: data) {
if let error = error as? DecodingError { return response
return .decoding(error: error)
}
if let error = error as? RocketChatError {
return error
} }
return .unknown(error: error) let response = try decoder.decode(ErrorResponse.self, from: data)
throw RocketChatError.server(response: response)
}
.mapError { [weak self] error in
guard let error = error as? RocketChatError else {
return .unknown
}
self?.errorActionHandler.handle(error: error)
return error
} }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }

View File

@ -1,7 +1,15 @@
import Foundation import Foundation
enum RocketChatError: Error { struct ErrorResponse: Codable, Identifiable {
case decoding(error: Error) var id: String {
case unknown(error: Error) error
case unauthorized }
let error: String
}
enum RocketChatError: Error {
case server(response: ErrorResponse)
case unauthorized
case unknown
} }

View File

@ -16,7 +16,6 @@ final class MessagesLoader {
@Dependency private var client: RocketChatClientProtocol @Dependency private var client: RocketChatClientProtocol
@Dependency private var database: Database @Dependency private var database: Database
@Dependency private var serversDB: ServersDatabase @Dependency private var serversDB: ServersDatabase
@Dependency private var errorActionHandler: ErrorActionHandling
private var roomID: String? private var roomID: String?
@ -34,8 +33,8 @@ final class MessagesLoader {
client.syncMessages(rid: rid, updatedSince: date) client.syncMessages(rid: rid, updatedSince: date)
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] completion in .sink { [weak self] completion in
if case .failure(let error) = completion { if case .failure = completion {
self?.errorActionHandler.handle(error: error) self?.scheduledSyncMessages(in: room, from: newUpdatedSince)
} }
} receiveValue: { [weak self] messagesResponse in } receiveValue: { [weak self] messagesResponse in
let messages = messagesResponse.result.updated let messages = messagesResponse.result.updated
@ -58,9 +57,9 @@ final class MessagesLoader {
client.getHistory(rid: rid, t: room.t ?? "", latest: date) client.getHistory(rid: rid, t: room.t ?? "", latest: date)
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] completion in .sink { completion in
if case .failure(let error) = completion { if case .failure(let error) = completion {
self?.errorActionHandler.handle(error: error) print(error)
} }
} receiveValue: { [weak self] messagesResponse in } receiveValue: { [weak self] messagesResponse in
let messages = messagesResponse.messages let messages = messagesResponse.messages
@ -85,9 +84,9 @@ final class MessagesLoader {
client.sendRead(rid: rid) client.sendRead(rid: rid)
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] completion in .sink { completion in
if case .failure(let error) = completion { if case .failure(let error) = completion {
self?.errorActionHandler.handle(error: error) print(error)
} }
} receiveValue: { _ in } receiveValue: { _ in

View File

@ -11,7 +11,6 @@ final class RoomsLoader {
@Dependency private var client: RocketChatClientProtocol @Dependency private var client: RocketChatClientProtocol
@Dependency private var database: Database @Dependency private var database: Database
@Dependency private var serversDB: ServersDatabase @Dependency private var serversDB: ServersDatabase
@Dependency private var errorActionHandler: ErrorActionHandling
private var timer: Timer? private var timer: Timer?
private var cancellable = CancelBag() private var cancellable = CancelBag()
@ -33,8 +32,8 @@ final class RoomsLoader {
) )
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] completion in .sink { [weak self] completion in
if case .failure(let error) = completion { if case .failure = completion {
self?.errorActionHandler.handle(error: error) self?.scheduledLoadRooms(in: server)
} }
} receiveValue: { (roomsResponse, subscriptionsResponse) in } receiveValue: { (roomsResponse, subscriptionsResponse) in
let rooms = roomsResponse.update let rooms = roomsResponse.update