Skip to content
14 changes: 13 additions & 1 deletion Projects/DataSource/Sources/NetworkService/NetworkService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,19 @@ final class NetworkService {
}
}

let (data, response) = try await URLSession.shared.data(for: request)
let data: Data
let response: URLResponse
do {
(data, response) = try await URLSession.shared.data(for: request)
} catch let error as URLError {
if [.notConnectedToInternet, .timedOut, .networkConnectionLost].contains(error.code) {
throw NetworkError.needRetry
} else {
throw NetworkError.unknown(description: error.localizedDescription)
}
} catch {
throw NetworkError.unknown(description: error.localizedDescription)
}

if withPlugins {
for plugin in plugins {
Expand Down
60 changes: 48 additions & 12 deletions Projects/DataSource/Sources/Repository/EmotionRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,64 @@ final class EmotionRepository: EmotionRepositoryProtocol {

func fetchEmotions() async throws -> [EmotionEntity] {
let endpoint = EmotionEndpoint.fetchEmotions
guard let response = try await networkService.request(endpoint: endpoint, type: [EmotionResponseDTO].self)
else { return [] }

let emotionEntities = response.compactMap({ $0.toEmotionEntity() })
return emotionEntities
do {
guard let response = try await networkService.request(endpoint: endpoint, type: [EmotionResponseDTO].self)
else { return [] }

let emotionEntities = response.compactMap({ $0.toEmotionEntity() })
return emotionEntities
} catch let error as NetworkError {
switch error {
case .needRetry, .invalidURL, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
} catch {
throw DomainError.unknown
}
}

func loadEmotion(date: String) async throws -> EmotionEntity? {
let endpoint = EmotionEndpoint.loadEmotion(date: date)
guard let response = try await networkService.request(endpoint: endpoint, type: EmotionResponseDTO.self)
else { throw NetworkError.unknown(description: "Emotion Reponse를 받아오지 못했습니다.") }

let emotionEntity = response.toEmotionEntity()
return emotionEntity
do {
guard let response = try await networkService.request(endpoint: endpoint, type: EmotionResponseDTO.self)
else { throw NetworkError.unknown(description: "Emotion Reponse를 받아오지 못했습니다.") }

let emotionEntity = response.toEmotionEntity()
return emotionEntity
} catch let error as NetworkError {
switch error {
case .needRetry, .invalidURL, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
} catch {
throw DomainError.unknown
}
Comment on lines +37 to +52
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

loadEmotion에서 NetworkError를 직접 throw한 후 같은 메서드 내에서 다시 catch하는 것은 우회적입니다.

Line 39에서 response가 nil일 때 NetworkError.unknown을 throw하는데, 이는 바로 아래 catch let error as NetworkError에서 잡혀 DomainError.business로 변환됩니다. 직접 DomainError를 throw하는 것이 더 명확합니다. 또한 "Reponse"는 "Response"의 오타입니다.

🛠️ 수정 제안
         do {
             guard let response = try await networkService.request(endpoint: endpoint, type: EmotionResponseDTO.self)
-            else { throw NetworkError.unknown(description: "Emotion Reponse를 받아오지 못했습니다.") }
+            else { throw DomainError.business("Emotion Response를 받아오지 못했습니다.") }
 
             let emotionEntity = response.toEmotionEntity()
             return emotionEntity
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/DataSource/Sources/Repository/EmotionRepository.swift` around lines
37 - 52, In loadEmotion, don’t throw a NetworkError when the response is nil
then immediately catch and map it — throw the appropriate DomainError directly
instead; replace the guard’s throw NetworkError.unknown(...) with throw
DomainError.business("Emotion Response를 받아오지 못했습니다.") (fixing the "Reponse" ->
"Response" typo), so the downstream catch for NetworkError in the same method no
longer needs to translate this case.

}

func registerEmotion(emotion: String) async throws -> [RecommendedRoutineEntity] {
let endpoint = EmotionEndpoint.registerEmotion(emotion: emotion)
guard let response = try await networkService.request(endpoint: endpoint, type: RecommendedRoutineListResponseDTO.self)
else { return [] }

let recommendedRoutineEntity = response.recommendedRoutines.compactMap({ $0.toRecommendedRoutineEntity() })
return recommendedRoutineEntity
do {
guard let response = try await networkService.request(endpoint: endpoint, type: RecommendedRoutineListResponseDTO.self)
else { return [] }

let recommendedRoutineEntity = response.recommendedRoutines.compactMap({ $0.toRecommendedRoutineEntity() })
return recommendedRoutineEntity
} catch let error as NetworkError {
switch error {
case .needRetry, .invalidURL, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
} catch {
throw DomainError.unknown
}
}
}
27 changes: 25 additions & 2 deletions Projects/DataSource/Sources/Repository/FileRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,34 @@ final class FileRepository: FileRepositoryProtocol {
let dtos = fileNames.map { FilePresignedConditionDTO(prefix: prefix, fileName: $0) }
let endpoint = FilePresignedEndpoint.fetchPresignedURL(presignedConditions: dtos)

return try await networkService.request(endpoint: endpoint, type: [String:String].self)
do {
return try await networkService.request(endpoint: endpoint, type: [String:String].self)
} catch let error as NetworkError {
switch error {
case .needRetry, .invalidURL, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
} catch {
throw DomainError.unknown
}
}

func uploadFile(url: String, data: Data) async throws {
let endPoint = S3Endpoint.uploadImage(uploadURL: url, data: data)
_ = try await networkService.request(endpoint: endPoint, type: EmptyResponseDTO.self)

do {
_ = try await networkService.request(endpoint: endPoint, type: EmptyResponseDTO.self)
} catch let error as NetworkError {
switch error {
case .needRetry, .invalidURL, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
} catch {
throw DomainError.unknown
}
}
}
19 changes: 15 additions & 4 deletions Projects/DataSource/Sources/Repository/LocationRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,22 @@ final class LocationRepository: NSObject, LocationRepositoryProtocol {

let endpoint = LocationEndpoint.fetchAddress(longitude: longitude, latitude: latitude)

guard let response = try await networkService.request(endpoint: endpoint, type: KakaoLocationResponseDTO.self)
else { return nil }
do {
guard let response = try await networkService.request(endpoint: endpoint, type: KakaoLocationResponseDTO.self)
else { return nil }

let location = response.toLocationEntity(fallbackLongitude: coordinate.longitude, fallbackLatitude: coordinate.latitude)
return location
let location = response.toLocationEntity(fallbackLongitude: coordinate.longitude, fallbackLatitude: coordinate.latitude)
return location
} catch let error as NetworkError {
switch error {
case .needRetry, .invalidURL, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
Comment on lines +53 to +59
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# LocationRepository.swift 파일의 실제 내용 확인 (53-59줄 포함)
find . -name "LocationRepository.swift" -type f

Repository: YAPP-Github/Bitnagil-iOS

Length of output: 133


🏁 Script executed:

# NetworkError 정의 확인
find . -name "*.swift" -type f | xargs grep -l "enum NetworkError" | head -5

Repository: YAPP-Github/Bitnagil-iOS

Length of output: 131


🏁 Script executed:

# 다른 Repository 파일들에서 동일한 패턴 검색
find . -path "*/Repository/*" -name "*Repository.swift" -type f | head -10

Repository: YAPP-Github/Bitnagil-iOS

Length of output: 728


🏁 Script executed:

# LocationRepository.swift 파일의 lines 47-62 확인
cat -n ./Projects/DataSource/Sources/Repository/LocationRepository.swift | sed -n '40,65p'

Repository: YAPP-Github/Bitnagil-iOS

Length of output: 1238


🏁 Script executed:

# NetworkError 정의 확인
cat -n ./Projects/DataSource/Sources/NetworkService/NetworkError.swift

Repository: YAPP-Github/Bitnagil-iOS

Length of output: 1311


🏁 Script executed:

# 다른 Repository들의 에러 처리 패턴 확인
for file in ./Projects/DataSource/Sources/Repository/{AppConfigRepository,AuthRepository,EmotionRepository,FileRepository,OnboardingRepository,RecommendedRoutineRepository,ReportRepository,RoutineRepository,UserDataRepository}.swift; do
  echo "=== $file ===";
  grep -A 10 "catch let error as NetworkError" "$file" | head -15;
done

Repository: YAPP-Github/Bitnagil-iOS

Length of output: 3602


.invalidURL.emptyDatarequireRetry로 매핑하는 것이 부적절함

.needRetry는 타임아웃/연결 끊김 등 일시적 네트워크 오류이므로 재시도가 의미 있습니다. 그러나 .invalidURL은 URL 구성 오류로 재시도해도 동일하게 실패하며, .emptyData는 서버 응답 자체가 비어있는 경우라 클라이언트 재시도로 해결될 가능성이 낮습니다.

이 문제는 LocationRepository에만 국한된 것이 아니라 EmotionRepository, FileRepository, OnboardingRepository, RecommendedRoutineRepository, ReportRepository, RoutineRepository 등 다수의 Repository에서 동일한 패턴으로 반복되고 있습니다. 이는 체계적인 설계 문제로, 다음과 같이 수정하는 것이 적절합니다:

제안하는 수정
         } catch let error as NetworkError {
             switch error {
-            case .needRetry, .invalidURL, .emptyData:
+            case .needRetry:
                 throw DomainError.requireRetry
+            case .invalidURL, .emptyData:
+                throw DomainError.business(error.description)
             default:
                 throw DomainError.business(error.description)
             }

이 수정은 LocationRepository뿐만 아니라 동일한 패턴을 사용하는 모든 Repository 파일에 적용되어야 합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/DataSource/Sources/Repository/LocationRepository.swift` around lines
53 - 59, The catch mapping in LocationRepository currently treats .invalidURL
and .emptyData as transient and throws DomainError.requireRetry; change the
switch in the NetworkError catch so only .needRetry maps to
DomainError.requireRetry, and map .invalidURL and .emptyData to
DomainError.business with the error description (i.e., treat them as
non-retryable business errors). Update the same pattern in the other
repositories that share this logic (EmotionRepository, FileRepository,
OnboardingRepository, RecommendedRoutineRepository, ReportRepository,
RoutineRepository) so all use .needRetry => DomainError.requireRetry and all
other NetworkError cases => DomainError.business(error.description).

} catch {
throw DomainError.unknown
}
}

private func requestAuthorizationIfNeeded() async -> CLAuthorizationStatus {
Expand Down
54 changes: 44 additions & 10 deletions Projects/DataSource/Sources/Repository/OnboardingRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,21 @@ final class OnboardingRepository: OnboardingRepositoryProtocol {

func loadOnboardingResult() async throws -> OnboardingEntity {
let endpoint = OnboardingEndpoint.loadOnboardingResult
guard let response = try await networkService.request(endpoint: endpoint, type: OnboardingResponseDTO.self)
else { throw UserError.onboardingLoadFailed }

let onboardingEntity = response.toOnboardingEntity()
return onboardingEntity
do {
guard let response = try await networkService.request(endpoint: endpoint, type: OnboardingResponseDTO.self)
else { throw UserError.onboardingLoadFailed }

let onboardingEntity = response.toOnboardingEntity()
return onboardingEntity
} catch let error as NetworkError {
switch error {
case .needRetry, .invalidURL, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
Comment on lines +24 to +29
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the NetworkError enum definition
find . -name "*.swift" -type f | xargs grep -l "enum NetworkError"

Repository: YAPP-Github/Bitnagil-iOS

Length of output: 131


🏁 Script executed:

# Look at the OnboardingRepository.swift file to see all error handling
cat -n Projects/DataSource/Sources/Repository/OnboardingRepository.swift

Repository: YAPP-Github/Bitnagil-iOS

Length of output: 3369


🏁 Script executed:

# Search for all occurrences of invalidURL case in the codebase
rg "case.*invalidURL" --type swift -B 2 -A 2

Repository: YAPP-Github/Bitnagil-iOS

Length of output: 11831


🏁 Script executed:

# Find where NetworkError is defined and check its cases
rg "enum NetworkError" --type swift -A 20

Repository: YAPP-Github/Bitnagil-iOS

Length of output: 1986


.invalidURLDomainError.requireRetry로 매핑하는 것을 재검토 필요

.invalidURL은 잘못된 URL 구성(개발자 실수)으로 인한 에러이므로 재시도해도 동일하게 실패합니다. .needRetry.emptyData는 일시적인 네트워크 문제일 수 있어 재시도가 의미 있지만, .invalidURLDomainError.business로 매핑하는 것이 더 적절합니다.

이 패턴이 OnboardingRepository의 세 메서드 모두에 적용되어 있으며, 코드베이스의 다른 모든 Repository(RoutineRepository, FileRepository, ReportRepository 등)에서도 동일하게 반복되고 있으므로 함께 수정이 필요합니다.

🔧 수정 제안
         } catch let error as NetworkError {
             switch error {
-            case .needRetry, .invalidURL, .emptyData:
+            case .needRetry, .emptyData:
                 throw DomainError.requireRetry
             default:
                 throw DomainError.business(error.description)
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
switch error {
case .needRetry, .invalidURL, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
switch error {
case .needRetry, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/DataSource/Sources/Repository/OnboardingRepository.swift` around
lines 24 - 29, The switch currently maps `.invalidURL` to
DomainError.requireRetry along with `.needRetry` and `.emptyData`; change the
mapping so only transient errors (`.needRetry` and `.emptyData`) throw
DomainError.requireRetry, while `.invalidURL` should throw
DomainError.business(error.description). Update this logic in the switch blocks
inside OnboardingRepository's three methods (the switch handling `error` that
currently lists `.needRetry, .invalidURL, .emptyData`) and apply the same fix
consistently across other repositories that repeat this pattern (e.g.,
RoutineRepository, FileRepository, ReportRepository) so `.invalidURL` is treated
as a business error while retryable cases remain requireRetry.

}
Comment on lines +17 to +30
Copy link

@coderabbitai coderabbitai bot Feb 17, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

loadOnboardingResult에 일반 catch 블록이 누락되었습니다.

다른 모든 리포지토리 메서드(registerOnboarding, registerRecommendedRoutines 포함)에는 catch { throw DomainError.unknown } 블록이 있지만, 이 메서드에만 누락되었습니다. Line 19에서 throw UserError.onboardingLoadFailed가 do 블록 안에서 발생하면 catch let error as NetworkError에 매칭되지 않아 UserError가 그대로 전파됩니다. 의도적이라면 괜찮지만, 일관성을 위해 확인이 필요합니다.

🐛 일관성을 위한 수정 제안

UserError.onboardingLoadFailed를 의도적으로 전파하려면 do 블록 밖으로 이동하거나, 일반 catch를 추가하세요:

         } catch let error as NetworkError {
             switch error {
             case .needRetry, .invalidURL, .emptyData:
                 throw DomainError.requireRetry
             default:
                 throw DomainError.business(error.description)
             }
+        } catch let error as UserError {
+            throw error
+        } catch {
+            throw DomainError.unknown
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/DataSource/Sources/Repository/OnboardingRepository.swift` around
lines 17 - 30, The method loadOnboardingResult currently only catches
NetworkError (catch let error as NetworkError) so non-network errors like
UserError.onboardingLoadFailed will escape; to align with other repository
methods (e.g., registerOnboarding, registerRecommendedRoutines) add a general
catch after the NetworkError catch that throws DomainError.unknown, or
alternatively move the throw UserError.onboardingLoadFailed outside the do so
it’s handled consistently; update loadOnboardingResult accordingly to ensure all
non-NetworkError exceptions are mapped to DomainError.unknown.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

UserError.onboardingLoadFailed 를 그대로 throw 하기 위한 의도된 부분입니다

Copy link

Choose a reason for hiding this comment

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

@taipaise 이해했습니다! UserError.onboardingLoadFailedDomainError로 변환하지 않고 그대로 전파하기 위한 의도된 설계라는 것을 확인했습니다. 감사합니다.


✏️ Learnings added
Learnt from: taipaise
Repo: YAPP-Github/Bitnagil-iOS PR: 85
File: Projects/DataSource/Sources/Repository/OnboardingRepository.swift:17-30
Timestamp: 2026-02-17T11:00:01.598Z
Learning: OnboardingRepository의 loadOnboardingResult 메서드에서 UserError.onboardingLoadFailed는 의도적으로 DomainError로 변환하지 않고 그대로 throw 됩니다. 이는 다른 메서드들(registerOnboarding, registerRecommendedRoutines)과 달리 일반 catch 블록이 없는 이유이며, NetworkError만 DomainError로 변환하고 UserError는 원본 그대로 전파하는 것이 의도된 동작입니다.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: choijungp
Repo: YAPP-Github/Bitnagil-iOS PR: 19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.

Learnt from: choijungp
Repo: YAPP-Github/Bitnagil-iOS PR: 19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.

}

func registerOnboarding(onboardingEntity: OnboardingEntity) async throws -> [RecommendedRoutineEntity] {
Expand All @@ -27,15 +37,39 @@ final class OnboardingRepository: OnboardingRepositoryProtocol {
realOutingFrequency: onboardingEntity.frequency,
targetOutingFrequency: onboardingEntity.outdoor)
let endpoint = OnboardingEndpoint.registerOnboarding(onboarding: onboardingDTO)
guard let response = try await networkService.request(endpoint: endpoint, type: RecommendedRoutineListResponseDTO.self)
else { return [] }

let recommendedRoutineEntity = response.recommendedRoutines.compactMap({ $0.toRecommendedRoutineEntity() })
return recommendedRoutineEntity

do {
guard let response = try await networkService.request(endpoint: endpoint, type: RecommendedRoutineListResponseDTO.self)
else { return [] }

let recommendedRoutineEntity = response.recommendedRoutines.compactMap({ $0.toRecommendedRoutineEntity() })
return recommendedRoutineEntity
} catch let error as NetworkError {
switch error {
case .needRetry, .invalidURL, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
} catch {
throw DomainError.unknown
}
}

func registerRecommendedRoutines(selectedRoutines: [Int]) async throws {
let endpoint = OnboardingEndpoint.registerRecommendedRoutine(selectedRoutines: selectedRoutines)
_ = try await networkService.request(endpoint: endpoint, type: EmptyResponseDTO.self)

do {
_ = try await networkService.request(endpoint: endpoint, type: EmptyResponseDTO.self)
} catch let error as NetworkError {
switch error {
case .needRetry, .invalidURL, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
} catch {
throw DomainError.unknown
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,46 @@ final class RecommendedRoutineRepository: RecommendedRoutineRepositoryProtocol {
func fetchRecommendedRoutine(id: Int) async throws -> RecommendedRoutineEntity? {
let endpoint = RecommendedRoutineEndpoint.fetchRecommendedRoutine(id: id)

guard let recommendedRoutineDTO = try await networkService.request(endpoint: endpoint, type: RecommendedRoutineDTO.self)
else { return nil }

return recommendedRoutineDTO.toRecommendedRoutineEntity()
do {
guard let recommendedRoutineDTO = try await networkService.request(endpoint: endpoint, type: RecommendedRoutineDTO.self)
else { return nil }

return recommendedRoutineDTO.toRecommendedRoutineEntity()
} catch let error as NetworkError {
switch error {
case .needRetry, .invalidURL, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
Comment on lines +22 to +27
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n Projects/DataSource/Sources/Repository/RecommendedRoutineRepository.swift

Repository: YAPP-Github/Bitnagil-iOS

Length of output: 2549


🏁 Script executed:

rg -n "\.needRetry.*\.invalidURL.*\.emptyData" --type swift

Repository: YAPP-Github/Bitnagil-iOS

Length of output: 2614


🏁 Script executed:

rg -n "case \.needRetry" --type swift -A 3

Repository: YAPP-Github/Bitnagil-iOS

Length of output: 10040


.invalidURL, .emptyDatarequireRetry로 매핑하지 않기

.needRetry는 타임아웃/연결 끊김 같은 일시적 오류이므로 재시도가 의미 있습니다. 그러나 .invalidURL은 영구적 오류로 재시도해도 항상 실패하고, .emptyData도 일시적 오류가 아닐 가능성이 높습니다. 현재 이 경우들을 DomainError.requireRetry로 매핑하면 사용자에게 오도하는 "재시도" 프롬프트를 표시하게 됩니다.

이 패턴은 RecommendedRoutineRepository 외에도 LocationRepository, RoutineRepository, ReportRepository, OnboardingRepository, FileRepository, EmotionRepository 등 9개 파일의 21개 위치에서 동일하게 사용되고 있어 프로젝트 전반에 걸친 개선이 필요합니다.

제안된 수정
         } catch let error as NetworkError {
             switch error {
-            case .needRetry, .invalidURL, .emptyData:
+            case .needRetry:
                 throw DomainError.requireRetry
             default:
                 throw DomainError.business(error.description)
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
switch error {
case .needRetry, .invalidURL, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
switch error {
case .needRetry:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/DataSource/Sources/Repository/RecommendedRoutineRepository.swift`
around lines 22 - 27, The switch incorrectly maps .invalidURL and .emptyData to
DomainError.requireRetry (only .needRetry should trigger retries); update the
switch in RecommendedRoutineRepository (and the same pattern in
LocationRepository, RoutineRepository, ReportRepository, OnboardingRepository,
FileRepository, EmotionRepository, etc.) so that case .needRetry throws
DomainError.requireRetry, while .invalidURL and .emptyData (and other
non-transient cases) throw DomainError.business(error.description) (preserving
the existing default behavior) to avoid prompting retries for permanent errors.

} catch {
throw DomainError.unknown
}
}

func fetchRecommendedRoutines() async throws -> [RecommendedRoutineEntity] {
let endpoint = RecommendedRoutineEndpoint.fetchRecommendedRoutines
guard let response = try await networkService.request(endpoint: endpoint, type: RecommendedRoutineDictionaryResponseDTO.self)
else { return [] }

var entities: [RecommendedRoutineEntity] = []
for (category, recommendedRoutines) in response.recommendedRoutines {
let recommendedRoutineEntity = recommendedRoutines.compactMap({ $0.toRecommendedRoutineEntity(category: category) })
entities.append(contentsOf: recommendedRoutineEntity)
do {
guard let response = try await networkService.request(endpoint: endpoint, type: RecommendedRoutineDictionaryResponseDTO.self)
else { return [] }

var entities: [RecommendedRoutineEntity] = []
for (category, recommendedRoutines) in response.recommendedRoutines {
let recommendedRoutineEntity = recommendedRoutines.compactMap({ $0.toRecommendedRoutineEntity(category: category) })
entities.append(contentsOf: recommendedRoutineEntity)
}

return entities
} catch let error as NetworkError {
switch error {
case .needRetry, .invalidURL, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
} catch {
throw DomainError.unknown
}

return entities
}
}
62 changes: 49 additions & 13 deletions Projects/DataSource/Sources/Repository/ReportRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,30 +33,66 @@ final class ReportRepository: ReportRepositoryProtocol {
)

let endpoint = ReportEndpoint.register(report: reportDTO)
guard let id = try await networkService.request(endpoint: endpoint, type: Int.self) else { return nil }

return id
do {
guard let id = try await networkService.request(endpoint: endpoint, type: Int.self) else { return nil }

return id
} catch let error as NetworkError {
switch error {
case .needRetry, .invalidURL, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
} catch {
throw DomainError.unknown
}
}

func fetchReports() async throws -> [ReportEntity] {
let endpoint = ReportEndpoint.fetchReports
guard let response = try await networkService.request(endpoint: endpoint, type: ReportDictonaryDTO.self)
else { return [] }

var reportEntities: [ReportEntity] = []
for (date, reports) in response.reportInfos {
let reportHistories = reports.compactMap({ try? $0.toReportEntity(date: date) })
reportEntities += reportHistories
}
do {
guard let response = try await networkService.request(endpoint: endpoint, type: ReportDictonaryDTO.self)
else { return [] }

var reportEntities: [ReportEntity] = []
for (date, reports) in response.reportInfos {
let reportHistories = reports.compactMap({ try? $0.toReportEntity(date: date) })
reportEntities += reportHistories
}

return reportEntities
return reportEntities
} catch let error as NetworkError {
switch error {
case .needRetry, .invalidURL, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
} catch {
throw DomainError.unknown
}
}

func fetchReportDetail(reportId: Int) async throws -> ReportEntity? {
let endpoint = ReportEndpoint.fetchReportDetail(reportId: reportId)
guard let response = try await networkService.request(endpoint: endpoint, type: ReportDTO.self)
else { return nil }

return try response.toReportEntity()
do {
guard let response = try await networkService.request(endpoint: endpoint, type: ReportDTO.self)
else { return nil }

return try response.toReportEntity()
} catch let error as NetworkError {
switch error {
case .needRetry, .invalidURL, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
} catch {
throw DomainError.unknown
}
}
}
Loading