Skip to content

Commit

Permalink
Capture all unknown errors
Browse files Browse the repository at this point in the history
  • Loading branch information
LaurentTreguier committed Sep 13, 2024
1 parent b29848b commit 36ed931
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 61 deletions.
6 changes: 4 additions & 2 deletions Fyreplace/Events/Event.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import SwiftUI

protocol Event {}

struct ErrorEvent: Event {
protocol UnfortunateEvent: Event {}

struct ErrorEvent: UnfortunateEvent {
let error: UnexpectedError

init(_ error: UnexpectedError) {
Expand All @@ -14,7 +16,7 @@ extension Event {
typealias error = ErrorEvent
}

struct FailureEvent: Event {
struct FailureEvent: UnfortunateEvent {
let title: LocalizedStringKey
let text: LocalizedStringKey
}
Expand Down
6 changes: 3 additions & 3 deletions Fyreplace/Views/MainView.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import SwiftUI

struct MainView: View, MainViewProtocol {
@EnvironmentObject
var eventBus: EventBus

@State
var showError = false

Expand All @@ -13,9 +16,6 @@ struct MainView: View, MainViewProtocol {
@State
var failures: [FailureEvent] = []

@EnvironmentObject
private var eventBus: EventBus

@Environment(\.api)
private var api

Expand Down
40 changes: 19 additions & 21 deletions Fyreplace/Views/Screens/LoginScreenProtocol.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
protocol LoginScreenProtocol: LoadingViewProtocol {
var eventBus: EventBus { get }
var api: APIProtocol { get }

var identifier: String { get nonmutating set }
Expand All @@ -15,12 +14,8 @@ extension LoginScreenProtocol {
}

func submit() async {
await callWhileLoading(failOn: eventBus) {
if isWaitingForRandomCode {
try await createToken()
} else {
try await sendEmail()
}
await callWhileLoading {
try await (isWaitingForRandomCode ? createToken() : sendEmail())
}
}

Expand All @@ -29,37 +24,38 @@ extension LoginScreenProtocol {
randomCode = ""
}

func sendEmail() async throws {
func sendEmail() async throws -> UnfortunateEvent? {
let response = try await api.createNewToken(body: .json(.init(identifier: identifier)))

switch response {
case .ok:
isWaitingForRandomCode = true
return nil

case .badRequest:
eventBus.send(.failure(
return .failure(
title: "Error.BadRequest.Title",
text: "Error.BadRequest.Message"
))
)

case .forbidden:
eventBus.send(.failure(
return .failure(
title: "Error.Forbidden.Title",
text: "Error.Forbidden.Message"
))
)

case .notFound:
eventBus.send(.failure(
return .failure(
title: "Login.Error.NotFound.Title",
text: "Login.Error.NotFound.Message"
))
)

case .default:
eventBus.send(.error(UnknownError()))
return .error(UnknownError())
}
}

func createToken() async throws {
func createToken() async throws -> UnfortunateEvent? {
let response = try await api.createToken(body: .json(.init(identifier: identifier, secret: randomCode)))

switch response {
Expand All @@ -74,22 +70,24 @@ extension LoginScreenProtocol {
#if !os(macOS)
scheduleTokenRefresh()
#endif

return nil
}

case .badRequest:
eventBus.send(.failure(
return .failure(
title: "Account.Error.CreateToken.BadRequest.Title",
text: "Account.Error.CreateToken.BadRequest.Message"
))
)

case .notFound:
eventBus.send(.failure(
return .failure(
title: "Login.Error.NotFound.Title",
text: "Login.Error.NotFound.Message"
))
)

case .default:
eventBus.send(.error(UnknownError()))
return .error(UnknownError())
}
}
}
52 changes: 25 additions & 27 deletions Fyreplace/Views/Screens/RegisterScreenProtocol.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
protocol RegisterScreenProtocol: LoadingViewProtocol {
var eventBus: EventBus { get }
var api: APIProtocol { get }

var username: String { get nonmutating set }
Expand All @@ -19,12 +18,8 @@ extension RegisterScreenProtocol {
}

func submit() async {
await callWhileLoading(failOn: eventBus) {
if isWaitingForRandomCode {
try await createToken()
} else {
try await sendEmail()
}
await callWhileLoading {
try await (isWaitingForRandomCode ? createToken() : sendEmail())
}
}

Expand All @@ -34,62 +29,63 @@ extension RegisterScreenProtocol {
isRegistering = false
}

func sendEmail() async throws {
func sendEmail() async throws -> UnfortunateEvent? {
let response = try await api.createUser(body: .json(.init(email: email, username: username)))

switch response {
case .created:
isWaitingForRandomCode = true
isRegistering = true
return nil

case let .badRequest(badRequest):
switch badRequest.body {
case let .json(json) where json.violations?.first?.field == "createUser.input.username":
eventBus.send(.failure(
return .failure(
title: "Register.Error.CreateUser.BadRequest.Username.Title",
text: "Register.Error.CreateUser.BadRequest.Username.Message"
))
)

case let .json(json) where json.violations?.first?.field == "createUser.input.email":
eventBus.send(.failure(
return .failure(
title: "Register.Error.CreateUser.BadRequest.Email.Title",
text: "Register.Error.CreateUser.BadRequest.Email.Message"
))
)

case .json:
eventBus.send(.failure(
return .failure(
title: "Error.BadRequest.Title",
text: "Error.BadRequest.Message"
))
)
}

case .forbidden:
eventBus.send(.failure(
return .failure(
title: "Register.Error.CreateUser.Forbidden.Title",
text: "Register.Error.CreateUser.Forbidden.Message"
))
)

case let .conflict(conflict):
switch conflict.body {
case let .json(explanation) where explanation.reason == "username_taken":
eventBus.send(.failure(
return .failure(
title: "Register.Error.CreateUser.Conflict.Username.Title",
text: "Register.Error.CreateUser.Conflict.Username.Message"
))
)

case .json:
eventBus.send(.failure(
return .failure(
title: "Register.Error.CreateUser.Conflict.Email.Title",
text: "Register.Error.CreateUser.Conflict.Email.Message"
))
)
}

case .default:
eventBus.send(.error(UnknownError()))
return .error(UnknownError())
}
}

func createToken() async throws {
func createToken() async throws -> UnfortunateEvent? {
let response = try await api.createToken(body: .json(.init(identifier: email, secret: randomCode)))

switch response {
Expand All @@ -106,22 +102,24 @@ extension RegisterScreenProtocol {
#if !os(macOS)
scheduleTokenRefresh()
#endif

return nil
}

case .badRequest:
eventBus.send(.failure(
return .failure(
title: "Account.Error.CreateToken.BadRequest.Title",
text: "Account.Error.CreateToken.BadRequest.Message"
))
)

case .notFound:
eventBus.send(.failure(
return .failure(
title: "Register.Error.CreateToken.NotFound.Title",
text: "Register.Error.CreateToken.NotFound.Message"
))
)

case .default:
eventBus.send(.error(UnknownError()))
return .error(UnknownError())
}
}
}
3 changes: 3 additions & 0 deletions Fyreplace/Views/Screens/SettingsScreen.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import SwiftUI

struct SettingsScreen: View, SettingsScreenProtocol {
@EnvironmentObject
var eventBus: EventBus

@KeychainStorage("connection.token")
var token

Expand Down
27 changes: 19 additions & 8 deletions Fyreplace/Views/ViewProtocol.swift
Original file line number Diff line number Diff line change
@@ -1,31 +1,42 @@
import OpenAPIRuntime
import Sentry

protocol ViewProtocol {}
protocol ViewProtocol {
var eventBus: EventBus { get }
}

protocol LoadingViewProtocol: ViewProtocol {
var isLoading: Bool { get nonmutating set }
}

@MainActor
extension ViewProtocol {
func call(failOn eventBus: EventBus, action: () async throws -> Void) async {
func call(action: () async throws -> UnfortunateEvent?) async {
let unfortunateEvent: UnfortunateEvent?

do {
try await action()
unfortunateEvent = try await action()
} catch is ClientError {
eventBus.send(.error(ConnectionError()))
unfortunateEvent = .error(ConnectionError())
} catch {
eventBus.send(.error(UnknownError()))
SentrySDK.capture(error: error)
unfortunateEvent = .error(UnknownError())
}

if let event = unfortunateEvent {
eventBus.send(event)

if let event = unfortunateEvent as? ErrorEvent {
SentrySDK.capture(error: event.error)
}
}
}
}

@MainActor
extension LoadingViewProtocol {
func callWhileLoading(failOn eventBus: EventBus, action: () async throws -> Void) async {
func callWhileLoading(action: () async throws -> UnfortunateEvent?) async {
isLoading = true
await call(failOn: eventBus, action: action)
await call(action: action)
isLoading = false
}
}
Expand Down

0 comments on commit 36ed931

Please sign in to comment.