Skip to content
Merged
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
6 changes: 4 additions & 2 deletions Sources/ScryfallKit/Extensions/Card+helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,12 @@ extension Card {
public func getPrice(for currency: Currency) -> String? {
switch currency {
case .usd: return prices.usd
case .eur: return prices.eur
case .tix: return prices.tix
case .usdFoil: return prices.usdFoil
case .usdEtched: return prices.usdEtched
case .eur: return prices.eur
case .eurFoil: return prices.eurFoil
case .eurEtched: return prices.eurEtched
case .tix: return prices.tix
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions Sources/ScryfallKit/Models/Card/Card+Face.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ extension Card {
public var colors: [Card.Color]?
/// This face's defense, if any
public var defense: String?
/// The just-for-fun name printed on the card (such as for Godzilla series cards).
public var flavorName: String?
/// This card's flavor text if any
public var flavorText: String?
/// An ID for this card face's art that remains consistent across reprints
Expand Down Expand Up @@ -64,6 +66,7 @@ extension Card {
colorIndicator: [Card.Color]? = nil,
colors: [Card.Color]? = nil,
defense: String? = nil,
flavorName: String? = nil,
flavorText: String? = nil,
illustrationId: UUID? = nil,
imageUris: ImageUris? = nil,
Expand All @@ -83,6 +86,7 @@ extension Card {
self.colorIndicator = colorIndicator
self.colors = colors
self.defense = defense
self.flavorName = flavorName
self.flavorText = flavorText
self.illustrationId = illustrationId
self.imageUris = imageUris
Expand Down
10 changes: 8 additions & 2 deletions Sources/ScryfallKit/Models/Card/Card+Prices.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,22 @@ extension Card {
public var usdFoil: String?
/// The price of this card's etched printing in usd
public var usdEtched: String?
/// The price of this card in eur.
/// The price of this card in eur
public var eur: String?
/// The price of this card's foil printing in eur
public var eurFoil: String?
/// The price of this card's etched printing in eur
public var eurEtched: String?

public init(tix: String? = nil, usd: String? = nil, usdFoil: String? = nil, usdEtched: String? = nil, eur: String? = nil)
public init(tix: String? = nil, usd: String? = nil, usdFoil: String? = nil, usdEtched: String? = nil, eur: String? = nil, eurFoil: String? = nil, eurEtched: String? = nil)
{
self.tix = tix
self.usd = usd
self.usdFoil = usdFoil
self.usdEtched = usdEtched
self.eur = eur
self.eurFoil = eurFoil
self.eurEtched = eurEtched
}
}
}
2 changes: 1 addition & 1 deletion Sources/ScryfallKit/Models/Card/Card+RelatedCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ extension Card {
/// A Magic card that's related to another Magic card
///
/// - Note: In the documentation of this struct, "this card" will refer to the `RelatedCard` object while "the original card" will refer to the `Card` object that contains this object
public struct RelatedCard: Codable, Hashable, Sendable {
public struct RelatedCard: Codable, Identifiable, Hashable, Sendable {
/// The type of relationship
public enum Component: String, Codable, CaseIterable, Hashable, Sendable {
/// This card is a token that's made by the original card
Expand Down
6 changes: 3 additions & 3 deletions Sources/ScryfallKit/Models/Enums.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ public enum SortDirection: String, Codable, CaseIterable, Sendable {
}

/// Formats for playing Magic: the Gathering
public enum Format: String, CaseIterable, Sendable {
public enum Format: String, Codable, CaseIterable, Sendable {
case standard, future, historic, timeless, gladiator, pioneer, modern, legacy, pauper, vintage,
penny, commander, oathbreaker, standardbrawl, brawl, alchemy, paupercommander, duel, oldschool,
premodern, predh
}

/// Currency types that Scryfall provides prices for
public enum Currency: String, CaseIterable, Sendable {
case usd, eur, tix, usdFoil, usdEtched
public enum Currency: String, Codable, CaseIterable, Sendable {
case usd, usdFoil, usdEtched, eur, eurFoil, eurEtched, tix
}
35 changes: 25 additions & 10 deletions Sources/ScryfallKit/Networking/NetworkService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,28 @@ protocol NetworkServiceProtocol: Sendable {
}

struct NetworkService: NetworkServiceProtocol, Sendable {
let userAgent: String?
let logger: Logger?

init(logger: Logger?) {
init(userAgent: String?, logger: Logger?) {
self.userAgent = userAgent
self.logger = logger
}

func request<T: Decodable & Sendable>(
_ request: EndpointRequest, as type: T.Type,
completion: @Sendable @escaping (Result<T, Error>) -> Void
) {
guard let urlRequest = request.urlRequest else {
guard var urlRequest = request.urlRequest else {
logger?.error("Invalid url request")
completion(.failure(ScryfallKitError.invalidUrl))
return
}

if let userAgent {
urlRequest.setValue(userAgent, forHTTPHeaderField: "User-Agent")
}

logger?.trace("Starting request: \(urlRequest.debugDescription)")
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
do {
Expand All @@ -52,23 +58,32 @@ struct NetworkService: NetworkServiceProtocol, Sendable {
throw error
}

guard let content = data else {
throw ScryfallKitError.noDataReturned
}

guard let httpStatus = (response as? HTTPURLResponse)?.statusCode else {
throw ScryfallKitError.failedToCast("httpStatus property of response to HTTPURLResponse")
}

logger?.debug("HTTP \(httpStatus): \(data.flatMap { String(data: $0, encoding: .utf8) } ?? "Couldn't represent response body as string")")

guard let content = data else {
throw ScryfallKitError.noDataReturned
}

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

if (200..<300).contains(httpStatus) {
let responseBody = String(data: content, encoding: .utf8)
logger?.debug("\(responseBody ?? "Couldn't represent response body as string")")
return try decoder.decode(dataType, from: content)
do {
return try decoder.decode(dataType, from: content)
} catch {
throw ScryfallKitError.failedToDecode(content)
}
} else {
let httpError = try decoder.decode(ScryfallError.self, from: content)
let httpError: ScryfallError
do {
httpError = try decoder.decode(ScryfallError.self, from: content)
} catch {
throw ScryfallKitError.httpError(httpStatus, content)
}
throw ScryfallKitError.scryfallError(httpError)
}
}
Expand Down
8 changes: 5 additions & 3 deletions Sources/ScryfallKit/ScryfallClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ public final class ScryfallClient: Sendable {
let networkService: NetworkServiceProtocol

/// Initialize an instance of the ScryfallClient
/// - Parameter logger: The logger to use. Pass nil to disable logging
public init(logger: Logger? = nil) {
self.networkService = NetworkService(logger: logger)
/// - Parameters:
/// - userAgent: The value for the HTTP User-Agent header; required by Scryfall's API usage terms
/// - logger: The logger to use. Pass nil to disable logging
public init(userAgent: String? = nil, logger: Logger? = nil) {
self.networkService = NetworkService(userAgent: userAgent, logger: logger)
}

/// Perform a search using an array of ``CardFieldFilter`` objects.
Expand Down
6 changes: 6 additions & 0 deletions Sources/ScryfallKit/ScryfallKitError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public enum ScryfallKitError: LocalizedError, CustomStringConvertible {
case singleFacedCard
case noDataReturned
case failedToCast(String)
case failedToDecode(Data)
case httpError(Int, Data)

public var errorDescription: String? {
description
Expand All @@ -30,6 +32,10 @@ public enum ScryfallKitError: LocalizedError, CustomStringConvertible {
return "No data was returned by the server"
case .failedToCast(let details):
return "Failed to cast \(details)"
case .failedToDecode:
return "Failed to decode response into valid JSON"
case .httpError(let statusCode, _):
return "Failed with HTTP \(statusCode)"
}
}
}
Loading