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

[CAT-161] Feature: token interceptor를 포함한 API 모듈 수정 #18

Merged
merged 4 commits into from
Aug 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
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation

import Dependencies

public protocol TargetType {
public protocol APIBaseRequest {
var baseURL: String { get }
var path: String { get }
var method: HTTPMethod { get }
Expand All @@ -20,8 +20,10 @@ public protocol TargetType {

public enum API {
public static var apiBaseURL: String {
guard let baseURL = Bundle.main.object(forInfoDictionaryKey: "BASE_URL") as? String else { fatalError("url missing") }
return "https://" + baseURL
guard let baseURL = Bundle.main.object(forInfoDictionaryKey: "BASE_URL") as? String else {
fatalError("url missing")
}
return baseURL
}
}

Expand All @@ -42,7 +44,7 @@ public enum RequestParams {
case body(_ parameter: Encodable)
}

public extension TargetType {
public extension APIBaseRequest {
var contentType: ContentType {
return .json
}
Expand Down
42 changes: 42 additions & 0 deletions Projects/Core/APIClient/Interface/APIClientInterface.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// APIClientInterface.swift
// APIClient
//
// Created by 김지현 on 8/6/24.
// Copyright © 2024 PomoNyang. All rights reserved.
//

import Foundation

import Logger

import Dependencies
import DependenciesMacros

@DependencyClient
public struct APIClient {
public var apiRequest: @Sendable (
_ request: APIBaseRequest,
_ isWithInterceptor: Bool
) async throws -> (Data, URLResponse)

public func apiRequest<T: Decodable>(
request: APIBaseRequest,
as: T.Type,
isWithInterceptor: Bool = true
) async throws -> T {
let (data, _) = try await self.apiRequest(request, isWithInterceptor)

do {
let decodedData = try JSONDecoder().decode(T.self, from: data)
return decodedData
} catch {
throw NetworkError.decodingError
}
}
}

extension APIClient: TestDependencyKey {
public static let previewValue = Self()
public static let testValue = Self()
}
60 changes: 33 additions & 27 deletions Projects/Core/APIClient/Interface/Model/NetworkError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,39 @@
import Foundation

public enum NetworkError: Error {
case requestError(_ description: String)
case noResponseError
case authorizationError
case decodingError
case serverError
case networkConnectionError
case timeOutError
case unknownError
case requestError(_ description: String)
case apiError(_ description: String)
case noResponseError
case authorizationError
case decodingError
case serverError
case networkConnectionError
case timeOutError
case unknownError
case refreshTokenExpired

var errorMessage: String {
switch self {
case let .requestError(description):
return "Request Error: \(description)"
case .decodingError:
return "Decoding Error"
case .serverError:
return "Server Error"
case .networkConnectionError:
return "Network Connection Error"
case .timeOutError:
return "Timeout Error"
case .unknownError:
return "Unknown Error"
case .noResponseError:
return "No Response Error"
case .authorizationError:
return "Autorization Error"
}
public var errorMessage: String {
switch self {
case let .requestError(description):
return "Request Error: \(description)"
case let .apiError(description):
return "API Error: \(description)"
case .decodingError:
return "Decoding Error"
case .serverError:
return "Server Error"
case .networkConnectionError:
return "Network Connection Error"
case .timeOutError:
return "Timeout Error"
case .unknownError:
return "Unknown Error"
case .noResponseError:
return "No Response Error"
case .authorizationError:
return "Autorization Error"
case .refreshTokenExpired:
return "Refresh Token has been expired Error"
}
}
}
17 changes: 0 additions & 17 deletions Projects/Core/APIClient/Interface/TokenInterceptorInterface.swift

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,23 @@

import Foundation
import APIClientInterface
import KeychainClientInterface
import Shared


extension TargetType {
func asURLRequest(keychainClient: KeychainClient) async throws -> URLRequest {
let url = URL(string: baseURL)!
var urlRequest = URLRequest(url: url.appendingPathComponent(path))
extension APIBaseRequest {
func asURLRequest() async throws -> URLRequest {
let baseURL = URL(string: "https://\(self.baseURL)")!

Check warning on line 15 in Projects/Core/APIClient/Sources/APIBaseRequest.swift

View workflow job for this annotation

GitHub Actions / Run Swiftlint

Force unwrapping should be avoided (force_unwrapping)
var urlRequest = URLRequest(url: baseURL.appendingPathComponent(path))
urlRequest.httpMethod = method.rawValue
urlRequest.setValue(
contentType.rawValue,
forHTTPHeaderField: HTTPHeaderField.contentType.rawValue
)
if let jwt = keychainClient.read(key: "jwt") {
urlRequest.addValue(
jwt,
forHTTPHeaderField: HTTPHeaderField.authentication.rawValue
)
}

switch parameters {
case .query(let request):
let params = try request.toDictionary()
let queryParams = params.map { URLQueryItem(name: $0.key, value: "\($0.value)") }
var components = URLComponents(string: url.appendingPathComponent(path).absoluteString)
var components = URLComponents(string: baseURL.appendingPathComponent(path).absoluteString)
components?.queryItems = queryParams
urlRequest.url = components?.url

Expand All @@ -57,7 +49,7 @@
}
}

public extension TargetType {
public extension APIBaseRequest {
var contentType: ContentType {
return .json
}
Expand Down
86 changes: 86 additions & 0 deletions Projects/Core/APIClient/Sources/APIClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// APIClient.swift
// APIClient
//
// Created by 김지현 on 8/6/24.
// Copyright © 2024 PomoNyang. All rights reserved.
//

import Foundation

import Logger
import APIClientInterface

import Dependencies

extension APIClient: DependencyKey {
public static let liveValue: APIClient = .live()

public static func live() -> Self {

Check warning on line 20 in Projects/Core/APIClient/Sources/APIClient.swift

View workflow job for this annotation

GitHub Actions / Run Swiftlint

Don't include vertical whitespace (empty line) after opening braces (vertical_whitespace_opening_braces)
actor Session {
nonisolated let tokenInterceptor: TokenInterceptor

let session: URLSession = {
let config = URLSessionConfiguration.default
return URLSession(configuration: config)
}()

let decoder: JSONDecoder = JSONDecoder()

init(tokenInterceptor: TokenInterceptor) {
self.tokenInterceptor = tokenInterceptor
}

func sendRequest(
_ request: APIBaseRequest,
isWithInterceptor: Bool,
retryCnt: Int = 0
) async throws -> (Data, URLResponse) {
guard retryCnt < 3 else { throw throwNetworkErr(.timeOutError) }

var urlRequest = try await request.asURLRequest()
if isWithInterceptor {
urlRequest = try await tokenInterceptor.adapt(urlRequest)
}
Logger.shared.log(category: .network, "API Request:\n\(dump(urlRequest))")

let (data, response) = try await session.data(for: urlRequest)

guard let httpResponse = response as? HTTPURLResponse else {
let error = NetworkError.noResponseError
Logger.shared.log(level: .error, category: .network, "API Error:\n\(dump(error))")
throw error
}

switch httpResponse.statusCode {
case 200..<300:
return (data, response)
case 401:
try await tokenInterceptor.retry(for: self.session)
return try await sendRequest(request, isWithInterceptor: isWithInterceptor, retryCnt: retryCnt + 1)
case 400..<500:
throw throwNetworkErr(.requestError("bad request"))
case 500..<600:
throw throwNetworkErr(.serverError)
default:
throw throwNetworkErr(.unknownError)
}

func throwNetworkErr(_ error: NetworkError) -> NetworkError {
Logger.shared.log(level: .error, category: .network, "\(error.localizedDescription):\n\(dump(error))")
return error
}
}
}

let tokenInterceptor = TokenInterceptor()
let session = Session(tokenInterceptor: tokenInterceptor)

return .init(
apiRequest: { request, isWithInterceptor in
return try await session.sendRequest(request, isWithInterceptor: isWithInterceptor)
}
)
}
}
65 changes: 0 additions & 65 deletions Projects/Core/APIClient/Sources/APIReqeustLoader.swift

This file was deleted.

34 changes: 0 additions & 34 deletions Projects/Core/APIClient/Sources/Plugin/EventLoggerDelegate.swift

This file was deleted.

Loading
Loading