Skip to content
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// swift-tools-version: 5.7
// swift-tools-version: 5.9

import PackageDescription

let package = Package(
name: "RetroSwift",
platforms: [
.macOS("10.15"),
.iOS("13.0"),
.tvOS("13.0"),
.watchOS("6.0")
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6)
],
products: [
.library(
Expand All @@ -21,7 +21,6 @@ let package = Package(
path: "RetroSwift"),
.testTarget(
name: "RetroSwiftTests",
// FIXME: Linter to be added
dependencies: ["RetroSwift"],
path: "RetroSwiftTests")
]
Expand Down
58 changes: 54 additions & 4 deletions RetroSwift/ApiContractDescription/Domain/Domain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,61 @@ open class Domain {
let operationResult = try await transport.sendRequest(with: requestParams)
let responseData = try operationResult.response.get()

if responseData.isEmpty, Response.self is Empty.Type {
// swiftlint:disable:next force_cast
return Empty() as! Response
} else {
if responseData.isEmpty {
if Response.self is EmptyResponseDecodable.Type || Domain.isEitherWithEmptyResponse(Response.self) {
return try JSONDecoder().decode(Response.self, from: Domain.emptyJsonData)
}
}

do {
return try JSONDecoder().decode(Response.self, from: responseData)
} catch let decodingError as DecodingError {
let errorDescription = Domain.describeDecodingError(decodingError, data: responseData)
print("[RetroSwift] Decoding error: \(errorDescription)")
throw decodingError
}
}
}

private extension Domain {
static func describeDecodingError(_ error: DecodingError, data: Data) -> String {
let jsonPreview = String(data: data.prefix(500), encoding: .utf8) ?? "Unable to preview"

switch error {
case .keyNotFound(let key, let context):
return """
Key '\(key.stringValue)' not found.
Path: \(context.codingPath.map { $0.stringValue }.joined(separator: " -> "))
Debug: \(context.debugDescription)
JSON preview: \(jsonPreview)
"""
case .typeMismatch(let type, let context):
return """
Type mismatch for type '\(type)'.
Path: \(context.codingPath.map { $0.stringValue }.joined(separator: " -> "))
Debug: \(context.debugDescription)
JSON preview: \(jsonPreview)
"""
case .valueNotFound(let type, let context):
return """
Value of type '\(type)' not found.
Path: \(context.codingPath.map { $0.stringValue }.joined(separator: " -> "))
Debug: \(context.debugDescription)
JSON preview: \(jsonPreview)
"""
case .dataCorrupted(let context):
return """
Data corrupted.
Path: \(context.codingPath.map { $0.stringValue }.joined(separator: " -> "))
Debug: \(context.debugDescription)
JSON preview: \(jsonPreview)
"""
@unknown default:
return "Unknown decoding error: \(error.localizedDescription)"
}
}
}

private extension Domain {
static let emptyJsonData = Data("{}".utf8)
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,62 @@
import Foundation

extension Domain {
public enum Either<Response: Decodable, ErrorResponse: Decodable>: Decodable {
public extension Domain {
enum Either<Response: Decodable, ErrorResponse: Decodable>: Decodable {
case response(Response)
case errorResponse(ErrorResponse)
}
}

extension Domain.Either {
public init(from decoder: Decoder) throws {
private protocol EitherCheckable {
static var responseType: Any.Type { get }
}

extension Domain.Either: EitherCheckable {
static var responseType: Any.Type { Response.self }
}

extension Domain {
static func isEitherWithEmptyResponse(_ type: Any.Type) -> Bool {
guard let eitherType = type as? EitherCheckable.Type else {
return false
}
return eitherType.responseType is EmptyResponseDecodable.Type
}
}

public extension Domain.Either {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()

if Response.self is EmptyResponseDecodable.Type {
if let errorValue = try? container.decode(ErrorResponse.self),
!errorValue.isEmptyErrorResponse {
self = .errorResponse(errorValue)
return
}
self = try .response(container.decode(Response.self))
return
}

if let value = try? container.decode(Response.self) {
self = .response(value)
} else {
let errorValue = try container.decode(ErrorResponse.self)
self = .errorResponse(errorValue)
return
}
let errorValue = try container.decode(ErrorResponse.self)
self = .errorResponse(errorValue)
}
}

public protocol EmptyErrorCheckable {
var isEmptyErrorResponse: Bool { get }
}

extension EmptyErrorCheckable {
public var isEmptyErrorResponse: Bool { false }
}

extension Decodable {
var isEmptyErrorResponse: Bool {
(self as? EmptyErrorCheckable)?.isEmptyErrorResponse ?? false
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import Foundation

public protocol EmptyResponseDecodable: Decodable {
init()
}

extension Domain {
public struct Empty: Decodable { }
public struct Empty: EmptyResponseDecodable {
public init() {}
}
}