Skip to content
This repository has been archived by the owner on Apr 17, 2024. It is now read-only.

Commit

Permalink
test combine
Browse files Browse the repository at this point in the history
  • Loading branch information
BeryJu committed Dec 18, 2023
1 parent 5ff290e commit fa3c3b5
Show file tree
Hide file tree
Showing 1,605 changed files with 79,817 additions and 108,056 deletions.
1,610 changes: 548 additions & 1,062 deletions .openapi-generator/FILES

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .openapi-generator/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
6.2.0
7.1.0
1 change: 0 additions & 1 deletion Cartfile

This file was deleted.

4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ build:
docker run \
--rm -v ${PWD}:/local \
--user ${UID}:${GID} \
docker.io/openapitools/openapi-generator-cli:v6.2.0 generate \
docker.io/openapitools/openapi-generator-cli:v7.1.0 generate \
-i /local/schema.yml \
-g swift5 \
-g swift-combine \
-o /local \
-c /local/config.yaml
rm -rf ./test
Expand Down
25 changes: 25 additions & 0 deletions OpenAPITransport/Package.swift
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"
),
]
)
306 changes: 306 additions & 0 deletions OpenAPITransport/Sources/OpenAPITransport.swift
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)
}
}
33 changes: 0 additions & 33 deletions Package.swift

This file was deleted.

Loading

0 comments on commit fa3c3b5

Please sign in to comment.