Skip to content

Commit 9e36fbf

Browse files
haIIux0xLeif
andauthored
Generalize Error Handling (#28)
* added new errorhandling file, removed vertical space to clean swiftlint * created more global error handling method to be reused * added little documentation * adds more generalized error enum * Add Scribe to project dependecies * Add a basic shared Scribe object * Make Errors private and specific * Use t.error for mock errors * Use new ErrorHandler singleton * Add ErrorHandler and ToastErrorPlugin * Removed singleton ErrorHandler * Add ScribeLogging protocol * Move logging files to a folder --------- Co-authored-by: Leif <[email protected]>
1 parent 798b058 commit 9e36fbf

11 files changed

+176
-32
lines changed

Diff for: base/App/Profile/ProfileScreen.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ struct ProfileScreen: View {
3535
RegisterScreen(
3636
registerViewModel: RegisterViewModel(
3737
// TODO: (base-Template) Update to Production
38-
registerNetworking: SuccessMockRegisterService()
38+
registerNetworking: NoDataFailureMockRegisterService()
3939
)
4040
)
4141
}

Diff for: base/App/Profile/Register/RegisterNetworking.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
// RegisterNetworking.swift
1515
//
1616

17-
import Foundation
17+
import t
1818

1919
protocol RegisterNetworking {
2020
mutating func register(
@@ -65,7 +65,7 @@ struct NoDataFailureMockRegisterService: RegisterNetworking {
6565
password: String,
6666
passwordConfirmation: String
6767
) async throws -> User {
68-
throw AuthError.noData
68+
throw t.error(description: "No Data")
6969
}
7070
}
7171

@@ -77,6 +77,6 @@ struct ValidationFailureMockRegisterService: RegisterNetworking {
7777
password: String,
7878
passwordConfirmation: String
7979
) async throws -> User {
80-
throw AuthError.validation(reason: "Mock Failure")
80+
throw t.error(description: "Validation")
8181
}
8282
}

Diff for: base/App/Profile/Register/RegisterViewModel.swift

+13-20
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,27 @@ import Foundation
2020
The `RegisterViewModel` conforms to type `ObservableObject`,
2121
and acts as mediator between the view and the networking layer.
2222
*/
23-
class RegisterViewModel: ObservableObject {
23+
class RegisterViewModel: ObservableObject, ErrorHandling {
2424
private var registerNetworking: RegisterNetworking
2525
private var registerTask: Task<Void, Never>?
2626

27+
var errorHandler: ErrorHandler
28+
2729
@Published var username = ""
2830
@Published var email = ""
2931
@Published var password = ""
3032
@Published var confirmationPassword = ""
3133

32-
init(registerNetworking: RegisterNetworking) {
34+
init(
35+
registerNetworking: RegisterNetworking,
36+
errorHandler: ErrorHandler = ErrorHandler(
37+
plugins: [
38+
ToastErrorPlugin()
39+
]
40+
)
41+
) {
3342
self.registerNetworking = registerNetworking
43+
self.errorHandler = errorHandler
3444
}
3545

3646
var areFieldsValidated: Bool {
@@ -63,29 +73,12 @@ class RegisterViewModel: ObservableObject {
6373
)
6474
}
6575
} catch {
66-
handle(error: error)
76+
errorHandler.handle(error: error)
6777
}
6878
}
6979

7080
registerTask = task
7181

7282
return task
7383
}
74-
75-
private func handle(error: Error) {
76-
#if DEBUG
77-
Navigation.path.toast(
78-
title: "Error",
79-
message: error.localizedDescription,
80-
style: .error
81-
)
82-
#else
83-
print("⚠️ Error! - \(error)")
84-
Navigation.path.toast(
85-
title: "Error",
86-
message: "There was an error registering your account, please try again.",
87-
style: .error
88-
)
89-
#endif
90-
}
9184
}

Diff for: base/Networking/Implementation/AuthNetworking+Default.swift

+14-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@
1616

1717
import Foundation
1818

19+
private enum AuthNetworkingError: LocalizedError {
20+
case noData
21+
case validation(reason: String)
22+
23+
var errorDescription: String? {
24+
switch self {
25+
case .noData: return "\(#fileID): No Data"
26+
case let .validation(reason): return "\(#fileID): Validation: \(reason)"
27+
}
28+
}
29+
}
30+
1931
extension AuthNetworking {
2032
var tokenFileName: String { "base.token" }
2133

@@ -75,13 +87,13 @@ extension AuthNetworking {
7587
let httpResponse = dataResponse.response as? HTTPURLResponse
7688

7789
guard httpResponse?.statusCode == 200 else {
78-
throw AuthError.validation(
90+
throw AuthNetworkingError.validation(
7991
reason: "Status code was not 200, but was: '\(httpResponse?.statusCode ?? -1)'"
8092
)
8193
}
8294

8395
guard let data = dataResponse.data else {
84-
throw AuthError.noData
96+
throw AuthNetworkingError.noData
8597
}
8698

8799
let decoder = JSONDecoder()

Diff for: base/Networking/Interface/AuthNetworking+Models.swift

+4-6
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,11 @@
1717
import Foundation
1818
import t
1919

20-
enum AuthError: LocalizedError {
21-
case noData
20+
private enum RegisterPayloadError: LocalizedError {
2221
case validation(reason: String)
2322

2423
var errorDescription: String? {
2524
switch self {
26-
case .noData: return "\(#fileID): No Data"
2725
case let .validation(reason): return "\(#fileID): Validation: \(reason)"
2826
}
2927
}
@@ -59,7 +57,7 @@ struct RegisterPayload: Codable {
5957
try t.assert(email.matches(of: regex).isEmpty == false)
6058
}
6159
} catch {
62-
throw AuthError.validation(reason: "Invalid Email Address")
60+
throw RegisterPayloadError.validation(reason: "Invalid Email Address")
6361
}
6462
}
6563

@@ -69,7 +67,7 @@ struct RegisterPayload: Codable {
6967
try t.assert(name.count < 256)
7068
}
7169
} catch {
72-
throw AuthError.validation(reason: "Username is too long")
70+
throw RegisterPayloadError.validation(reason: "Username is too long")
7371
}
7472
}
7573

@@ -83,7 +81,7 @@ struct RegisterPayload: Codable {
8381
try t.assert(password.matches(of: regex).isEmpty == false)
8482
}
8583
} catch {
86-
throw AuthError.validation(
84+
throw RegisterPayloadError.validation(
8785
reason: """
8886
Passwords must contain:
8987
- At least 10 characters

Diff for: base/Utilities/Errors/ErrorHandler.swift

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// base template generated by OpenBytes on 12/17/22.
3+
// Copyright (c) 2023 OpenBytes
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the conditions found at the following link:
11+
// https://github.com/0xOpenBytes/ios-BASE/blob/main/LICENSE
12+
//
13+
// Created by Rob Maltese.
14+
// ErrorHandler.swift
15+
//
16+
17+
import Plugin
18+
19+
class ErrorHandler: Pluginable {
20+
var plugins: [any Plugin]
21+
22+
init(plugins: [any Plugin]) {
23+
self.plugins = plugins
24+
}
25+
26+
func handle(error: Error) {
27+
Task {
28+
try? await handle(value: error)
29+
}
30+
}
31+
}

Diff for: base/Utilities/Errors/ErrorHandling.swift

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// base template generated by OpenBytes on 2/16/23.
3+
// Copyright (c) 2023 OpenBytes
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the conditions found at the following link:
11+
// https://github.com/0xOpenBytes/ios-BASE/blob/main/LICENSE
12+
//
13+
// Created by Leif.
14+
// ErrorHandling.swift
15+
//
16+
17+
protocol ErrorHandling {
18+
var errorHandler: ErrorHandler { get set }
19+
}

Diff for: base/Utilities/Errors/Plugins/ToastErrorPlugin.swift

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//
2+
// base template generated by OpenBytes on 2/16/23.
3+
// Copyright (c) 2023 OpenBytes
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the conditions found at the following link:
11+
// https://github.com/0xOpenBytes/ios-BASE/blob/main/LICENSE
12+
//
13+
// Created by Leif.
14+
// ToastErrorPlugin.swift
15+
//
16+
17+
import Plugin
18+
import Scribe
19+
20+
struct ToastErrorPlugin: ImmutablePlugin {
21+
func handle(value: Error) async throws {
22+
baseScribe.shared.error(
23+
Scribe.Message(stringLiteral: value.localizedDescription)
24+
)
25+
26+
#if DEBUG
27+
Navigation.path.toast(
28+
title: "Error",
29+
message: value.localizedDescription,
30+
style: .error
31+
)
32+
#else
33+
Navigation.path.toast(
34+
title: "Error",
35+
message: "Encountered an error, please try again.",
36+
style: .error
37+
)
38+
#endif
39+
}
40+
}

Diff for: base/Utilities/Logging/ScribeLogging.swift

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// base template generated by OpenBytes on 2/17/23.
3+
// Copyright (c) 2023 OpenBytes
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the conditions found at the following link:
11+
// https://github.com/0xOpenBytes/ios-BASE/blob/main/LICENSE
12+
//
13+
// Created by Leif.
14+
// ScribeLogging.swift
15+
//
16+
17+
import Scribe
18+
19+
protocol ScribeLogging {
20+
var scribe: Scribe { get set }
21+
}

Diff for: base/Utilities/Logging/baseScribe.swift

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// base template generated by OpenBytes on 2/16/23.
3+
// Copyright (c) 2023 OpenBytes
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the conditions found at the following link:
11+
// https://github.com/0xOpenBytes/ios-BASE/blob/main/LICENSE
12+
//
13+
// Created by Leif.
14+
// baseScribe.swift
15+
//
16+
17+
import Scribe
18+
19+
class baseScribe: Scribe {
20+
static var shared: baseScribe = baseScribe(
21+
label: "base.scribe",
22+
plugins: [
23+
24+
]
25+
)
26+
}

Diff for: project.yml

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ packages:
55
Fork:
66
url: https://github.com/0xLeif/Fork
77
from: 1.0.0
8+
Scribe:
9+
url: https://github.com/0xLeif/Scribe
10+
from: 1.2.0
811
o:
912
url: https://github.com/0xOpenBytes/o
1013
from: 2.0.0
@@ -48,6 +51,7 @@ targets:
4851
- "Swish"
4952
dependencies:
5053
- package: Fork
54+
- package: Scribe
5155
- package: o
5256
- package: c
5357
postCompileScripts:

0 commit comments

Comments
 (0)