Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEAT: 상황에 따른 에러 핸들링 #16

Merged
merged 10 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions HANE24.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
0E9868252B2B6C0B00E127DC /* CalendarHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9868242B2B6C0B00E127DC /* CalendarHeaderView.swift */; };
0E9868272B2B76BE00E127DC /* CalendarBodyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9868262B2B76BE00E127DC /* CalendarBodyView.swift */; };
0EBBF14E29B08D580076AAB9 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBBF14D29B08D580076AAB9 /* ErrorView.swift */; };
0ECF97A62BEDE7CB00C37095 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECF97A52BEDE7CB00C37095 /* Error.swift */; };
0ECF97A82BEDE8EA00C37095 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECF97A72BEDE8EA00C37095 /* ErrorHandler.swift */; };
0ED6E3E32B354D320026E69D /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0ED6E3E22B354D310026E69D /* WidgetKit.framework */; };
0ED6E3E52B354D320026E69D /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0ED6E3E42B354D320026E69D /* SwiftUI.framework */; };
0ED6E3E82B354D320026E69D /* HANE24WidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED6E3E72B354D320026E69D /* HANE24WidgetBundle.swift */; };
Expand Down Expand Up @@ -134,6 +136,8 @@
0E9868242B2B6C0B00E127DC /* CalendarHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarHeaderView.swift; sourceTree = "<group>"; };
0E9868262B2B76BE00E127DC /* CalendarBodyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarBodyView.swift; sourceTree = "<group>"; };
0EBBF14D29B08D580076AAB9 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = "<group>"; };
0ECF97A52BEDE7CB00C37095 /* Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = "<group>"; };
0ECF97A72BEDE8EA00C37095 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = "<group>"; };
0ED6E3E12B354D310026E69D /* HANE24WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = HANE24WidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
0ED6E3E22B354D310026E69D /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
0ED6E3E42B354D320026E69D /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
Expand Down Expand Up @@ -360,6 +364,7 @@
isa = PBXGroup;
children = (
9AF730F5299E074800AF2E53 /* Structs.swift */,
0ECF97A52BEDE7CB00C37095 /* Error.swift */,
9AF730FC299F602D00AF2E53 /* JSONs.swift */,
D68CC3E72BCC1764008D62E2 /* ReissueModel.swift */,
D6E780C82BCCE84300FB547D /* CalendarModel.swift */,
Expand All @@ -375,6 +380,7 @@
D6C528F72BBECE0700F51A06 /* ReissueVM.swift */,
D6185AE02BB3C71800E6944A /* CalendarVM.swift */,
0E3ED8A72BB13727001B0BAE /* NetworkManager.swift */,
0ECF97A72BEDE8EA00C37095 /* ErrorHandler.swift */,
0EE06CBC2BB2819500B4988C /* HomeVM.swift */,
0E6B608E29AC850D009D8BC4 /* NetworkMonitoringManager.swift */,
);
Expand Down Expand Up @@ -628,6 +634,7 @@
D6E780CE2BCD030200FB547D /* Tapbar.swift in Sources */,
D68CC3F32BCC1F2A008D62E2 /* ListItemButton.swift in Sources */,
9AF730FD299F602D00AF2E53 /* JSONs.swift in Sources */,
0ECF97A82BEDE8EA00C37095 /* ErrorHandler.swift in Sources */,
D6E780D42BCD04AB00FB547D /* ReissueButton.swift in Sources */,
D6AECA262BECCBF5009A8018 /* AccTimeCardsView.swift in Sources */,
0EE58193299CC24000EE3351 /* MoreView.swift in Sources */,
Expand Down Expand Up @@ -670,6 +677,7 @@
D6C528F82BBECE0700F51A06 /* ReissueVM.swift in Sources */,
D6E19CF32BEB427B005DF8C3 /* Error.swift in Sources */,
9AF730F3299D599300AF2E53 /* TagLogView.swift in Sources */,
0ECF97A62BEDE7CB00C37095 /* Error.swift in Sources */,
0E0FE5F32BB3E6110050498E /* CircularProgressBar.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
1 change: 0 additions & 1 deletion HANE24/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ struct ContentView: View {
UserDefaults.standard.setValue(0, forKey: "MonthlySelectionOption")
UserDefaults.standard.set(true, forKey: "isFirst")
}

do {
try hane.isSignIn = await hane.isLogin() ? true : false
self.signInChecked = true
Expand Down
65 changes: 65 additions & 0 deletions HANE24/Model/Error.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,71 @@

import Foundation

enum CustomError: Error {
case tokenExpired
case wrongQueryType
case networkDisconnected
case unAuthorized
case internalServer
case responseBodyEmpty
case decodeFailed
case invalidURL
case unknownError(String)
case none
}

extension CustomError: LocalizedError {
public var errorDescription: String? {
switch self {
case .tokenExpired:
return "사용자 토큰이 만료되었습니다"
case .wrongQueryType:
return "잘못된 요청입니다"
case .networkDisconnected:
return "네트워크 상태가 원활하지 않습니다"
case .unAuthorized:
return "알 수 없는 사용자입니다"
case .internalServer:
return "서버 에러 발생"
case .responseBodyEmpty:
return "내부 에러 발생"
case .decodeFailed:
return "내부 에러 발생"
case .invalidURL:
return "잘못된 접근입니다"
case .unknownError:
return "원인을 알 수 없는 에러 발생"
case .none:
return nil
}
}

public var recoverySuggestion: String? {
switch self {
case .tokenExpired:
return "다시 로그인해주세요"
case .wrongQueryType:
return "다시 시도해주세요"
case .networkDisconnected:
return "Wi-Fi 혹은 데이터 확인 후 다시 시도해주세요"
case .unAuthorized:
return "로그인 정보를 다시 확인해주세요"
case .internalServer:
return "개발팀에게 문의해주세요"
case .responseBodyEmpty:
return "개발팀에게 문의해주세요"
case .decodeFailed:
return "개발팀에게 문의해주세요"
case .invalidURL:
return "개발팀에게 문의해주세요"
case .unknownError:
return "개발팀에게 문의해주세요"
case .none:
return nil
}
}
}

// API 처리에 대한 실패
enum ReissueError: Error {
case tokenExpired(String)
Expand Down
11 changes: 11 additions & 0 deletions HANE24/View/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import SwiftUI
struct MainView: View {
@EnvironmentObject var hane: Hane
@StateObject var homeVM = HomeVM()
@ObservedObject var errorHandler = ErrorHandler.shared

@State var selection = 1
@Environment(\.colorScheme) var colorScheme
Expand Down Expand Up @@ -41,6 +42,7 @@ struct MainView: View {
try await homeVM.refresh()
} catch {
print("error on MainView \(error.localizedDescription)")
print("error: ", error)
}
}

Expand All @@ -50,6 +52,15 @@ struct MainView: View {
NoticeView(showNotice: $isNoticedTagLatencyInfo, notice: hane.tagLatencyNotice)
}
}
.alert(
"에러가 발생했어요",
isPresented: $errorHandler.showAlert) {
Button("확인") {
errorHandler.errorType = CustomError.none
}
} message: {
Text(errorHandler.errorType.recoverySuggestion ?? "개발팀에 문의해주세요")
}
}
}

Expand Down
71 changes: 71 additions & 0 deletions HANE24/ViewModel/ErrorHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// ErrorHandler.swift
// 24HANE
//
// Created by Katherine JANG on 5/10/24.
//

import Foundation
import SwiftUI


class ErrorHandler: ObservableObject {

static let shared = ErrorHandler()

var errorType: CustomError = .none
@Published var showAlert: Bool = false
@Published var signInRequired: Bool = false

private init() { }

func errorFromHttpRequest(_ statusCode: Int?) throws {
switch statusCode {
case 400:
throw CustomError.wrongQueryType
case 401:
throw CustomError.unAuthorized
case 500:
throw CustomError.internalServer
default:
throw CustomError.unknownError("\(statusCode)")

Check warning on line 31 in HANE24/ViewModel/ErrorHandler.swift

View workflow job for this annotation

GitHub Actions / Test

string interpolation produces a debug description for an optional value; did you mean to make this explicit?
}
}

@MainActor
func updateErrorView() {
switch self.errorType {
case .tokenExpired, .unAuthorized:
self.signInRequired = true
case .wrongQueryType, .networkDisconnected, .internalServer, .responseBodyEmpty,
.decodeFailed, .unknownError, .invalidURL:
self.showAlert = true
case .none:
break
}
}

func verifyError(_ error: Error) async {
switch error {
case DecodingError.dataCorrupted:
self.errorType = .decodeFailed
case URLError.timedOut:
self.errorType = .networkDisconnected
case URLError.networkConnectionLost:
self.errorType = .networkDisconnected
case is CustomError:
self.errorType = error as? CustomError ?? .none
default:
self.errorType = .unknownError(error.localizedDescription.description)
}
}
Comment on lines +48 to +61
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드가 클라이언트 에러를 구분하는 책임을 맡은 만큼 함수 이름에서도 errorFromHttpRequest처럼 그 부분이 보였으면 좋겠습니다.


Check warning on line 62 in HANE24/ViewModel/ErrorHandler.swift

View workflow job for this annotation

GitHub Actions / Test

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
@MainActor
func handleError(_ error: Error) {
Task {
await self.verifyError(error)
self.updateErrorView()
}
}

}
37 changes: 23 additions & 14 deletions HANE24/ViewModel/HomeVM.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,29 +49,38 @@
}

@MainActor
func updateAccumulationTimes() async throws {
guard let accTimes = try await NetworkManager.shared.apiRequest("/v3/tag-log/accumulationTimes", .get, type: AccumulationTimes.self) else {
func updateAccumulationTimes() async {
do {
guard let accTimes = try await NetworkManager.shared.apiRequest("/v3/tag-log/accumulationTimes", .get, type: AccumulationTimes.self) else {
throw MyError.tokenExpired("")
}
self.accumulationTimes = accTimes
self.dailyAccumulationTime = accTimes.todayAccumulationTime
} catch {
ErrorHandler.shared.handleError(error)
}
self.accumulationTimes = accTimes
self.dailyAccumulationTime = accTimes.todayAccumulationTime
}

//TODO: 요청한 데이터가 nil일 경우 에러 핸들링

Check warning on line 64 in HANE24/ViewModel/HomeVM.swift

View workflow job for this annotation

GitHub Actions / Test

Comment Spacing Violation: Prefer at least one space after slashes for comments (comment_spacing)
@MainActor
func updateMainInfo() async throws {
guard let mainInfo = try await NetworkManager.shared.apiRequest("/v3/tag-log/maininfo", .get, type: MainInfo.self) else {
throw MyError.tokenExpired("")
}
self.mainInfo = mainInfo
self.isInCluster = mainInfo.inoutState == "IN"
self.fundInfoNotice = mainInfo.infoMessages.fundInfoNotice
self.tagLatencyNotice = mainInfo.infoMessages.tagLatencyNotice
func updateMainInfo() async {
do {
guard let mainInfo = try await NetworkManager.shared.apiRequest("/v3/tag-log/maininfo", .get, type: MainInfo.self) else {
throw MyError.tokenExpired("")
}
self.mainInfo = mainInfo
self.isInCluster = mainInfo.inoutState == "IN"
self.fundInfoNotice = mainInfo.infoMessages.fundInfoNotice
self.tagLatencyNotice = mainInfo.infoMessages.tagLatencyNotice
} catch {
print("error caught")
ErrorHandler.shared.handleError(error)
}
}

@MainActor
func refresh() async throws {
try await self.updateMainInfo()
try await self.updateAccumulationTimes()
await self.updateMainInfo()
await self.updateAccumulationTimes()
}
}
Loading