diff --git a/Sources/Core/NXCache.swift b/Sources/Core/NXCache.swift index 6b195c0..a598a54 100644 --- a/Sources/Core/NXCache.swift +++ b/Sources/Core/NXCache.swift @@ -7,14 +7,14 @@ import Foundation -/// Response cache behavior for successful GET responses and in-flight identical requests. +/// 성공한 GET 응답과 진행 중인 동일 요청에 대한 응답 캐시 동작입니다. public enum NXCache: Sendable, Equatable { - /// Does not cache responses and executes identical requests separately. + /// 응답을 캐시하지 않고 동일한 요청을 각각 별도로 실행합니다. case disabled - /// Stores successful GET responses in memory for the specified TTL and reuses in-flight identical GET request results without validator revalidation. + /// 지정된 TTL 동안 성공한 GET 응답을 메모리에 저장하고, 검증자 재검증 없이 진행 중인 동일 GET 요청 결과를 재사용합니다. case memory(ttl: TimeInterval) - /// Stores successful GET responses in memory for the specified TTL and revalidates expired validator-backed `200` responses. + /// 지정된 TTL 동안 성공한 GET 응답을 메모리에 저장하고, 만료된 검증자 기반 `200` 응답을 재검증합니다. /// - /// The cache belongs to the `NXAPIClient` instance that receives this policy. Recreating the client creates an independent cache. + /// 캐시는 이 정책을 받는 `NXAPIClient` 인스턴스에 속합니다. 클라이언트를 다시 생성하면 독립적인 캐시가 생깁니다. case revalidatingMemory(ttl: TimeInterval) } diff --git a/Sources/Core/NXClientConfiguration.swift b/Sources/Core/NXClientConfiguration.swift index 6379770..0a06ce5 100644 --- a/Sources/Core/NXClientConfiguration.swift +++ b/Sources/Core/NXClientConfiguration.swift @@ -7,11 +7,11 @@ import Foundation -/// Shared configuration applied to every request created by an API client. +/// API 클라이언트가 생성하는 모든 요청에 적용되는 공통 설정입니다. /// -/// ## Overview +/// ## 개요 /// -/// Put shared networking behavior here once, then reuse it through `NXAPIClient`. +/// 공통 네트워킹 동작을 한 곳에 정의하고 `NXAPIClient`를 통해 재사용하세요. /// /// ```swift /// import Foundation @@ -30,40 +30,40 @@ import Foundation /// let client = NXAPIClient(configuration: configuration) /// ``` public struct NXClientConfiguration: Sendable { - /// Base URL used to resolve relative request paths. + /// 상대 경로 요청을 해석할 때 사용하는 기본 URL입니다. public let baseURL: URL - /// Headers added to every request. Request-level headers overwrite these if keys conflict. + /// 모든 요청에 추가되는 헤더입니다. 키가 겹치면 요청 단위 헤더가 이 헤더를 덮어씁니다. public let headers: [String: String] - /// Transport used to execute the assembled `URLRequest`. + /// 조립된 `URLRequest` 실행에 사용하는 전송 계층입니다. public let transport: any NXHTTPTransport - /// Logger that receives request lifecycle events. + /// 요청 라이프사이클 이벤트를 수신하는 로거입니다. public let logger: any NXLogger - /// Interceptors applied to every request. + /// 모든 요청에 적용되는 인터셉터입니다. public let interceptors: [any NXHTTPInterceptor] - /// Response cache behavior for successful GET responses and in-flight identical requests. + /// 성공한 GET 응답과 진행 중인 동일 요청에 대한 캐시 동작입니다. public let cache: NXCache - /// Decoder used by typed requests to decode the response body. + /// 타입 지정 요청에서 응답 본문을 디코딩할 때 사용하는 디코더입니다. public let decoder: JSONDecoder - /// Encoder used for JSON request bodies when no encoder is passed to `json(_:encoder:)`. + /// `json(_:encoder:)`에 인코더가 전달되지 않을 때 JSON 요청 본문을 만드는 인코더입니다. public let encoder: JSONEncoder - /// Decoder that can map failed server responses into domain-specific errors. + /// 실패한 서버 응답을 도메인 전용 오류로 매핑할 수 있는 디코더입니다. public let serverErrorDecoder: any NXServerErrorDecoder - /// Provider used by authenticated requests to fetch and refresh bearer tokens. + /// 인증 요청에서 베어러 토큰을 조회하고 갱신하는 데 사용하는 공급자입니다. public let authTokenProvider: (any NXAuthTokenProvider)? - /// Creates a client configuration. + /// 클라이언트 구성을 생성합니다. /// /// - Parameters: - /// - baseURL: Base URL used to resolve relative request paths. - /// - headers: Headers added to every request. - /// - transport: Transport used to execute requests. - /// - logger: Logger that receives request lifecycle events. - /// - interceptors: Interceptors applied to every request. - /// - cache: Response cache behavior for successful GET responses and in-flight identical requests. - /// - decoder: Decoder used for typed responses. - /// - encoder: Encoder used for JSON request bodies. - /// - serverErrorDecoder: Decoder used to map failed responses to custom errors. - /// - authTokenProvider: Provider used for authenticated requests. + /// - baseURL: 상대 경로 요청을 해석할 때 사용하는 기본 URL입니다. + /// - headers: 모든 요청에 추가되는 헤더입니다. + /// - transport: 요청 실행을 담당하는 전송 계층입니다. + /// - logger: 요청 라이프사이클 이벤트를 받는 로거입니다. + /// - interceptors: 모든 요청에 적용되는 인터셉터 목록입니다. + /// - cache: 성공한 GET 응답 및 진행 중인 동일 요청에 대한 캐시 동작입니다. + /// - decoder: 타입 지정 응답 디코딩에 사용하는 디코더입니다. + /// - encoder: JSON 요청 본문 인코딩에 사용하는 인코더입니다. + /// - serverErrorDecoder: 실패 응답을 커스텀 오류로 매핑하는 디코더입니다. + /// - authTokenProvider: 인증 요청에 사용되는 공급자입니다. public init( baseURL: URL, headers: [String: String] = [:], diff --git a/Sources/Core/NXError.swift b/Sources/Core/NXError.swift index 3115e10..08be4c0 100644 --- a/Sources/Core/NXError.swift +++ b/Sources/Core/NXError.swift @@ -7,26 +7,26 @@ import Foundation -/// Errors produced by Nexa while assembling, sending, validating, or decoding requests. +/// Nexa가 요청 조립, 전송, 유효성 검사, 디코딩 과정에서 발생시키는 오류입니다. public enum NXError: Error, Sendable { - /// Request assembly failed because the input could not produce a valid HTTP request. + /// 입력으로 유효한 HTTP 요청을 만들 수 없어 요청 조립이 실패했습니다. case invalidRequest(String) - /// The request required authentication but no token was available. + /// 요청에 인증이 필요했지만 사용 가능한 토큰이 없습니다. case authenticationRequired - /// The request required authentication but no auth token provider was configured. + /// 요청에 인증이 필요했지만 인증 토큰 공급자가 설정되지 않았습니다. case authProviderUnavailable - /// The underlying transport failed with a `URLError`. + /// 전송 계층에서 `URLError`가 발생했습니다. case transport(URLError) - /// The request timed out. + /// 요청이 시간 초과되었습니다. case timeout - /// The request was cancelled. + /// 요청이 취소되었습니다. case cancelled - /// Response validation failed for the received status code. + /// 수신한 상태 코드에 대한 응답 유효성 검사가 실패했습니다. case invalidStatus(statusCode: Int, data: Data?) - /// The server returned a failing status code and a custom server error was decoded. + /// 서버가 실패 상태 코드를 반환했고 사용자 정의 서버 오류로 디코딩되었습니다. case server(statusCode: Int, data: Data?, underlying: any Error) - /// Response decoding failed for a successful request. + /// 성공한 요청의 응답 디코딩이 실패했습니다. case decoding(any Error, data: Data?) - /// An uncategorized error occurred. + /// 분류되지 않은 오류가 발생했습니다. case unknown(any Error) } diff --git a/Sources/Core/NXHTTPMethod.swift b/Sources/Core/NXHTTPMethod.swift index 673c224..43a71d4 100644 --- a/Sources/Core/NXHTTPMethod.swift +++ b/Sources/Core/NXHTTPMethod.swift @@ -7,7 +7,7 @@ import Foundation -/// HTTP methods supported by Nexa request builders. +/// Nexa 요청 빌더가 지원하는 HTTP 메서드입니다. public enum NXHTTPMethod: String, Sendable, Hashable { case get = "GET" case post = "POST" diff --git a/Sources/Core/NXLogging.swift b/Sources/Core/NXLogging.swift index 78b7cc1..0f94c51 100644 --- a/Sources/Core/NXLogging.swift +++ b/Sources/Core/NXLogging.swift @@ -7,7 +7,7 @@ import Foundation -/// Request lifecycle events emitted by Nexa loggers. +/// Nexa 로거가 내보내는 요청 라이프사이클 이벤트입니다. public enum NXLogEvent: Sendable { case requestStart(NXRequestStartLog) case requestEnd(NXRequestEndLog) @@ -16,20 +16,20 @@ public enum NXLogEvent: Sendable { case authRefresh(NXAuthRefreshLog) } -/// Structured payload emitted when a request attempt starts. +/// 요청 시도 시작 시점에 출력되는 구조화된 페이로드입니다. public struct NXRequestStartLog: Sendable { - /// Stable identifier shared by all attempts of the same logical request. + /// 동일한 논리 요청의 모든 시도에서 공유되는 안정적 식별자입니다. public let requestIdentifier: UUID - /// Current attempt number starting from `1`. + /// 현재 시도 번호(`1`부터 시작)입니다. public let attemptNumber: Int - /// HTTP method string for the outgoing request. + /// 전송할 요청의 HTTP 메서드 문자열입니다. public let method: String - /// Fully resolved request URL string. + /// 완전히 해석된 요청 URL 문자열입니다. public let url: String - /// Final headers included in the request. + /// 요청에 포함된 최종 헤더입니다. public let headers: [String: String] - /// Creates a request-start log payload. + /// 요청 시작 로그 페이로드를 생성합니다. public init( requestIdentifier: UUID, attemptNumber: Int, @@ -45,20 +45,20 @@ public struct NXRequestStartLog: Sendable { } } -/// Structured payload emitted when a request attempt ends successfully. +/// 요청 시도가 성공적으로 끝났을 때 출력되는 구조화된 페이로드입니다. public struct NXRequestEndLog: Sendable { - /// Stable identifier shared by all attempts of the same logical request. + /// 동일한 논리 요청의 모든 시도에서 공유되는 안정적인 식별자입니다. public let requestIdentifier: UUID - /// Current attempt number starting from `1`. + /// 현재 시도 번호(`1`부터 시작)입니다. public let attemptNumber: Int - /// HTTP status code returned by the server. + /// 서버가 반환한 HTTP 상태 코드입니다. public let statusCode: Int - /// Elapsed wall-clock time for the attempt. + /// 시도의 실제 경과 시간입니다. public let elapsedTime: TimeInterval - /// Response payload size in bytes. + /// 응답 페이로드 크기(바이트)입니다. public let payloadSize: Int - /// Creates a request-end log payload. + /// 요청 완료 로그 페이로드를 생성합니다. public init( requestIdentifier: UUID, attemptNumber: Int, @@ -74,18 +74,18 @@ public struct NXRequestEndLog: Sendable { } } -/// Structured payload emitted when a request attempt fails. +/// 요청 시도가 실패했을 때 출력되는 구조화된 페이로드입니다. public struct NXRequestFailureLog: Sendable { - /// Stable identifier shared by all attempts of the same logical request. + /// 동일한 논리 요청의 모든 시도에서 공유되는 안정적인 식별자입니다. public let requestIdentifier: UUID - /// Current attempt number starting from `1`. + /// 현재 시도 번호(`1`부터 시작)입니다. public let attemptNumber: Int - /// Elapsed wall-clock time for the attempt. + /// 시도의 실제 경과 시간입니다. public let elapsedTime: TimeInterval - /// Human-readable description of the failure. + /// 사람이 읽기 쉬운 실패 설명입니다. public let errorDescription: String - /// Creates a request-failure log payload. + /// 요청 실패 로그 페이로드를 생성합니다. public init( requestIdentifier: UUID, attemptNumber: Int, @@ -99,16 +99,16 @@ public struct NXRequestFailureLog: Sendable { } } -/// Structured payload emitted when Nexa schedules another retry attempt. +/// Nexa가 다음 재시도를 예약할 때 출력되는 구조화된 페이로드입니다. public struct NXRetryLog: Sendable { - /// Stable identifier shared by all attempts of the same logical request. + /// 동일한 논리 요청의 모든 시도에서 공유되는 안정적인 식별자입니다. public let requestIdentifier: UUID - /// Attempt number that will run next. + /// 다음에 실행될 시도 번호입니다. public let nextAttemptNumber: Int - /// Delay before the next attempt begins. + /// 다음 시도 시작 전 대기 시간입니다. public let delay: TimeInterval - /// Creates a retry log payload. + /// 재시도 로그 페이로드를 생성합니다. public init(requestIdentifier: UUID, nextAttemptNumber: Int, delay: TimeInterval) { self.requestIdentifier = requestIdentifier self.nextAttemptNumber = nextAttemptNumber @@ -116,35 +116,35 @@ public struct NXRetryLog: Sendable { } } -/// Structured payload emitted after an auth token refresh attempt finishes. +/// 인증 토큰 갱신 시도 종료 후 출력되는 구조화된 페이로드입니다. public struct NXAuthRefreshLog: Sendable { - /// Identifier of the request that started the refresh. + /// 갱신을 시작한 요청의 식별자입니다. public let requestIdentifier: UUID - /// Whether the refresh attempt succeeded. + /// 갱신 시도 성공 여부입니다. public let succeeded: Bool - /// Creates an auth-refresh log payload. + /// 인증 갱신 로그 페이로드를 생성합니다. public init(requestIdentifier: UUID, succeeded: Bool) { self.requestIdentifier = requestIdentifier self.succeeded = succeeded } } -/// Receives structured request lifecycle events from Nexa. +/// Nexa의 구조화된 요청 라이프사이클 이벤트를 수신합니다. /// -/// ## Overview +/// ## 개요 /// -/// Adopt `NXLogger` to forward request lifecycle events into your own logging or analytics pipeline. +/// 요청 라이프사이클 이벤트를 자체 로깅 또는 분석 파이프라인으로 전달하려면 `NXLogger`를 채택하세요. public protocol NXLogger: Sendable { - /// Handles one log event emitted by Nexa. + /// Nexa가 발행한 로그 이벤트 한 건을 처리합니다. func log(_ event: NXLogEvent) async } -/// Logger that ignores every emitted event. +/// 발생한 모든 이벤트를 무시하는 로거입니다. public struct NXNoopLogger: NXLogger { - /// Creates a logger that performs no work. + /// 아무 작업도 수행하지 않는 로거를 생성합니다. public init() {} - /// Ignores the incoming log event. + /// 전달된 로그 이벤트를 무시합니다. public func log(_ event: NXLogEvent) async {} } diff --git a/Sources/Core/NXNetworkMetrics.swift b/Sources/Core/NXNetworkMetrics.swift index 797d8bd..3f45b90 100644 --- a/Sources/Core/NXNetworkMetrics.swift +++ b/Sources/Core/NXNetworkMetrics.swift @@ -7,21 +7,21 @@ import Foundation -/// Value snapshot of one `URLSession` task's network activity. +/// 하나의 `URLSession` 작업에서 발생한 네트워크 활동의 값 스냅샷입니다. /// -/// `NXURLSessionTransport` creates this value before delivering it to an `NXNetworkMetricsObserver`. -/// Custom `NXHTTPTransport` implementations do not produce this value. +/// `NXURLSessionTransport`는 이 값을 `NXNetworkMetricsObserver`에 전달하기 전에 생성합니다. +/// 사용자 정의 `NXHTTPTransport` 구현은 이 값을 생성하지 않습니다. public struct NXNetworkMetrics: Sendable, Equatable { - /// Total duration of the URL loading task. + /// URL 로딩 작업의 총 소요 시간입니다. public let taskDuration: TimeInterval? - /// Number of redirects followed by the URL loading task. + /// URL 로딩 작업이 따라간 리다이렉트 횟수입니다. public let redirectCount: Int - /// Number of transactions collected for the URL loading task. + /// URL 로딩 작업에서 수집한 트랜잭션 개수입니다. public let transactionCount: Int - /// Transaction snapshots in the order reported by `URLSession`. + /// `URLSession`이 보고한 순서를 보존한 트랜잭션 스냅샷입니다. public let transactions: [NXNetworkTransactionMetrics] - /// Creates a network metrics snapshot. + /// 네트워크 메트릭 스냅샷을 생성합니다. public init( taskDuration: TimeInterval?, redirectCount: Int, @@ -35,20 +35,20 @@ public struct NXNetworkMetrics: Sendable, Equatable { } } -/// Value snapshot of one URL loading transaction. +/// 하나의 URL 로딩 트랜잭션에 대한 값 스냅샷입니다. public struct NXNetworkTransactionMetrics: Sendable, Equatable { - /// Duration of DNS lookup when URLSession reports both timestamps. + /// URLSession이 두 타임스탬프를 모두 보고한 경우의 DNS 조회 시간입니다. public let domainLookupDuration: TimeInterval? - /// Duration from connection start through connection end when URLSession reports both timestamps. + /// URLSession이 두 타임스탬프를 모두 보고한 경우 연결 시작부터 종료까지의 시간입니다. public let connectionDuration: TimeInterval? - /// Duration of TLS negotiation when URLSession reports both timestamps. + /// URLSession이 두 타임스탬프를 모두 보고한 경우 TLS 협상 시간입니다. public let secureConnectionDuration: TimeInterval? - /// Duration from request start until the first response byte when URLSession reports both timestamps. + /// URLSession이 두 타임스탬프를 모두 보고한 경우 요청 시작부터 첫 바이트 응답 수신까지의 시간입니다. public let timeToFirstByte: TimeInterval? - /// Whether URLSession reused an existing connection. + /// URLSession이 기존 연결을 재사용했는지 여부입니다. public let isConnectionReused: Bool - /// Creates a transaction metrics snapshot. + /// 트랜잭션 메트릭 스냅샷을 생성합니다. public init( domainLookupDuration: TimeInterval?, connectionDuration: TimeInterval?, diff --git a/Sources/Core/NXNetworkMetricsObserver.swift b/Sources/Core/NXNetworkMetricsObserver.swift index fd544c6..0182339 100644 --- a/Sources/Core/NXNetworkMetricsObserver.swift +++ b/Sources/Core/NXNetworkMetricsObserver.swift @@ -5,10 +5,10 @@ // Created by opfic on 8/23/26. // -/// Receives metrics snapshots collected by `NXURLSessionTransport`. +/// `NXURLSessionTransport`에서 수집한 메트릭 스냅샷을 수신합니다. /// -/// Metrics delivery is independent of request completion and logger event order. +/// 메트릭 전달은 요청 완료와 로거 이벤트 순서와 독립적으로 수행됩니다. public protocol NXNetworkMetricsObserver: Sendable { - /// Records one network metrics snapshot. + /// 네트워크 메트릭 스냅샷을 한 건 기록합니다. func record(_ metrics: NXNetworkMetrics) async } diff --git a/Sources/Core/NXProtocols.swift b/Sources/Core/NXProtocols.swift index 0674a1a..1f0a223 100644 --- a/Sources/Core/NXProtocols.swift +++ b/Sources/Core/NXProtocols.swift @@ -7,38 +7,38 @@ import Foundation -/// Abstraction over the networking backend that executes `URLRequest` values. +/// `URLRequest` 값을 실행하는 네트워크 전송 계층의 추상화입니다. /// -/// ## Overview +/// ## 개요 /// -/// Adopt `NXHTTPTransport` to stub network responses in tests or replace the default `URLSession` transport. +/// 테스트에서 네트워크 응답을 스텁 처리하거나 기본 `URLSession` 전송 계층을 교체하려면 `NXHTTPTransport`를 채택하세요. public protocol NXHTTPTransport: Sendable { - /// Sends a prepared request and returns the raw HTTP response. + /// 준비된 요청을 전송하고 원시 HTTP 응답을 반환합니다. func send(_ request: URLRequest) async throws -> NXRawResponse } -/// Decodes failed server responses into domain-specific errors. +/// 실패한 서버 응답을 도메인별 오류로 디코딩합니다. public protocol NXServerErrorDecoder: Sendable { - /// Attempts to decode a custom error from a failed HTTP response. + /// 실패한 HTTP 응답에서 커스텀 오류를 디코딩하려고 시도합니다. func decodeServerError(data: Data, response: HTTPURLResponse, decoder: JSONDecoder) -> (any Error)? } -/// Default server error decoder that does not produce a custom error. +/// 커스텀 오류를 생성하지 않는 기본 서버 오류 디코더입니다. public struct NXDefaultServerErrorDecoder: NXServerErrorDecoder { - /// Creates the default server error decoder. + /// 기본 서버 오류 디코더를 생성합니다. public init() {} - /// Always returns `nil`. + /// 항상 `nil`을 반환합니다. public func decodeServerError(data: Data, response: HTTPURLResponse, decoder: JSONDecoder) -> (any Error)? { nil } } -/// Provides bearer tokens for authenticated requests and token refresh. +/// 인증 요청과 토큰 갱신을 위한 베어러 토큰을 제공합니다. /// -/// ## Overview +/// ## 개요 /// -/// Configure an auth token provider when requests use `.authorized()`. +/// 요청이 `.authorized()`를 사용할 때는 인증 토큰 공급자를 구성하세요. /// /// ```swift /// import Nexa @@ -54,8 +54,8 @@ public struct NXDefaultServerErrorDecoder: NXServerErrorDecoder { /// } /// ``` public protocol NXAuthTokenProvider: Sendable { - /// Returns the current access token if one is available. + /// 사용 가능한 현재 액세스 토큰을 반환합니다. func currentAccessToken() async throws -> String? - /// Refreshes the access token and returns the new value if refresh succeeds. + /// 액세스 토큰을 갱신하고 갱신에 성공하면 새 값을 반환합니다. func refreshAccessToken() async throws -> String? } diff --git a/Sources/Core/NXRawResponse.swift b/Sources/Core/NXRawResponse.swift index 87f87ea..d4e81f8 100644 --- a/Sources/Core/NXRawResponse.swift +++ b/Sources/Core/NXRawResponse.swift @@ -7,14 +7,14 @@ import Foundation -/// Raw HTTP response returned by Nexa before decoding. +/// Nexa에서 디코딩 전 반환하는 원시 HTTP 응답입니다. public struct NXRawResponse: Sendable { - /// Raw response body data. + /// 원시 응답 본문 데이터입니다. public var data: Data - /// HTTP response associated with the response body. + /// 응답 본문과 연결된 HTTP 응답입니다. public var response: HTTPURLResponse - /// Creates a raw response container. + /// 원시 응답 컨테이너를 생성합니다. public init(data: Data, response: HTTPURLResponse) { self.data = data self.response = response diff --git a/Sources/Core/NXRequestBody.swift b/Sources/Core/NXRequestBody.swift index f060bda..150b077 100644 --- a/Sources/Core/NXRequestBody.swift +++ b/Sources/Core/NXRequestBody.swift @@ -7,7 +7,7 @@ import Foundation -/// Request body representations supported by Nexa. +/// Nexa가 지원하는 요청 본문 표현 방식입니다. public enum NXRequestBody: Sendable { case data(Data) diff --git a/Sources/Core/NXRetryBackoff.swift b/Sources/Core/NXRetryBackoff.swift index df9bc24..236406d 100644 --- a/Sources/Core/NXRetryBackoff.swift +++ b/Sources/Core/NXRetryBackoff.swift @@ -7,11 +7,11 @@ import Foundation -/// Delay strategy used between retry attempts. +/// 재시도 간격에 사용되는 지연 전략입니다. public enum NXRetryBackoff: Sendable { - /// Uses a fixed delay for every retry attempt. + /// 매 재시도마다 고정 지연을 사용합니다. case fixed(TimeInterval) - /// Doubles the delay every attempt until the maximum delay is reached. + /// 매 시도마다 지연을 두 배로 늘려 최대 지연에 도달할 때까지 반복합니다. case exponential(base: TimeInterval, maxDelay: TimeInterval) func delay(forAttempt attemptNumber: Int) -> TimeInterval { diff --git a/Sources/Core/NXRetryJitter.swift b/Sources/Core/NXRetryJitter.swift index 445f13e..8ddbdcb 100644 --- a/Sources/Core/NXRetryJitter.swift +++ b/Sources/Core/NXRetryJitter.swift @@ -5,10 +5,10 @@ // Created by opfic on 8/23/26. // -/// Randomization applied to local retry backoff delays. +/// 로컬 재시도 백오프 지연에 적용하는 무작위화입니다. public enum NXRetryJitter: Sendable, Equatable { - /// Keeps the local backoff delay unchanged. + /// 로컬 백오프 지연을 변경하지 않습니다. case none - /// Uses a random value within the local backoff delay range. + /// 로컬 백오프 지연 범위 내의 임의 값을 사용합니다. case full } diff --git a/Sources/Core/NXValidationPolicy.swift b/Sources/Core/NXValidationPolicy.swift index ca0d4e2..eb84e7d 100644 --- a/Sources/Core/NXValidationPolicy.swift +++ b/Sources/Core/NXValidationPolicy.swift @@ -7,13 +7,13 @@ import Foundation -/// Rules that determine whether a response status code is treated as successful. +/// 응답 상태 코드의 성공 판정 규칙입니다. public enum NXValidationPolicy: Sendable { - /// Disables status code validation. + /// 상태 코드 유효성 검사를 비활성화합니다. case none - /// Accepts status codes in the `200..<300` range. + /// `200..<300` 범위의 상태 코드만 허용합니다. case successStatusCode - /// Accepts only the provided set of status codes. + /// 지정한 상태 코드 집합만 허용합니다. case statusCodes(Set) func allows(statusCode: Int) -> Bool { diff --git a/Sources/Public/NXAPIClient.swift b/Sources/Public/NXAPIClient.swift index ca77d39..28c5fd3 100644 --- a/Sources/Public/NXAPIClient.swift +++ b/Sources/Public/NXAPIClient.swift @@ -7,13 +7,13 @@ import Foundation -/// Shared entry point for building and sending HTTP requests with a single client configuration. +/// 단일 클라이언트 설정으로 HTTP 요청을 구성하고 전송하기 위한 공용 진입점입니다. /// -/// ## Overview +/// ## 개요 /// -/// Create a client once, then start requests from relative paths. +/// 클라이언트를 한 번 생성한 뒤 상대 경로로 요청을 시작하세요. /// -/// When caching is enabled, each `NXAPIClient` initializer creates an independent in-memory cache and in-flight request store. Reuse one client through a service or dependency container to share cached responses. Copies of the same client value retain the same store. +/// 캐시를 사용하면 각 `NXAPIClient` 초기화 시 독립적인 인메모리 캐시와 인플라이트 요청 저장소가 생성됩니다. 캐시 응답을 공유하려면 서비스나 의존성 컨테이너에서 하나의 클라이언트를 재사용하세요. 동일 클라이언트 값의 복사본은 같은 저장소를 유지합니다. /// /// ```swift /// import Foundation @@ -35,15 +35,15 @@ import Foundation /// .send(as: User.self) /// ``` /// -/// Use the method-based builders for every direct request. `NXEndpoint` retains its typed builder configuration contract for source compatibility. +/// 모든 직접 요청은 메서드 기반 빌더를 사용하세요. `NXEndpoint`는 소스 호환성을 위해 타입 지정 빌더 구성 계약을 유지합니다. public struct NXAPIClient: Sendable { private let configuration: NXClientConfiguration private let responseCacheStore: NXResponseCacheStore? private let authRefreshCoordinator: NXAuthRefreshCoordinator - /// Creates a client that uses the provided configuration for all requests. + /// 모든 요청에 제공된 설정을 사용하는 클라이언트를 생성합니다. /// - /// - Parameter configuration: Shared settings such as the base URL, transport, logger, and auth provider. + /// - Parameter configuration: 기본 URL, 전송 계층, 로거, 인증 공급자 같은 공용 설정입니다. public init(configuration: NXClientConfiguration) { self.init( configuration: configuration, @@ -70,20 +70,20 @@ public struct NXAPIClient: Sendable { } } - /// Creates an untyped `GET` request builder for the given path. + /// 지정한 경로에 대한 타입 미지정 `GET` 요청 빌더를 생성합니다. /// - /// - Parameter path: Path relative to the configured base URL. - /// - Returns: A request builder that can be further configured before sending. + /// - Parameter path: 설정된 기본 URL 기준 상대 경로입니다. + /// - Returns: 전송 전에 추가로 설정할 수 있는 요청 빌더입니다. public func get(_ path: String = "") -> NXRequestBuilder { request(method: .get, path: path) } - /// Creates a typed `GET` request builder for the given path. + /// 지정한 경로에 대한 타입 지정 `GET` 요청 빌더를 생성합니다. /// /// - Parameters: - /// - path: Path relative to the configured base URL. - /// - type: Response type to decode when the request succeeds. - /// - Returns: A typed request builder that decodes into `Response`. + /// - path: 설정된 기본 URL 기준 상대 경로입니다. + /// - type: 요청이 성공했을 때 디코딩할 응답 타입입니다. + /// - Returns: `Response`로 디코딩하는 타입 지정 요청 빌더입니다. @available(*, deprecated, message: "Use get(_:) followed by send(as:).") public func get( _ path: String = "", @@ -92,94 +92,94 @@ public struct NXAPIClient: Sendable { typedRequest(method: .get, path: path) } - /// Creates an untyped `POST` request builder for the given path. + /// 지정한 경로에 대한 타입 미지정 `POST` 요청 빌더를 생성합니다. /// - /// - Parameter path: Path relative to the configured base URL. - /// - Returns: A request builder that can be further configured before sending. + /// - Parameter path: 설정된 기본 URL 기준 상대 경로입니다. + /// - Returns: 전송 전에 추가로 설정할 수 있는 요청 빌더입니다. public func post(_ path: String) -> NXRequestBuilder { request(method: .post, path: path) } - /// Creates a typed `POST` request builder for the given path. + /// 지정한 경로에 대한 타입 지정 `POST` 요청 빌더를 생성합니다. /// /// - Parameters: - /// - path: Path relative to the configured base URL. - /// - type: Response type to decode when the request succeeds. - /// - Returns: A typed request builder that decodes into `Response`. + /// - path: 설정된 기본 URL 기준 상대 경로입니다. + /// - type: 요청이 성공했을 때 디코딩할 응답 타입입니다. + /// - Returns: `Response`로 디코딩하는 타입 지정 요청 빌더입니다. @available(*, deprecated, message: "Use post(_:) followed by send(as:).") public func post(_ path: String, as type: Response.Type) -> NXTypedRequestBuilder { typedRequest(method: .post, path: path) } - /// Creates an untyped `PUT` request builder for the given path. + /// 지정한 경로에 대한 타입 미지정 `PUT` 요청 빌더를 생성합니다. /// - /// - Parameter path: Path relative to the configured base URL. - /// - Returns: A request builder that can be further configured before sending. + /// - Parameter path: 설정된 기본 URL 기준 상대 경로입니다. + /// - Returns: 전송 전에 추가로 설정할 수 있는 요청 빌더입니다. public func put(_ path: String) -> NXRequestBuilder { request(method: .put, path: path) } - /// Creates a typed `PUT` request builder for the given path. + /// 지정한 경로에 대한 타입 지정 `PUT` 요청 빌더를 생성합니다. /// /// - Parameters: - /// - path: Path relative to the configured base URL. - /// - type: Response type to decode when the request succeeds. - /// - Returns: A typed request builder that decodes into `Response`. + /// - path: 설정된 기본 URL 기준 상대 경로입니다. + /// - type: 요청이 성공했을 때 디코딩할 응답 타입입니다. + /// - Returns: `Response`로 디코딩하는 타입 지정 요청 빌더입니다. @available(*, deprecated, message: "Use put(_:) followed by send(as:).") public func put(_ path: String, as type: Response.Type) -> NXTypedRequestBuilder { typedRequest(method: .put, path: path) } - /// Creates an untyped `PATCH` request builder for the given path. + /// 지정한 경로에 대한 타입 미지정 `PATCH` 요청 빌더를 생성합니다. /// - /// - Parameter path: Path relative to the configured base URL. - /// - Returns: A request builder that can be further configured before sending. + /// - Parameter path: 설정된 기본 URL 기준 상대 경로입니다. + /// - Returns: 전송 전에 추가로 설정할 수 있는 요청 빌더입니다. public func patch(_ path: String) -> NXRequestBuilder { request(method: .patch, path: path) } - /// Creates a typed `PATCH` request builder for the given path. + /// 지정한 경로에 대한 타입 지정 `PATCH` 요청 빌더를 생성합니다. /// /// - Parameters: - /// - path: Path relative to the configured base URL. - /// - type: Response type to decode when the request succeeds. - /// - Returns: A typed request builder that decodes into `Response`. + /// - path: 설정된 기본 URL 기준 상대 경로입니다. + /// - type: 요청이 성공했을 때 디코딩할 응답 타입입니다. + /// - Returns: `Response`로 디코딩하는 타입 지정 요청 빌더입니다. @available(*, deprecated, message: "Use patch(_:) followed by send(as:).") public func patch(_ path: String, as type: Response.Type) -> NXTypedRequestBuilder { typedRequest(method: .patch, path: path) } - /// Creates an untyped `DELETE` request builder for the given path. + /// 지정한 경로에 대한 타입 미지정 `DELETE` 요청 빌더를 생성합니다. /// - /// - Parameter path: Path relative to the configured base URL. - /// - Returns: A request builder that can be further configured before sending. + /// - Parameter path: 설정된 기본 URL 기준 상대 경로입니다. + /// - Returns: 전송 전에 추가로 설정할 수 있는 요청 빌더입니다. public func delete(_ path: String) -> NXRequestBuilder { request(method: .delete, path: path) } - /// Creates a typed `DELETE` request builder for the given path. + /// 지정한 경로에 대한 타입 지정 `DELETE` 요청 빌더를 생성합니다. /// /// - Parameters: - /// - path: Path relative to the configured base URL. - /// - type: Response type to decode when the request succeeds. - /// - Returns: A typed request builder that decodes into `Response`. + /// - path: 설정된 기본 URL 기준 상대 경로입니다. + /// - type: 요청이 성공했을 때 디코딩할 응답 타입입니다. + /// - Returns: `Response`로 디코딩하는 타입 지정 요청 빌더입니다. @available(*, deprecated, message: "Use delete(_:) followed by send(as:).") public func delete(_ path: String, as type: Response.Type) -> NXTypedRequestBuilder { typedRequest(method: .delete, path: path) } - /// Builds a typed request from an endpoint value. + /// 엔드포인트 값에서 타입 지정 요청을 구성합니다. /// - /// - Parameter endpoint: Endpoint that defines the HTTP method, path, and optional request customization. - /// - Returns: A typed request builder configured from the endpoint. + /// - Parameter endpoint: HTTP 메서드, 경로, 선택적 요청 커스터마이즈를 정의하는 엔드포인트입니다. + /// - Returns: 엔드포인트 기반으로 설정된 타입 지정 요청 빌더입니다. public func request(_ endpoint: E) -> NXTypedRequestBuilder { endpoint.configure(typedRequest(method: endpoint.method, path: endpoint.path)) } - /// Sends an endpoint request and decodes its response. + /// 엔드포인트 요청을 전송하고 응답을 디코딩합니다. /// - /// - Parameter endpoint: Endpoint that defines the request. - /// - Returns: Decoded response value for the endpoint. + /// - Parameter endpoint: 요청 동작을 정의한 엔드포인트입니다. + /// - Returns: 엔드포인트에 대한 디코딩된 응답 값입니다. public func send(_ endpoint: E) async throws -> E.Response { try await request(endpoint).requestBuilder.send(as: E.Response.self) } diff --git a/Sources/Public/NXEndpoint.swift b/Sources/Public/NXEndpoint.swift index 366a58a..fe5be11 100644 --- a/Sources/Public/NXEndpoint.swift +++ b/Sources/Public/NXEndpoint.swift @@ -7,11 +7,11 @@ import Foundation -/// Describes a reusable API endpoint with a fixed response type. +/// 고정 응답 타입을 갖는 재사용 가능한 API 엔드포인트를 설명합니다. /// -/// ## Overview +/// ## 개요 /// -/// Use an endpoint when the same request shape should be reusable and keep its response type attached. +/// 동일한 요청 구조를 반복 사용하면서 응답 타입을 함께 유지해야 할 때 엔드포인트를 사용합니다. /// /// ```swift /// import Foundation @@ -36,24 +36,24 @@ import Foundation /// let user = try await client.send(UserEndpoint(identifier: 42)) /// ``` public protocol NXEndpoint { - /// Response type produced when the endpoint request succeeds. + /// 엔드포인트 요청이 성공했을 때 생성되는 응답 타입입니다. associatedtype Response: Decodable - /// HTTP method used for the request. + /// 요청에 사용되는 HTTP 메서드입니다. var method: NXHTTPMethod { get } - /// Path relative to the client's base URL. + /// 클라이언트 기본 URL 기준 상대 경로입니다. var path: String { get } - /// Applies endpoint-specific customization to the builder before sending. + /// 전송 전에 엔드포인트별 커스터마이즈를 빌더에 적용합니다. /// - /// - Parameter builder: Default typed request builder created from `method` and `path`. - /// - Returns: Customized typed request builder. + /// - Parameter builder: `method`와 `path`로 생성한 기본 타입 지정 요청 빌더입니다. + /// - Returns: 커스터마이즈된 타입 지정 요청 빌더입니다. func configure(_ builder: NXTypedRequestBuilder) -> NXTypedRequestBuilder } public extension NXEndpoint { - /// Returns the builder unchanged when the endpoint needs no extra customization. + /// 엔드포인트에 추가 커스터마이즈가 필요하지 않으면 빌더를 그대로 반환합니다. /// - /// - Parameter builder: Default typed request builder created from `method` and `path`. - /// - Returns: The same builder instance. + /// - Parameter builder: `method`와 `path`로 생성한 기본 타입 지정 요청 빌더입니다. + /// - Returns: 동일한 빌더 인스턴스입니다. func configure(_ builder: NXTypedRequestBuilder) -> NXTypedRequestBuilder { builder } diff --git a/Sources/Public/NXHTTPInterceptor.swift b/Sources/Public/NXHTTPInterceptor.swift index f357e08..1d07e7c 100644 --- a/Sources/Public/NXHTTPInterceptor.swift +++ b/Sources/Public/NXHTTPInterceptor.swift @@ -7,11 +7,11 @@ import Foundation -/// Intercepts a request execution step and decides how the chain continues. +/// 요청 실행 단계 하나를 가로채 체인의 진행 방식을 결정합니다. /// -/// ## Overview +/// ## 개요 /// -/// Implement an interceptor when you need cross-cutting request behavior such as tracing, custom headers, or response observation. An interceptor can replace request URL, headers, and body, but must preserve the configured HTTP method. +/// 추적, 커스텀 헤더, 응답 관찰 같은 횡단 관심사가 필요할 때 인터셉터를 구현하세요. 인터셉터는 요청 URL, 헤더, 바디를 바꿀 수 있으나 설정된 HTTP 메서드는 유지해야 합니다. /// /// ```swift /// import Foundation @@ -29,41 +29,41 @@ import Foundation /// } /// ``` public protocol NXHTTPInterceptor: Sendable { - /// Handles one request execution step. + /// 요청 실행 단계 하나를 처리합니다. /// /// - Parameters: - /// - context: Current request execution state. - /// - next: Closure that continues the interceptor chain. - /// - Returns: Raw HTTP response produced by the current interceptor or a later step in the chain. + /// - context: 현재 요청 실행 상태입니다. + /// - next: 인터셉터 체인을 이어주는 클로저입니다. + /// - Returns: 현재 인터셉터 또는 이후 단계에서 생성된 원시 HTTP 응답입니다. func intercept( context: NXRequestExecutionContext, next: @escaping @Sendable (NXRequestExecutionContext) async throws -> NXRawResponse ) async throws -> NXRawResponse } -/// Snapshot of the current request execution state exposed to interceptors. +/// 인터셉터에게 노출되는 현재 요청 실행 상태의 스냅샷입니다. /// -/// `NXRequestExecutionContext` gives interceptors access to the prepared request, request identifier, retry attempt number, and custom metadata. +/// `NXRequestExecutionContext`는 준비된 요청, 요청 식별자, 재시도 시도 번호, 커스텀 메타데이터를 인터셉터에서 조회할 수 있게 합니다. public struct NXRequestExecutionContext: Sendable { - /// Request currently being executed. + /// 현재 실행 중인 요청입니다. public let request: URLRequest - /// Stable identifier shared by all attempts of the same logical request. + /// 동일한 논리적 요청의 모든 시도에서 공유되는 안정적인 식별자입니다. public let requestIdentifier: UUID - /// Current attempt number starting from `1`. + /// 현재 시도 번호(`1`부터 시작)입니다. public let attemptNumber: Int - /// Custom string metadata attached to the request. + /// 요청에 붙는 커스텀 문자열 메타데이터입니다. public let userInfo: [String: String] let specification: RequestSpec let clientConfiguration: NXClientConfiguration let authRefreshCoordinator: NXAuthRefreshCoordinator - /// Returns a copy of the context with a different request value. + /// 다른 요청 값으로 만든 컨텍스트 사본을 반환합니다. /// - /// - Parameter request: Replacement request to use for the remaining chain. Its `httpMethod` must equal the configured request method. - /// - Returns: A new execution context with the updated request. + /// - Parameter request: 이후 체인에서 사용할 대체 요청입니다. `httpMethod`는 설정된 요청 메서드와 같아야 합니다. + /// - Returns: 업데이트된 요청을 담은 새 실행 컨텍스트입니다. /// - /// Passing a request with a different or missing `httpMethod` ends execution with ``NXError/invalidRequest(_:)`` before later interceptors, logging, caching, or transport. + /// 다른 값이거나 누락된 `httpMethod`를 가진 요청을 전달하면, 이후 인터셉터/로깅/캐시/트랜스포트 전에 ``NXError/invalidRequest(_:)``로 실행이 종료됩니다. public func replacingRequest(_ request: URLRequest) -> Self { Self( request: request, diff --git a/Sources/Public/NXRequestBuilder.swift b/Sources/Public/NXRequestBuilder.swift index b5d4315..ff310c0 100644 --- a/Sources/Public/NXRequestBuilder.swift +++ b/Sources/Public/NXRequestBuilder.swift @@ -7,11 +7,11 @@ import Foundation -/// Value-semantic builder for configuring and sending HTTP requests. +/// HTTP 요청 구성과 전송을 값 의미론으로 다루는 빌더입니다. /// -/// ## Overview +/// ## 개요 /// -/// Use `NXRequestBuilder` to inspect the prepared request, receive an `NXRawResponse` with `send()`, or decode a response with `send(as:)`. `send()` is the only raw-response execution API. +/// `NXRequestBuilder`를 사용해 준비된 요청을 확인하고 `send()`로 `NXRawResponse`를 받거나 `send(as:)`로 응답을 디코딩할 수 있습니다. 원시 응답 실행 API는 `send()`뿐입니다. /// /// ```swift /// import Foundation @@ -50,73 +50,73 @@ public struct NXRequestBuilder: Sendable { self.requestSpec = requestSpec } - /// Appends a query item to the request URL. + /// 요청 URL에 쿼리 항목을 추가합니다. /// /// - Parameters: - /// - key: Query item name. - /// - value: Query item value converted with `String(describing:)`. - /// - Returns: Updated request builder. + /// - key: 쿼리 항목 이름입니다. + /// - value: `String(describing:)`로 변환한 쿼리 항목 값입니다. + /// - Returns: 업데이트된 요청 빌더입니다. public func query(_ key: String, _ value: some CustomStringConvertible) -> Self { modifying { requestSpec in requestSpec.queryItems.append(URLQueryItem(name: key, value: String(describing: value))) } } - /// Sets or replaces a single HTTP header. + /// 단일 HTTP 헤더를 설정하거나 교체합니다. /// /// - Parameters: - /// - key: Header field name. - /// - value: Header field value. - /// - Returns: Updated request builder. + /// - key: 헤더 필드 이름입니다. + /// - value: 헤더 필드 값입니다. + /// - Returns: 업데이트된 요청 빌더입니다. public func header(_ key: String, _ value: String) -> Self { modifying { requestSpec in requestSpec.headers[key] = value } } - /// Merges multiple HTTP headers into the request. + /// 요청에 여러 HTTP 헤더를 병합합니다. /// - /// - Parameter values: Header field names and values to add. Existing keys are overwritten by incoming values. - /// - Returns: Updated request builder. + /// - Parameter values: 추가할 헤더 필드 이름/값입니다. 기존 키는 새 값으로 덮어씁니다. + /// - Returns: 업데이트된 요청 빌더입니다. public func headers(_ values: [String: String]) -> Self { modifying { requestSpec in requestSpec.headers.merge(values) { _, newValue in newValue } } } - /// Sets the `Accept` header. + /// `Accept` 헤더를 설정합니다. /// - /// - Parameter value: Media type value for the `Accept` header. - /// - Returns: Updated request builder. + /// - Parameter value: `Accept` 헤더의 미디어 타입 값입니다. + /// - Returns: 업데이트된 요청 빌더입니다. public func accept(_ value: String) -> Self { header("Accept", value) } - /// Marks the request as requiring authentication through the configured auth token provider. + /// 설정된 인증 토큰 공급자를 통해 인증이 필요함을 표시합니다. /// - /// - Returns: Updated request builder. + /// - Returns: 업데이트된 요청 빌더입니다. public func authorized() -> Self { modifying { requestSpec in requestSpec.authRequirement = .required } } - /// Sets the per-request timeout interval. + /// 요청별 타임아웃 간격을 설정합니다. /// - /// - Parameter seconds: Timeout interval in seconds. Negative values are clamped to `0`. - /// - Returns: Updated request builder. + /// - Parameter seconds: 타임아웃 간격(초). 음수 값은 `0`으로 보정됩니다. + /// - Returns: 업데이트된 요청 빌더입니다. public func timeout(_ seconds: TimeInterval) -> Self { modifying { requestSpec in requestSpec.timeout = max(0, seconds) } } - /// Encodes an `Encodable` value as JSON and sets the `Content-Type` header. + /// `Encodable` 값을 JSON으로 인코딩하고 `Content-Type` 헤더를 설정합니다. /// /// - Parameters: - /// - value: Value to encode into the request body. - /// - encoder: Encoder to use. When omitted, the client configuration encoder is used. - /// - Returns: Updated request builder. + /// - value: 요청 본문에 인코딩할 값입니다. + /// - encoder: 사용할 인코더입니다. 생략하면 클라이언트 설정의 인코더를 사용합니다. + /// - Returns: 업데이트된 요청 빌더입니다. public func json(_ value: T, encoder: JSONEncoder? = nil) throws -> Self { let selectedEncoder = encoder ?? clientConfiguration.encoder let encodedValue = try selectedEncoder.encode(value) @@ -127,36 +127,36 @@ public struct NXRequestBuilder: Sendable { } } - /// Sets raw request body data. + /// 원시 요청 본문 데이터를 설정합니다. /// - /// - Parameter data: Data to place in the HTTP body. - /// - Returns: Updated request builder. + /// - Parameter data: HTTP 본문에 넣을 데이터입니다. + /// - Returns: 업데이트된 요청 빌더입니다. public func body(_ data: Data) -> Self { modifying { requestSpec in requestSpec.body = .data(data) } } - /// Sets or replaces the `Content-Type` header. + /// `Content-Type` 헤더를 설정하거나 교체합니다. /// - /// - Parameter value: Media type value for the `Content-Type` header. - /// - Returns: Updated request builder. + /// - Parameter value: `Content-Type` 헤더의 미디어 타입 값입니다. + /// - Returns: 업데이트된 요청 빌더입니다. public func contentType(_ value: String) -> Self { modifying { requestSpec in requestSpec.headers["Content-Type"] = value } } - /// Applies retry behavior to the request. + /// 요청에 재시도 정책을 적용합니다. /// /// - Parameters: - /// - maxAttempts: Maximum number of attempts including the initial request. Defaults to `3`. - /// - backoff: Delay strategy used between retry attempts. - /// - retryableStatusCodes: Status codes that should trigger a retry. - /// - allowing: Additional HTTP methods that can retry alongside the default idempotent methods. - /// - maximumServerDelay: Upper limit applied to a server-provided retry delay. - /// - jitter: Randomization applied to local backoff delays. - /// - Returns: Updated request builder. + /// - maxAttempts: 초기 요청을 포함한 최대 시도 횟수입니다. 기본값은 `3`입니다. + /// - backoff: 재시도 간 사용되는 지연 전략입니다. + /// - retryableStatusCodes: 재시도를 유발할 상태 코드입니다. + /// - allowing: 기본 멱등 메서드와 함께 재시도를 허용할 추가 HTTP 메서드입니다. + /// - maximumServerDelay: 서버 제공 재시도 지연에 적용되는 상한값입니다. + /// - jitter: 로컬 백오프 지연에 적용되는 무작위화입니다. + /// - Returns: 업데이트된 요청 빌더입니다. public func retry( maxAttempts: Int = 3, backoff: NXRetryBackoff = .fixed(0), @@ -177,36 +177,36 @@ public struct NXRequestBuilder: Sendable { } } - /// Applies a response validation policy to the request. + /// 요청에 응답 유효성 정책을 적용합니다. /// - /// - Parameter policy: Validation rule used after the transport returns a response. - /// - Returns: Updated request builder. + /// - Parameter policy: 전송 계층이 응답을 반환한 뒤 적용할 유효성 검사 규칙입니다. + /// - Returns: 업데이트된 요청 빌더입니다. public func validate(_ policy: NXValidationPolicy) -> Self { modifying { requestSpec in requestSpec.validationPolicy = policy } } - /// Appends a request-scoped interceptor. + /// 요청 단위 인터셉터를 추가합니다. /// - /// - Parameter interceptor: Interceptor to insert after global interceptors. - /// - Returns: Updated request builder. + /// - Parameter interceptor: 전역 인터셉터 뒤에 추가할 인터셉터입니다. + /// - Returns: 업데이트된 요청 빌더입니다. public func intercept(_ interceptor: any NXHTTPInterceptor) -> Self { modifying { requestSpec in requestSpec.requestInterceptors.append(interceptor) } } - /// Assembles the final `URLRequest` without sending it. + /// 최종 `URLRequest`를 전송하지 않고 조립합니다. /// - /// - Returns: Fully prepared URL request. + /// - Returns: 완전히 준비된 URLRequest입니다. public func preparedURLRequest() async throws -> URLRequest { try NXRequestAssembler.assemble(clientConfiguration: clientConfiguration, requestSpec: requestSpec) } - /// Sends the request and returns the raw HTTP response. + /// 요청을 전송하고 원시 HTTP 응답을 반환합니다. /// - /// - Returns: Raw response data and HTTP metadata. + /// - Returns: 원시 응답 데이터와 HTTP 메타데이터입니다. public func send() async throws -> NXRawResponse { try await NXRequestExecutor.executeRaw( clientConfiguration: clientConfiguration, @@ -216,25 +216,25 @@ public struct NXRequestBuilder: Sendable { ) } - /// Sends the request and decodes the response into the contextual response type. + /// 요청을 전송하고 호출 문맥에서 결정된 응답 타입으로 응답을 디코딩합니다. /// - /// - Returns: Decoded response value for `Response`. + /// - Returns: `Response`에 대한 디코딩된 응답 값입니다. public func send() async throws -> Response { try await send(as: Response.self) } - /// Sends the request and decodes the response into the given response type. + /// 요청을 전송하고 지정한 응답 타입으로 디코딩합니다. /// - /// - Parameter type: Response type to decode when the request succeeds. - /// - Returns: Decoded response value for `Response`. + /// - Parameter type: 요청이 성공했을 때 디코딩할 응답 타입입니다. + /// - Returns: `Response`에 대한 디코딩된 응답 값입니다. public func send(as type: Response.Type) async throws -> Response { try await decoded(type) } - /// Converts the builder into a typed builder that decodes the response. + /// 응답을 디코딩할 타입 지정 빌더로 변환합니다. /// - /// - Parameter type: Response type to decode when the request succeeds. - /// - Returns: Typed request builder for `Response`. + /// - Parameter type: 요청이 성공했을 때 디코딩할 응답 타입입니다. + /// - Returns: `Response`용 타입 지정 요청 빌더입니다. @available(*, deprecated, message: "Use send(as:) or a contextual send().") public func `as`(_ type: Response.Type) -> NXTypedRequestBuilder { NXTypedRequestBuilder(requestBuilder: self) diff --git a/Sources/Public/NXTypedRequestBuilder.swift b/Sources/Public/NXTypedRequestBuilder.swift index 861bf87..b9b69da 100644 --- a/Sources/Public/NXTypedRequestBuilder.swift +++ b/Sources/Public/NXTypedRequestBuilder.swift @@ -7,11 +7,11 @@ import Foundation -/// Value-semantic builder for configuring requests that decode into a specific response type. +/// 특정 응답 타입으로 디코딩하는 요청을 구성하는 값 의미론 빌더입니다. /// -/// ## Overview +/// ## 개요 /// -/// `NXTypedRequestBuilder` is retained for `NXEndpoint` configuration compatibility. It does not provide raw-response execution. For method-based requests, use `NXRequestBuilder.send(as:)`. +/// `NXTypedRequestBuilder`는 `NXEndpoint` 구성 호환성을 위해 유지됩니다. 원시 응답 실행 API는 제공하지 않습니다. 메서드 기반 요청은 `NXRequestBuilder.send(as:)`를 사용하세요. /// /// ```swift /// import Foundation @@ -31,93 +31,93 @@ import Foundation public struct NXTypedRequestBuilder: Sendable where Response: Decodable { let requestBuilder: NXRequestBuilder - /// Appends a query item to the request URL. + /// 요청 URL에 쿼리 항목을 추가합니다. /// /// - Parameters: - /// - key: Query item name. - /// - value: Query item value converted with `String(describing:)`. - /// - Returns: Updated typed request builder. + /// - key: 쿼리 항목 이름입니다. + /// - value: `String(describing:)`로 변환한 쿼리 항목 값입니다. + /// - Returns: 업데이트된 타입 지정 요청 빌더입니다. public func query(_ key: String, _ value: some CustomStringConvertible) -> Self { Self(requestBuilder: requestBuilder.query(key, value)) } - /// Sets or replaces a single HTTP header. + /// 단일 HTTP 헤더를 설정하거나 교체합니다. /// /// - Parameters: - /// - key: Header field name. - /// - value: Header field value. - /// - Returns: Updated typed request builder. + /// - key: 헤더 필드 이름입니다. + /// - value: 헤더 필드 값입니다. + /// - Returns: 업데이트된 타입 지정 요청 빌더입니다. public func header(_ key: String, _ value: String) -> Self { Self(requestBuilder: requestBuilder.header(key, value)) } - /// Merges multiple HTTP headers into the request. + /// 요청에 여러 HTTP 헤더를 병합합니다. /// - /// - Parameter values: Header field names and values to add. Existing keys are overwritten by incoming values. - /// - Returns: Updated typed request builder. + /// - Parameter values: 추가할 헤더 필드 이름/값입니다. 기존 키는 새 값으로 덮어씁니다. + /// - Returns: 업데이트된 타입 지정 요청 빌더입니다. public func headers(_ values: [String: String]) -> Self { Self(requestBuilder: requestBuilder.headers(values)) } - /// Sets the `Accept` header. + /// `Accept` 헤더를 설정합니다. /// - /// - Parameter value: Media type value for the `Accept` header. - /// - Returns: Updated typed request builder. + /// - Parameter value: `Accept` 헤더의 미디어 타입 값입니다. + /// - Returns: 업데이트된 타입 지정 요청 빌더입니다. public func accept(_ value: String) -> Self { Self(requestBuilder: requestBuilder.accept(value)) } - /// Marks the request as requiring authentication through the configured auth token provider. + /// 설정된 인증 토큰 공급자를 통해 인증이 필요함을 표시합니다. /// - /// - Returns: Updated typed request builder. + /// - Returns: 업데이트된 타입 지정 요청 빌더입니다. public func authorized() -> Self { Self(requestBuilder: requestBuilder.authorized()) } - /// Sets the per-request timeout interval. + /// 요청별 타임아웃 간격을 설정합니다. /// - /// - Parameter seconds: Timeout interval in seconds. Negative values are clamped to `0`. - /// - Returns: Updated typed request builder. + /// - Parameter seconds: 타임아웃 간격(초). 음수 값은 `0`으로 보정됩니다. + /// - Returns: 업데이트된 타입 지정 요청 빌더입니다. public func timeout(_ seconds: TimeInterval) -> Self { Self(requestBuilder: requestBuilder.timeout(seconds)) } - /// Encodes an `Encodable` value as JSON and sets the `Content-Type` header. + /// `Encodable` 값을 JSON으로 인코딩하고 `Content-Type` 헤더를 설정합니다. /// /// - Parameters: - /// - value: Value to encode into the request body. - /// - encoder: Encoder to use. When omitted, the client configuration encoder is used. - /// - Returns: Updated typed request builder. + /// - value: 요청 본문에 인코딩할 값입니다. + /// - encoder: 사용할 인코더입니다. 생략하면 클라이언트 설정의 인코더를 사용합니다. + /// - Returns: 업데이트된 타입 지정 요청 빌더입니다. public func json(_ value: T, encoder: JSONEncoder? = nil) throws -> Self { try Self(requestBuilder: requestBuilder.json(value, encoder: encoder)) } - /// Sets raw request body data. + /// 원시 요청 본문 데이터를 설정합니다. /// - /// - Parameter data: Data to place in the HTTP body. - /// - Returns: Updated typed request builder. + /// - Parameter data: HTTP 본문에 넣을 데이터입니다. + /// - Returns: 업데이트된 타입 지정 요청 빌더입니다. public func body(_ data: Data) -> Self { Self(requestBuilder: requestBuilder.body(data)) } - /// Sets or replaces the `Content-Type` header. + /// `Content-Type` 헤더를 설정하거나 교체합니다. /// - /// - Parameter value: Media type value for the `Content-Type` header. - /// - Returns: Updated typed request builder. + /// - Parameter value: `Content-Type` 헤더의 미디어 타입 값입니다. + /// - Returns: 업데이트된 타입 지정 요청 빌더입니다. public func contentType(_ value: String) -> Self { Self(requestBuilder: requestBuilder.contentType(value)) } - /// Applies retry behavior to the request. + /// 요청에 재시도 정책을 적용합니다. /// /// - Parameters: - /// - maxAttempts: Maximum number of attempts including the initial request. Defaults to `3`. - /// - backoff: Delay strategy used between retry attempts. - /// - retryableStatusCodes: Status codes that should trigger a retry. - /// - allowing: Additional HTTP methods that can retry alongside the default idempotent methods. - /// - maximumServerDelay: Upper limit applied to a server-provided retry delay. - /// - jitter: Randomization applied to local backoff delays. - /// - Returns: Updated typed request builder. + /// - maxAttempts: 초기 요청을 포함한 최대 시도 횟수입니다. 기본값은 `3`입니다. + /// - backoff: 재시도 간 사용되는 지연 전략입니다. + /// - retryableStatusCodes: 재시도를 유발할 상태 코드입니다. + /// - allowing: 기본 멱등 메서드와 함께 재시도를 허용할 추가 HTTP 메서드입니다. + /// - maximumServerDelay: 서버 제공 재시도 지연에 적용되는 상한값입니다. + /// - jitter: 로컬 백오프 지연에 적용되는 무작위화입니다. + /// - Returns: 업데이트된 타입 지정 요청 빌더입니다. public func retry( maxAttempts: Int = 3, backoff: NXRetryBackoff = .fixed(0), @@ -136,33 +136,33 @@ public struct NXTypedRequestBuilder: Sendable where Response: Decodabl )) } - /// Applies a response validation policy to the request. + /// 요청에 응답 유효성 정책을 적용합니다. /// - /// - Parameter policy: Validation rule used after the transport returns a response. - /// - Returns: Updated typed request builder. + /// - Parameter policy: 전송 계층이 응답을 반환한 뒤 적용할 유효성 검사 규칙입니다. + /// - Returns: 업데이트된 타입 지정 요청 빌더입니다. public func validate(_ policy: NXValidationPolicy) -> Self { Self(requestBuilder: requestBuilder.validate(policy)) } - /// Appends a request-scoped interceptor. + /// 요청 단위 인터셉터를 추가합니다. /// - /// - Parameter interceptor: Interceptor to insert after global interceptors. - /// - Returns: Updated typed request builder. + /// - Parameter interceptor: 전역 인터셉터 뒤에 추가할 인터셉터입니다. + /// - Returns: 업데이트된 타입 지정 요청 빌더입니다. public func intercept(_ interceptor: any NXHTTPInterceptor) -> Self { Self(requestBuilder: requestBuilder.intercept(interceptor)) } - /// Assembles the final `URLRequest` without sending it. + /// 최종 `URLRequest`를 전송하지 않고 조립합니다. /// - /// - Returns: Fully prepared URL request. + /// - Returns: 완전히 준비된 URLRequest입니다. public func preparedURLRequest() async throws -> URLRequest { try await requestBuilder.preparedURLRequest() } - /// Sends the request and decodes the response into `Response`. + /// 요청을 전송하고 `Response`로 응답을 디코딩합니다. /// - /// - Returns: Decoded response value. - /// - Throws: `NXError` if the request fails or decoding fails. + /// - Returns: 디코딩된 응답 값입니다. + /// - Throws: 요청 실패 또는 디코딩 실패 시 `NXError`가 발생합니다. public func send() async throws -> Response { try await requestBuilder.send(as: Response.self) } diff --git a/Sources/Runtime/NXAuthInterceptor.swift b/Sources/Runtime/NXAuthInterceptor.swift index 6012834..7acc727 100644 --- a/Sources/Runtime/NXAuthInterceptor.swift +++ b/Sources/Runtime/NXAuthInterceptor.swift @@ -8,6 +8,7 @@ import Foundation struct NXAuthInterceptor: NXHTTPInterceptor { + // 인증이 필요한 요청의 access token 적용과 갱신을 조정하는 interceptor func intercept( context: NXRequestExecutionContext, next: @escaping @Sendable (NXRequestExecutionContext) async throws -> NXRawResponse diff --git a/Sources/Runtime/NXAuthRefreshCoordinator.swift b/Sources/Runtime/NXAuthRefreshCoordinator.swift index df62ee0..9327871 100644 --- a/Sources/Runtime/NXAuthRefreshCoordinator.swift +++ b/Sources/Runtime/NXAuthRefreshCoordinator.swift @@ -8,7 +8,9 @@ import Foundation actor NXAuthRefreshCoordinator { + // 현재 client에서 공유하는 진행 중 access token 갱신 작업 private var inFlightRefresh: InFlightRefresh? + // 갱신 작업별 결과 대기 continuation을 보관하는 저장소 private var waiters: [UUID: [UUID: CheckedContinuation]] = [:] private let onWaiterRegistered: (@Sendable () -> Void)? private let onRefreshCompleted: (@Sendable () -> Void)? @@ -21,6 +23,7 @@ actor NXAuthRefreshCoordinator { self.onRefreshCompleted = onRefreshCompleted } + // access token 갱신 작업을 공유하는 메서드 func refreshAccessToken( using provider: any NXAuthTokenProvider, logger: any NXLogger, @@ -62,6 +65,7 @@ actor NXAuthRefreshCoordinator { return try await wait(for: inFlightRefresh) } + // 호출자의 갱신 결과 대기를 등록하는 메서드 private func wait(for inFlightRefresh: InFlightRefresh) async throws -> String? { let waiterIdentifier = UUID() @@ -125,6 +129,7 @@ actor NXAuthRefreshCoordinator { continuation.resume(throwing: CancellationError()) } + // 갱신 결과를 대기 continuation에 전달하는 메서드 private func complete( _ inFlightRefresh: InFlightRefresh, with result: Result @@ -134,6 +139,7 @@ actor NXAuthRefreshCoordinator { } self.inFlightRefresh = nil + // 완료된 갱신 작업의 결과 대기 continuation 목록 let continuations = waiters.removeValue(forKey: inFlightRefresh.identifier).map { Array($0.values) } ?? [] diff --git a/Sources/Runtime/NXInterceptorChain.swift b/Sources/Runtime/NXInterceptorChain.swift index 41799de..ad165b4 100644 --- a/Sources/Runtime/NXInterceptorChain.swift +++ b/Sources/Runtime/NXInterceptorChain.swift @@ -8,11 +8,13 @@ import Foundation enum NXInterceptorChain { + // interceptor와 transport를 실행하는 메서드 static func execute( context: NXRequestExecutionContext, interceptors: [any NXHTTPInterceptor], transport: any NXHTTPTransport ) async throws -> NXRawResponse { + // 다음 실행 단계에 요청 정보를 전달하는 중첩 메서드 @Sendable func proceed(index: Int, context: NXRequestExecutionContext) async throws -> NXRawResponse { guard context.request.httpMethod == context.specification.method.rawValue else { throw NXError.invalidRequest("Interceptor request method must match the request specification method.") diff --git a/Sources/Runtime/NXLoggerInterceptor.swift b/Sources/Runtime/NXLoggerInterceptor.swift index ce5d669..6d0c87d 100644 --- a/Sources/Runtime/NXLoggerInterceptor.swift +++ b/Sources/Runtime/NXLoggerInterceptor.swift @@ -8,6 +8,7 @@ import Foundation struct NXLoggerInterceptor: NXHTTPInterceptor { + // 요청 생명주기 로그를 기록하는 interceptor func intercept( context: NXRequestExecutionContext, next: @escaping @Sendable (NXRequestExecutionContext) async throws -> NXRawResponse diff --git a/Sources/Runtime/NXRequestExecutor.swift b/Sources/Runtime/NXRequestExecutor.swift index 264e1f7..da81e89 100644 --- a/Sources/Runtime/NXRequestExecutor.swift +++ b/Sources/Runtime/NXRequestExecutor.swift @@ -8,6 +8,7 @@ import Foundation enum NXRequestExecutor { + // 요청을 조립하고 원시 응답을 검증하는 실행 경계 static func executeRaw( clientConfiguration: NXClientConfiguration, responseCacheStore: NXResponseCacheStore?, @@ -52,6 +53,7 @@ enum NXRequestExecutor { } } + // 원시 응답을 지정한 타입으로 디코딩하는 실행 경계 static func executeDecode( clientConfiguration: NXClientConfiguration, responseCacheStore: NXResponseCacheStore?, @@ -77,6 +79,7 @@ enum NXRequestExecutor { } } + // 실행 interceptor를 구성하는 메서드 private static func runtimeInterceptors( clientConfiguration: NXClientConfiguration, responseCacheStore: NXResponseCacheStore?, diff --git a/Sources/Runtime/NXResponseCacheInterceptor.swift b/Sources/Runtime/NXResponseCacheInterceptor.swift index 5ebe28b..1900f8b 100644 --- a/Sources/Runtime/NXResponseCacheInterceptor.swift +++ b/Sources/Runtime/NXResponseCacheInterceptor.swift @@ -11,10 +11,12 @@ struct NXResponseCacheInterceptor: NXHTTPInterceptor { let cache: NXCache let store: NXResponseCacheStore + // cache 적용과 저장소 호출 조정 역할 func intercept( context: NXRequestExecutionContext, next: @escaping @Sendable (NXRequestExecutionContext) async throws -> NXRawResponse ) async throws -> NXRawResponse { + // cache 저장소에 전달할 만료 시간과 재검증 여부 let cachePolicy: (ttl: TimeInterval, revalidatesExpiredResponse: Bool)? = switch cache { case let .memory(ttl): (ttl, false) @@ -63,6 +65,7 @@ struct NXResponseCacheInterceptor: NXHTTPInterceptor { ) } + // 재검증 결과로 반환할 원시 응답 구성 역할 private static func revalidatedResponse( rawResponse: NXRawResponse, revalidationContext: NXCacheRevalidationContext? diff --git a/Sources/Runtime/NXResponseCacheStore.swift b/Sources/Runtime/NXResponseCacheStore.swift index b8a5097..32bba24 100644 --- a/Sources/Runtime/NXResponseCacheStore.swift +++ b/Sources/Runtime/NXResponseCacheStore.swift @@ -30,9 +30,12 @@ struct NXCacheRevalidationContext: Sendable { } actor NXResponseCacheStore { + // cache 키별 응답과 만료 시각을 보관하는 저장소 private var responses: [NXRequestCacheKey: CachedResponse] = [:] + // cache 키별 진행 중 요청 작업을 보관하는 저장소 private var inFlightTasks: [NXRequestCacheKey: Task] = [:] + // cache 조회와 저장을 조정하는 메서드 func response( for key: NXRequestCacheKey, ttl: TimeInterval, @@ -41,6 +44,7 @@ actor NXResponseCacheStore { load: @escaping @Sendable (NXCacheRevalidationContext?) async throws -> NXRawResponse ) async throws -> NXRawResponse { let now = Date() + // 만료 응답과 validator를 전달하는 재검증 문맥 var revalidationContext: NXCacheRevalidationContext? if let cachedResponse = responses[key] { diff --git a/Sources/Runtime/NXResponsePipeline.swift b/Sources/Runtime/NXResponsePipeline.swift index 6392773..1ba8b03 100644 --- a/Sources/Runtime/NXResponsePipeline.swift +++ b/Sources/Runtime/NXResponsePipeline.swift @@ -40,6 +40,7 @@ enum NXResponsePipeline { } } + // 발생한 오류를 NXError로 변환하는 메서드 static func map(error: any Error) -> NXError { if let nxError = error as? NXError { return nxError diff --git a/Sources/Runtime/NXRetryInterceptor.swift b/Sources/Runtime/NXRetryInterceptor.swift index 8a7de88..4191966 100644 --- a/Sources/Runtime/NXRetryInterceptor.swift +++ b/Sources/Runtime/NXRetryInterceptor.swift @@ -14,6 +14,7 @@ struct NXRetryInterceptor: NXHTTPInterceptor { self.dependencies = dependencies } + // 재시도 정책에 따라 요청 실행을 조정하는 interceptor func intercept( context: NXRequestExecutionContext, next: @escaping @Sendable (NXRequestExecutionContext) async throws -> NXRawResponse diff --git a/Sources/Runtime/NXURLSessionTaskMetricsDelegate.swift b/Sources/Runtime/NXURLSessionTaskMetricsDelegate.swift index 23f26e9..466631d 100644 --- a/Sources/Runtime/NXURLSessionTaskMetricsDelegate.swift +++ b/Sources/Runtime/NXURLSessionTaskMetricsDelegate.swift @@ -14,6 +14,7 @@ final class NXURLSessionTaskMetricsDelegate: NSObject, URLSessionTaskDelegate { self.metricsObserver = metricsObserver } + // URLSession task 측정값을 Nexa snapshot으로 전달하는 delegate 메서드 func urlSession( _ session: URLSession, task: URLSessionTask, @@ -33,6 +34,7 @@ private struct NXURLSessionTaskMetricsSource: NXNetworkMetricsSource { let redirectCount: Int let transactions: [NXURLSessionTaskTransactionMetricsSource] + // URLSession task 측정값에서 전송 snapshot source를 구성하는 initializer init(metrics: URLSessionTaskMetrics) { taskInterval = metrics.taskInterval redirectCount = metrics.redirectCount @@ -51,6 +53,7 @@ private struct NXURLSessionTaskTransactionMetricsSource: NXNetworkTransactionMet let responseStartDate: Date? let isConnectionReused: Bool + // URLSession transaction 측정값에서 transaction snapshot source를 구성하는 initializer init(metrics: URLSessionTaskTransactionMetrics) { domainLookupStartDate = metrics.domainLookupStartDate domainLookupEndDate = metrics.domainLookupEndDate diff --git a/Sources/Runtime/NXURLSessionTransport.swift b/Sources/Runtime/NXURLSessionTransport.swift index 2ce07ef..14d407e 100644 --- a/Sources/Runtime/NXURLSessionTransport.swift +++ b/Sources/Runtime/NXURLSessionTransport.swift @@ -7,23 +7,23 @@ import Foundation -/// Default HTTP transport backed by `URLSession`. +/// `URLSession` 기반 기본 HTTP 전송 계층입니다. public struct NXURLSessionTransport: NXHTTPTransport, Sendable { let urlSession: URLSession let metricsObserver: (any NXNetworkMetricsObserver)? - /// Creates a transport that sends requests through a `URLSession` without collecting metrics. + /// 메트릭을 수집하지 않고 `URLSession`으로 요청을 전송하는 전송 계층을 생성합니다. /// - /// - Parameter urlSession: Session used to perform requests. + /// - Parameter urlSession: 요청을 실행할 세션입니다. public init(urlSession: URLSession = .shared) { self.urlSession = urlSession metricsObserver = nil } - /// Creates a transport that sends requests through a `URLSession` and records metrics snapshots. + /// `URLSession`으로 요청을 전송하고 메트릭 스냅샷을 기록하는 전송 계층을 생성합니다. /// - /// - Parameter urlSession: Session used to perform requests. - /// - Parameter metricsObserver: Observer that receives URLSession metrics snapshots. + /// - Parameter urlSession: 요청을 실행할 세션입니다. + /// - Parameter metricsObserver: `URLSession` 메트릭 스냅샷을 수신하는 옵저버입니다. public init( urlSession: URLSession = .shared, metricsObserver: any NXNetworkMetricsObserver @@ -32,10 +32,10 @@ public struct NXURLSessionTransport: NXHTTPTransport, Sendable { self.metricsObserver = metricsObserver } - /// Sends a prepared request through the underlying `URLSession`. + /// 준비된 요청을 내부 `URLSession`으로 전송합니다. /// - /// - Parameter request: Prepared request to execute. - /// - Returns: Raw response data and HTTP metadata. + /// - Parameter request: 실행할 준비된 요청입니다. + /// - Returns: 원시 응답 데이터와 HTTP 메타데이터입니다. public func send(_ request: URLRequest) async throws -> NXRawResponse { let metricsDelegate = metricsObserver.map(NXURLSessionTaskMetricsDelegate.init) let (data, response) = try await urlSession.data(for: request, delegate: metricsDelegate)