This repository has been archived by the owner on Apr 17, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1,605 changed files
with
79,817 additions
and
108,056 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
6.2.0 | ||
7.1.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// swift-tools-version:5.1 | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "OpenAPITransport", | ||
platforms: [ | ||
.iOS(.v13), | ||
.macOS(.v10_15) | ||
], | ||
products: [ | ||
.library( | ||
name: "OpenAPITransport", | ||
targets: ["OpenAPITransport"] | ||
), | ||
], | ||
dependencies: [], | ||
targets: [ | ||
.target( | ||
name: "OpenAPITransport", | ||
dependencies: [], | ||
path: "Sources" | ||
), | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,306 @@ | ||
// OpenAPITransport.swift | ||
// | ||
// Generated by openapi-generator | ||
// https://openapi-generator.tech | ||
|
||
import Foundation | ||
import Combine | ||
|
||
// MARK: - OpenAPITransport | ||
|
||
public protocol OpenAPITransport: AnyObject { | ||
var baseURL: URL? { get } | ||
|
||
func send(request: URLRequest) -> AnyPublisher<OpenAPITransportResponse, OpenAPITransportError> | ||
|
||
func cancelAll() | ||
} | ||
|
||
public struct OpenAPITransportResponse { | ||
public let data: Data | ||
public let statusCode: Int | ||
|
||
public init(data: Data, statusCode: Int) { | ||
self.data = data | ||
self.statusCode = statusCode | ||
} | ||
} | ||
|
||
public struct OpenAPITransportError: Error, CustomStringConvertible, LocalizedError { | ||
public let statusCode: Int | ||
public let description: String | ||
public let errorDescription: String? | ||
/// It might be source network error | ||
public let nestedError: Error? | ||
/// Data may contain additional reason info (like json payload) | ||
public let data: Data | ||
|
||
public init( | ||
statusCode: Int, | ||
description: String? = nil, | ||
errorDescription: String? = nil, | ||
nestedError: Error? = nil, | ||
data: Data = Data() | ||
) { | ||
self.statusCode = statusCode | ||
self.errorDescription = errorDescription | ||
self.nestedError = nestedError | ||
self.data = data | ||
if let description = description { | ||
self.description = description | ||
} else { | ||
var summary = "OpenAPITransportError with status \(statusCode)" | ||
if let nestedError = nestedError { | ||
summary.append(contentsOf: ", \(nestedError.localizedDescription)") | ||
} | ||
self.description = summary | ||
} | ||
} | ||
} | ||
|
||
// MARK: - Policy | ||
|
||
/// Policy to define whether response is successful or requires authentication | ||
public protocol ResponsePolicy { | ||
func defineState(for request: URLRequest, output: URLSession.DataTaskPublisher.Output) -> AnyPublisher<ResponseState, Never> | ||
} | ||
|
||
public enum ResponseState { | ||
/// Return success to client | ||
case success | ||
/// Return error to client | ||
case failure | ||
/// Repeat request | ||
case retry | ||
} | ||
|
||
// MARK: - Interceptor | ||
|
||
/// Define how to customize URL request before network call | ||
public protocol Interceptor { | ||
/// Customize request before performing. Add headers or encrypt body for example. | ||
func intercept(request: URLRequest) -> AnyPublisher<URLRequest, OpenAPITransportError> | ||
|
||
/// Customize response before handling. Decrypt body for example. | ||
func intercept(output: URLSession.DataTaskPublisher.Output) -> AnyPublisher<URLSession.DataTaskPublisher.Output, OpenAPITransportError> | ||
} | ||
|
||
// MARK: - Transport delegate | ||
|
||
public protocol OpenAPITransportDelegate: AnyObject { | ||
func willStart(request: URLRequest) | ||
|
||
func didFinish(request: URLRequest, response: HTTPURLResponse?, data: Data) | ||
|
||
func didFinish(request: URLRequest, error: Error) | ||
} | ||
|
||
// MARK: - Implementation | ||
|
||
open class URLSessionOpenAPITransport: OpenAPITransport { | ||
public struct Config { | ||
public var baseURL: URL? | ||
public var session: URLSession | ||
public var processor: Interceptor | ||
public var policy: ResponsePolicy | ||
public weak var delegate: OpenAPITransportDelegate? | ||
|
||
public init( | ||
baseURL: URL? = nil, | ||
session: URLSession = .shared, | ||
processor: Interceptor = DefaultInterceptor(), | ||
policy: ResponsePolicy = DefaultResponsePolicy(), | ||
delegate: OpenAPITransportDelegate? = nil | ||
) { | ||
self.baseURL = baseURL | ||
self.session = session | ||
self.processor = processor | ||
self.policy = policy | ||
self.delegate = delegate | ||
} | ||
} | ||
|
||
private var cancellable = Set<AnyCancellable>() | ||
public var config: Config | ||
public var baseURL: URL? { config.baseURL } | ||
|
||
public init(config: Config = .init()) { | ||
self.config = config | ||
} | ||
|
||
open func send(request: URLRequest) -> AnyPublisher<OpenAPITransportResponse, OpenAPITransportError> { | ||
config.processor | ||
// Add custom headers or refresh token if needed | ||
.intercept(request: request) | ||
.flatMap { request -> AnyPublisher<OpenAPITransportResponse, OpenAPITransportError> in | ||
self.config.delegate?.willStart(request: request) | ||
// Perform network call | ||
return self.config.session.dataTaskPublisher(for: request) | ||
.mapError { | ||
self.config.delegate?.didFinish(request: request, error: $0) | ||
return OpenAPITransportError(statusCode: $0.code.rawValue, description: "Network call finished fails") | ||
} | ||
.flatMap { output in | ||
self.config.processor.intercept(output: output) | ||
} | ||
.flatMap { output -> AnyPublisher<OpenAPITransportResponse, OpenAPITransportError> in | ||
let response = output.response as? HTTPURLResponse | ||
self.config.delegate?.didFinish(request: request, response: response, data: output.data) | ||
return self.config.policy.defineState(for: request, output: output) | ||
.setFailureType(to: OpenAPITransportError.self) | ||
.flatMap { state -> AnyPublisher<OpenAPITransportResponse, OpenAPITransportError> in | ||
switch state { | ||
case .success: | ||
let transportResponse = OpenAPITransportResponse(data: output.data, statusCode: 200) | ||
return Result.success(transportResponse).publisher.eraseToAnyPublisher() | ||
case .retry: | ||
return Fail(error: OpenAPITransportError.retryError).eraseToAnyPublisher() | ||
case .failure: | ||
let code = response?.statusCode ?? OpenAPITransportError.noResponseCode | ||
let transportError = OpenAPITransportError(statusCode: code, data: output.data) | ||
return Fail(error: transportError).eraseToAnyPublisher() | ||
} | ||
}.eraseToAnyPublisher() | ||
} | ||
.eraseToAnyPublisher() | ||
} | ||
.retry(times: 2) { error -> Bool in | ||
return error.statusCode == OpenAPITransportError.retryError.statusCode | ||
}.eraseToAnyPublisher() | ||
} | ||
|
||
open func cancelAll() { | ||
cancellable.removeAll() | ||
} | ||
} | ||
|
||
public final class DefaultInterceptor: Interceptor { | ||
public init() {} | ||
|
||
public func intercept(request: URLRequest) -> AnyPublisher<URLRequest, OpenAPITransportError> { | ||
Just(request) | ||
.setFailureType(to: OpenAPITransportError.self) | ||
.eraseToAnyPublisher() | ||
} | ||
|
||
public func intercept(output: URLSession.DataTaskPublisher.Output) -> AnyPublisher<URLSession.DataTaskPublisher.Output, OpenAPITransportError> { | ||
Just(output) | ||
.setFailureType(to: OpenAPITransportError.self) | ||
.eraseToAnyPublisher() | ||
} | ||
} | ||
|
||
public final class DefaultResponsePolicy: ResponsePolicy { | ||
public init() {} | ||
|
||
public func defineState(for request: URLRequest, output: URLSession.DataTaskPublisher.Output) -> AnyPublisher<ResponseState, Never> { | ||
let state: ResponseState | ||
switch (output.response as? HTTPURLResponse)?.statusCode { | ||
case .some(200...299): state = .success | ||
default: state = .failure | ||
} | ||
return Just(state).eraseToAnyPublisher() | ||
} | ||
} | ||
|
||
/// Custom transport errors. It begins with 6.. not to conflict with HTTP codes | ||
public extension OpenAPITransportError { | ||
static let incorrectAuthenticationCode = 600 | ||
static func incorrectAuthenticationError(_ nestedError: Error? = nil) -> OpenAPITransportError { | ||
OpenAPITransportError( | ||
statusCode: OpenAPITransportError.incorrectAuthenticationCode, | ||
description: "Impossible to add authentication headers to request", | ||
errorDescription: NSLocalizedString( | ||
"Impossible to add authentication headers to request", | ||
comment: "Incorrect authentication" | ||
), | ||
nestedError: nestedError | ||
) | ||
} | ||
|
||
static let failedAuthenticationRefreshCode = 601 | ||
static func failedAuthenticationRefreshError(_ nestedError: Error? = nil) -> OpenAPITransportError { | ||
OpenAPITransportError( | ||
statusCode: OpenAPITransportError.failedAuthenticationRefreshCode, | ||
description: "Error while refreshing authentication", | ||
errorDescription: NSLocalizedString( | ||
"Error while refreshing authentication", | ||
comment: "Failed authentication refresh" | ||
), | ||
nestedError: nestedError | ||
) | ||
} | ||
|
||
static let noResponseCode = 603 | ||
static func noResponseError(_ nestedError: Error? = nil) -> OpenAPITransportError { | ||
OpenAPITransportError( | ||
statusCode: OpenAPITransportError.noResponseCode, | ||
description: "There is no HTTP URL response", | ||
errorDescription: NSLocalizedString( | ||
"There is no HTTP URL response", | ||
comment: "No response" | ||
), | ||
nestedError: nestedError | ||
) | ||
} | ||
|
||
static let badURLCode = 604 | ||
static func badURLError(_ nestedError: Error? = nil) -> OpenAPITransportError { | ||
OpenAPITransportError( | ||
statusCode: OpenAPITransportError.badURLCode, | ||
description: "Request URL cannot be created with given parameters", | ||
errorDescription: NSLocalizedString( | ||
"Request URL cannot be created with given parameters", | ||
comment: "Bad URL" | ||
), | ||
nestedError: nestedError | ||
) | ||
} | ||
|
||
static let invalidResponseMappingCode = 605 | ||
static func invalidResponseMappingError(data: Data) -> OpenAPITransportError { | ||
OpenAPITransportError( | ||
statusCode: OpenAPITransportError.invalidResponseMappingCode, | ||
description: "Response data cannot be expected object scheme", | ||
errorDescription: NSLocalizedString( | ||
"Response data cannot be expected object scheme", | ||
comment: "Invalid response mapping" | ||
), | ||
data: data | ||
) | ||
} | ||
|
||
static let retryErrorCode = 606 | ||
static let retryError = OpenAPITransportError(statusCode: OpenAPITransportError.retryErrorCode) | ||
} | ||
|
||
// MARK: - Private | ||
|
||
private extension Publishers { | ||
struct RetryIf<P: Publisher>: Publisher { | ||
typealias Output = P.Output | ||
typealias Failure = P.Failure | ||
|
||
let publisher: P | ||
let times: Int | ||
let condition: (P.Failure) -> Bool | ||
|
||
func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input { | ||
guard times > 0 else { return publisher.receive(subscriber: subscriber) } | ||
|
||
publisher.catch { (error: P.Failure) -> AnyPublisher<Output, Failure> in | ||
if condition(error) { | ||
return RetryIf(publisher: publisher, times: times - 1, condition: condition).eraseToAnyPublisher() | ||
} else { | ||
return Fail(error: error).eraseToAnyPublisher() | ||
} | ||
}.receive(subscriber: subscriber) | ||
} | ||
} | ||
} | ||
|
||
private extension Publisher { | ||
func retry(times: Int, if condition: @escaping (Failure) -> Bool) -> Publishers.RetryIf<Self> { | ||
Publishers.RetryIf(publisher: self, times: times, condition: condition) | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.