From df9500c40d9bf4e62f5dc9aa9aae87c68045a609 Mon Sep 17 00:00:00 2001 From: Sean Kelley Date: Mon, 24 Aug 2026 13:09:17 -0700 Subject: [PATCH] Audit data types to ensure they agree with documentation/actual returned data. --- .../ScryfallKit/Models/Card/Card+Face.swift | 8 +++++ .../Models/Card/Card+Legalities.swift | 6 ++++ .../Models/Card/Card+Preview.swift | 8 ++--- .../ScryfallKit/Models/Card/Card+Symbol.swift | 10 ++++++- .../ScryfallKit/Models/Card/Card+enums.swift | 22 +++++++++++++- Sources/ScryfallKit/Models/Card/Card.swift | 30 +++++++++++++++++-- Sources/ScryfallKit/Models/Catalog.swift | 8 ++++- Sources/ScryfallKit/Models/MTGSet.swift | 4 +++ .../ScryfallKit/Models/UnknownDecodable.swift | 1 + .../ScryfallKitTests/CaseIterableTests.swift | 17 +++++++++++ 10 files changed, 104 insertions(+), 10 deletions(-) diff --git a/Sources/ScryfallKit/Models/Card/Card+Face.swift b/Sources/ScryfallKit/Models/Card/Card+Face.swift index 547ad24..306d65d 100644 --- a/Sources/ScryfallKit/Models/Card/Card+Face.swift +++ b/Sources/ScryfallKit/Models/Card/Card+Face.swift @@ -17,6 +17,8 @@ extension Card { public var oracleId: String? /// The name of the artist who illustrated this card public var artist: String? + /// The identifier of the artist who illustrated this card face + public var artistId: UUID? /// An array of the colors in this card’s color indicator or nil if it doesn't have one /// /// Color indicators are used to specify the color of a card that has no mana symbols @@ -33,6 +35,8 @@ extension Card { public var illustrationId: UUID? /// An object listing available imagery for this card. public var imageUris: ImageUris? + /// The layout of this card face, if the card is reversible + public var layout: Layout? /// This card's starting loyalty counters if it's a planeswalker public var loyalty: String? /// The mana cost for this card. @@ -63,6 +67,7 @@ extension Card { public init( artist: String? = nil, + artistId: UUID? = nil, colorIndicator: [Card.Color]? = nil, colors: [Card.Color]? = nil, defense: String? = nil, @@ -70,6 +75,7 @@ extension Card { flavorText: String? = nil, illustrationId: UUID? = nil, imageUris: ImageUris? = nil, + layout: Layout? = nil, loyalty: String? = nil, manaCost: String, name: String, @@ -83,6 +89,7 @@ extension Card { watermark: String? = nil ) { self.artist = artist + self.artistId = artistId self.colorIndicator = colorIndicator self.colors = colors self.defense = defense @@ -90,6 +97,7 @@ extension Card { self.flavorText = flavorText self.illustrationId = illustrationId self.imageUris = imageUris + self.layout = layout self.loyalty = loyalty self.manaCost = manaCost self.name = name diff --git a/Sources/ScryfallKit/Models/Card/Card+Legalities.swift b/Sources/ScryfallKit/Models/Card/Card+Legalities.swift index 14ae015..ffc661a 100644 --- a/Sources/ScryfallKit/Models/Card/Card+Legalities.swift +++ b/Sources/ScryfallKit/Models/Card/Card+Legalities.swift @@ -22,12 +22,14 @@ extension Card { public let gladiator: Legality? public let oathbreaker: Legality? public let standardbrawl: Legality? + public let competitivebrawl: Legality? public let alchemy: Legality? public let paupercommander: Legality? public let duel: Legality? public let oldschool: Legality? public let premodern: Legality? public let predh: Legality? + public let tlr: Legality? public init( standard: Legality?, @@ -45,12 +47,14 @@ extension Card { gladiator: Legality?, oathbreaker: Legality?, standardbrawl: Legality?, + competitivebrawl: Legality?, alchemy: Legality?, paupercommander: Legality?, duel: Legality?, oldschool: Legality?, premodern: Legality?, predh: Legality?, + tlr: Legality?, ) { self.standard = standard self.historic = historic @@ -67,12 +71,14 @@ extension Card { self.gladiator = gladiator self.oathbreaker = oathbreaker self.standardbrawl = standardbrawl + self.competitivebrawl = competitivebrawl self.alchemy = alchemy self.paupercommander = paupercommander self.duel = duel self.oldschool = oldschool self.premodern = premodern self.predh = predh + self.tlr = tlr } } } diff --git a/Sources/ScryfallKit/Models/Card/Card+Preview.swift b/Sources/ScryfallKit/Models/Card/Card+Preview.swift index 067b107..de2d435 100644 --- a/Sources/ScryfallKit/Models/Card/Card+Preview.swift +++ b/Sources/ScryfallKit/Models/Card/Card+Preview.swift @@ -8,13 +8,13 @@ extension Card { /// Metadata about a Magic card's preview public struct Preview: Codable, Hashable, Sendable { /// The name of the source that previewed this card. - public var source: String + public var source: String? /// A link to the preview for this card. - public var sourceUri: String + public var sourceUri: String? /// The date this card was previewed. - public var previewedAt: String + public var previewedAt: String? - public init(source: String, sourceUri: String, previewedAt: String) { + public init(source: String? = nil, sourceUri: String? = nil, previewedAt: String? = nil) { self.source = source self.sourceUri = sourceUri self.previewedAt = previewedAt diff --git a/Sources/ScryfallKit/Models/Card/Card+Symbol.swift b/Sources/ScryfallKit/Models/Card/Card+Symbol.swift index eca120f..d80f84e 100644 --- a/Sources/ScryfallKit/Models/Card/Card+Symbol.swift +++ b/Sources/ScryfallKit/Models/Card/Card+Symbol.swift @@ -21,7 +21,13 @@ extension Card { public var transposable: Bool /// True if this symbol is a mana symbol public var representsMana: Bool + /// The amount that this symbol adds to a card's mana value + public var manaValue: Double? /// The amount that this symbol adds to a card's converted mana cost + /// + /// - Note: Scryfall no longer documents this property, but every symbol in a symbology + /// response still includes it, so it is included here for completeness. + @available(*, deprecated, renamed: "manaValue") public var cmc: Double? /// True if this symbol _could_ appear in a card's mana cost public var appearsInManaCosts: Bool @@ -37,7 +43,7 @@ extension Card { public var gathererAlternates: [String]? /// A link to an SVG of this symbol public var svgUri: String? - + /// A computed ID for this symbol which is just the `symbol` property public var id: String { symbol } @@ -48,6 +54,7 @@ extension Card { english: String, transposable: Bool, representsMana: Bool, + manaValue: Double? = nil, cmc: Double? = nil, appearsInManaCosts: Bool, funny: Bool, @@ -62,6 +69,7 @@ extension Card { self.english = english self.transposable = transposable self.representsMana = representsMana + self.manaValue = manaValue self.cmc = cmc self.appearsInManaCosts = appearsInManaCosts self.funny = funny diff --git a/Sources/ScryfallKit/Models/Card/Card+enums.swift b/Sources/ScryfallKit/Models/Card/Card+enums.swift index 07095d9..c9da400 100644 --- a/Sources/ScryfallKit/Models/Card/Card+enums.swift +++ b/Sources/ScryfallKit/Models/Card/Card+enums.swift @@ -121,6 +121,26 @@ extension Card { } } + /// The security stamp printed on a card + public enum SecurityStamp: RawRepresentable, Codable, CaseIterable, Sendable, Equatable, Hashable + { + case oval, triangle, acorn, circle, arena, heart + /// A security stamp that hasn't been added to ScryfallKit yet + case unknown(String) + + /// All known security stamps + public static let allCases: [Card.SecurityStamp] = [ + .oval, .triangle, .acorn, .circle, .arena, .heart, + ] + + public var rawValue: String { + switch self { + case .unknown(let unknownRawValue): unknownRawValue + default: String(describing: self) + } + } + } + /// Layouts for a Magic card /// /// [Scryfall documentation](https://scryfall.com/docs/api/layouts) @@ -198,7 +218,7 @@ extension Card { } /// Effects applied to a Magic card frame - /// + /// /// [Scryfall documentation](https://scryfall.com/docs/api/frames#frame-effects) public enum FrameEffect: RawRepresentable, Codable, Sendable, CaseIterable, Equatable, Hashable { case legendary, miracle, draft, devoid, tombstone, showcase, companion, etched, snow, lesson, battle, gravestone, vehicle, borderless, extended, spree, textless, enchantment, inverted diff --git a/Sources/ScryfallKit/Models/Card/Card.swift b/Sources/ScryfallKit/Models/Card/Card.swift index 7fd440b..76875b6 100644 --- a/Sources/ScryfallKit/Models/Card/Card.swift +++ b/Sources/ScryfallKit/Models/Card/Card.swift @@ -24,7 +24,9 @@ public struct Card: Codable, Identifiable, Hashable, Sendable { /// The identifier of the etched version of this card on TCGPlayer public var tcgplayerEtchedId: Int? /// The identifier of this card on Card Market - public var cardMarketId: Int? + public var cardmarketId: Int? + /// The identifier of this card on Wizards of the Coast's [Gatherer](https://gatherer.wizards.com/Pages/Default.aspx) + public var resourceId: String? /// The identifier for this card’s oracle identity. /// /// For more information on this property, see [Scryfall's documentation](https://scryfall.com/docs/api/cards#core-card-fields) @@ -87,6 +89,8 @@ public struct Card: Codable, Identifiable, Hashable, Sendable { public var name: String /// The oracle text for this card public var oracleText: String? + /// This card’s rank/popularity on Penny Dreadful. Not all cards are ranked. + public var pennyRank: Int? /// True if this card is an oversized card public var oversized: Bool /// The power of this card if it's a creature @@ -104,6 +108,10 @@ public struct Card: Codable, Identifiable, Hashable, Sendable { // MARK: Print fields /// The name of the artist who illustrated this card public var artist: String? + /// The identifiers of the artists who illustrated this card + public var artistIds: [UUID]? + /// The lit Unfinity attraction lights on this card, if any + public var attractionLights: [Int]? /// True if this card was printed in booster packs public var booster: Bool /// The color of this card's border @@ -175,6 +183,8 @@ public struct Card: Codable, Identifiable, Hashable, Sendable { public var setUri: String /// This card's set code public var set: String + /// The identifier of this card's set on Scryfall + public var setId: UUID /// True if this was a story spotlight card public var storySpotlight: Bool /// True if this card doesn't have any text on it @@ -183,6 +193,8 @@ public struct Card: Codable, Identifiable, Hashable, Sendable { public var variation: Bool /// The id of the card this card is a variation of public var variationOf: UUID? + /// The security stamp on this card, if any + public var securityStamp: SecurityStamp? /// This card's watermark, if any public var watermark: String? /// An object with information on when this card was previewed and by whom @@ -196,7 +208,8 @@ public struct Card: Codable, Identifiable, Hashable, Sendable { multiverseIds: [Int]? = nil, tcgplayerId: Int? = nil, tcgplayerEtchedId: Int? = nil, - cardMarketId: Int? = nil, + cardmarketId: Int? = nil, + resourceId: String? = nil, id: UUID, oracleId: String, lang: String, @@ -222,6 +235,7 @@ public struct Card: Codable, Identifiable, Hashable, Sendable { manaCost: String? = nil, name: String, oracleText: String? = nil, + pennyRank: Int? = nil, oversized: Bool, power: String? = nil, producedMana: [Color]? = nil, @@ -229,6 +243,8 @@ public struct Card: Codable, Identifiable, Hashable, Sendable { toughness: String? = nil, typeLine: String? = nil, artist: String? = nil, + artistIds: [UUID]? = nil, + attractionLights: [Int]? = nil, booster: Bool, borderColor: BorderColor, cardBackId: UUID? = nil, @@ -263,10 +279,12 @@ public struct Card: Codable, Identifiable, Hashable, Sendable { setType: MTGSet.Kind, setUri: String, set: String, + setId: UUID, storySpotlight: Bool, textless: Bool, variation: Bool, variationOf: UUID? = nil, + securityStamp: SecurityStamp? = nil, watermark: String? = nil, preview: Preview? = nil ) { @@ -276,7 +294,8 @@ public struct Card: Codable, Identifiable, Hashable, Sendable { self.multiverseIds = multiverseIds self.tcgplayerId = tcgplayerId self.tcgplayerEtchedId = tcgplayerEtchedId - self.cardMarketId = cardMarketId + self.cardmarketId = cardmarketId + self.resourceId = resourceId self.id = id self.oracleId = oracleId self.lang = lang @@ -302,6 +321,7 @@ public struct Card: Codable, Identifiable, Hashable, Sendable { self.manaCost = manaCost self.name = name self.oracleText = oracleText + self.pennyRank = pennyRank self.oversized = oversized self.power = power self.producedMana = producedMana @@ -309,6 +329,8 @@ public struct Card: Codable, Identifiable, Hashable, Sendable { self.toughness = toughness self.typeLine = typeLine self.artist = artist + self.artistIds = artistIds + self.attractionLights = attractionLights self.booster = booster self.borderColor = borderColor self.cardBackId = cardBackId @@ -343,10 +365,12 @@ public struct Card: Codable, Identifiable, Hashable, Sendable { self.setType = setType self.setUri = setUri self.set = set + self.setId = setId self.storySpotlight = storySpotlight self.textless = textless self.variation = variation self.variationOf = variationOf + self.securityStamp = securityStamp self.watermark = watermark self.preview = preview } diff --git a/Sources/ScryfallKit/Models/Catalog.swift b/Sources/ScryfallKit/Models/Catalog.swift index dafc828..90eb211 100644 --- a/Sources/ScryfallKit/Models/Catalog.swift +++ b/Sources/ScryfallKit/Models/Catalog.swift @@ -30,13 +30,19 @@ public struct Catalog: Codable, Sendable { case watermarks } + /// A link to the current catalog on Scryfall’s API + /// - Note: Scryfall's documentation lists this as required but it's absent from the catalog + /// returned by /cards/autocomplete + public var uri: String? + /// The number of items in the `data` array public var totalValues: Int /// An array of data points public var data: [String] - public init(totalValues: Int, data: [String]) { + public init(uri: String? = nil, totalValues: Int, data: [String]) { + self.uri = uri self.totalValues = totalValues self.data = data } diff --git a/Sources/ScryfallKit/Models/MTGSet.swift b/Sources/ScryfallKit/Models/MTGSet.swift index dbfddfb..e8ab8a9 100644 --- a/Sources/ScryfallKit/Models/MTGSet.swift +++ b/Sources/ScryfallKit/Models/MTGSet.swift @@ -64,6 +64,8 @@ public struct MTGSet: Codable, Identifiable, Hashable, Sendable { public var code: String /// The unique code for this set on MTGO, which may differ from the regular code. public var mtgoCode: String? + /// The unique code for this set on MTG Arena, which may differ from the regular code. + public var arenaCode: String? /// This set’s ID on [TCGplayer’s API](https://docs.tcgplayer.com/docs), also known as the groupId. public var tcgplayerId: Int? /// The English name of the set. @@ -103,6 +105,7 @@ public struct MTGSet: Codable, Identifiable, Hashable, Sendable { id: UUID, code: String, mtgoCode: String? = nil, + arenaCode: String? = nil, tcgplayerId: Int? = nil, name: String, setType: Kind, @@ -123,6 +126,7 @@ public struct MTGSet: Codable, Identifiable, Hashable, Sendable { self.id = id self.code = code self.mtgoCode = mtgoCode + self.arenaCode = arenaCode self.tcgplayerId = tcgplayerId self.name = name self.setType = setType diff --git a/Sources/ScryfallKit/Models/UnknownDecodable.swift b/Sources/ScryfallKit/Models/UnknownDecodable.swift index 59b147a..2627b80 100644 --- a/Sources/ScryfallKit/Models/UnknownDecodable.swift +++ b/Sources/ScryfallKit/Models/UnknownDecodable.swift @@ -24,4 +24,5 @@ extension UnknownDecodable { extension Card.FrameEffect: UnknownDecodable {} extension Card.Layout: UnknownDecodable {} +extension Card.SecurityStamp: UnknownDecodable {} extension MTGSet.Kind: UnknownDecodable {} diff --git a/Tests/ScryfallKitTests/CaseIterableTests.swift b/Tests/ScryfallKitTests/CaseIterableTests.swift index cbe7422..68ab57b 100644 --- a/Tests/ScryfallKitTests/CaseIterableTests.swift +++ b/Tests/ScryfallKitTests/CaseIterableTests.swift @@ -110,6 +110,23 @@ final class CaseIterableTests: XCTestCase { XCTAssertTrue(contains) } + func testSecurityStamp() { + let stub = Card.SecurityStamp.oval + let contains = switch stub { + case .oval: Card.SecurityStamp.allCases.contains(.oval) + case .triangle: Card.SecurityStamp.allCases.contains(.triangle) + case .acorn: Card.SecurityStamp.allCases.contains(.acorn) + case .circle: Card.SecurityStamp.allCases.contains(.circle) + case .arena: Card.SecurityStamp.allCases.contains(.arena) + case .heart: Card.SecurityStamp.allCases.contains(.heart) + case .unknown(let string): + // Unknown case shouldn't be in allCases + !Card.SecurityStamp.allCases.contains(.unknown(string)) + } + + XCTAssertTrue(contains) + } + func testSetType() { let stub = MTGSet.Kind.funny let contains = switch stub {