Skip to content

Commit 4c627cb

Browse files
authored
Merge pull request #51 from YAPP-Github/BOOK-72-refactor/#44
refactor: 네트워크 아키텍처 리팩토링
2 parents 82b871a + 1a7fcb4 commit 4c627cb

14 files changed

Lines changed: 136 additions & 45 deletions

File tree

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
// Copyright © 2025 Booket. All rights reserved
22

3+
import Combine
4+
35
public protocol NetworkProvider {
46
@discardableResult
57
func request<T: Decodable>(
68
target: RequestTarget,
7-
type: T.Type,
8-
isRetry: Bool
9-
) async throws -> T
9+
type: T.Type
10+
) -> AnyPublisher<T, Error>
1011
}

src/Projects/BKData/Sources/Interface/Storage/TokenProvider.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
public protocol TokenProvider {
44
var accessToken: String? { get }
5-
func refreshIfNeeded() async throws
5+
func refreshIfNeeded()
66
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright © 2025 Booket. All rights reserved
2+
3+
import Combine
4+
import Foundation
5+
6+
final class URLSessionRequestor: NetworkRequestable {
7+
private let session: URLSession
8+
9+
init(session: URLSession = .shared) {
10+
self.session = session
11+
}
12+
13+
func data(for request: URLRequest) -> AnyPublisher<(Data, URLResponse), Error> {
14+
session.dataTaskPublisher(for: request)
15+
.map { ($0.data, $0.response) }
16+
.mapError { $0 as Error }
17+
.eraseToAnyPublisher()
18+
}
19+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright © 2025 Booket. All rights reserved
2+
3+
import Foundation
4+
5+
struct RetryTrigger: Error {}

src/Projects/BKNetwork/Sources/Extension/Data+.swift

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,8 @@ import Foundation
44

55
extension Data {
66
func decode<T: Decodable>(
7-
to type: T.Type,
8-
with response: URLResponse
7+
to type: T.Type
98
) throws -> T {
10-
guard let httpResponse = response as? HTTPURLResponse else {
11-
throw NetworkError.invalidResponse
12-
}
13-
14-
switch httpResponse.statusCode {
15-
case 200...299:
16-
break
17-
case 401:
18-
throw NetworkError.unauthorized
19-
case 500...599:
20-
throw NetworkError.internalServerError
21-
default:
22-
throw NetworkError.invalidResponse
23-
}
24-
259
return try JSONDecoder().decode(T.self, from: self)
2610
}
2711
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright © 2025 Booket. All rights reserved
2+
3+
import Foundation
4+
5+
extension HTTPURLResponse {
6+
func validate() throws {
7+
switch statusCode {
8+
case 200...299:
9+
return
10+
case 401:
11+
throw NetworkError.unauthorized
12+
case 500...599:
13+
throw NetworkError.internalServerError
14+
default:
15+
throw NetworkError.invalidResponse
16+
}
17+
}
18+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright © 2025 Booket. All rights reserved
2+
3+
import Foundation
4+
5+
extension Optional {
6+
func orThrow(
7+
_ error: @autoclosure () -> Error
8+
) throws -> Wrapped {
9+
guard let value = self else { throw error() }
10+
return value
11+
}
12+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright © 2025 Booket. All rights reserved
2+
3+
import Combine
4+
5+
extension Publisher {
6+
func retryIf(
7+
_ shouldRetry: @escaping (Failure) -> Bool,
8+
maxRetries: Int
9+
) -> AnyPublisher<Output, Failure> {
10+
self.catch { error -> AnyPublisher<Output, Failure> in
11+
guard maxRetries > 0, shouldRetry(error) else {
12+
return Fail(error: error).eraseToAnyPublisher()
13+
}
14+
return self
15+
.retryIf(shouldRetry, maxRetries: maxRetries - 1)
16+
.eraseToAnyPublisher()
17+
}
18+
.eraseToAnyPublisher()
19+
}
20+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright © 2025 Booket. All rights reserved
2+
3+
import Foundation
4+
5+
extension URLResponse {
6+
var asHTTP: HTTPURLResponse? {
7+
self as? HTTPURLResponse
8+
}
9+
}

src/Projects/BKNetwork/Sources/Helper/AuthInterceptor.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,23 @@ public struct AuthInterceptor {
1010
self.tokenProvider = tokenProvider
1111
}
1212

13-
func adapt(_ request: URLRequest) throws -> URLRequest {
13+
func adapt(_ request: URLRequest) -> URLRequest {
1414
guard let token = tokenProvider.accessToken else { return request }
1515

1616
var adapted = request
1717
adapted.addAuthorization(token)
1818
return adapted
1919
}
2020

21-
func retryIfNeeded(_ response: URLResponse?, _ data: Data) async throws -> Bool {
22-
guard let httpResponse = response as? HTTPURLResponse else { return false }
21+
func retryIfNeeded(
22+
_ response: URLResponse,
23+
_ data: Data
24+
) throws {
25+
let httpResponse = try response.asHTTP
26+
.orThrow(NetworkError.invalidResponse)
2327
if httpResponse.statusCode == 401 {
24-
try await tokenProvider.refreshIfNeeded()
25-
return true
28+
tokenProvider.refreshIfNeeded()
29+
throw RetryTrigger()
2630
}
27-
return false
2831
}
2932
}

0 commit comments

Comments
 (0)