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

test combine #5

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
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.3.0-SNAPSHOT
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.2.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)
}
}
19 changes: 12 additions & 7 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PackageDescription
let package = Package(
name: "authentikClient",
platforms: [
.iOS(.v9),
.iOS(.v13),
.macOS(.v10_11),
.tvOS(.v9),
.watchOS(.v3),
Expand All @@ -16,18 +16,23 @@ let package = Package(
name: "authentikClient",
targets: ["authentikClient"]
),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/Flight-School/AnyCodable", from: "0.6.1"),
.library(
name: "OpenAPITransport",
targets: ["OpenAPITransport"]
),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "authentikClient",
dependencies: ["AnyCodable", ],
path: "authentikClient/Classes"
dependencies: ["OpenAPITransport", ],
path: "authentikClient/Sources"
),
.target(
name: "OpenAPITransport",
dependencies: [],
path: "OpenAPITransport/Sources"
),
]
)
Loading