// Copyright © 2024 Ryan Booker. All rights reserved.
import Foundation
import Toolbox
public struct Response<T>: CustomStringConvertible, Decodable where T: CustomStringConvertible & Decodable {
public let meta: Metadata
public let data: Result<T, Failure>
public var description: String {
data.reduce(success: \.description, failure: \.description)
}
public init(meta: Metadata, data: Result<T, Failure>) {
self.meta = meta
self.data = data
}
public enum CodingKeys: CodingKey {
case meta, data, error
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.meta = try container.decode(Metadata.self, forKey: .meta)
do {
let data = try container.decode(T.self, forKey: .data)
self.data = .success(data)
} catch {
let reasons = try container.decode([Failure.Reason].self, forKey: .error)
self.data = .failure(.init(reasons: reasons))
}
}
}
// MARK: - Repsonse types
extension Response {
public struct Failure: CustomStringConvertible, Decodable, Error {
public struct Reason: CustomStringConvertible, Decodable {
public let code: Int
public let msg: String
public var description: String {
"\(code): \(msg)"
}
}
public let reasons: [Reason]
public var description: String {
"""
Whoops!
\(reasons.map(\.description).joined(separator: "\n\n"))
"""
}
}
public struct Metadata: Decodable {
public let id: String
public let node: String
public let ms: Int
}
}