|
| 1 | +import Foundation |
| 2 | + |
| 3 | +public enum NetworkError: Error { |
| 4 | + case badURL |
| 5 | + case invalidResponse |
| 6 | + case decodingError |
| 7 | +} |
| 8 | + |
| 9 | +public class TCGDex { |
| 10 | + |
| 11 | + private let baseURL = "https://api.tcgdex.net/v2/en" |
| 12 | + private let session = URLSession.shared |
| 13 | + private let decoder = JSONDecoder() |
| 14 | + |
| 15 | + init() { |
| 16 | + decoder.dateDecodingStrategy = .iso8601 |
| 17 | + } |
| 18 | + |
| 19 | + public func cards() async throws -> [CardBrief] { |
| 20 | + return try await fetch("/cards") |
| 21 | + } |
| 22 | + |
| 23 | + public func card(id: String) async throws -> CardType? { |
| 24 | + return try await fetch("/cards/\(id)") |
| 25 | + } |
| 26 | + |
| 27 | + public func set(id: String) async throws -> Set? { |
| 28 | + return try await fetch("/sets/\(id)") |
| 29 | + } |
| 30 | + |
| 31 | + public func sets() async throws -> [SetBrief] { |
| 32 | + return try await fetch("/sets") |
| 33 | + } |
| 34 | + |
| 35 | + public func serie(id: String) async throws -> Serie? { |
| 36 | + return try await fetch("/series/\(id)") |
| 37 | + } |
| 38 | + |
| 39 | + public func series() async throws -> [SerieBrief] { |
| 40 | + return try await fetch("/series") |
| 41 | + } |
| 42 | + |
| 43 | + |
| 44 | + private func fetch<T: Decodable>(_ endpoint: String) async throws -> T { |
| 45 | + guard let url = URL(string: "\(baseURL)/\(endpoint)") else { |
| 46 | + throw NetworkError.badURL |
| 47 | + } |
| 48 | + |
| 49 | + let (data, response) = try await session.data(from: url) |
| 50 | + |
| 51 | + guard let httpResponse = response as? HTTPURLResponse, |
| 52 | + (200...299).contains(httpResponse.statusCode) else { |
| 53 | + throw NetworkError.invalidResponse |
| 54 | + } |
| 55 | + |
| 56 | + do { |
| 57 | + return try decoder.decode(T.self, from: data) |
| 58 | + } catch { |
| 59 | + print(error) |
| 60 | + |
| 61 | + throw NetworkError.decodingError |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | +} |
| 66 | + |
| 67 | +public struct Serie : Codable { |
| 68 | + let id: String |
| 69 | + let logo: String |
| 70 | + let name: String |
| 71 | + let sets: [SetBrief] |
| 72 | +} |
| 73 | + |
| 74 | + |
| 75 | +public struct SetBrief: Codable { |
| 76 | + public let id: String |
| 77 | + public let name: String |
| 78 | + public let logo: String? |
| 79 | + public let cardCount: SetBriefCardCount |
| 80 | +} |
| 81 | + |
| 82 | +public struct SetBriefCardCount : Codable { |
| 83 | + let total: Int |
| 84 | + let official: Int |
| 85 | +} |
| 86 | + |
| 87 | +public struct Legal: Codable { |
| 88 | + public let expanded: Bool |
| 89 | + public let standard: Bool |
| 90 | +} |
| 91 | + |
| 92 | +public struct Set: Codable { |
| 93 | + public let cardCount: SetCardCount |
| 94 | + public let cards: [CardBrief] |
| 95 | + public let id: String |
| 96 | + public let logo: String |
| 97 | + public let legal: Legal |
| 98 | + public let name: String |
| 99 | + public let releaseDate: String |
| 100 | + public let serie: SerieBrief |
| 101 | + public let symbol: String |
| 102 | +} |
| 103 | + |
| 104 | +public struct SerieBrief: Codable { |
| 105 | + public let id: String |
| 106 | + public let name: String |
| 107 | +} |
| 108 | + |
| 109 | +public struct SetCardCount : Codable { |
| 110 | + let firstEd: Int |
| 111 | + let holo: Int |
| 112 | + let normal: Int |
| 113 | + let official: Int |
| 114 | + let reverse: Int |
| 115 | + let total: Int |
| 116 | +} |
| 117 | + |
| 118 | +public struct CardBrief: Codable { |
| 119 | + let id: String |
| 120 | + let localId: String |
| 121 | + let name: String |
| 122 | + let image: String? |
| 123 | +} |
| 124 | + |
| 125 | +public enum CardType: Decodable { |
| 126 | + case pokemon(PokemonCard) |
| 127 | + case trainer(TrainerCard) |
| 128 | + case energy(EnergyCard) |
| 129 | + |
| 130 | + enum CodingKeys: String, CodingKey { |
| 131 | + case category = "category" |
| 132 | + } |
| 133 | + |
| 134 | + public init(from decoder: Decoder) throws { |
| 135 | + let container = try decoder.container(keyedBy: CodingKeys.self) |
| 136 | + let type = try container.decode(String.self, forKey: .category) |
| 137 | + |
| 138 | + switch type { |
| 139 | + case "Pokemon": |
| 140 | + let data = try PokemonCard(from: decoder) |
| 141 | + self = .pokemon(data) |
| 142 | + case "Trainer": |
| 143 | + let data = try TrainerCard(from: decoder) |
| 144 | + self = .trainer(data) |
| 145 | + case "Energy": |
| 146 | + let data = try EnergyCard(from: decoder) |
| 147 | + self = .energy(data) |
| 148 | + default: |
| 149 | + throw DecodingError.dataCorruptedError(forKey: .category, in: container, debugDescription: "Unknown type") |
| 150 | + } |
| 151 | + } |
| 152 | +} |
| 153 | + |
| 154 | +public protocol Card { |
| 155 | + var id: String {get} |
| 156 | + var localId: String {get} |
| 157 | + var name: String {get} |
| 158 | + var image: String? {get} |
| 159 | + var category: String {get} |
| 160 | + var illustrator: String? {get} |
| 161 | + var rarity: String? {get} |
| 162 | + var set: SetBrief {get} |
| 163 | + var variants: CardVariants {get} |
| 164 | + var boosters: [Booster]? {get} |
| 165 | + var pricing: Pricing {get} |
| 166 | + var updated: Date {get} |
| 167 | + var legal: Legal {get} |
| 168 | +} |
| 169 | + |
| 170 | +public struct TrainerCard: Card, Codable { |
| 171 | + public let id: String |
| 172 | + public let localId: String |
| 173 | + public let name: String |
| 174 | + public let image: String? |
| 175 | + public let category: String |
| 176 | + public let illustrator: String? |
| 177 | + public let rarity: String? |
| 178 | + public let set: SetBrief |
| 179 | + public let variants: CardVariants |
| 180 | + public let boosters: [Booster]? |
| 181 | + public let pricing: Pricing |
| 182 | + public let updated: Date |
| 183 | + public let legal: Legal |
| 184 | + |
| 185 | + public let effect: String |
| 186 | + public let trainerType: String? |
| 187 | +} |
| 188 | + |
| 189 | +public struct EnergyCard: Card, Codable { |
| 190 | + public let id: String |
| 191 | + public let localId: String |
| 192 | + public let name: String |
| 193 | + public let image: String? |
| 194 | + public let category: String |
| 195 | + public let illustrator: String? |
| 196 | + public let rarity: String? |
| 197 | + public let set: SetBrief |
| 198 | + public let variants: CardVariants |
| 199 | + public let boosters: [Booster]? |
| 200 | + public let pricing: Pricing |
| 201 | + public let updated: Date |
| 202 | + public let legal: Legal |
| 203 | + |
| 204 | + public let effect: String? |
| 205 | + public let energyType: String |
| 206 | +} |
| 207 | + |
| 208 | +public struct PokemonCard: Card, Codable { |
| 209 | + public let id: String |
| 210 | + public let localId: String |
| 211 | + public let name: String |
| 212 | + public let image: String? |
| 213 | + public let category: String |
| 214 | + public let illustrator: String? |
| 215 | + public let rarity: String? |
| 216 | + public let set: SetBrief |
| 217 | + public let variants: CardVariants |
| 218 | + public let boosters: [Booster]? |
| 219 | + public let pricing: Pricing |
| 220 | + public let updated: Date |
| 221 | + public let legal: Legal |
| 222 | + |
| 223 | + public let dexId: [Int]? |
| 224 | + public let hp: Int? |
| 225 | + public let types: [String]? |
| 226 | + public let evolveFrom: String? |
| 227 | + public let description: String? |
| 228 | + public let level: String? |
| 229 | + public let stage: String? |
| 230 | + public let suffix: String? |
| 231 | + public let item: PokemonCardItem? |
| 232 | +} |
| 233 | + |
| 234 | +public struct PokemonCardItem : Codable { |
| 235 | + let name: String |
| 236 | + let effect: String |
| 237 | +} |
| 238 | + |
| 239 | +public struct CardVariants: Codable { |
| 240 | + public let normal: Bool |
| 241 | + public let reverse: Bool |
| 242 | + public let holo: Bool |
| 243 | + public let firstEdition: Bool |
| 244 | +} |
| 245 | + |
| 246 | +public struct Booster: Codable { |
| 247 | + public let id: String |
| 248 | + public let name: String |
| 249 | + public let logo: String? |
| 250 | + public let artworkFront: String? |
| 251 | + public let artworkBack: String? |
| 252 | + |
| 253 | + enum CodingKeys: String, CodingKey { |
| 254 | + case id |
| 255 | + case name |
| 256 | + case logo |
| 257 | + case artworkFront = "artwork_front" |
| 258 | + case artworkBack = "artwork_back" |
| 259 | + } |
| 260 | +} |
| 261 | + |
| 262 | +public struct Pricing: Codable { |
| 263 | + public let tcgplayer: TCGPlayerPricing? |
| 264 | + public let cardmarket: CardMarketPricing? |
| 265 | +} |
| 266 | + |
| 267 | +public struct TCGPlayerPricing: Codable { |
| 268 | + public let updated: Date |
| 269 | + public let unit: String |
| 270 | + public let normal: TCGPlayerPricingVariants? |
| 271 | + public let holofoil: TCGPlayerPricingVariants? |
| 272 | + public let reverseholofoil : TCGPlayerPricingVariants? |
| 273 | + |
| 274 | + enum CodingKeys: String, CodingKey { |
| 275 | + case updated |
| 276 | + case unit |
| 277 | + case normal |
| 278 | + case holofoil |
| 279 | + case reverseholofoil = "reverse-holofoil" |
| 280 | + } |
| 281 | +} |
| 282 | + |
| 283 | +public struct TCGPlayerPricingVariants: Codable { |
| 284 | + public let lowPrice: Double? |
| 285 | + public let midPrice: Double? |
| 286 | + public let highPrice: Double? |
| 287 | + public let marketPrice: Double? |
| 288 | + public let directLowPrice: Double? |
| 289 | + |
| 290 | +} |
| 291 | + |
| 292 | +public struct CardMarketPricing: Codable { |
| 293 | + public let updated: Date |
| 294 | + public let unit: String |
| 295 | + public let avg: Double? |
| 296 | + public let low: Double? |
| 297 | + public let trend: Double? |
| 298 | + public let avg1: Double? |
| 299 | + public let avg7: Double? |
| 300 | + public let avg30: Double? |
| 301 | + public let avgHolo: Double? |
| 302 | + public let lowHolo: Double? |
| 303 | + public let trendHolo: Double? |
| 304 | + public let avg1Holo: Double? |
| 305 | + public let avg7Holo: Double? |
| 306 | + public let avg30Holo: Double? |
| 307 | + |
| 308 | + enum CodingKeys: String, CodingKey { |
| 309 | + case updated |
| 310 | + case unit |
| 311 | + case avg |
| 312 | + case low |
| 313 | + case trend |
| 314 | + case avg1 |
| 315 | + case avg7 |
| 316 | + case avg30 |
| 317 | + case avgHolo = "avg-holo" |
| 318 | + case lowHolo = "low-holo" |
| 319 | + case trendHolo = "trend-holo" |
| 320 | + case avg1Holo = "avg1-holo" |
| 321 | + case avg7Holo = "avg7-holo" |
| 322 | + case avg30Holo = "avg30-holo" |
| 323 | + |
| 324 | + } |
| 325 | +} |
0 commit comments