feat: added HTTP status codes to `EndpointErrorResult.apiError` responses
This commit is contained in:
parent
dd4c9072a9
commit
6358386303
|
|
@ -1,5 +1,9 @@
|
|||
# Changelog
|
||||
|
||||
### 1.44.0
|
||||
|
||||
- **Added**: HTTP status codes to `EndpointErrorResult.apiError` responses
|
||||
|
||||
### 1.43.1
|
||||
|
||||
- **Fixed**: build scripts submodule url
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@
|
|||
open class BaseCancellable: Cancellable {
|
||||
private(set) public var isCancelled = false
|
||||
|
||||
public init() {}
|
||||
|
||||
open func cancel() {
|
||||
isCancelled = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
public struct Cancellables {
|
||||
public enum Cancellables {
|
||||
public static func nonCancellable() -> Cancellable {
|
||||
NonCancellable()
|
||||
}
|
||||
|
|
@ -28,21 +28,27 @@ public struct Cancellables {
|
|||
public static func scoped(scopeCancellableClosure: ScopeCancellable.ScopeCancellableClosure) -> Cancellable {
|
||||
ScopeCancellable(scopeCancellableClosure: scopeCancellableClosure)
|
||||
}
|
||||
|
||||
public static func weakTargetClosure<T: AnyObject>(target: T?,
|
||||
cancelClosure: @escaping WeakTargetCancellable<T>.CancelClosure) -> Cancellable {
|
||||
|
||||
WeakTargetCancellable(target: target, cancelClosure: cancelClosure)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0.0, *)
|
||||
public func withTaskCancellableClosure<T>(closure: (@escaping (T) -> Void) -> Cancellable) async -> T {
|
||||
let cancellableBag = BaseCancellableBag()
|
||||
|
||||
return await withTaskCancellationHandler(handler: {
|
||||
cancellableBag.cancel()
|
||||
}, operation: {
|
||||
return await withTaskCancellationHandler(operation: {
|
||||
await withCheckedContinuation { continuation in
|
||||
closure {
|
||||
continuation.resume(returning: $0)
|
||||
}
|
||||
.add(to: cancellableBag)
|
||||
}
|
||||
}, onCancel: {
|
||||
cancellableBag.cancel()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,9 +68,9 @@ open class DefaultJsonNetworkService: ApiInteractor {
|
|||
defaultServer: openApi.defaultServer)
|
||||
}
|
||||
|
||||
open func process<B: Encodable, S: Decodable, F: Decodable, R>(request: EndpointRequest<B, S>,
|
||||
open func process<B: Encodable, S: Decodable, AE: Decodable, R>(request: EndpointRequest<B, S>,
|
||||
mapSuccess: @escaping Closure<S, R>,
|
||||
mapFailure: @escaping Closure<F, R>,
|
||||
mapFailure: @escaping Closure<FailureMappingInput<AE>, R>,
|
||||
mapNetworkError: @escaping Closure<MoyaError, R>,
|
||||
completion: @escaping ParameterClosure<R>) -> TIFoundationUtils.Cancellable {
|
||||
|
||||
|
|
@ -115,9 +115,9 @@ open class DefaultJsonNetworkService: ApiInteractor {
|
|||
return cancellableBag
|
||||
}
|
||||
|
||||
open func process<S: Decodable, F: Decodable, R>(request: SerializedRequest,
|
||||
open func process<S: Decodable, AE: Decodable, R>(request: SerializedRequest,
|
||||
mapSuccess: @escaping Closure<S, R>,
|
||||
mapFailure: @escaping Closure<F, R>,
|
||||
mapFailure: @escaping Closure<FailureMappingInput<AE>, R>,
|
||||
mapNetworkError: @escaping Closure<MoyaError, R>,
|
||||
completion: @escaping ParameterClosure<R>) -> TIFoundationUtils.Cancellable {
|
||||
|
||||
|
|
@ -152,8 +152,10 @@ open class DefaultJsonNetworkService: ApiInteractor {
|
|||
}
|
||||
|
||||
let decodeResult = rawResponse.decode(mapping: [
|
||||
((successStatusCodes, CommonMediaTypes.applicationJson.rawValue), jsonDecoder.decoding(to: mapSuccess)),
|
||||
((failureStatusCodes, CommonMediaTypes.applicationJson.rawValue), jsonDecoder.decoding(to: mapFailure)),
|
||||
KeyValueTuple(.json(with: successStatusCodes), jsonDecoder.decoding(to: mapSuccess)),
|
||||
KeyValueTuple(.json(with: failureStatusCodes), jsonDecoder.decoding(to: {
|
||||
mapFailure(FailureMappingInput($0, rawResponse.statusCode))
|
||||
})),
|
||||
])
|
||||
|
||||
let pluginResult: Result<Response, MoyaError>
|
||||
|
|
@ -192,10 +194,10 @@ open class DefaultJsonNetworkService: ApiInteractor {
|
|||
preprocessors.append(securityPreprocessor)
|
||||
}
|
||||
|
||||
private static func preprocess<B,S,P: Collection>(request: EndpointRequest<B,S>,
|
||||
private static func preprocess<B, S, P: Collection>(request: EndpointRequest<B, S>,
|
||||
preprocessors: P,
|
||||
cancellableBag: BaseCancellableBag,
|
||||
completion: @escaping (Result<EndpointRequest<B,S>, Error>) -> Void)
|
||||
completion: @escaping (Result<EndpointRequest<B, S>, Error>) -> Void)
|
||||
where P.Element == EndpointRequestPreprocessor {
|
||||
|
||||
guard let preprocessor = preprocessors.first else {
|
||||
|
|
|
|||
|
|
@ -27,10 +27,11 @@ public protocol ApiInteractor {
|
|||
associatedtype NetworkError
|
||||
|
||||
typealias RequestResult<S: Decodable, AE: Decodable> = EndpointRequestResult<S, AE, NetworkError>
|
||||
typealias FailureMappingInput<AE> = (apiError: AE, statusCode: Int)
|
||||
|
||||
func process<B: Encodable, S: Decodable, AE: Decodable, R>(request: EndpointRequest<B, S>,
|
||||
mapSuccess: @escaping Closure<S, R>,
|
||||
mapFailure: @escaping Closure<AE, R>,
|
||||
mapFailure: @escaping Closure<FailureMappingInput<AE>, R>,
|
||||
mapNetworkError: @escaping Closure<NetworkError, R>,
|
||||
completion: @escaping ParameterClosure<R>) -> Cancellable
|
||||
}
|
||||
|
|
@ -40,14 +41,14 @@ public extension ApiInteractor {
|
|||
func process<B: Encodable, S, F>(request: EndpointRequest<B, S>) async -> RequestResult<S, F> {
|
||||
await process(request: request,
|
||||
mapSuccess: Result.success,
|
||||
mapFailure: { .failure(.apiError($0)) },
|
||||
mapFailure: { .failure(.apiError($0.apiError, $0.statusCode)) },
|
||||
mapNetworkError: { .failure(.networkError($0)) })
|
||||
}
|
||||
|
||||
func process<B: Encodable, S: Decodable, F: Decodable, R>(request: EndpointRequest<B, S>,
|
||||
mapSuccess: @escaping Closure<S, R>,
|
||||
mapFailure: @escaping Closure<F, R>,
|
||||
mapNetworkError: @escaping Closure<NetworkError, R>) async -> R {
|
||||
func process<B: Encodable, S: Decodable, AE: Decodable, R>(request: EndpointRequest<B, S>,
|
||||
mapSuccess: @escaping Closure<S, R>,
|
||||
mapFailure: @escaping Closure<FailureMappingInput<AE>, R>,
|
||||
mapNetworkError: @escaping Closure<NetworkError, R>) async -> R {
|
||||
|
||||
await withTaskCancellableClosure { completion in
|
||||
process(request: request,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,6 @@
|
|||
//
|
||||
|
||||
public enum EndpointErrorResult<ApiError, NetworkError>: Error {
|
||||
case apiError(ApiError)
|
||||
case apiError(ApiError, Int)
|
||||
case networkError(NetworkError)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,15 +23,12 @@
|
|||
import Foundation
|
||||
import TISwiftUtils
|
||||
|
||||
public typealias StatusCodeMimeType = (statusCode: Int, mimeType: String?)
|
||||
public typealias StatusCodesMimeType = (statusCodes: Set<Int>, mimeType: String?)
|
||||
|
||||
public typealias DecodingClosure<R> = ThrowableClosure<Data, R>
|
||||
|
||||
public extension ResponseType {
|
||||
typealias DecodingClosure<R> = ThrowableClosure<Data, R>
|
||||
|
||||
func decode<R>(mapping: [KeyValueTuple<StatusCodeMimeType, DecodingClosure<R>>]) -> Result<R, ErrorType> {
|
||||
for ((mappingStatusCode, mappingMimeType), decodeClosure) in mapping
|
||||
where mappingStatusCode == statusCode && mappingMimeType == mimeType {
|
||||
for (statusCodesMimeType, decodeClosure) in mapping
|
||||
where statusCodesMimeType.statusCode == statusCode && statusCodesMimeType.mimeType == mimeType {
|
||||
do {
|
||||
return .success(try decodeClosure(data))
|
||||
} catch {
|
||||
|
|
@ -52,7 +49,8 @@ public extension ResponseType {
|
|||
|
||||
func decode<R>(mapping: [KeyValueTuple<StatusCodesMimeType, DecodingClosure<R>>]) -> Result<R, ErrorType> {
|
||||
decode(mapping: mapping.map { key, value in
|
||||
key.statusCodes.map { KeyValueTuple(StatusCodeMimeType($0, key.mimeType), value) }
|
||||
}.flatMap { $0 })
|
||||
key.statusCodes.map { KeyValueTuple(StatusCodeMimeType(statusCode: $0, mimeType: key.mimeType), value) }
|
||||
}
|
||||
.flatMap { $0 })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
public struct StatusCodeMimeType {
|
||||
public let statusCode: Int
|
||||
public let mimeType: String?
|
||||
|
||||
public init(statusCode: Int, mimeType: String?) {
|
||||
self.statusCode = statusCode
|
||||
self.mimeType = mimeType
|
||||
}
|
||||
|
||||
public static func json(with statusCode: Int) -> Self {
|
||||
.init(statusCode: statusCode, mimeType: CommonMediaTypes.applicationJson.rawValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
public struct StatusCodesMimeType {
|
||||
public let statusCodes: Set<Int>
|
||||
public let mimeType: String?
|
||||
|
||||
public init(statusCodes: Set<Int>, mimeType: String?) {
|
||||
self.statusCodes = statusCodes
|
||||
self.mimeType = mimeType
|
||||
}
|
||||
|
||||
public static func json(with statusCodes: Set<Int>) -> Self {
|
||||
.init(statusCodes: statusCodes, mimeType: CommonMediaTypes.applicationJson.rawValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -29,13 +29,13 @@ public struct AlertAction {
|
|||
public let id = UUID()
|
||||
|
||||
/// Alert button title
|
||||
public let title: String
|
||||
public var title: String
|
||||
|
||||
/// Alert button style
|
||||
public let style: UIAlertAction.Style
|
||||
public var style: UIAlertAction.Style
|
||||
|
||||
/// Alert button action
|
||||
public let action: VoidClosure?
|
||||
public var action: VoidClosure?
|
||||
|
||||
public init(title: String, style: UIAlertAction.Style = .default, action: VoidClosure? = nil) {
|
||||
self.title = title
|
||||
|
|
|
|||
|
|
@ -26,19 +26,19 @@ import UIKit
|
|||
public struct AlertDescriptor {
|
||||
|
||||
/// Alert title
|
||||
public let title: String?
|
||||
public var title: String?
|
||||
|
||||
/// Alert message
|
||||
public let message: String?
|
||||
public var message: String?
|
||||
|
||||
/// Alert style
|
||||
public let style: UIAlertController.Style
|
||||
public var style: UIAlertController.Style
|
||||
|
||||
/// Alert tint color
|
||||
public let tintColor: UIColor
|
||||
public var tintColor: UIColor
|
||||
|
||||
/// Alert actions
|
||||
public let actions: [AlertAction]
|
||||
public var actions: [AlertAction]
|
||||
|
||||
public init(title: String? = nil,
|
||||
message: String? = nil,
|
||||
|
|
|
|||
Loading…
Reference in New Issue