diff --git a/.swiftlint.yml b/.swiftlint.yml deleted file mode 100644 index e14069c..0000000 --- a/.swiftlint.yml +++ /dev/null @@ -1,10 +0,0 @@ -included: - - Sources - - Tests - -identifier_name: - min_length: 2 - -disabled_rules: - - line_length - - nesting diff --git a/Package.swift b/Package.swift index 1550d16..97fe625 100644 --- a/Package.swift +++ b/Package.swift @@ -4,23 +4,23 @@ import PackageDescription let package = Package( - name: "ScryfallKit", - platforms: [.macOS(.v10_13), .iOS(.v12)], - products: [ - .library( - name: "ScryfallKit", - targets: ["ScryfallKit"]) - ], - dependencies: [ - .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), - ], - targets: [ - .target( - name: "ScryfallKit" - ), - .testTarget( - name: "ScryfallKitTests", - dependencies: ["ScryfallKit"] - ) - ] + name: "ScryfallKit", + platforms: [.macOS(.v10_13), .iOS(.v12)], + products: [ + .library( + name: "ScryfallKit", + targets: ["ScryfallKit"]) + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0") + ], + targets: [ + .target( + name: "ScryfallKit" + ), + .testTarget( + name: "ScryfallKitTests", + dependencies: ["ScryfallKit"] + ), + ] ) diff --git a/Package@swift-5.7.swift b/Package@swift-5.7.swift index 64717c7..dd4372f 100644 --- a/Package@swift-5.7.swift +++ b/Package@swift-5.7.swift @@ -4,22 +4,22 @@ import PackageDescription let package = Package( - name: "ScryfallKit", - platforms: [.macOS(.v10_13), .iOS(.v12)], - products: [ - .library( - name: "ScryfallKit", - targets: ["ScryfallKit"]) - ], - dependencies: [ - .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), - ], - targets: [ - .target( - name: "ScryfallKit" - ), - .testTarget( - name: "ScryfallKitTests", - dependencies: ["ScryfallKit"]) - ] + name: "ScryfallKit", + platforms: [.macOS(.v10_13), .iOS(.v12)], + products: [ + .library( + name: "ScryfallKit", + targets: ["ScryfallKit"]) + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0") + ], + targets: [ + .target( + name: "ScryfallKit" + ), + .testTarget( + name: "ScryfallKitTests", + dependencies: ["ScryfallKit"]), + ] ) diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift index c6f5e4c..d2915e7 100644 --- a/Package@swift-5.9.swift +++ b/Package@swift-5.9.swift @@ -4,23 +4,23 @@ import PackageDescription let package = Package( - name: "ScryfallKit", - platforms: [.macOS(.v10_13), .iOS(.v12)], - products: [ - .library( - name: "ScryfallKit", - targets: ["ScryfallKit"]) - ], - dependencies: [ - .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), - ], - targets: [ - .target( - name: "ScryfallKit" - ), - .testTarget( - name: "ScryfallKitTests", - dependencies: ["ScryfallKit"] - ) - ] + name: "ScryfallKit", + platforms: [.macOS(.v10_13), .iOS(.v12)], + products: [ + .library( + name: "ScryfallKit", + targets: ["ScryfallKit"]) + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0") + ], + targets: [ + .target( + name: "ScryfallKit" + ), + .testTarget( + name: "ScryfallKitTests", + dependencies: ["ScryfallKit"] + ), + ] ) diff --git a/SampleCode/ScryfallSearcher/ScryfallSearcher/CardView.swift b/SampleCode/ScryfallSearcher/ScryfallSearcher/CardView.swift index e851977..3b9d98e 100644 --- a/SampleCode/ScryfallSearcher/ScryfallSearcher/CardView.swift +++ b/SampleCode/ScryfallSearcher/ScryfallSearcher/CardView.swift @@ -2,54 +2,54 @@ // CardView.swift // -import SwiftUI import ScryfallKit +import SwiftUI struct CardView: View { - var card: Card - - var body: some View { - VStack { - AsyncImage(url: card.getImageURL(type: .normal)) { image in - image - .resizable() - .scaledToFit() - } placeholder: { - Text(card.name) - ProgressView() - } - - GroupBox { - HStack { - Text(card.name) - Spacer() - Text(card.manaCost ?? "") - } - - Divider() - - Text(card.oracleText ?? "") - .padding(.bottom) - Text(card.flavorText ?? "") - .italic() - - if let powerAndToughness = card.powerAndToughness { - Divider() - HStack { - Spacer() - Text(powerAndToughness) - } - } - } + var card: Card + + var body: some View { + VStack { + AsyncImage(url: card.getImageURL(type: .normal)) { image in + image + .resizable() + .scaledToFit() + } placeholder: { + Text(card.name) + ProgressView() + } + + GroupBox { + HStack { + Text(card.name) + Spacer() + Text(card.manaCost ?? "") } + + Divider() + + Text(card.oracleText ?? "") + .padding(.bottom) + Text(card.flavorText ?? "") + .italic() + + if let powerAndToughness = card.powerAndToughness { + Divider() + HStack { + Spacer() + Text(powerAndToughness) + } + } + } } + } } extension Card { - var powerAndToughness: String? { - guard let power, let toughness else { return nil } - return "\(power)/\(toughness)" - } + var powerAndToughness: String? { + guard let power, let toughness else { return nil } + return "\(power)/\(toughness)" + } } //struct SwiftUIView_Previews: PreviewProvider { diff --git a/SampleCode/ScryfallSearcher/ScryfallSearcher/ContentView.swift b/SampleCode/ScryfallSearcher/ScryfallSearcher/ContentView.swift index 1c8ae0c..3154cfb 100644 --- a/SampleCode/ScryfallSearcher/ScryfallSearcher/ContentView.swift +++ b/SampleCode/ScryfallSearcher/ScryfallSearcher/ContentView.swift @@ -3,86 +3,86 @@ // ScryfallSearcher // -import SwiftUI import ScryfallKit +import SwiftUI struct ContentView: View { - private let client = ScryfallClient() - private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) + private let client = ScryfallClient() + private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) - @State private var loading = false - @State private var query = "" - @State private var cards = [Card]() - @State private var error: String? - @State private var showError = false + @State private var loading = false + @State private var query = "" + @State private var cards = [Card]() + @State private var error: String? + @State private var showError = false - var body: some View { - ScrollView { - TextField("Search for Magic: the Gathering cards", text: $query) - .textFieldStyle(.roundedBorder) - .autocorrectionDisabled(true) - .textInputAutocapitalization(.never) - .onSubmit { - search(query: query) - } + var body: some View { + ScrollView { + TextField("Search for Magic: the Gathering cards", text: $query) + .textFieldStyle(.roundedBorder) + .autocorrectionDisabled(true) + .textInputAutocapitalization(.never) + .onSubmit { + search(query: query) + } - if loading { - ProgressView() - } else if cards.isEmpty { - Text("Perform a search to view cards") - } else { - LazyVGrid(columns: columns) { - ForEach(cards) { card in - AsyncImage(url: card.getImageURL(type: .normal)) { image in - image - .resizable() - .scaledToFit() - } placeholder: { - Text(card.name) - ProgressView() - } - } - } + if loading { + ProgressView() + } else if cards.isEmpty { + Text("Perform a search to view cards") + } else { + LazyVGrid(columns: columns) { + ForEach(cards) { card in + AsyncImage(url: card.getImageURL(type: .normal)) { image in + image + .resizable() + .scaledToFit() + } placeholder: { + Text(card.name) + ProgressView() } - - Spacer() + } } - .alert("Error", isPresented: $showError, presenting: error, actions: { _ in }) { error in - Text(error) - } - .refreshable { - guard !query.isEmpty else { return } - search(query: query) - } - .padding() - } + } - private func search(query: String) { - error = nil - loading = true + Spacer() + } + .alert("Error", isPresented: $showError, presenting: error, actions: { _ in }) { error in + Text(error) + } + .refreshable { + guard !query.isEmpty else { return } + search(query: query) + } + .padding() + } - Task { - do { - let results = try await client.searchCards(query: query) - await MainActor.run { - cards = results.data - } - } catch { - await MainActor.run { - showError = true - self.error = error.localizedDescription - } - } + private func search(query: String) { + error = nil + loading = true - await MainActor.run { - loading = false - } + Task { + do { + let results = try await client.searchCards(query: query) + await MainActor.run { + cards = results.data } + } catch { + await MainActor.run { + showError = true + self.error = error.localizedDescription + } + } + + await MainActor.run { + loading = false + } } + } } struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } + static var previews: some View { + ContentView() + } } diff --git a/SampleCode/ScryfallSearcher/ScryfallSearcher/ScryfallSearcherApp.swift b/SampleCode/ScryfallSearcher/ScryfallSearcher/ScryfallSearcherApp.swift index 6f9f3bb..7f25aaf 100644 --- a/SampleCode/ScryfallSearcher/ScryfallSearcher/ScryfallSearcherApp.swift +++ b/SampleCode/ScryfallSearcher/ScryfallSearcher/ScryfallSearcherApp.swift @@ -9,9 +9,9 @@ import SwiftUI @main struct ScryfallSearcherApp: App { - var body: some Scene { - WindowGroup { - SearchView() - } + var body: some Scene { + WindowGroup { + SearchView() } + } } diff --git a/SampleCode/ScryfallSearcher/ScryfallSearcher/SearchView.swift b/SampleCode/ScryfallSearcher/ScryfallSearcher/SearchView.swift index 8bd0350..99de9ef 100644 --- a/SampleCode/ScryfallSearcher/ScryfallSearcher/SearchView.swift +++ b/SampleCode/ScryfallSearcher/ScryfallSearcher/SearchView.swift @@ -1,69 +1,69 @@ -import SwiftUI import ScryfallKit +import SwiftUI struct SearchView: View { - private let client = ScryfallClient() - private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) + private let client = ScryfallClient() + private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) - @State private var loading = false - @State private var query = "" - @State private var cards = [Card]() - @State private var error: String? - @State private var showError = false + @State private var loading = false + @State private var query = "" + @State private var cards = [Card]() + @State private var error: String? + @State private var showError = false - var body: some View { - NavigationStack { - ScrollView { - TextField("Search for Magic: the Gathering cards", text: $query) - .textFieldStyle(.roundedBorder) - .autocorrectionDisabled(true) - .onSubmit(search) + var body: some View { + NavigationStack { + ScrollView { + TextField("Search for Magic: the Gathering cards", text: $query) + .textFieldStyle(.roundedBorder) + .autocorrectionDisabled(true) + .onSubmit(search) - if loading { - ProgressView() - } else if cards.isEmpty { - Text("Perform a search to view cards") - } else { - LazyVGrid(columns: columns) { - ForEach(cards) { card in - NavigationLink(value: card) { - AsyncImage(url: card.getImageURL(type: .normal)) { image in - image - .resizable() - .scaledToFit() - } placeholder: { - Text(card.name) - ProgressView() - } - } - } - }.navigationDestination(for: Card.self) { CardView(card: $0) } + if loading { + ProgressView() + } else if cards.isEmpty { + Text("Perform a search to view cards") + } else { + LazyVGrid(columns: columns) { + ForEach(cards) { card in + NavigationLink(value: card) { + AsyncImage(url: card.getImageURL(type: .normal)) { image in + image + .resizable() + .scaledToFit() + } placeholder: { + Text(card.name) + ProgressView() } + } } + }.navigationDestination(for: Card.self) { CardView(card: $0) } } - .padding() - .alert("Error", isPresented: $showError, presenting: error, actions: { _ in }) { error in - Text(error) - } + } } + .padding() + .alert("Error", isPresented: $showError, presenting: error, actions: { _ in }) { error in + Text(error) + } + } - private func search() { - loading = true - error = nil - showError = false - - Task { - do { - let results = try await client.searchCards(query: query) - await MainActor.run { - cards = results.data - } - } catch { - self.error = error.localizedDescription - self.showError = true - } + private func search() { + loading = true + error = nil + showError = false - loading = false + Task { + do { + let results = try await client.searchCards(query: query) + await MainActor.run { + cards = results.data } + } catch { + self.error = error.localizedDescription + self.showError = true + } + + loading = false } + } } diff --git a/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step0CreateView.swift b/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step0CreateView.swift index aba4919..9fd3321 100644 --- a/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step0CreateView.swift +++ b/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step0CreateView.swift @@ -1,7 +1,7 @@ import SwiftUI struct SearchView: View { - var body: some View { - Text("Hello, World!") - } + var body: some View { + Text("Hello, World!") + } } diff --git a/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step1AddInput.swift b/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step1AddInput.swift index ced0dc5..5144e75 100644 --- a/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step1AddInput.swift +++ b/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step1AddInput.swift @@ -1,11 +1,11 @@ import SwiftUI struct SearchView: View { - @State private var query = "" + @State private var query = "" - var body: some View { - TextField("Search for Magic: the Gathering cards", text: $query) - .textFieldStyle(.roundedBorder) - .padding() - } + var body: some View { + TextField("Search for Magic: the Gathering cards", text: $query) + .textFieldStyle(.roundedBorder) + .padding() + } } diff --git a/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step2AddLazyVGrid.swift b/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step2AddLazyVGrid.swift index 6907507..563bafb 100644 --- a/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step2AddLazyVGrid.swift +++ b/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step2AddLazyVGrid.swift @@ -1,24 +1,23 @@ -import SwiftUI import ScryfallKit +import SwiftUI struct SearchView: View { - private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) + private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) - @State private var query = "" - @State private var cards = [Card]() + @State private var query = "" + @State private var cards = [Card]() - var body: some View { - ScrollView { - TextField("Search for Magic: the Gathering cards", text: $query) - .textFieldStyle(.roundedBorder) + var body: some View { + ScrollView { + TextField("Search for Magic: the Gathering cards", text: $query) + .textFieldStyle(.roundedBorder) - LazyVGrid(columns: columns) { - ForEach(cards) { card in + LazyVGrid(columns: columns) { + ForEach(cards) { card in - } - } } - .padding() + } } + .padding() + } } - diff --git a/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step3AddAsyncImage.swift b/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step3AddAsyncImage.swift index d9e8477..683d905 100644 --- a/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step3AddAsyncImage.swift +++ b/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step3AddAsyncImage.swift @@ -1,30 +1,30 @@ -import SwiftUI import ScryfallKit +import SwiftUI struct SearchView: View { - private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) + private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) - @State private var query = "" - @State private var cards = [Card]() + @State private var query = "" + @State private var cards = [Card]() - var body: some View { - ScrollView { - TextField("Search for Magic: the Gathering cards", text: $query) - .textFieldStyle(.roundedBorder) + var body: some View { + ScrollView { + TextField("Search for Magic: the Gathering cards", text: $query) + .textFieldStyle(.roundedBorder) - LazyVGrid(columns: columns) { - ForEach(cards) { card in - AsyncImage(url: card.getImageURL(type: .normal)) { image in - image - .resizable() - .scaledToFit() - } placeholder: { - Text(card.name) - ProgressView() - } - } - } + LazyVGrid(columns: columns) { + ForEach(cards) { card in + AsyncImage(url: card.getImageURL(type: .normal)) { image in + image + .resizable() + .scaledToFit() + } placeholder: { + Text(card.name) + ProgressView() + } } - .padding() + } } + .padding() + } } diff --git a/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step4AddSearch.swift b/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step4AddSearch.swift index d4298dd..cc2e85c 100644 --- a/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step4AddSearch.swift +++ b/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section1BasicUI/Step4AddSearch.swift @@ -1,45 +1,45 @@ -import SwiftUI import ScryfallKit +import SwiftUI struct SearchView: View { - private let client = ScryfallClient() - private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) + private let client = ScryfallClient() + private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) - @State private var query = "" - @State private var cards = [Card]() + @State private var query = "" + @State private var cards = [Card]() - var body: some View { - ScrollView { - TextField("Search for Magic: the Gathering cards", text: $query) - .textFieldStyle(.roundedBorder) - .onSubmit(search) + var body: some View { + ScrollView { + TextField("Search for Magic: the Gathering cards", text: $query) + .textFieldStyle(.roundedBorder) + .onSubmit(search) - LazyVGrid(columns: columns) { - ForEach(cards) { card in - AsyncImage(url: card.getImageURL(type: .normal)) { image in - image - .resizable() - .scaledToFit() - } placeholder: { - Text(card.name) - ProgressView() - } - } - } + LazyVGrid(columns: columns) { + ForEach(cards) { card in + AsyncImage(url: card.getImageURL(type: .normal)) { image in + image + .resizable() + .scaledToFit() + } placeholder: { + Text(card.name) + ProgressView() + } } - .padding() + } } + .padding() + } - private func search() { - Task { - do { - let results = try await client.searchCards(query: query) - await MainActor.run { - cards = results.data - } - } catch { - print(error) - } + private func search() { + Task { + do { + let results = try await client.searchCards(query: query) + await MainActor.run { + cards = results.data } + } catch { + print(error) + } } + } } diff --git a/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section2RefineUX/Step1ShowErrors.swift b/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section2RefineUX/Step1ShowErrors.swift index aa84b3c..3244951 100644 --- a/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section2RefineUX/Step1ShowErrors.swift +++ b/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section2RefineUX/Step1ShowErrors.swift @@ -1,55 +1,54 @@ -import SwiftUI import ScryfallKit +import SwiftUI struct SearchView: View { - private let client = ScryfallClient() - private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) + private let client = ScryfallClient() + private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) - @State private var query = "" - @State private var cards = [Card]() - @State private var error: String? - @State private var showError = false + @State private var query = "" + @State private var cards = [Card]() + @State private var error: String? + @State private var showError = false - var body: some View { - ScrollView { - TextField("Search for Magic: the Gathering cards", text: $query) - .textFieldStyle(.roundedBorder) - .onSubmit(search) + var body: some View { + ScrollView { + TextField("Search for Magic: the Gathering cards", text: $query) + .textFieldStyle(.roundedBorder) + .onSubmit(search) - LazyVGrid(columns: columns) { - ForEach(cards) { card in - AsyncImage(url: card.getImageURL(type: .normal)) { image in - image - .resizable() - .scaledToFit() - } placeholder: { - Text(card.name) - ProgressView() - } - } - } - } - .padding() - .alert("Error", isPresented: $showError, presenting: error, actions: { _ in }) { error in - Text(error) + LazyVGrid(columns: columns) { + ForEach(cards) { card in + AsyncImage(url: card.getImageURL(type: .normal)) { image in + image + .resizable() + .scaledToFit() + } placeholder: { + Text(card.name) + ProgressView() + } } + } } + .padding() + .alert("Error", isPresented: $showError, presenting: error, actions: { _ in }) { error in + Text(error) + } + } - private func search() { - error = nil - showError = false + private func search() { + error = nil + showError = false - Task { - do { - let results = try await client.searchCards(query: query) - await MainActor.run { - cards = results.data - } - } catch { - self.error = error.localizedDescription - self.showError = true - } + Task { + do { + let results = try await client.searchCards(query: query) + await MainActor.run { + cards = results.data } + } catch { + self.error = error.localizedDescription + self.showError = true + } } + } } - diff --git a/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section2RefineUX/Step2LoadingIndicator.swift b/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section2RefineUX/Step2LoadingIndicator.swift index faaef0e..15a65fa 100644 --- a/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section2RefineUX/Step2LoadingIndicator.swift +++ b/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section2RefineUX/Step2LoadingIndicator.swift @@ -1,63 +1,62 @@ -import SwiftUI import ScryfallKit +import SwiftUI struct SearchView: View { - private let client = ScryfallClient() - private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) - - @State private var loading = false - @State private var query = "" - @State private var cards = [Card]() - @State private var error: String? - @State private var showError = false - - var body: some View { - ScrollView { - TextField("Search for Magic: the Gathering cards", text: $query) - .textFieldStyle(.roundedBorder) - .onSubmit(search) - - if loading { - ProgressView() - } else { - LazyVGrid(columns: columns) { - ForEach(cards) { card in - AsyncImage(url: card.getImageURL(type: .normal)) { image in - image - .resizable() - .scaledToFit() - } placeholder: { - Text(card.name) - ProgressView() - } - } - } + private let client = ScryfallClient() + private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) + + @State private var loading = false + @State private var query = "" + @State private var cards = [Card]() + @State private var error: String? + @State private var showError = false + + var body: some View { + ScrollView { + TextField("Search for Magic: the Gathering cards", text: $query) + .textFieldStyle(.roundedBorder) + .onSubmit(search) + + if loading { + ProgressView() + } else { + LazyVGrid(columns: columns) { + ForEach(cards) { card in + AsyncImage(url: card.getImageURL(type: .normal)) { image in + image + .resizable() + .scaledToFit() + } placeholder: { + Text(card.name) + ProgressView() } + } } - .padding() - .alert("Error", isPresented: $showError, presenting: error, actions: { _ in }) { error in - Text(error) - } + } } - - private func search() { - loading = true - error = nil - showError = false - - Task { - do { - let results = try await client.searchCards(query: query) - await MainActor.run { - cards = results.data - } - } catch { - self.error = error.localizedDescription - self.showError = true - } - - loading = false + .padding() + .alert("Error", isPresented: $showError, presenting: error, actions: { _ in }) { error in + Text(error) + } + } + + private func search() { + loading = true + error = nil + showError = false + + Task { + do { + let results = try await client.searchCards(query: query) + await MainActor.run { + cards = results.data } + } catch { + self.error = error.localizedDescription + self.showError = true + } + + loading = false } + } } - diff --git a/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section2RefineUX/Step3DisableAutocorrect.swift b/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section2RefineUX/Step3DisableAutocorrect.swift index 9902c10..ec3606d 100644 --- a/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section2RefineUX/Step3DisableAutocorrect.swift +++ b/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section2RefineUX/Step3DisableAutocorrect.swift @@ -1,64 +1,63 @@ -import SwiftUI import ScryfallKit +import SwiftUI struct SearchView: View { - private let client = ScryfallClient() - private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) - - @State private var loading = false - @State private var query = "" - @State private var cards = [Card]() - @State private var error: String? - @State private var showError = false - - var body: some View { - ScrollView { - TextField("Search for Magic: the Gathering cards", text: $query) - .textFieldStyle(.roundedBorder) - .autocorrectionDisabled(true) - .onSubmit(search) - - if loading { - ProgressView() - } else { - LazyVGrid(columns: columns) { - ForEach(cards) { card in - AsyncImage(url: card.getImageURL(type: .normal)) { image in - image - .resizable() - .scaledToFit() - } placeholder: { - Text(card.name) - ProgressView() - } - } - } + private let client = ScryfallClient() + private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) + + @State private var loading = false + @State private var query = "" + @State private var cards = [Card]() + @State private var error: String? + @State private var showError = false + + var body: some View { + ScrollView { + TextField("Search for Magic: the Gathering cards", text: $query) + .textFieldStyle(.roundedBorder) + .autocorrectionDisabled(true) + .onSubmit(search) + + if loading { + ProgressView() + } else { + LazyVGrid(columns: columns) { + ForEach(cards) { card in + AsyncImage(url: card.getImageURL(type: .normal)) { image in + image + .resizable() + .scaledToFit() + } placeholder: { + Text(card.name) + ProgressView() } + } } - .padding() - .alert("Error", isPresented: $showError, presenting: error, actions: { _ in }) { error in - Text(error) - } + } } - - private func search() { - loading = true - error = nil - showError = false - - Task { - do { - let results = try await client.searchCards(query: query) - await MainActor.run { - cards = results.data - } - } catch { - self.error = error.localizedDescription - self.showError = true - } - - loading = false + .padding() + .alert("Error", isPresented: $showError, presenting: error, actions: { _ in }) { error in + Text(error) + } + } + + private func search() { + loading = true + error = nil + showError = false + + Task { + do { + let results = try await client.searchCards(query: query) + await MainActor.run { + cards = results.data } + } catch { + self.error = error.localizedDescription + self.showError = true + } + + loading = false } + } } - diff --git a/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section2RefineUX/Step4ZeroState.swift b/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section2RefineUX/Step4ZeroState.swift index 3d3d650..efa6123 100644 --- a/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section2RefineUX/Step4ZeroState.swift +++ b/Sources/ScryfallKit/Documentation.docc/SearchTutorial/Resources/Code/Section2RefineUX/Step4ZeroState.swift @@ -1,65 +1,65 @@ -import SwiftUI import ScryfallKit +import SwiftUI struct SearchView: View { - private let client = ScryfallClient() - private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) + private let client = ScryfallClient() + private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) - @State private var loading = false - @State private var query = "" - @State private var cards = [Card]() - @State private var error: String? - @State private var showError = false + @State private var loading = false + @State private var query = "" + @State private var cards = [Card]() + @State private var error: String? + @State private var showError = false - var body: some View { - ScrollView { - TextField("Search for Magic: the Gathering cards", text: $query) - .textFieldStyle(.roundedBorder) - .autocorrectionDisabled(true) - .onSubmit(search) + var body: some View { + ScrollView { + TextField("Search for Magic: the Gathering cards", text: $query) + .textFieldStyle(.roundedBorder) + .autocorrectionDisabled(true) + .onSubmit(search) - if loading { - ProgressView() - } else if cards.isEmpty { - Text("Perform a search to view cards") - } else { - LazyVGrid(columns: columns) { - ForEach(cards) { card in - AsyncImage(url: card.getImageURL(type: .normal)) { image in - image - .resizable() - .scaledToFit() - } placeholder: { - Text(card.name) - ProgressView() - } - } - } + if loading { + ProgressView() + } else if cards.isEmpty { + Text("Perform a search to view cards") + } else { + LazyVGrid(columns: columns) { + ForEach(cards) { card in + AsyncImage(url: card.getImageURL(type: .normal)) { image in + image + .resizable() + .scaledToFit() + } placeholder: { + Text(card.name) + ProgressView() } + } } - .padding() - .alert("Error", isPresented: $showError, presenting: error, actions: { _ in }) { error in - Text(error) - } + } } + .padding() + .alert("Error", isPresented: $showError, presenting: error, actions: { _ in }) { error in + Text(error) + } + } - private func search() { - loading = true - error = nil - showError = false - - Task { - do { - let results = try await client.searchCards(query: query) - await MainActor.run { - cards = results.data - } - } catch { - self.error = error.localizedDescription - self.showError = true - } + private func search() { + loading = true + error = nil + showError = false - loading = false + Task { + do { + let results = try await client.searchCards(query: query) + await MainActor.run { + cards = results.data } + } catch { + self.error = error.localizedDescription + self.showError = true + } + + loading = false } + } } diff --git a/Sources/ScryfallKit/Extensions/Card+helpers.swift b/Sources/ScryfallKit/Extensions/Card+helpers.swift index 3889d4d..2ec3b82 100644 --- a/Sources/ScryfallKit/Extensions/Card+helpers.swift +++ b/Sources/ScryfallKit/Extensions/Card+helpers.swift @@ -8,108 +8,110 @@ import Foundation import OSLog -public extension Card { - /// Get the legality of a card in a given format - /// - Parameter format: The format to get legality for - /// - Returns: The legality of this card for the given format - func getLegality(for format: Format) -> Legality { - switch format { - case .brawl: - return legalities.brawl ?? .notLegal - case .standard: - return legalities.standard ?? .notLegal - case .historic: - return legalities.historic ?? .notLegal - case .pioneer: - return legalities.pioneer ?? .notLegal - case .modern: - return legalities.modern ?? .notLegal - case .legacy: - return legalities.legacy ?? .notLegal - case .pauper: - return legalities.pauper ?? .notLegal - case .vintage: - return legalities.vintage ?? .notLegal - case .penny: - return legalities.penny ?? .notLegal - case .commander: - return legalities.commander ?? .notLegal - } +extension Card { + /// Get the legality of a card in a given format + /// - Parameter format: The format to get legality for + /// - Returns: The legality of this card for the given format + public func getLegality(for format: Format) -> Legality { + switch format { + case .brawl: + return legalities.brawl ?? .notLegal + case .standard: + return legalities.standard ?? .notLegal + case .historic: + return legalities.historic ?? .notLegal + case .pioneer: + return legalities.pioneer ?? .notLegal + case .modern: + return legalities.modern ?? .notLegal + case .legacy: + return legalities.legacy ?? .notLegal + case .pauper: + return legalities.pauper ?? .notLegal + case .vintage: + return legalities.vintage ?? .notLegal + case .penny: + return legalities.penny ?? .notLegal + case .commander: + return legalities.commander ?? .notLegal } + } - /// Get the price string for a given currency - /// - Parameter type: The currency you want the price string for - /// - Returns: The price string, if present. Nil if not - 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 - } + /// Get the price string for a given currency + /// - Parameter type: The currency you want the price string for + /// - Returns: The price string, if present. Nil if not + 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 } + } } // Multifaced helpers -public extension Card { - /// Get an attribute for a multifaced card - /// - Parameters: - /// - keyPath: A KeyPath for the desired attribute - /// - useSecondFace: Whether or not the attribute from the second face should be used - /// - Returns: The requested property from the desired face - func getAttributeForFace(keyPath: KeyPath, useSecondFace: Bool) throws -> PropType { - guard let faces = cardFaces else { throw ScryfallKitError.singleFacedCard } - return useSecondFace ? faces[1][keyPath: keyPath] : faces[0][keyPath: keyPath] - } - - /// Get the URL for a specific image type - /// - Parameters: - /// - type: The desired image type - /// - getSecondFace: Whether or not the second face of a card should be retrieved - func getImageURL(type: ImageType, getSecondFace: Bool = false) -> URL? { - var cardImageUris = self.imageUris +extension Card { + /// Get an attribute for a multifaced card + /// - Parameters: + /// - keyPath: A KeyPath for the desired attribute + /// - useSecondFace: Whether or not the attribute from the second face should be used + /// - Returns: The requested property from the desired face + public func getAttributeForFace(keyPath: KeyPath, useSecondFace: Bool) + throws -> PropType + { + guard let faces = cardFaces else { throw ScryfallKitError.singleFacedCard } + return useSecondFace ? faces[1][keyPath: keyPath] : faces[0][keyPath: keyPath] + } - if let faces = cardFaces { - // Some cards have multiple "faces" but don't have unique images for those faces - // Flip and split cards are examples of this - let faceImageUris = getSecondFace ? faces[1].imageUris : faces[0].imageUris - if faceImageUris != nil { - cardImageUris = faceImageUris - } - } + /// Get the URL for a specific image type + /// - Parameters: + /// - type: The desired image type + /// - getSecondFace: Whether or not the second face of a card should be retrieved + public func getImageURL(type: ImageType, getSecondFace: Bool = false) -> URL? { + var cardImageUris = self.imageUris - guard let uris = cardImageUris else { - return nil - } + if let faces = cardFaces { + // Some cards have multiple "faces" but don't have unique images for those faces + // Flip and split cards are examples of this + let faceImageUris = getSecondFace ? faces[1].imageUris : faces[0].imageUris + if faceImageUris != nil { + cardImageUris = faceImageUris + } + } - guard let uri = uris.uri(for: type) else { - if #available(iOS 14.0, macOS 11.0, *) { - Logger.main.error("No URI for image type \(type.rawValue)") - } else { - print("No URI for image type \(type)") - } - return nil - } + guard let uris = cardImageUris else { + return nil + } - return URL(string: uri) + guard let uri = uris.uri(for: type) else { + if #available(iOS 14.0, macOS 11.0, *) { + Logger.main.error("No URI for image type \(type.rawValue)") + } else { + print("No URI for image type \(type)") + } + return nil } - /// Get an image URL with backups in case the card doesn't have the desired ImageType - /// - /// Example: If you want the normal sized image uri but are okay with the large size if normal isn't available, you would call this method as: `getImageURL(types: [.normal, .large])` - /// - /// - Parameters: - /// - types: A list of ImageTypes ordered by preference - /// - getSecondFace: Whether or not the second face of a card should be retrieved - func getImageURL(types: [ImageType], getSecondFace: Bool = false) -> URL? { - for type in types { - guard let url = getImageURL(type: type, getSecondFace: getSecondFace) else { - continue - } + return URL(string: uri) + } - return url - } + /// Get an image URL with backups in case the card doesn't have the desired ImageType + /// + /// Example: If you want the normal sized image uri but are okay with the large size if normal isn't available, you would call this method as: `getImageURL(types: [.normal, .large])` + /// + /// - Parameters: + /// - types: A list of ImageTypes ordered by preference + /// - getSecondFace: Whether or not the second face of a card should be retrieved + public func getImageURL(types: [ImageType], getSecondFace: Bool = false) -> URL? { + for type in types { + guard let url = getImageURL(type: type, getSecondFace: getSecondFace) else { + continue + } - return nil + return url } + + return nil + } } diff --git a/Sources/ScryfallKit/Extensions/MTGSet+date.swift b/Sources/ScryfallKit/Extensions/MTGSet+date.swift index 87604a7..ae7f830 100644 --- a/Sources/ScryfallKit/Extensions/MTGSet+date.swift +++ b/Sources/ScryfallKit/Extensions/MTGSet+date.swift @@ -4,14 +4,14 @@ import Foundation -public extension MTGSet { - /// A `Date` created from ``releasedAt`` - var date: Date? { - guard let releasedAt = releasedAt else { return nil } +extension MTGSet { + /// A `Date` created from ``releasedAt`` + public var date: Date? { + guard let releasedAt = releasedAt else { return nil } - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd" + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" - return formatter.date(from: releasedAt) - } + return formatter.date(from: releasedAt) + } } diff --git a/Sources/ScryfallKit/Logger.swift b/Sources/ScryfallKit/Logger.swift index 33f2f21..d070490 100644 --- a/Sources/ScryfallKit/Logger.swift +++ b/Sources/ScryfallKit/Logger.swift @@ -8,9 +8,9 @@ import OSLog @available(macOS 11.0, *) @available(iOS 14.0, *) extension Logger { - static let subsystem = "dev.hearst.scryfallkit" - static let main = Logger(subsystem: subsystem, category: "ScryfallKit") - static let client = Logger(subsystem: subsystem, category: "ScryfallClient") - static let network = Logger(subsystem: subsystem, category: "Network") - static let decoder = Logger(subsystem: subsystem, category: "Decoder") + static let subsystem = "dev.hearst.scryfallkit" + static let main = Logger(subsystem: subsystem, category: "ScryfallKit") + static let client = Logger(subsystem: subsystem, category: "ScryfallClient") + static let network = Logger(subsystem: subsystem, category: "Network") + static let decoder = Logger(subsystem: subsystem, category: "Decoder") } diff --git a/Sources/ScryfallKit/Models/Card/Card+Face.swift b/Sources/ScryfallKit/Models/Card/Card+Face.swift index b08289f..b0f0f61 100644 --- a/Sources/ScryfallKit/Models/Card/Card+Face.swift +++ b/Sources/ScryfallKit/Models/Card/Card+Face.swift @@ -1,96 +1,98 @@ // // Card+Face.swift -// +// import Foundation extension Card { - /// A single face of a multi-faced card + /// A single face of a multi-faced card + /// + /// [Scryfall documentation](https://scryfall.com/docs/api/layouts#card-faces) + public struct Face: Codable, Hashable, Sendable { + /// The converted mana cost of this card face, now called the "mana value" + public var cmc: Double? + /// The identifier for this card face’s oracle identity. + /// + /// For more information on this property, see [Scryfall's documentation](https://scryfall.com/docs/api/cards#core-card-fields) + public var oracleId: String? + /// The name of the artist who illustrated this card + public var artist: String? + /// 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 + public var colorIndicator: [Card.Color]? + /// An array of the colors in this card's mana cost + public var colors: [Card.Color]? + /// This card's flavor text if any + public var flavorText: String? + /// An ID for this card face's art that remains consistent across reprints + public var illustrationId: UUID? + /// An object listing available imagery for this card. + public var imageUris: ImageUris? + /// This card's starting loyalty counters if it's a planeswalker + public var loyalty: String? + /// The mana cost for this card. + /// + /// This value will be any empty string "" if the cost is absent. Remember that per the game rules, a missing mana cost and a mana cost of {0} are different values. + public var manaCost: String + /// The name of this card /// - /// [Scryfall documentation](https://scryfall.com/docs/api/layouts#card-faces) - public struct Face: Codable, Hashable, Sendable { - /// The converted mana cost of this card face, now called the "mana value" - public var cmc: Double? - /// The identifier for this card face’s oracle identity. - /// - /// For more information on this property, see [Scryfall's documentation](https://scryfall.com/docs/api/cards#core-card-fields) - public var oracleId: String? - /// The name of the artist who illustrated this card - public var artist: String? - /// 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 - public var colorIndicator: [Card.Color]? - /// An array of the colors in this card's mana cost - public var colors: [Card.Color]? - /// This card's flavor text if any - public var flavorText: String? - /// An ID for this card face's art that remains consistent across reprints - public var illustrationId: UUID? - /// An object listing available imagery for this card. - public var imageUris: ImageUris? - /// This card's starting loyalty counters if it's a planeswalker - public var loyalty: String? - /// The mana cost for this card. - /// - /// This value will be any empty string "" if the cost is absent. Remember that per the game rules, a missing mana cost and a mana cost of {0} are different values. - public var manaCost: String - /// The name of this card - /// - /// If the card has multiple faces the names will be separated by a "//" such as "Wear // Tear" - public var name: String - /// The oracle text for this card - public var oracleText: String? - /// The power of this card if it's a creature - public var power: String? - /// The localized name printed on this card, if any. - public var printedName: String? - /// The localized text printed on this card, if any. - public var printedText: String? - /// The localized type line printed on this card, if any. - public var printedTypeLine: String? - /// The toughness of this card if it's a creature - public var toughness: String? - /// This card's types, separated by a space - /// - Note: Tokens don't have type lines - public var typeLine: String? - /// This card's watermark, if any - public var watermark: String? + /// If the card has multiple faces the names will be separated by a "//" such as "Wear // Tear" + public var name: String + /// The oracle text for this card + public var oracleText: String? + /// The power of this card if it's a creature + public var power: String? + /// The localized name printed on this card, if any. + public var printedName: String? + /// The localized text printed on this card, if any. + public var printedText: String? + /// The localized type line printed on this card, if any. + public var printedTypeLine: String? + /// The toughness of this card if it's a creature + public var toughness: String? + /// This card's types, separated by a space + /// - Note: Tokens don't have type lines + public var typeLine: String? + /// This card's watermark, if any + public var watermark: String? - public init(artist: String? = nil, - colorIndicator: [Card.Color]? = nil, - colors: [Card.Color]? = nil, - flavorText: String? = nil, - illustrationId: UUID? = nil, - imageUris: ImageUris? = nil, - loyalty: String? = nil, - manaCost: String, - name: String, - oracleText: String? = nil, - power: String? = nil, - printedName: String? = nil, - printedText: String? = nil, - printedTypeLine: String? = nil, - toughness: String? = nil, - typeLine: String? = nil, - watermark: String? = nil) { - self.artist = artist - self.colorIndicator = colorIndicator - self.colors = colors - self.flavorText = flavorText - self.illustrationId = illustrationId - self.imageUris = imageUris - self.loyalty = loyalty - self.manaCost = manaCost - self.name = name - self.oracleText = oracleText - self.power = power - self.printedName = printedName - self.printedText = printedText - self.printedTypeLine = printedTypeLine - self.toughness = toughness - self.typeLine = typeLine - self.watermark = watermark - } + public init( + artist: String? = nil, + colorIndicator: [Card.Color]? = nil, + colors: [Card.Color]? = nil, + flavorText: String? = nil, + illustrationId: UUID? = nil, + imageUris: ImageUris? = nil, + loyalty: String? = nil, + manaCost: String, + name: String, + oracleText: String? = nil, + power: String? = nil, + printedName: String? = nil, + printedText: String? = nil, + printedTypeLine: String? = nil, + toughness: String? = nil, + typeLine: String? = nil, + watermark: String? = nil + ) { + self.artist = artist + self.colorIndicator = colorIndicator + self.colors = colors + self.flavorText = flavorText + self.illustrationId = illustrationId + self.imageUris = imageUris + self.loyalty = loyalty + self.manaCost = manaCost + self.name = name + self.oracleText = oracleText + self.power = power + self.printedName = printedName + self.printedText = printedText + self.printedTypeLine = printedTypeLine + self.toughness = toughness + self.typeLine = typeLine + self.watermark = watermark } + } } diff --git a/Sources/ScryfallKit/Models/Card/Card+ImageUris.swift b/Sources/ScryfallKit/Models/Card/Card+ImageUris.swift index 8d579c3..05daa31 100644 --- a/Sources/ScryfallKit/Models/Card/Card+ImageUris.swift +++ b/Sources/ScryfallKit/Models/Card/Card+ImageUris.swift @@ -5,52 +5,55 @@ import Foundation extension Card { - /// Image URIs for each ``Card/ImageType`` - public struct ImageUris: Codable, Hashable, Sendable { - /// A link to a small full card image. - /// - Note: Designed for use as thumbnail or list icon. - public let small: String? - /// A link to a medium-sized full card image - public let normal: String? - /// A link to a large full card image - public let large: String? - /// A link to a transparent, rounded full card PNG. - /// - Note: This is the best image to use for videos or other high-quality content. - public let png: String? - /// A link to a rectangular crop of the card’s art only. - /// - Note: Not guaranteed to be perfect for cards with outlier designs or strange frame arrangements - public let artCrop: String? - /// A link to a full card image with the rounded corners and the majority of the border cropped off. - /// - Note: Designed for dated contexts where rounded images can’t be used. - public let borderCrop: String? + /// Image URIs for each ``Card/ImageType`` + public struct ImageUris: Codable, Hashable, Sendable { + /// A link to a small full card image. + /// - Note: Designed for use as thumbnail or list icon. + public let small: String? + /// A link to a medium-sized full card image + public let normal: String? + /// A link to a large full card image + public let large: String? + /// A link to a transparent, rounded full card PNG. + /// - Note: This is the best image to use for videos or other high-quality content. + public let png: String? + /// A link to a rectangular crop of the card’s art only. + /// - Note: Not guaranteed to be perfect for cards with outlier designs or strange frame arrangements + public let artCrop: String? + /// A link to a full card image with the rounded corners and the majority of the border cropped off. + /// - Note: Designed for dated contexts where rounded images can’t be used. + public let borderCrop: String? - public init(small: String?, normal: String?, large: String?, png: String?, artCrop: String?, borderCrop: String?) { - self.small = small - self.normal = normal - self.large = large - self.png = png - self.artCrop = artCrop - self.borderCrop = borderCrop - } + public init( + small: String?, normal: String?, large: String?, png: String?, artCrop: String?, + borderCrop: String? + ) { + self.small = small + self.normal = normal + self.large = large + self.png = png + self.artCrop = artCrop + self.borderCrop = borderCrop + } - /// Get the URI for a specific image type - /// - Parameter type: The image type to retrieve the URI for - /// - Returns: The URI, if present - public func uri(for type: Card.ImageType) -> String? { - switch type { - case .artCrop: - return artCrop - case .borderCrop: - return borderCrop - case .large: - return large - case .png: - return png - case .normal: - return normal - case .small: - return small - } - } + /// Get the URI for a specific image type + /// - Parameter type: The image type to retrieve the URI for + /// - Returns: The URI, if present + public func uri(for type: Card.ImageType) -> String? { + switch type { + case .artCrop: + return artCrop + case .borderCrop: + return borderCrop + case .large: + return large + case .png: + return png + case .normal: + return normal + case .small: + return small + } } + } } diff --git a/Sources/ScryfallKit/Models/Card/Card+Legalities.swift b/Sources/ScryfallKit/Models/Card/Card+Legalities.swift index 22f4999..94247d5 100644 --- a/Sources/ScryfallKit/Models/Card/Card+Legalities.swift +++ b/Sources/ScryfallKit/Models/Card/Card+Legalities.swift @@ -1,43 +1,45 @@ // // Card+Legalities.swift -// +// import Foundation extension Card { - /// The legality of a Magic card in each format - public struct Legalities: Codable, Hashable, Sendable { - public let standard: Legality? - public let historic: Legality? - public let pioneer: Legality? - public let modern: Legality? - public let legacy: Legality? - public let pauper: Legality? - public let vintage: Legality? - public let penny: Legality? - public let commander: Legality? - public let brawl: Legality? + /// The legality of a Magic card in each format + public struct Legalities: Codable, Hashable, Sendable { + public let standard: Legality? + public let historic: Legality? + public let pioneer: Legality? + public let modern: Legality? + public let legacy: Legality? + public let pauper: Legality? + public let vintage: Legality? + public let penny: Legality? + public let commander: Legality? + public let brawl: Legality? - public init(standard: Legality?, - historic: Legality?, - pioneer: Legality?, - modern: Legality?, - legacy: Legality?, - pauper: Legality?, - vintage: Legality?, - penny: Legality?, - commander: Legality?, - brawl: Legality?) { - self.standard = standard - self.historic = historic - self.pioneer = pioneer - self.modern = modern - self.legacy = legacy - self.pauper = pauper - self.vintage = vintage - self.penny = penny - self.commander = commander - self.brawl = brawl - } + public init( + standard: Legality?, + historic: Legality?, + pioneer: Legality?, + modern: Legality?, + legacy: Legality?, + pauper: Legality?, + vintage: Legality?, + penny: Legality?, + commander: Legality?, + brawl: Legality? + ) { + self.standard = standard + self.historic = historic + self.pioneer = pioneer + self.modern = modern + self.legacy = legacy + self.pauper = pauper + self.vintage = vintage + self.penny = penny + self.commander = commander + self.brawl = brawl } + } } diff --git a/Sources/ScryfallKit/Models/Card/Card+ManaCost.swift b/Sources/ScryfallKit/Models/Card/Card+ManaCost.swift index 084c53a..e3f2e31 100644 --- a/Sources/ScryfallKit/Models/Card/Card+ManaCost.swift +++ b/Sources/ScryfallKit/Models/Card/Card+ManaCost.swift @@ -5,32 +5,35 @@ import Foundation extension Card { - /// The mana cost of a card - /// - /// Returned by ``ScryfallClient/parseManaCost(_:completion:)`` - /// - /// [Scryfall documentation](https://scryfall.com/docs/api/colors) - public struct ManaCost: Codable { - /// The normalized cost, with correctly-ordered and wrapped mana symbols. - public var cost: String - /// The mana value. If you submit Un-set mana symbols, this decimal could include fractional parts. - public var cmc: Double - /// The colors of the given cost. - public var colors: [Card.Color] - /// True if the cost is colorless. - public var colorless: Bool - /// True if the cost is monocolored. - public var monocolored: Bool - /// True if the cost is multicolored. - public var multicolored: Bool + /// The mana cost of a card + /// + /// Returned by ``ScryfallClient/parseManaCost(_:completion:)`` + /// + /// [Scryfall documentation](https://scryfall.com/docs/api/colors) + public struct ManaCost: Codable { + /// The normalized cost, with correctly-ordered and wrapped mana symbols. + public var cost: String + /// The mana value. If you submit Un-set mana symbols, this decimal could include fractional parts. + public var cmc: Double + /// The colors of the given cost. + public var colors: [Card.Color] + /// True if the cost is colorless. + public var colorless: Bool + /// True if the cost is monocolored. + public var monocolored: Bool + /// True if the cost is multicolored. + public var multicolored: Bool - public init(cost: String, cmc: Double, colors: [Card.Color], colorless: Bool, monocolored: Bool, multicolored: Bool) { - self.cost = cost - self.cmc = cmc - self.colors = colors - self.colorless = colorless - self.monocolored = monocolored - self.multicolored = multicolored - } + public init( + cost: String, cmc: Double, colors: [Card.Color], colorless: Bool, monocolored: Bool, + multicolored: Bool + ) { + self.cost = cost + self.cmc = cmc + self.colors = colors + self.colorless = colorless + self.monocolored = monocolored + self.multicolored = multicolored } + } } diff --git a/Sources/ScryfallKit/Models/Card/Card+Preview.swift b/Sources/ScryfallKit/Models/Card/Card+Preview.swift index 9604382..067b107 100644 --- a/Sources/ScryfallKit/Models/Card/Card+Preview.swift +++ b/Sources/ScryfallKit/Models/Card/Card+Preview.swift @@ -1,23 +1,23 @@ // // Card+Preview.swift -// +// import Foundation 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 - /// A link to the preview for this card. - public var sourceUri: String - /// The date this card was previewed. - public var previewedAt: String + /// 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 + /// A link to the preview for this card. + public var sourceUri: String + /// The date this card was previewed. + public var previewedAt: String - public init(source: String, sourceUri: String, previewedAt: String) { - self.source = source - self.sourceUri = sourceUri - self.previewedAt = previewedAt - } + public init(source: String, sourceUri: String, previewedAt: String) { + self.source = source + self.sourceUri = sourceUri + self.previewedAt = previewedAt } + } } diff --git a/Sources/ScryfallKit/Models/Card/Card+Prices.swift b/Sources/ScryfallKit/Models/Card/Card+Prices.swift index 40c8e4e..a2d801f 100644 --- a/Sources/ScryfallKit/Models/Card/Card+Prices.swift +++ b/Sources/ScryfallKit/Models/Card/Card+Prices.swift @@ -3,22 +3,23 @@ // extension Card { - /// Daily price information for a Magic card - public struct Prices: Codable, Hashable, Sendable { - /// The price of this card in tix - public var tix: String? - /// The price of this card in usd - public var usd: String? - /// The price of this card's foil printing in usd - public var usdFoil: String? - /// The price of this card in eur. - public var eur: String? + /// Daily price information for a Magic card + public struct Prices: Codable, Hashable, Sendable { + /// The price of this card in tix + public var tix: String? + /// The price of this card in usd + public var usd: String? + /// The price of this card's foil printing in usd + public var usdFoil: String? + /// The price of this card in eur. + public var eur: String? - public init(tix: String? = nil, usd: String? = nil, usdFoil: String? = nil, eur: String? = nil) { - self.tix = tix - self.usd = usd - self.usdFoil = usdFoil - self.eur = eur - } + public init(tix: String? = nil, usd: String? = nil, usdFoil: String? = nil, eur: String? = nil) + { + self.tix = tix + self.usd = usd + self.usdFoil = usdFoil + self.eur = eur } + } } diff --git a/Sources/ScryfallKit/Models/Card/Card+RelatedCard.swift b/Sources/ScryfallKit/Models/Card/Card+RelatedCard.swift index 755c0ec..3f69359 100644 --- a/Sources/ScryfallKit/Models/Card/Card+RelatedCard.swift +++ b/Sources/ScryfallKit/Models/Card/Card+RelatedCard.swift @@ -1,43 +1,45 @@ // // Card+RelatedCard.swift -// +// import Foundation 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 { - /// The type of relationship - public enum Component: String, Codable, CaseIterable, Hashable, Sendable { - /// This card is a token that's made by the original card - case token - /// This card melds with the original card - case meldPart = "meld_part" - /// This card is the result of melding the original card with its other half - case meldResult = "meld_result" - /// This card combos with the original card - case comboPiece = "combo_piece" - } + /// 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 { + /// The type of relationship + public enum Component: String, Codable, CaseIterable, Hashable, Sendable { + /// This card is a token that's made by the original card + case token + /// This card melds with the original card + case meldPart = "meld_part" + /// This card is the result of melding the original card with its other half + case meldResult = "meld_result" + /// This card combos with the original card + case comboPiece = "combo_piece" + } - /// The Scryfall ID of this card - public var id: UUID - /// The type of relationship this card has to the original card - public var component: Component - /// The name of this card - public var name: String - /// The space separated types of this card - public var typeLine: String - /// A URI where you can retrieve a full object describing this card on Scryfall’s API - public var uri: String + /// The Scryfall ID of this card + public var id: UUID + /// The type of relationship this card has to the original card + public var component: Component + /// The name of this card + public var name: String + /// The space separated types of this card + public var typeLine: String + /// A URI where you can retrieve a full object describing this card on Scryfall’s API + public var uri: String - public init(id: UUID, component: RelatedCard.Component, name: String, typeLine: String, uri: String) { - self.id = id - self.component = component - self.name = name - self.typeLine = typeLine - self.uri = uri - } + public init( + id: UUID, component: RelatedCard.Component, name: String, typeLine: String, uri: String + ) { + self.id = id + self.component = component + self.name = name + self.typeLine = typeLine + self.uri = uri } + } } diff --git a/Sources/ScryfallKit/Models/Card/Card+Ruling.swift b/Sources/ScryfallKit/Models/Card/Card+Ruling.swift index 6ee99a7..259b0d4 100644 --- a/Sources/ScryfallKit/Models/Card/Card+Ruling.swift +++ b/Sources/ScryfallKit/Models/Card/Card+Ruling.swift @@ -5,40 +5,40 @@ import Foundation extension Card { - /// An object representing a ruling on a specific card - public struct Ruling: Codable, Identifiable, Sendable { - /// A value or combination of values that can identify a ruling. Used to find rulings for specific cards - public enum Identifier { - case scryfallID(id: String) - case mtgoID(id: Int) - case multiverseID(id: Int) - case arenaID(id: Int) - case collectorNumberSet(collectorNumber: String, set: String) - } + /// An object representing a ruling on a specific card + public struct Ruling: Codable, Identifiable, Sendable { + /// A value or combination of values that can identify a ruling. Used to find rulings for specific cards + public enum Identifier { + case scryfallID(id: String) + case mtgoID(id: Int) + case multiverseID(id: Int) + case arenaID(id: Int) + case collectorNumberSet(collectorNumber: String, set: String) + } - /// A computer-readable string indicating which company produced this ruling - public enum Source: String, Codable, Sendable { - case scryfall - case wotc - } + /// A computer-readable string indicating which company produced this ruling + public enum Source: String, Codable, Sendable { + case scryfall + case wotc + } - /// A computer-readable string indicating which company produced this ruling - public var source: Source - /// The date when the ruling or note was published. - public var publishedAt: String - /// The text of the ruling. - public var comment: String - /// The card's oracle id - public var oracleId: String + /// A computer-readable string indicating which company produced this ruling + public var source: Source + /// The date when the ruling or note was published. + public var publishedAt: String + /// The text of the ruling. + public var comment: String + /// The card's oracle id + public var oracleId: String - /// An id made by concatenating the oracle id and the comment itself - public var id: String { oracleId + comment } + /// An id made by concatenating the oracle id and the comment itself + public var id: String { oracleId + comment } - public init(source: Source, publishedAt: String, comment: String, oracleId: String) { - self.source = source - self.publishedAt = publishedAt - self.comment = comment - self.oracleId = oracleId - } + public init(source: Source, publishedAt: String, comment: String, oracleId: String) { + self.source = source + self.publishedAt = publishedAt + self.comment = comment + self.oracleId = oracleId } + } } diff --git a/Sources/ScryfallKit/Models/Card/Card+Symbol.swift b/Sources/ScryfallKit/Models/Card/Card+Symbol.swift index a6d108c..2ea0332 100644 --- a/Sources/ScryfallKit/Models/Card/Card+Symbol.swift +++ b/Sources/ScryfallKit/Models/Card/Card+Symbol.swift @@ -5,60 +5,62 @@ import Foundation extension Card { - /// A symbol that could appear on a Magic: the Gathering card - public struct Symbol: Codable, Identifiable, Sendable { - /// The textual representation of this symbol - public var symbol: String - /// A more loose variation of the symbol. - /// - /// Example: "{W}" has a `looseVariant` of "W" - public var looseVariant: String? - /// An english description of this symbol's meaning - public var english: String - /// True if this symbol is transposable - /// - /// Scryfall doesn't provide any more information on what this means but inspecting which symbols are marked as transposable reveals that only hybrid and half mana symbols are transposable. - 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 converted mana cost - public var cmc: Double? - /// True if this symbol _could_ appear in a card's mana cost - public var appearsInManaCosts: Bool - /// True if this symbol is funny - public var funny: Bool - /// The colors that make up this symbol - public var colors: [Color] - /// Alternate notations for this symbol that used on Wizards of the Coast's [Gatherer](https://gatherer.wizards.com/Pages/Default.aspx) - public var gathererAlternates: [String]? - /// A link to an SVG of this symbol - public var svgUri: String? + /// A symbol that could appear on a Magic: the Gathering card + public struct Symbol: Codable, Identifiable, Sendable { + /// The textual representation of this symbol + public var symbol: String + /// A more loose variation of the symbol. + /// + /// Example: "{W}" has a `looseVariant` of "W" + public var looseVariant: String? + /// An english description of this symbol's meaning + public var english: String + /// True if this symbol is transposable + /// + /// Scryfall doesn't provide any more information on what this means but inspecting which symbols are marked as transposable reveals that only hybrid and half mana symbols are transposable. + 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 converted mana cost + public var cmc: Double? + /// True if this symbol _could_ appear in a card's mana cost + public var appearsInManaCosts: Bool + /// True if this symbol is funny + public var funny: Bool + /// The colors that make up this symbol + public var colors: [Color] + /// Alternate notations for this symbol that used on Wizards of the Coast's [Gatherer](https://gatherer.wizards.com/Pages/Default.aspx) + 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 } + /// A computed ID for this symbol which is just the `symbol` property + public var id: String { symbol } - public init(symbol: String, - looseVariant: String? = nil, - english: String, - transposable: Bool, - representsMana: Bool, - cmc: Double? = nil, - appearsInManaCosts: Bool, - funny: Bool, - colors: [Color], - gathererAlternates: [String]? = nil, - svgUri: String? = nil) { - self.symbol = symbol - self.looseVariant = looseVariant - self.english = english - self.transposable = transposable - self.representsMana = representsMana - self.cmc = cmc - self.appearsInManaCosts = appearsInManaCosts - self.funny = funny - self.colors = colors - self.gathererAlternates = gathererAlternates - self.svgUri = svgUri - } + public init( + symbol: String, + looseVariant: String? = nil, + english: String, + transposable: Bool, + representsMana: Bool, + cmc: Double? = nil, + appearsInManaCosts: Bool, + funny: Bool, + colors: [Color], + gathererAlternates: [String]? = nil, + svgUri: String? = nil + ) { + self.symbol = symbol + self.looseVariant = looseVariant + self.english = english + self.transposable = transposable + self.representsMana = representsMana + self.cmc = cmc + self.appearsInManaCosts = appearsInManaCosts + self.funny = funny + self.colors = colors + self.gathererAlternates = gathererAlternates + self.svgUri = svgUri } + } } diff --git a/Sources/ScryfallKit/Models/Card/Card+enums.swift b/Sources/ScryfallKit/Models/Card/Card+enums.swift index aaecea1..448deea 100644 --- a/Sources/ScryfallKit/Models/Card/Card+enums.swift +++ b/Sources/ScryfallKit/Models/Card/Card+enums.swift @@ -6,211 +6,216 @@ import Foundation import OSLog extension Card { - /// A value or combination of values that uniquely identify a Magic card - public enum Identifier { - case scryfallID(id: String) - case mtgoID(id: Int) - case multiverseID(id: Int) - case arenaID(id: Int) - case tcgPlayerID(id: Int) - case cardMarketID(id: Int) - case setCodeCollectorNo(setCode: String, collectorNo: String, lang: String? = nil) - - /// The name of the service that the identifer is linked to - var provider: String { - switch self { - case .mtgoID: - return "mtgo" - case .multiverseID: - return "multiverse" - case .arenaID: - return "arena" - case .tcgPlayerID: - return "tcgplayer" - case .cardMarketID: - return "cardmarket" - default: - return "scryfall" - } - } - - /// The id value of the identifier, if present. Only not present for set code + collector number - var id: String? { - switch self { - case .scryfallID(let id): - return id - case .mtgoID(let id): - return String(id) - case .multiverseID(let id): - return String(id) - case .arenaID(let id): - return String(id) - case .tcgPlayerID(let id): - return String(id) - case .cardMarketID(let id): - return String(id) - default: - return nil - } - } + /// A value or combination of values that uniquely identify a Magic card + public enum Identifier { + case scryfallID(id: String) + case mtgoID(id: Int) + case multiverseID(id: Int) + case arenaID(id: Int) + case tcgPlayerID(id: Int) + case cardMarketID(id: Int) + case setCodeCollectorNo(setCode: String, collectorNo: String, lang: String? = nil) + + /// The name of the service that the identifer is linked to + var provider: String { + switch self { + case .mtgoID: + return "mtgo" + case .multiverseID: + return "multiverse" + case .arenaID: + return "arena" + case .tcgPlayerID: + return "tcgplayer" + case .cardMarketID: + return "cardmarket" + default: + return "scryfall" + } } - /// A value or combination of values that uniquely identifies a Magic card for the purposes of retrieving a collection of cards. - public enum CollectionIdentifier { - case scryfallID(id: String) - case mtgoID(id: Int) - case multiverseID(id: Int) - case oracleID(id: String) - case illustrationID(id: String) - case name(_: String) - case nameAndSet(name: String, set: String) - case collectorNoAndSet(collectorNo: String, set: String) - - var json: [String: String] { - switch self { - case .scryfallID(let id): - return ["id": id] - case .mtgoID(let id): - return ["mtgo_id": "\(id)"] - case .multiverseID(let id): - return ["multiverse_id": "\(id)"] - case .oracleID(let id): - return ["oracle_id": id] - case .illustrationID(let id): - return ["illustration_id": id] - case .name(let name): - return ["name": name] - case .nameAndSet(let name, let set): - return ["name": name, "set": set] - case .collectorNoAndSet(let collectorNo, let set): - return ["collector_number": collectorNo, "set": set] - } - } + /// The id value of the identifier, if present. Only not present for set code + collector number + var id: String? { + switch self { + case .scryfallID(let id): + return id + case .mtgoID(let id): + return String(id) + case .multiverseID(let id): + return String(id) + case .arenaID(let id): + return String(id) + case .tcgPlayerID(let id): + return String(id) + case .cardMarketID(let id): + return String(id) + default: + return nil + } } - - /// Finishes for a printed card - public enum Finish: String, Codable, CaseIterable, Sendable { - case nonfoil, foil, etched, glossy + } + + /// A value or combination of values that uniquely identifies a Magic card for the purposes of retrieving a collection of cards. + public enum CollectionIdentifier { + case scryfallID(id: String) + case mtgoID(id: Int) + case multiverseID(id: Int) + case oracleID(id: String) + case illustrationID(id: String) + case name(_: String) + case nameAndSet(name: String, set: String) + case collectorNoAndSet(collectorNo: String, set: String) + + var json: [String: String] { + switch self { + case .scryfallID(let id): + return ["id": id] + case .mtgoID(let id): + return ["mtgo_id": "\(id)"] + case .multiverseID(let id): + return ["multiverse_id": "\(id)"] + case .oracleID(let id): + return ["oracle_id": id] + case .illustrationID(let id): + return ["illustration_id": id] + case .name(let name): + return ["name": name] + case .nameAndSet(let name, let set): + return ["name": name, "set": set] + case .collectorNoAndSet(let collectorNo, let set): + return ["collector_number": collectorNo, "set": set] + } } - - /// Status of Scryfall's image asset for this card - /// - /// [Scryfall documentation](https://scryfall.com/docs/api/images#image-statuses) - public enum ImageStatus: String, Codable, CaseIterable, Sendable { - case missing, placeholder, lowres - case highresScan = "highres_scan" + } + + /// Finishes for a printed card + public enum Finish: String, Codable, CaseIterable, Sendable { + case nonfoil, foil, etched, glossy + } + + /// Status of Scryfall's image asset for this card + /// + /// [Scryfall documentation](https://scryfall.com/docs/api/images#image-statuses) + public enum ImageStatus: String, Codable, CaseIterable, Sendable { + case missing, placeholder, lowres + case highresScan = "highres_scan" + } + + /// Types of images provided by Scryfall + /// + /// [Scryfall documentation](https://scryfall.com/docs/api/images) + public enum ImageType: String, Codable, CaseIterable { + case png, large, normal, small + case artCrop = "art_crop" + case borderCrop = "border_crop" + } + + /// Card rarities + public enum Rarity: String, Codable, CaseIterable, Comparable, Sendable { + case common, uncommon, rare, special, mythic, bonus + + /// Order according to Scryfall + public static func < (lhs: Card.Rarity, rhs: Card.Rarity) -> Bool { + let order: [Card.Rarity] = [.bonus, .special, .common, .uncommon, .rare, .mythic] + return order.firstIndex(of: lhs)! < order.firstIndex(of: rhs)! } - - /// Types of images provided by Scryfall + } + + /// Layouts for a Magic card + /// + /// [Scryfall documentation](https://scryfall.com/docs/api/layouts) + public enum Layout: String, CaseIterable, Codable, Sendable { + case normal, split, flip, transform, meld, leveler, saga, adventure, planar, scheme, vanguard, + token, emblem, augment, host, `class`, battle, `case`, mutate, prototype, unknown + case modalDfc = "modal_dfc" + case doubleSided = "double_sided" + case doubleFacedToken = "double_faced_token" + case artSeries = "art_series" + case reversibleCard = "reversible_card" + + /// Codable initializer /// - /// [Scryfall documentation](https://scryfall.com/docs/api/images) - public enum ImageType: String, Codable, CaseIterable { - case png, large, normal, small - case artCrop = "art_crop" - case borderCrop = "border_crop" - } - - /// Card rarities - public enum Rarity: String, Codable, CaseIterable, Comparable, Sendable { - case common, uncommon, rare, special, mythic, bonus - - /// Order according to Scryfall - public static func < (lhs: Card.Rarity, rhs: Card.Rarity) -> Bool { - let order: [Card.Rarity] = [.bonus, .special, .common, .uncommon, .rare, .mythic] - return order.firstIndex(of: lhs)! < order.firstIndex(of: rhs)! + /// If this initializer fails to decode a value, instead of throwing an error, it will decode as the ``ScryfallKit/Card/Layout-swift.enum/unknown`` type and print a message to the logs. + /// - Parameter decoder: The Decoder to try decoding a ``ScryfallKit/Card/Layout-swift.enum`` from + public init(from decoder: Decoder) throws { + self = (try? Self(rawValue: decoder.singleValueContainer().decode(RawValue.self))) ?? .unknown + if self == .unknown, let rawValue = try? String(from: decoder) { + if #available(iOS 14.0, macOS 11.0, *) { + Logger.decoder.error("Decoded unknown Layout: \(rawValue)") + } else { + print("Decoded unknown Layout: \(rawValue)") } + } } - - /// Layouts for a Magic card - /// - /// [Scryfall documentation](https://scryfall.com/docs/api/layouts) - public enum Layout: String, CaseIterable, Codable, Sendable { - case normal, split, flip, transform, meld, leveler, saga, adventure, planar, scheme, vanguard, token, emblem, augment, host, `class`, battle, `case`, mutate, prototype, unknown - case modalDfc = "modal_dfc" - case doubleSided = "double_sided" - case doubleFacedToken = "double_faced_token" - case artSeries = "art_series" - case reversibleCard = "reversible_card" - - /// Codable initializer - /// - /// If this initializer fails to decode a value, instead of throwing an error, it will decode as the ``ScryfallKit/Card/Layout-swift.enum/unknown`` type and print a message to the logs. - /// - Parameter decoder: The Decoder to try decoding a ``ScryfallKit/Card/Layout-swift.enum`` from - public init(from decoder: Decoder) throws { - self = (try? Self(rawValue: decoder.singleValueContainer().decode(RawValue.self))) ?? .unknown - if self == .unknown, let rawValue = try? String(from: decoder) { - if #available(iOS 14.0, macOS 11.0, *) { - Logger.decoder.error("Decoded unknown Layout: \(rawValue)") - } else { - print("Decoded unknown Layout: \(rawValue)") - } - } - } - } - - /// Machine-readable strings representing a card's legality in different formats - public enum Legality: String, Codable, CaseIterable, Hashable, Sendable { - /// This card is legal to be played in this format - case legal - /// This card is restricted in this format (players may only have one copy in their deck) - case restricted - /// This card has been banned in this format - case banned - /// This card is not legal in this format (ex: an uncommon is not legal in pauper) - case notLegal = "not_legal" - - public var label: String { - switch self { - case .notLegal: - return "Not Legal" - default: - return rawValue.capitalized - } - } - } - - /// A string representing one of the colors (and colorless) in Magic - public enum Color: String, Codable, CaseIterable, Comparable, Sendable { - // swiftlint:disable:next identifier_name - case W, U, B, R, G, C - - public static func < (lhs: Color, rhs: Color) -> Bool { - let order: [Color] = [.W, .U, .B, .R, .G, .C] - return order.firstIndex(of: lhs)! < order.firstIndex(of: rhs)! - } + } + + /// Machine-readable strings representing a card's legality in different formats + public enum Legality: String, Codable, CaseIterable, Hashable, Sendable { + /// This card is legal to be played in this format + case legal + /// This card is restricted in this format (players may only have one copy in their deck) + case restricted + /// This card has been banned in this format + case banned + /// This card is not legal in this format (ex: an uncommon is not legal in pauper) + case notLegal = "not_legal" + + public var label: String { + switch self { + case .notLegal: + return "Not Legal" + default: + return rawValue.capitalized + } } + } - /// Card border colors - public enum BorderColor: String, Codable, CaseIterable, Sendable { - case black, borderless, gold, silver, white - } + /// A string representing one of the colors (and colorless) in Magic + public enum Color: String, Codable, CaseIterable, Comparable, Sendable { + // swiftlint:disable:next identifier_name + case W, U, B, R, G, C - /// Card frames - /// - /// [Scryfall documentation](https://scryfall.com/docs/api/frames) - public enum Frame: String, Codable, CaseIterable, Sendable { - case v1993 = "1993" - case v1997 = "1997" - case v2003 = "2003" - case v2015 = "2015" - case future + public static func < (lhs: Color, rhs: Color) -> Bool { + let order: [Color] = [.W, .U, .B, .R, .G, .C] + return order.firstIndex(of: lhs)! < order.firstIndex(of: rhs)! } - - /// Effects applied to a Magic card frame - /// - /// [Scryfall documentation](https://scryfall.com/docs/api/frames#frame-effects) - public enum FrameEffect: String, Codable, CaseIterable, Sendable { - case legendary, miracle, nyxtouched, draft, devoid, tombstone, colorshifted, inverted, sunmoondfc, compasslanddfc, originpwdfc, mooneldrazidfc, waxingandwaningmoondfc, showcase, extendedart, companion, etched, snow, lesson, convertdfc, fandfc, battle, gravestone, fullart, vehicle, borderless, extended, spree, unknown - - public init(from decoder: Decoder) throws { - self = try FrameEffect(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown - if self == .unknown, let rawValue = try? String(from: decoder) { - if #available(iOS 14.0, macOS 11.0, *) { - Logger.decoder.error("Decoded unknown FrameEffect: \(rawValue)") - } else { - print("Decoded unknown FrameEffect: \(rawValue)") - } - } + } + + /// Card border colors + public enum BorderColor: String, Codable, CaseIterable, Sendable { + case black, borderless, gold, silver, white + } + + /// Card frames + /// + /// [Scryfall documentation](https://scryfall.com/docs/api/frames) + public enum Frame: String, Codable, CaseIterable, Sendable { + case v1993 = "1993" + case v1997 = "1997" + case v2003 = "2003" + case v2015 = "2015" + case future + } + + /// Effects applied to a Magic card frame + /// + /// [Scryfall documentation](https://scryfall.com/docs/api/frames#frame-effects) + public enum FrameEffect: String, Codable, CaseIterable, Sendable { + case legendary, miracle, nyxtouched, draft, devoid, tombstone, colorshifted, inverted, + sunmoondfc, compasslanddfc, originpwdfc, mooneldrazidfc, waxingandwaningmoondfc, showcase, + extendedart, companion, etched, snow, lesson, convertdfc, fandfc, battle, gravestone, fullart, + vehicle, borderless, extended, spree, unknown + + public init(from decoder: Decoder) throws { + self = + try FrameEffect(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown + if self == .unknown, let rawValue = try? String(from: decoder) { + if #available(iOS 14.0, macOS 11.0, *) { + Logger.decoder.error("Decoded unknown FrameEffect: \(rawValue)") + } else { + print("Decoded unknown FrameEffect: \(rawValue)") } + } } + } } diff --git a/Sources/ScryfallKit/Models/Card/Card.swift b/Sources/ScryfallKit/Models/Card/Card.swift index 51c932b..306c1e1 100644 --- a/Sources/ScryfallKit/Models/Card/Card.swift +++ b/Sources/ScryfallKit/Models/Card/Card.swift @@ -1,6 +1,6 @@ // // Card.swift -// +// import Foundation @@ -8,337 +8,339 @@ import Foundation /// /// [Scryfall documentation](https://scryfall.com/docs/api/cards) public struct Card: Codable, Identifiable, Hashable, Sendable { - // MARK: Core fields - /// The identifier of this card on Scryfall - public var id: UUID - /// The identifier of this card on MTG Arena - public var arenaId: Int? - /// The identifier of this card on MTG Online - public var mtgoId: Int? - /// The identifier of the foil version of this card on MTG Online - public var mtgoFoilId: Int? - /// The identifiers of this card on Wizards of the Coast's [Gatherer](https://gatherer.wizards.com/Pages/Default.aspx) - public var multiverseIds: [Int]? - /// The identifier of this card on TCGPlayer - public var tcgplayerId: Int? - /// 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? - /// 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) - /// - Note: Scryfall's documentation lists this as required but it's nil for reversible cards - public var oracleId: String? - /// The language that this specific printing was printed in - public var lang: String - /// A link to where you can begin paginating all re/prints for this card on Scryfall’s API. - public var printsSearchUri: String - /// A link to this card’s rulings list on Scryfall’s API. - public var rulingsUri: String - /// A link to this card’s permapage on Scryfall’s website. - public var scryfallUri: String - /// A link to this card object on Scryfall’s API. - public var uri: String + // MARK: Core fields + /// The identifier of this card on Scryfall + public var id: UUID + /// The identifier of this card on MTG Arena + public var arenaId: Int? + /// The identifier of this card on MTG Online + public var mtgoId: Int? + /// The identifier of the foil version of this card on MTG Online + public var mtgoFoilId: Int? + /// The identifiers of this card on Wizards of the Coast's [Gatherer](https://gatherer.wizards.com/Pages/Default.aspx) + public var multiverseIds: [Int]? + /// The identifier of this card on TCGPlayer + public var tcgplayerId: Int? + /// 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? + /// 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) + /// - Note: Scryfall's documentation lists this as required but it's nil for reversible cards + public var oracleId: String? + /// The language that this specific printing was printed in + public var lang: String + /// A link to where you can begin paginating all re/prints for this card on Scryfall’s API. + public var printsSearchUri: String + /// A link to this card’s rulings list on Scryfall’s API. + public var rulingsUri: String + /// A link to this card’s permapage on Scryfall’s website. + public var scryfallUri: String + /// A link to this card object on Scryfall’s API. + public var uri: String - // MARK: Gameplay fields - /// An array of related cards (tokens, meld parts, etc) or nil if there are none - public var allParts: [RelatedCard]? - /// An array of all the faces that a card has or nil if it's a single-faced card - public var cardFaces: [Face]? - /// The converted mana cost of a card, now called the "mana value" - /// - Note: Scryfall's documentation lists this as required but it's nil for reversible cards - public var cmc: Double? - /// An array of colors representing this card's color identity. - /// - /// Color identity is used to determine what cards are legal in a Commander deck. See the [comprehensive rules](https://magic.wizards.com/en/rules) for more information - public var colorIdentity: [Color] - /// 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 - public var colorIndicator: [Color]? - /// An array of the colors in this card's mana cost - public var colors: [Color]? - /// This card’s overall rank/popularity on EDHREC. Not all cards are ranked. - public var edhrecRank: Int? - /// This card’s hand modifier, if it is Vanguard card. This value will contain a delta, such as -1. - public var handModifier: String? - /// An array of the keywords on this card (deathouch, first strike, etc) - public var keywords: [String] - /// This card's layout (normal, transform, split, etc) - public var layout: Layout - /// The formats that this card is legal in - public var legalities: Legalities - /// This card’s life modifier, if it is Vanguard card. This value will contain a delta, such as +2. - public var lifeModifier: String? - /// This card's starting loyalty counters if it's a planeswalker - public var loyalty: String? - /// The mana cost for this card. - /// - /// This value will be any empty string "" if the cost is absent. Remember that per the game rules, a missing mana cost and a mana cost of {0} are different values. - public var manaCost: String? - /// The name of this card - /// - /// If the card has multiple faces the names will be separated by a "//" such as "Wear // Tear" - public var name: String - /// The oracle text for this card - public var oracleText: String? - /// True if this card is an oversized card - public var oversized: Bool - /// The power of this card if it's a creature - public var power: String? - /// The colors of mana that this card _could_ produce - public var producedMana: [Color]? - /// True if this card is on the Reserved List - public var reserved: Bool - /// The toughness of this card if it's a creature - public var toughness: String? - /// This card's types, separated by a space - /// - Note: Tokens don't have type lines - public var typeLine: String? + // MARK: Gameplay fields + /// An array of related cards (tokens, meld parts, etc) or nil if there are none + public var allParts: [RelatedCard]? + /// An array of all the faces that a card has or nil if it's a single-faced card + public var cardFaces: [Face]? + /// The converted mana cost of a card, now called the "mana value" + /// - Note: Scryfall's documentation lists this as required but it's nil for reversible cards + public var cmc: Double? + /// An array of colors representing this card's color identity. + /// + /// Color identity is used to determine what cards are legal in a Commander deck. See the [comprehensive rules](https://magic.wizards.com/en/rules) for more information + public var colorIdentity: [Color] + /// 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 + public var colorIndicator: [Color]? + /// An array of the colors in this card's mana cost + public var colors: [Color]? + /// This card’s overall rank/popularity on EDHREC. Not all cards are ranked. + public var edhrecRank: Int? + /// This card’s hand modifier, if it is Vanguard card. This value will contain a delta, such as -1. + public var handModifier: String? + /// An array of the keywords on this card (deathouch, first strike, etc) + public var keywords: [String] + /// This card's layout (normal, transform, split, etc) + public var layout: Layout + /// The formats that this card is legal in + public var legalities: Legalities + /// This card’s life modifier, if it is Vanguard card. This value will contain a delta, such as +2. + public var lifeModifier: String? + /// This card's starting loyalty counters if it's a planeswalker + public var loyalty: String? + /// The mana cost for this card. + /// + /// This value will be any empty string "" if the cost is absent. Remember that per the game rules, a missing mana cost and a mana cost of {0} are different values. + public var manaCost: String? + /// The name of this card + /// + /// If the card has multiple faces the names will be separated by a "//" such as "Wear // Tear" + public var name: String + /// The oracle text for this card + public var oracleText: String? + /// True if this card is an oversized card + public var oversized: Bool + /// The power of this card if it's a creature + public var power: String? + /// The colors of mana that this card _could_ produce + public var producedMana: [Color]? + /// True if this card is on the Reserved List + public var reserved: Bool + /// The toughness of this card if it's a creature + public var toughness: String? + /// This card's types, separated by a space + /// - Note: Tokens don't have type lines + public var typeLine: String? - // MARK: Print fields - /// The name of the artist who illustrated this card - public var artist: String? - /// True if this card was printed in booster packs - public var booster: Bool - /// The color of this card's border - public var borderColor: BorderColor - /// The Scryfall ID for the card back design present on this card. - /// - Note: Scryfall's documentation lists this as required but it's nil for multi-faced cards - public var cardBackId: UUID? - /// This card's collector number - public var collectorNumber: String - /// True if you should consider avoiding use of this print downstream - /// - /// [Comment from Scryfall's blog](https://scryfall.com/blog/regarding-wotc-s-recent-statement-on-depictions-of-racism-220) - public var contentWarning: Bool? - /// Whether this card has been released digitally - public var digital: Bool - /// The different finishes that this card was printed in - public var finishes: [Card.Finish] - /// 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? - /// The frame effects if any - public var frameEffects: [FrameEffect]? - /// The type of frame this card was printed with - public var frame: Frame - /// True if this card's art is larger than normal - public var fullArt: Bool - /// An array of the games this card has been released in - public var games: [Game] - /// True if Scryfall has a high-res image of this card - public var highresImage: Bool - /// An ID for this card's art that remains consistent across reprints - public var illustrationId: UUID? - /// A computer-readable indicator for the state of this card’s image - public var imageStatus: ImageStatus - /// An object listing available imagery for this card. - public var imageUris: ImageUris? - /// An object containing daily price information for this card - public var prices: Prices - /// The localized name printed on this card, if any. - public var printedName: String? - /// The localized text printed on this card, if any. - public var printedText: String? - /// The localized type line printed on this card, if any. - public var printedTypeLine: String? - /// True if this card is a promotional printing - public var promo: Bool - /// An array of strings describing what categories of promo cards this card falls into. - public var promoTypes: [String]? - /// A dictionary of URIs to this card’s listing on major marketplaces. - public var purchaseUris: [String: String]? - /// This card's rarity - public var rarity: Rarity - /// A dictionary of links to other MTG resources - public var relatedUris: [String: String] - /// This card's release date - public var releasedAt: String - /// True if this card has been printed before - public var reprint: Bool - /// Link to this card's set on Scryfall - public var scryfallSetUri: String - /// This card's full set name - public var setName: String - /// A link to this card's set on Scryfall - public var setSearchUri: URL - /// The type of set this card was printed in - public var setType: MTGSet.`Type` - /// A link to this card's set object on the Scryfall API - public var setUri: String - /// This card's set code - public var set: String - /// True if this was a story spotlight card - public var storySpotlight: Bool - /// True if this card doesn't have any text on it - public var textless: Bool - /// True if this card is a variation of another printing - public var variation: Bool - /// The id of the card this card is a variation of - public var variationOf: UUID? - /// This card's watermark, if any - public var watermark: String? - /// An object with information on when this card was previewed and by whom - public var preview: Preview? + // MARK: Print fields + /// The name of the artist who illustrated this card + public var artist: String? + /// True if this card was printed in booster packs + public var booster: Bool + /// The color of this card's border + public var borderColor: BorderColor + /// The Scryfall ID for the card back design present on this card. + /// - Note: Scryfall's documentation lists this as required but it's nil for multi-faced cards + public var cardBackId: UUID? + /// This card's collector number + public var collectorNumber: String + /// True if you should consider avoiding use of this print downstream + /// + /// [Comment from Scryfall's blog](https://scryfall.com/blog/regarding-wotc-s-recent-statement-on-depictions-of-racism-220) + public var contentWarning: Bool? + /// Whether this card has been released digitally + public var digital: Bool + /// The different finishes that this card was printed in + public var finishes: [Card.Finish] + /// 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? + /// The frame effects if any + public var frameEffects: [FrameEffect]? + /// The type of frame this card was printed with + public var frame: Frame + /// True if this card's art is larger than normal + public var fullArt: Bool + /// An array of the games this card has been released in + public var games: [Game] + /// True if Scryfall has a high-res image of this card + public var highresImage: Bool + /// An ID for this card's art that remains consistent across reprints + public var illustrationId: UUID? + /// A computer-readable indicator for the state of this card’s image + public var imageStatus: ImageStatus + /// An object listing available imagery for this card. + public var imageUris: ImageUris? + /// An object containing daily price information for this card + public var prices: Prices + /// The localized name printed on this card, if any. + public var printedName: String? + /// The localized text printed on this card, if any. + public var printedText: String? + /// The localized type line printed on this card, if any. + public var printedTypeLine: String? + /// True if this card is a promotional printing + public var promo: Bool + /// An array of strings describing what categories of promo cards this card falls into. + public var promoTypes: [String]? + /// A dictionary of URIs to this card’s listing on major marketplaces. + public var purchaseUris: [String: String]? + /// This card's rarity + public var rarity: Rarity + /// A dictionary of links to other MTG resources + public var relatedUris: [String: String] + /// This card's release date + public var releasedAt: String + /// True if this card has been printed before + public var reprint: Bool + /// Link to this card's set on Scryfall + public var scryfallSetUri: String + /// This card's full set name + public var setName: String + /// A link to this card's set on Scryfall + public var setSearchUri: URL + /// The type of set this card was printed in + public var setType: MTGSet.`Type` + /// A link to this card's set object on the Scryfall API + public var setUri: String + /// This card's set code + public var set: String + /// True if this was a story spotlight card + public var storySpotlight: Bool + /// True if this card doesn't have any text on it + public var textless: Bool + /// True if this card is a variation of another printing + public var variation: Bool + /// The id of the card this card is a variation of + public var variationOf: UUID? + /// This card's watermark, if any + public var watermark: String? + /// An object with information on when this card was previewed and by whom + public var preview: Preview? - // swiftlint:disable function_body_length - public init(arenaId: Int? = nil, - mtgoId: Int? = nil, - mtgoFoilId: Int? = nil, - multiverseIds: [Int]? = nil, - tcgplayerId: Int? = nil, - tcgplayerEtchedId: Int? = nil, - cardMarketId: Int? = nil, - id: UUID, - oracleId: String, - lang: String, - printsSearchUri: String, - rulingsUri: String, - scryfallUri: String, - uri: String, - allParts: [RelatedCard]? = nil, - cardFaces: [Face]? = nil, - cmc: Double, - colorIdentity: [Color], - colorIndicator: [Color]? = nil, - colors: [Color]? = nil, - edhrecRank: Int? = nil, - handModifier: String? = nil, - keywords: [String], - layout: Layout, - legalities: Legalities, - lifeModifier: String? = nil, - loyalty: String? = nil, - manaCost: String? = nil, - name: String, - oracleText: String? = nil, - oversized: Bool, - power: String? = nil, - producedMana: [Color]? = nil, - reserved: Bool, - toughness: String? = nil, - typeLine: String? = nil, - artist: String? = nil, - booster: Bool, - borderColor: BorderColor, - cardBackId: UUID? = nil, - collectorNumber: String, - contentWarning: Bool? = nil, - digital: Bool, - finishes: [Finish], - flavorName: String? = nil, - flavorText: String? = nil, - frameEffects: [FrameEffect]? = nil, - frame: Frame, - fullArt: Bool, - games: [Game], - highresImage: Bool, - illustrationId: UUID? = nil, - imageStatus: ImageStatus, - imageUris: ImageUris? = nil, - prices: Prices, - printedName: String? = nil, - printedText: String? = nil, - printedTypeLine: String? = nil, - promo: Bool, - promoTypes: [String]? = nil, - purchaseUris: [String: String]? = nil, - rarity: Card.Rarity, - relatedUris: [String: String], - releasedAt: String, - reprint: Bool, - scryfallSetUri: String, - setName: String, - setSearchUri: URL, - setType: MTGSet.`Type`, - setUri: String, - set: String, - storySpotlight: Bool, - textless: Bool, - variation: Bool, - variationOf: UUID? = nil, - watermark: String? = nil, - preview: Preview? = nil) { - self.arenaId = arenaId - self.mtgoId = mtgoId - self.mtgoFoilId = mtgoFoilId - self.multiverseIds = multiverseIds - self.tcgplayerId = tcgplayerId - self.tcgplayerEtchedId = tcgplayerEtchedId - self.cardMarketId = cardMarketId - self.id = id - self.oracleId = oracleId - self.lang = lang - self.printsSearchUri = printsSearchUri - self.rulingsUri = rulingsUri - self.scryfallUri = scryfallUri - self.uri = uri - self.allParts = allParts - self.cardFaces = cardFaces - self.cmc = cmc - self.colorIdentity = colorIdentity - self.colorIndicator = colorIndicator - self.colors = colors - self.edhrecRank = edhrecRank - self.handModifier = handModifier - self.keywords = keywords - self.layout = layout - self.legalities = legalities - self.lifeModifier = lifeModifier - self.loyalty = loyalty - self.manaCost = manaCost - self.name = name - self.oracleText = oracleText - self.oversized = oversized - self.power = power - self.producedMana = producedMana - self.reserved = reserved - self.toughness = toughness - self.typeLine = typeLine - self.artist = artist - self.booster = booster - self.borderColor = borderColor - self.cardBackId = cardBackId - self.collectorNumber = collectorNumber - self.contentWarning = contentWarning - self.digital = digital - self.finishes = finishes - self.flavorName = flavorName - self.flavorText = flavorText - self.frameEffects = frameEffects - self.frame = frame - self.fullArt = fullArt - self.games = games - self.highresImage = highresImage - self.illustrationId = illustrationId - self.imageStatus = imageStatus - self.imageUris = imageUris - self.prices = prices - self.printedName = printedName - self.printedText = printedText - self.printedTypeLine = printedTypeLine - self.promo = promo - self.promoTypes = promoTypes - self.purchaseUris = purchaseUris - self.rarity = rarity - self.relatedUris = relatedUris - self.releasedAt = releasedAt - self.reprint = reprint - self.scryfallSetUri = scryfallSetUri - self.setName = setName - self.setSearchUri = setSearchUri - self.setType = setType - self.setUri = setUri - self.set = set - self.storySpotlight = storySpotlight - self.textless = textless - self.variation = variation - self.variationOf = variationOf - self.watermark = watermark - self.preview = preview - } - // swiftlint:enable function_body_length + // swiftlint:disable function_body_length + public init( + arenaId: Int? = nil, + mtgoId: Int? = nil, + mtgoFoilId: Int? = nil, + multiverseIds: [Int]? = nil, + tcgplayerId: Int? = nil, + tcgplayerEtchedId: Int? = nil, + cardMarketId: Int? = nil, + id: UUID, + oracleId: String, + lang: String, + printsSearchUri: String, + rulingsUri: String, + scryfallUri: String, + uri: String, + allParts: [RelatedCard]? = nil, + cardFaces: [Face]? = nil, + cmc: Double, + colorIdentity: [Color], + colorIndicator: [Color]? = nil, + colors: [Color]? = nil, + edhrecRank: Int? = nil, + handModifier: String? = nil, + keywords: [String], + layout: Layout, + legalities: Legalities, + lifeModifier: String? = nil, + loyalty: String? = nil, + manaCost: String? = nil, + name: String, + oracleText: String? = nil, + oversized: Bool, + power: String? = nil, + producedMana: [Color]? = nil, + reserved: Bool, + toughness: String? = nil, + typeLine: String? = nil, + artist: String? = nil, + booster: Bool, + borderColor: BorderColor, + cardBackId: UUID? = nil, + collectorNumber: String, + contentWarning: Bool? = nil, + digital: Bool, + finishes: [Finish], + flavorName: String? = nil, + flavorText: String? = nil, + frameEffects: [FrameEffect]? = nil, + frame: Frame, + fullArt: Bool, + games: [Game], + highresImage: Bool, + illustrationId: UUID? = nil, + imageStatus: ImageStatus, + imageUris: ImageUris? = nil, + prices: Prices, + printedName: String? = nil, + printedText: String? = nil, + printedTypeLine: String? = nil, + promo: Bool, + promoTypes: [String]? = nil, + purchaseUris: [String: String]? = nil, + rarity: Card.Rarity, + relatedUris: [String: String], + releasedAt: String, + reprint: Bool, + scryfallSetUri: String, + setName: String, + setSearchUri: URL, + setType: MTGSet.`Type`, + setUri: String, + set: String, + storySpotlight: Bool, + textless: Bool, + variation: Bool, + variationOf: UUID? = nil, + watermark: String? = nil, + preview: Preview? = nil + ) { + self.arenaId = arenaId + self.mtgoId = mtgoId + self.mtgoFoilId = mtgoFoilId + self.multiverseIds = multiverseIds + self.tcgplayerId = tcgplayerId + self.tcgplayerEtchedId = tcgplayerEtchedId + self.cardMarketId = cardMarketId + self.id = id + self.oracleId = oracleId + self.lang = lang + self.printsSearchUri = printsSearchUri + self.rulingsUri = rulingsUri + self.scryfallUri = scryfallUri + self.uri = uri + self.allParts = allParts + self.cardFaces = cardFaces + self.cmc = cmc + self.colorIdentity = colorIdentity + self.colorIndicator = colorIndicator + self.colors = colors + self.edhrecRank = edhrecRank + self.handModifier = handModifier + self.keywords = keywords + self.layout = layout + self.legalities = legalities + self.lifeModifier = lifeModifier + self.loyalty = loyalty + self.manaCost = manaCost + self.name = name + self.oracleText = oracleText + self.oversized = oversized + self.power = power + self.producedMana = producedMana + self.reserved = reserved + self.toughness = toughness + self.typeLine = typeLine + self.artist = artist + self.booster = booster + self.borderColor = borderColor + self.cardBackId = cardBackId + self.collectorNumber = collectorNumber + self.contentWarning = contentWarning + self.digital = digital + self.finishes = finishes + self.flavorName = flavorName + self.flavorText = flavorText + self.frameEffects = frameEffects + self.frame = frame + self.fullArt = fullArt + self.games = games + self.highresImage = highresImage + self.illustrationId = illustrationId + self.imageStatus = imageStatus + self.imageUris = imageUris + self.prices = prices + self.printedName = printedName + self.printedText = printedText + self.printedTypeLine = printedTypeLine + self.promo = promo + self.promoTypes = promoTypes + self.purchaseUris = purchaseUris + self.rarity = rarity + self.relatedUris = relatedUris + self.releasedAt = releasedAt + self.reprint = reprint + self.scryfallSetUri = scryfallSetUri + self.setName = setName + self.setSearchUri = setSearchUri + self.setType = setType + self.setUri = setUri + self.set = set + self.storySpotlight = storySpotlight + self.textless = textless + self.variation = variation + self.variationOf = variationOf + self.watermark = watermark + self.preview = preview + } + // swiftlint:enable function_body_length } diff --git a/Sources/ScryfallKit/Models/Catalog.swift b/Sources/ScryfallKit/Models/Catalog.swift index db6360f..8083c87 100644 --- a/Sources/ScryfallKit/Models/Catalog.swift +++ b/Sources/ScryfallKit/Models/Catalog.swift @@ -8,31 +8,31 @@ import Foundation /// /// [Scryfall documentation](https://scryfall.com/docs/api/catalogs) public struct Catalog: Codable, Sendable { - /// The catalog type. Each of these types represents a different `/catalogs` endpoint - public enum `Type`: String, Codable, CaseIterable { - case powers, toughnesses, loyalties, watermarks - case cardNames = "card-names" - case artistNames = "artist-names" - case wordBank = "word-bank" - case creatureTypes = "creature-types" - case planeswalkerTypes = "planeswalker-types" - case landTypes = "land-types" - case artifactTypes = "artifact-types" - case enchantmentTypes = "enchantment-types" - case spellTypes = "spell-types" - case keywordAbilities = "keyword-abilities" - case keywordActions = "keyword-actions" - case abilityWords = "ability-words" - } + /// The catalog type. Each of these types represents a different `/catalogs` endpoint + public enum `Type`: String, Codable, CaseIterable { + case powers, toughnesses, loyalties, watermarks + case cardNames = "card-names" + case artistNames = "artist-names" + case wordBank = "word-bank" + case creatureTypes = "creature-types" + case planeswalkerTypes = "planeswalker-types" + case landTypes = "land-types" + case artifactTypes = "artifact-types" + case enchantmentTypes = "enchantment-types" + case spellTypes = "spell-types" + case keywordAbilities = "keyword-abilities" + case keywordActions = "keyword-actions" + case abilityWords = "ability-words" + } - /// The number of items in the `data` array - public var totalValues: Int + /// The number of items in the `data` array + public var totalValues: Int - /// An array of data points - public var data: [String] + /// An array of data points + public var data: [String] - public init(totalValues: Int, data: [String]) { - self.totalValues = totalValues - self.data = data - } + public init(totalValues: Int, data: [String]) { + self.totalValues = totalValues + self.data = data + } } diff --git a/Sources/ScryfallKit/Models/Enums.swift b/Sources/ScryfallKit/Models/Enums.swift index 561dec4..a0e49e0 100644 --- a/Sources/ScryfallKit/Models/Enums.swift +++ b/Sources/ScryfallKit/Models/Enums.swift @@ -6,36 +6,36 @@ import Foundation /// Environments to play Magic: The Gathering in public enum Game: String, Codable, CaseIterable, Sendable { - case paper, mtgo, arena + case paper, mtgo, arena } /// Comparison strategies for determining what makes a card "unique" /// /// [Scryfall documentation](https://scryfall.com/docs/api/cards/search#unique-rollup-modes) public enum UniqueMode: String, Codable, CaseIterable, Sendable { - case cards, art, prints + case cards, art, prints } /// Fields that Scryfall can sort cards by /// /// [Scryfall documentation](https://scryfall.com/docs/api/cards/search#sorting-cards) public enum SortMode: String, Codable, CaseIterable, Sendable { - case name, set, released, rarity, color, usd, tix, eur, cmc, power, toughness, edhrec, artist + case name, set, released, rarity, color, usd, tix, eur, cmc, power, toughness, edhrec, artist } /// Directions that Scryfall can order cards in /// /// [Scryfall documentation](https://scryfall.com/docs/api/cards/search#sorting-cards) public enum SortDirection: String, Codable, CaseIterable, Sendable { - case auto, asc, desc + case auto, asc, desc } /// Formats for playing Magic: the Gathering public enum Format: String, CaseIterable, Sendable { - case standard, historic, pioneer, modern, legacy, pauper, vintage, penny, commander, brawl + case standard, historic, pioneer, modern, legacy, pauper, vintage, penny, commander, brawl } /// Currency types that Scryfall provides prices for public enum Currency: String, CaseIterable, Sendable { - case usd, eur, tix, usdFoil + case usd, eur, tix, usdFoil } diff --git a/Sources/ScryfallKit/Models/FieldFilter.swift b/Sources/ScryfallKit/Models/FieldFilter.swift index b565d19..4ab77f1 100644 --- a/Sources/ScryfallKit/Models/FieldFilter.swift +++ b/Sources/ScryfallKit/Models/FieldFilter.swift @@ -1,176 +1,176 @@ // // FieldFilter.swift -// +// /// A comparison operator for card metadata public enum ComparisonType: String, CaseIterable, Codable { - case lessThan = "<" - case lessThanOrEqual = "<=" - case greaterThan = ">" - case greaterThanOrEqual = ">=" - case equal = "=" - case notEqual = "!=" - case including = ":" + case lessThan = "<" + case lessThanOrEqual = "<=" + case greaterThan = ">" + case greaterThanOrEqual = ">=" + case equal = "=" + case notEqual = "!=" + case including = ":" } /// An enum representing a search filter public enum CardFieldFilter { - // Colors and Identity - case colors(String, ComparisonType = .including) - case colorIdentity(String, ComparisonType = .including) + // Colors and Identity + case colors(String, ComparisonType = .including) + case colorIdentity(String, ComparisonType = .including) - // Types and oracle text - case type(String) - case oracleText(String) - case fullOracleText(String) - case oracleId(String) - case keyword(String) + // Types and oracle text + case type(String) + case oracleText(String) + case fullOracleText(String) + case oracleId(String) + case keyword(String) - // Mana costs - case mana(String, ComparisonType = .equal) - case devotion(String, ComparisonType = .equal) - case produces(String, ComparisonType = .equal) - case cmc(String, ComparisonType = .equal) + // Mana costs + case mana(String, ComparisonType = .equal) + case devotion(String, ComparisonType = .equal) + case produces(String, ComparisonType = .equal) + case cmc(String, ComparisonType = .equal) - // Power, toughness, and Loyalty - case power(String, ComparisonType = .equal) - case toughness(String, ComparisonType = .equal) - case loyalty(String, ComparisonType = .equal) + // Power, toughness, and Loyalty + case power(String, ComparisonType = .equal) + case toughness(String, ComparisonType = .equal) + case loyalty(String, ComparisonType = .equal) - // Sets and Blocks - case set(String) - case block(String) - case collectorNumber(String, ComparisonType = .equal) + // Sets and Blocks + case set(String) + case block(String) + case collectorNumber(String, ComparisonType = .equal) - // Format based filters - case format(String) - case banned(String) - case restricted(String) + // Format based filters + case format(String) + case banned(String) + case restricted(String) - // Prices - case usd(String, ComparisonType = .equal) - case tix(String, ComparisonType = .equal) - case eur(String, ComparisonType = .equal) + // Prices + case usd(String, ComparisonType = .equal) + case tix(String, ComparisonType = .equal) + case eur(String, ComparisonType = .equal) - // Artist, flavor text, and watermark - case artist(String) - case flavor(String) - case watermark(String) + // Artist, flavor text, and watermark + case artist(String) + case flavor(String) + case watermark(String) - // Border, Frame - case border(Card.BorderColor) - case frame(Card.Frame) + // Border, Frame + case border(Card.BorderColor) + case frame(Card.Frame) - // Tagger tags - case art(String) - case function(String) + // Tagger tags + case art(String) + case function(String) - // Misc - case name(String) - case cube(String) - case game(Game) - case year(String, ComparisonType = .equal) - case language(String) - case `is`(String) - case not(String) - case include(String) - case rarity(String, ComparisonType = .equal) - case `in`(String) - case compoundOr([CardFieldFilter]) - case compoundAnd([CardFieldFilter]) + // Misc + case name(String) + case cube(String) + case game(Game) + case year(String, ComparisonType = .equal) + case language(String) + case `is`(String) + case not(String) + case include(String) + case rarity(String, ComparisonType = .equal) + case `in`(String) + case compoundOr([CardFieldFilter]) + case compoundAnd([CardFieldFilter]) - /// The Scryfall syntax query string representing the filter - public var filterString: String { - switch self { - case .fullOracleText(let value): - return "fo:\(value)" - case .oracleId(let id): - return "oracleid:\(id)" - case .keyword(let value): - return "keyword:\(value)" - case .compoundOr(let filters): - return "(\(filters.map { $0.filterString }.joined(separator: " or ")))" - case .compoundAnd(let filters): - return "(\(filters.map { $0.filterString }.joined(separator: " and ")))" - case .name(let value): - return "name:\(value)" - case .colors(let value, let comparison): - return "color\(comparison.rawValue)\(value)" - case .colorIdentity(let value, let comparison): - return "identity\(comparison.rawValue)\(value)" - case .type(let value): - return "type:\(value)" - case .oracleText(let value): - return "oracle:\(value)" - case .mana(let value, let comparison): - return "mana\(comparison.rawValue)\(value)" - case .devotion(let value, let comparison): - return "devotion\(comparison.rawValue)\(value)" - case .produces(let value, let comparison): - return "produces\(comparison.rawValue)\(value)" - case .cmc(let value, let comparison): - return "cmc\(comparison.rawValue)\(value)" - case .power(let value, let comparison): - return "power\(comparison.rawValue)\(value)" - case .toughness(let value, let comparison): - return "toughness\(comparison.rawValue)\(value)" - case .loyalty(let value, let comparison): - return "loyalty\(comparison.rawValue)\(value)" - case .set(let value): - return "set:\(value)" - case .block(let value): - return "block:\(value)" - case .collectorNumber(let value, let comparison): - return "collectorNumber\(comparison.rawValue)\(value)" - case .format(let value): - return "format:\(value)" - case .banned(let value): - return "banned:\(value)" - case .restricted(let value): - return "restricted:\(value)" - case .usd(let value, let comparison): - return "usd\(comparison.rawValue)\(value)" - case .tix(let value, let comparison): - return "tix\(comparison.rawValue)\(value)" - case .eur(let value, let comparison): - return "eur\(comparison.rawValue)\(value)" - case .artist(let value): - return "artist:\(value)" - case .flavor(let value): - return "flavor:\(value)" - case .watermark(let value): - return "watermark:\(value)" - case .border(let value): - return "border:\(value)" - case .frame(let value): - return "frame:\(value)" - case .art(let value): - return "art:\(value)" - case .function(let value): - return "function:\(value)" - case .cube(let value): - return "cube:\(value)" - case .game(let value): - return "game:\(value)" - case .year(let value, let comparison): - return "year\(comparison.rawValue)\(value)" - case .language(let value): - return "language:\(value)" - case .is(let value): - return "is:\(value)" - case .not(let value): - return "not:\(value)" - case .include(let value): - return "include:\(value)" - case .rarity(let value, let comparison): - return "rarity\(comparison.rawValue)\(value)" - case .in(let value): - return "in:\(value)" - } + /// The Scryfall syntax query string representing the filter + public var filterString: String { + switch self { + case .fullOracleText(let value): + return "fo:\(value)" + case .oracleId(let id): + return "oracleid:\(id)" + case .keyword(let value): + return "keyword:\(value)" + case .compoundOr(let filters): + return "(\(filters.map { $0.filterString }.joined(separator: " or ")))" + case .compoundAnd(let filters): + return "(\(filters.map { $0.filterString }.joined(separator: " and ")))" + case .name(let value): + return "name:\(value)" + case .colors(let value, let comparison): + return "color\(comparison.rawValue)\(value)" + case .colorIdentity(let value, let comparison): + return "identity\(comparison.rawValue)\(value)" + case .type(let value): + return "type:\(value)" + case .oracleText(let value): + return "oracle:\(value)" + case .mana(let value, let comparison): + return "mana\(comparison.rawValue)\(value)" + case .devotion(let value, let comparison): + return "devotion\(comparison.rawValue)\(value)" + case .produces(let value, let comparison): + return "produces\(comparison.rawValue)\(value)" + case .cmc(let value, let comparison): + return "cmc\(comparison.rawValue)\(value)" + case .power(let value, let comparison): + return "power\(comparison.rawValue)\(value)" + case .toughness(let value, let comparison): + return "toughness\(comparison.rawValue)\(value)" + case .loyalty(let value, let comparison): + return "loyalty\(comparison.rawValue)\(value)" + case .set(let value): + return "set:\(value)" + case .block(let value): + return "block:\(value)" + case .collectorNumber(let value, let comparison): + return "collectorNumber\(comparison.rawValue)\(value)" + case .format(let value): + return "format:\(value)" + case .banned(let value): + return "banned:\(value)" + case .restricted(let value): + return "restricted:\(value)" + case .usd(let value, let comparison): + return "usd\(comparison.rawValue)\(value)" + case .tix(let value, let comparison): + return "tix\(comparison.rawValue)\(value)" + case .eur(let value, let comparison): + return "eur\(comparison.rawValue)\(value)" + case .artist(let value): + return "artist:\(value)" + case .flavor(let value): + return "flavor:\(value)" + case .watermark(let value): + return "watermark:\(value)" + case .border(let value): + return "border:\(value)" + case .frame(let value): + return "frame:\(value)" + case .art(let value): + return "art:\(value)" + case .function(let value): + return "function:\(value)" + case .cube(let value): + return "cube:\(value)" + case .game(let value): + return "game:\(value)" + case .year(let value, let comparison): + return "year\(comparison.rawValue)\(value)" + case .language(let value): + return "language:\(value)" + case .is(let value): + return "is:\(value)" + case .not(let value): + return "not:\(value)" + case .include(let value): + return "include:\(value)" + case .rarity(let value, let comparison): + return "rarity\(comparison.rawValue)\(value)" + case .in(let value): + return "in:\(value)" } + } - /// The Scryfall syntax query string representing the filter, negated - public var negated: String { - "-\(filterString)" - } + /// The Scryfall syntax query string representing the filter, negated + public var negated: String { + "-\(filterString)" + } } diff --git a/Sources/ScryfallKit/Models/MTGSet.swift b/Sources/ScryfallKit/Models/MTGSet.swift index e116e10..20e0a79 100644 --- a/Sources/ScryfallKit/Models/MTGSet.swift +++ b/Sources/ScryfallKit/Models/MTGSet.swift @@ -9,127 +9,130 @@ import OSLog /// /// [Scryfall documentation](https://scryfall.com/docs/api/sets) public struct MTGSet: Codable, Identifiable, Hashable, Sendable { - /// An value that can be used to identify a set - public enum Identifier { - case code(code: String) - case scryfallID(id: String) - case tcgPlayerID(id: String) + /// An value that can be used to identify a set + public enum Identifier { + case code(code: String) + case scryfallID(id: String) + case tcgPlayerID(id: String) - var identifier: String { - switch self { - case .code(let code): - return code - case .scryfallID(let id): - return id - case .tcgPlayerID(let id): - return id - } - } + var identifier: String { + switch self { + case .code(let code): + return code + case .scryfallID(let id): + return id + case .tcgPlayerID(let id): + return id + } } + } - /// A machine-readable value describing the type of set this is. - /// - /// See [Scryfall's docs](https://scryfall.com/docs/api/sets#set-types) for more information on set types - public enum `Type`: String, Codable, Sendable { - // While "masters" is in fact not inclusive, it's also a name that we can't control - // swiftlint:disable:next inclusive_language - case core, expansion, masters, masterpiece, spellbook, commander, planechase, archenemy, vanguard, funny, starter, box, promo, token, memorabilia, arsenal, alchemy, minigame, unknown - case fromTheVault = "from_the_vault" - case premiumDeck = "premium_deck" - case duelDeck = "duel_deck" - case draftInnovation = "draft_innovation" - case treasureChest = "treasure_chest" + /// A machine-readable value describing the type of set this is. + /// + /// See [Scryfall's docs](https://scryfall.com/docs/api/sets#set-types) for more information on set types + public enum `Type`: String, Codable, Sendable { + // While "masters" is in fact not inclusive, it's also a name that we can't control + // swiftlint:disable:next inclusive_language + case core, expansion, masters, masterpiece, spellbook, commander, planechase, archenemy, + vanguard, funny, starter, box, promo, token, memorabilia, arsenal, alchemy, minigame, unknown + case fromTheVault = "from_the_vault" + case premiumDeck = "premium_deck" + case duelDeck = "duel_deck" + case draftInnovation = "draft_innovation" + case treasureChest = "treasure_chest" - public init(from decoder: Decoder) throws { - self = try Self(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown - if self == .unknown, let rawValue = try? String(from: decoder) { - if #available(iOS 14.0, macOS 11.0, *) { - Logger.main.warning("Decoded unknown MTGSet Type: \(rawValue)") - } else { - print("Decoded unknown MTGSet Type: \(rawValue)") - } - } + public init(from decoder: Decoder) throws { + self = try Self(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown + if self == .unknown, let rawValue = try? String(from: decoder) { + if #available(iOS 14.0, macOS 11.0, *) { + Logger.main.warning("Decoded unknown MTGSet Type: \(rawValue)") + } else { + print("Decoded unknown MTGSet Type: \(rawValue)") } + } } + } - /// A unique ID for this set on Scryfall that will not change. - public var id: UUID - /// The unique three to five-letter code for this set. - public var code: String - /// The unique code for this set on MTGO, which may differ from the regular code. - public var mtgoCode: 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. - public var name: String - /// A computer-readable classification for this set. - public var setType: MTGSet.`Type` - /// The date the set was released or the first card was printed in the set (in GMT-8 Pacific time). - public var releasedAt: String? - /// The block code for this set, if any. - public var blockCode: String? - /// The block or group name code for this set, if any. - public var block: String? - /// The set code for the parent set, if any. promo and token sets often have a parent set. - public var parentSetCode: String? - /// The number of cards in this set. - public var cardCount: Int - /// The denominator for the set’s printed collector numbers. - public var printedSize: Int? - /// True if this set was only released in a video game. - public var digital: Bool - /// True if this set contains only foil cards. - public var foilOnly: Bool - /// True if this set contains only nonfoil cards. - public var nonfoilOnly: Bool - /// A link to this set’s permapage on Scryfall’s website. - public var scryfallUri: String - /// A link to this set object on Scryfall’s API. - public var uri: String - /// A URI to an SVG file for this set’s icon on Scryfall’s CDN. - /// - /// - Note: Hotlinking this image isn’t recommended, because it may change slightly over time. You should download it and use it locally for your particular user interface needs. - public var iconSvgUri: String - /// A Scryfall API URI that you can request to begin paginating over the cards in this set. - public var searchUri: String + /// A unique ID for this set on Scryfall that will not change. + public var id: UUID + /// The unique three to five-letter code for this set. + public var code: String + /// The unique code for this set on MTGO, which may differ from the regular code. + public var mtgoCode: 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. + public var name: String + /// A computer-readable classification for this set. + public var setType: MTGSet.`Type` + /// The date the set was released or the first card was printed in the set (in GMT-8 Pacific time). + public var releasedAt: String? + /// The block code for this set, if any. + public var blockCode: String? + /// The block or group name code for this set, if any. + public var block: String? + /// The set code for the parent set, if any. promo and token sets often have a parent set. + public var parentSetCode: String? + /// The number of cards in this set. + public var cardCount: Int + /// The denominator for the set’s printed collector numbers. + public var printedSize: Int? + /// True if this set was only released in a video game. + public var digital: Bool + /// True if this set contains only foil cards. + public var foilOnly: Bool + /// True if this set contains only nonfoil cards. + public var nonfoilOnly: Bool + /// A link to this set’s permapage on Scryfall’s website. + public var scryfallUri: String + /// A link to this set object on Scryfall’s API. + public var uri: String + /// A URI to an SVG file for this set’s icon on Scryfall’s CDN. + /// + /// - Note: Hotlinking this image isn’t recommended, because it may change slightly over time. You should download it and use it locally for your particular user interface needs. + public var iconSvgUri: String + /// A Scryfall API URI that you can request to begin paginating over the cards in this set. + public var searchUri: String - public init(id: UUID, - code: String, - mtgoCode: String? = nil, - tcgplayerId: Int? = nil, - name: String, - setType: MTGSet.`Type`, - releasedAt: String? = nil, - blockCode: String? = nil, - block: String? = nil, - parentSetCode: String? = nil, - cardCount: Int, - printedSize: Int? = nil, - digital: Bool, - foilOnly: Bool, - nonfoilOnly: Bool, - scryfallUri: String, - uri: String, - iconSvgUri: String, - searchUri: String) { - self.id = id - self.code = code - self.mtgoCode = mtgoCode - self.tcgplayerId = tcgplayerId - self.name = name - self.setType = setType - self.releasedAt = releasedAt - self.blockCode = blockCode - self.block = block - self.parentSetCode = parentSetCode - self.cardCount = cardCount - self.printedSize = printedSize - self.digital = digital - self.foilOnly = foilOnly - self.nonfoilOnly = nonfoilOnly - self.scryfallUri = scryfallUri - self.uri = uri - self.iconSvgUri = iconSvgUri - self.searchUri = searchUri - } + public init( + id: UUID, + code: String, + mtgoCode: String? = nil, + tcgplayerId: Int? = nil, + name: String, + setType: MTGSet.`Type`, + releasedAt: String? = nil, + blockCode: String? = nil, + block: String? = nil, + parentSetCode: String? = nil, + cardCount: Int, + printedSize: Int? = nil, + digital: Bool, + foilOnly: Bool, + nonfoilOnly: Bool, + scryfallUri: String, + uri: String, + iconSvgUri: String, + searchUri: String + ) { + self.id = id + self.code = code + self.mtgoCode = mtgoCode + self.tcgplayerId = tcgplayerId + self.name = name + self.setType = setType + self.releasedAt = releasedAt + self.blockCode = blockCode + self.block = block + self.parentSetCode = parentSetCode + self.cardCount = cardCount + self.printedSize = printedSize + self.digital = digital + self.foilOnly = foilOnly + self.nonfoilOnly = nonfoilOnly + self.scryfallUri = scryfallUri + self.uri = uri + self.iconSvgUri = iconSvgUri + self.searchUri = searchUri + } } diff --git a/Sources/ScryfallKit/Models/ObjectList.swift b/Sources/ScryfallKit/Models/ObjectList.swift index 02766e6..730bb94 100644 --- a/Sources/ScryfallKit/Models/ObjectList.swift +++ b/Sources/ScryfallKit/Models/ObjectList.swift @@ -1,6 +1,6 @@ // // ObjectList.swift -// +// import Foundation @@ -8,24 +8,27 @@ import Foundation /// /// [Scryfall documentation](https://scryfall.com/docs/api/lists) public struct ObjectList: Codable, Sendable where T: Sendable { - /// The data contained in the list - public var data: [T] - /// True if there's a next page, nil if there's only one page - public var hasMore: Bool? - /// If there is a page beyond the current page, this field will contain a full API URI to that page. You may submit a HTTP GET request to that URI to continue paginating forward on this List. - public var nextPage: String? - /// If this is a list of Card objects, this field will contain the total number of cards found across all pages. - public var totalCards: Int? - /// An array of human-readable warnings issued when generating this list, as strings. - /// - /// Warnings are non-fatal issues that the API discovered with your input. In general, they indicate that the List will not contain the all of the information you requested. You should fix the warnings and re-submit your request. - public var warnings: [String]? + /// The data contained in the list + public var data: [T] + /// True if there's a next page, nil if there's only one page + public var hasMore: Bool? + /// If there is a page beyond the current page, this field will contain a full API URI to that page. You may submit a HTTP GET request to that URI to continue paginating forward on this List. + public var nextPage: String? + /// If this is a list of Card objects, this field will contain the total number of cards found across all pages. + public var totalCards: Int? + /// An array of human-readable warnings issued when generating this list, as strings. + /// + /// Warnings are non-fatal issues that the API discovered with your input. In general, they indicate that the List will not contain the all of the information you requested. You should fix the warnings and re-submit your request. + public var warnings: [String]? - public init(data: [T], hasMore: Bool? = nil, nextPage: String? = nil, totalCards: Int? = nil, warnings: [String]? = nil) { - self.data = data - self.hasMore = hasMore - self.nextPage = nextPage - self.totalCards = totalCards - self.warnings = warnings - } + public init( + data: [T], hasMore: Bool? = nil, nextPage: String? = nil, totalCards: Int? = nil, + warnings: [String]? = nil + ) { + self.data = data + self.hasMore = hasMore + self.nextPage = nextPage + self.totalCards = totalCards + self.warnings = warnings + } } diff --git a/Sources/ScryfallKit/Models/ScryfallError.swift b/Sources/ScryfallKit/Models/ScryfallError.swift index c46623e..baf7646 100644 --- a/Sources/ScryfallKit/Models/ScryfallError.swift +++ b/Sources/ScryfallKit/Models/ScryfallError.swift @@ -1,40 +1,42 @@ // // ScryfallError.swift -// +// import Foundation /// An error returned by the Scryfall REST API public struct ScryfallError: Codable, CustomStringConvertible, Sendable { - /// An integer HTTP status code for this error. - public var status: Int - /// A computer-friendly string representing the appropriate HTTP status code. - public var code: String - /// A human-readable string explaining the error. - public var details: String - /// A computer-friendly string that provides additional context for the main error. - /// - /// For example, an endpoint many generate HTTP 404 errors for different kinds of input. This field will provide a label for the specific kind of 404 failure, such as ambiguous. - public var type: String? - /// If your input also generated non-failure warnings, they will be provided as human-readable strings in this array. - public var warnings: [String]? + /// An integer HTTP status code for this error. + public var status: Int + /// A computer-friendly string representing the appropriate HTTP status code. + public var code: String + /// A human-readable string explaining the error. + public var details: String + /// A computer-friendly string that provides additional context for the main error. + /// + /// For example, an endpoint many generate HTTP 404 errors for different kinds of input. This field will provide a label for the specific kind of 404 failure, such as ambiguous. + public var type: String? + /// If your input also generated non-failure warnings, they will be provided as human-readable strings in this array. + public var warnings: [String]? - public init(status: Int, code: String, details: String, type: String? = nil, warnings: [String]? = nil) { - self.status = status - self.code = code - self.details = details - self.type = type - self.warnings = warnings - } + public init( + status: Int, code: String, details: String, type: String? = nil, warnings: [String]? = nil + ) { + self.status = status + self.code = code + self.details = details + self.type = type + self.warnings = warnings + } - public var description: String { - return """ - Scryfall Error - - Status: \(status) - - Code: \(code) - - Details: \(details) - - Type?: \(String(describing: type)) - - Warnings?: \(String(describing: warnings)) - """ - } + public var description: String { + return """ + Scryfall Error + - Status: \(status) + - Code: \(code) + - Details: \(details) + - Type?: \(String(describing: type)) + - Warnings?: \(String(describing: warnings)) + """ + } } diff --git a/Sources/ScryfallKit/Networking/EndpointRequests/CardRequests.swift b/Sources/ScryfallKit/Networking/EndpointRequests/CardRequests.swift index c1b90db..d48dfa3 100644 --- a/Sources/ScryfallKit/Networking/EndpointRequests/CardRequests.swift +++ b/Sources/ScryfallKit/Networking/EndpointRequests/CardRequests.swift @@ -1,130 +1,134 @@ // // SearchCards.swift -// +// import Foundation import OSLog struct SearchCards: EndpointRequest { - var body: Data? - var requestMethod: RequestMethod = .GET - var path = "cards/search" - var queryParams: [URLQueryItem] - - init(query: String, - unique: UniqueMode? = nil, - order: SortMode? = nil, - dir: SortDirection? = nil, - includeExtras: Bool? = nil, - includeMultilingual: Bool? = nil, - includeVariations: Bool? = nil, - page: Int? = nil) { - - self.queryParams = [ - URLQueryItem(name: "q", value: query), - URLQueryItem(name: "unique", value: unique?.rawValue), - URLQueryItem(name: "order", value: order?.rawValue), - URLQueryItem(name: "dir", value: dir?.rawValue), - URLQueryItem(name: "include_extras", value: includeExtras?.description), - URLQueryItem(name: "include_multilingual", value: includeMultilingual?.description), - URLQueryItem(name: "include_variations", value: includeVariations?.description), - URLQueryItem(name: "page", value: page?.description) - ] - } + var body: Data? + var requestMethod: RequestMethod = .GET + var path = "cards/search" + var queryParams: [URLQueryItem] + + init( + query: String, + unique: UniqueMode? = nil, + order: SortMode? = nil, + dir: SortDirection? = nil, + includeExtras: Bool? = nil, + includeMultilingual: Bool? = nil, + includeVariations: Bool? = nil, + page: Int? = nil + ) { + + self.queryParams = [ + URLQueryItem(name: "q", value: query), + URLQueryItem(name: "unique", value: unique?.rawValue), + URLQueryItem(name: "order", value: order?.rawValue), + URLQueryItem(name: "dir", value: dir?.rawValue), + URLQueryItem(name: "include_extras", value: includeExtras?.description), + URLQueryItem(name: "include_multilingual", value: includeMultilingual?.description), + URLQueryItem(name: "include_variations", value: includeVariations?.description), + URLQueryItem(name: "page", value: page?.description), + ] + } } struct GetCardNamed: EndpointRequest { - var body: Data? - var requestMethod: RequestMethod = .GET - var path = "cards/named" - var queryParams: [URLQueryItem] - - init(exact: String? = nil, fuzzy: String? = nil, set: String? = nil) { - queryParams = [ - URLQueryItem(name: "exact", value: exact), - URLQueryItem(name: "fuzzy", value: fuzzy), - URLQueryItem(name: "set", value: set) - ] - } + var body: Data? + var requestMethod: RequestMethod = .GET + var path = "cards/named" + var queryParams: [URLQueryItem] + + init(exact: String? = nil, fuzzy: String? = nil, set: String? = nil) { + queryParams = [ + URLQueryItem(name: "exact", value: exact), + URLQueryItem(name: "fuzzy", value: fuzzy), + URLQueryItem(name: "set", value: set), + ] + } } struct GetCardAutocomplete: EndpointRequest { - var body: Data? - var requestMethod: RequestMethod = .GET - var path = "cards/autocomplete" - var queryParams: [URLQueryItem] - - init(query: String, includeExtras: Bool? = nil) { - self.queryParams = [ - URLQueryItem(name: "q", value: query), - URLQueryItem(name: "include_extras", value: includeExtras?.description) - ] - } + var body: Data? + var requestMethod: RequestMethod = .GET + var path = "cards/autocomplete" + var queryParams: [URLQueryItem] + + init(query: String, includeExtras: Bool? = nil) { + self.queryParams = [ + URLQueryItem(name: "q", value: query), + URLQueryItem(name: "include_extras", value: includeExtras?.description), + ] + } } struct GetRandomCard: EndpointRequest { - var body: Data? - var requestMethod: RequestMethod = .GET - var path = "cards/random" - var queryParams: [URLQueryItem] - - init(query: String?) { - queryParams = [ - URLQueryItem(name: "q", value: query) - ] - } + var body: Data? + var requestMethod: RequestMethod = .GET + var path = "cards/random" + var queryParams: [URLQueryItem] + + init(query: String?) { + queryParams = [ + URLQueryItem(name: "q", value: query) + ] + } } struct GetCard: EndpointRequest { - let identifier: Card.Identifier - - var path: String { - switch identifier { - case .scryfallID(let id): - return "cards/\(id)" - case .setCodeCollectorNo(let setCode, let collectorNumber, let lang): - let pathStr = "cards/\(setCode)/\(collectorNumber)" - guard let language = lang else { return pathStr } - return "\(pathStr)/\(language)" - default: - // This guard should never trip. The only card identifier that doesn't have provider/id is the set code/collector - guard let id = identifier.id else { - if #available(iOS 14.0, macOS 11.0, *) { - Logger.main.error("Provided identifier doesn't have a provider or doesn't have an id") - } else { - print("Provided identifier doesn't have a provider or doesn't have an id") - } - - fatalError("Encountered a situation that shouldn't be possible: Card identifier's id property was nil") - } - - return "cards/\(identifier.provider)/\(id)" + let identifier: Card.Identifier + + var path: String { + switch identifier { + case .scryfallID(let id): + return "cards/\(id)" + case .setCodeCollectorNo(let setCode, let collectorNumber, let lang): + let pathStr = "cards/\(setCode)/\(collectorNumber)" + guard let language = lang else { return pathStr } + return "\(pathStr)/\(language)" + default: + // This guard should never trip. The only card identifier that doesn't have provider/id is the set code/collector + guard let id = identifier.id else { + if #available(iOS 14.0, macOS 11.0, *) { + Logger.main.error("Provided identifier doesn't have a provider or doesn't have an id") + } else { + print("Provided identifier doesn't have a provider or doesn't have an id") } + + fatalError( + "Encountered a situation that shouldn't be possible: Card identifier's id property was nil" + ) + } + + return "cards/\(identifier.provider)/\(id)" } + } - var queryParams = [URLQueryItem]() - var requestMethod: RequestMethod = .GET - var body: Data? + var queryParams = [URLQueryItem]() + var requestMethod: RequestMethod = .GET + var body: Data? } struct GetCardCollection: EndpointRequest { - var path = "cards/collection" - var queryParams: [URLQueryItem] = [] - var requestMethod: RequestMethod = .POST - var body: Data? - - init(identifiers: [Card.CollectionIdentifier]) { - let identifierJSON = identifiers.map { $0.json } - let requestBody: [String: [[String: String]]] = [ "identifiers": identifierJSON ] - - do { - body = try JSONSerialization.data(withJSONObject: requestBody) - } catch { - if #available(iOS 14.0, macOS 11.0, *) { - Logger.main.error("Errored serializing dict to JSON for GetCardCollection request") - } else { - print("Errored serializing dict to JSON for GetCardCollection request") - } - } + var path = "cards/collection" + var queryParams: [URLQueryItem] = [] + var requestMethod: RequestMethod = .POST + var body: Data? + + init(identifiers: [Card.CollectionIdentifier]) { + let identifierJSON = identifiers.map { $0.json } + let requestBody: [String: [[String: String]]] = ["identifiers": identifierJSON] + + do { + body = try JSONSerialization.data(withJSONObject: requestBody) + } catch { + if #available(iOS 14.0, macOS 11.0, *) { + Logger.main.error("Errored serializing dict to JSON for GetCardCollection request") + } else { + print("Errored serializing dict to JSON for GetCardCollection request") + } } + } } diff --git a/Sources/ScryfallKit/Networking/EndpointRequests/CatalogRequests.swift b/Sources/ScryfallKit/Networking/EndpointRequests/CatalogRequests.swift index 678cb44..02aa45b 100644 --- a/Sources/ScryfallKit/Networking/EndpointRequests/CatalogRequests.swift +++ b/Sources/ScryfallKit/Networking/EndpointRequests/CatalogRequests.swift @@ -5,13 +5,13 @@ import Foundation struct GetCatalog: EndpointRequest { - var catalogType: Catalog.`Type` + var catalogType: Catalog.`Type` - var path: String { - return "catalog/\(catalogType.rawValue)" - } + var path: String { + return "catalog/\(catalogType.rawValue)" + } - var queryParams: [URLQueryItem] = [] - var requestMethod: RequestMethod = .GET - var body: Data? + var queryParams: [URLQueryItem] = [] + var requestMethod: RequestMethod = .GET + var body: Data? } diff --git a/Sources/ScryfallKit/Networking/EndpointRequests/EndpointRequest.swift b/Sources/ScryfallKit/Networking/EndpointRequests/EndpointRequest.swift index 362656d..ec90cdd 100644 --- a/Sources/ScryfallKit/Networking/EndpointRequests/EndpointRequest.swift +++ b/Sources/ScryfallKit/Networking/EndpointRequests/EndpointRequest.swift @@ -1,42 +1,42 @@ // // EndpointRequest.swift -// +// import Foundation import OSLog protocol EndpointRequest { - var path: String { get } - var queryParams: [URLQueryItem] { get } - var requestMethod: RequestMethod { get } - var body: Data? { get } + var path: String { get } + var queryParams: [URLQueryItem] { get } + var requestMethod: RequestMethod { get } + var body: Data? { get } } extension EndpointRequest { - var urlRequest: URLRequest? { - var urlComponents = URLComponents(string: "https://api.scryfall.com/\(path)") - if !queryParams.isEmpty { - urlComponents?.queryItems = queryParams.compactMap { $0.value == nil ? nil : $0 } - } + var urlRequest: URLRequest? { + var urlComponents = URLComponents(string: "https://api.scryfall.com/\(path)") + if !queryParams.isEmpty { + urlComponents?.queryItems = queryParams.compactMap { $0.value == nil ? nil : $0 } + } - guard let url = urlComponents?.url else { - if #available(iOS 14.0, macOS 11.0, *) { - Logger.main.error("Couldn't make url") - } else { - print("Couldn't make url") - } - return nil - } + guard let url = urlComponents?.url else { + if #available(iOS 14.0, macOS 11.0, *) { + Logger.main.error("Couldn't make url") + } else { + print("Couldn't make url") + } + return nil + } - var urlRequest = URLRequest(url: url) - urlRequest.httpMethod = requestMethod.rawValue - urlRequest.httpBody = body - urlRequest.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") + var urlRequest = URLRequest(url: url) + urlRequest.httpMethod = requestMethod.rawValue + urlRequest.httpBody = body + urlRequest.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") - return urlRequest - } + return urlRequest + } } enum RequestMethod: String { - case GET, POST + case GET, POST } diff --git a/Sources/ScryfallKit/Networking/EndpointRequests/RulingsRequests.swift b/Sources/ScryfallKit/Networking/EndpointRequests/RulingsRequests.swift index d566f26..862bccb 100644 --- a/Sources/ScryfallKit/Networking/EndpointRequests/RulingsRequests.swift +++ b/Sources/ScryfallKit/Networking/EndpointRequests/RulingsRequests.swift @@ -1,27 +1,27 @@ // // RulingsRequests.swift -// +// import Foundation struct GetRulings: EndpointRequest { - var identifier: Card.Ruling.Identifier + var identifier: Card.Ruling.Identifier - var path: String { - switch identifier { - case .multiverseID(let id): - return "cards/multiverse/\(id)/rulings" - case .mtgoID(let id): - return "cards/mtgo/\(id)/rulings" - case .arenaID(let id): - return "cards/arena/\(id)/rulings" - case .collectorNumberSet(let collectorNumber, let set): - return "cards/\(set)/\(collectorNumber)/rulings" - case .scryfallID(let id): - return "cards/\(id)/rulings" - } + var path: String { + switch identifier { + case .multiverseID(let id): + return "cards/multiverse/\(id)/rulings" + case .mtgoID(let id): + return "cards/mtgo/\(id)/rulings" + case .arenaID(let id): + return "cards/arena/\(id)/rulings" + case .collectorNumberSet(let collectorNumber, let set): + return "cards/\(set)/\(collectorNumber)/rulings" + case .scryfallID(let id): + return "cards/\(id)/rulings" } - var queryParams: [URLQueryItem] = [] - var requestMethod: RequestMethod = .GET - var body: Data? + } + var queryParams: [URLQueryItem] = [] + var requestMethod: RequestMethod = .GET + var body: Data? } diff --git a/Sources/ScryfallKit/Networking/EndpointRequests/SetRequests.swift b/Sources/ScryfallKit/Networking/EndpointRequests/SetRequests.swift index fa9f004..67d8dea 100644 --- a/Sources/ScryfallKit/Networking/EndpointRequests/SetRequests.swift +++ b/Sources/ScryfallKit/Networking/EndpointRequests/SetRequests.swift @@ -1,30 +1,30 @@ // // SetEndpointRequest.swift -// +// import Foundation struct GetSets: EndpointRequest { - var path = "sets" - var queryParams: [URLQueryItem] = [] - var requestMethod: RequestMethod = .GET - var body: Data? + var path = "sets" + var queryParams: [URLQueryItem] = [] + var requestMethod: RequestMethod = .GET + var body: Data? } struct GetSet: EndpointRequest { - var identifier: MTGSet.Identifier + var identifier: MTGSet.Identifier - var path: String { - switch self.identifier { - case .code(let code): - return "sets/\(code)" - case .scryfallID(let id): - return "sets/\(id)" - case .tcgPlayerID(let id): - return "sets/tcglayer/\(id)" - } + var path: String { + switch self.identifier { + case .code(let code): + return "sets/\(code)" + case .scryfallID(let id): + return "sets/\(id)" + case .tcgPlayerID(let id): + return "sets/tcglayer/\(id)" } - var queryParams: [URLQueryItem] = [] - var requestMethod: RequestMethod = .GET - var body: Data? + } + var queryParams: [URLQueryItem] = [] + var requestMethod: RequestMethod = .GET + var body: Data? } diff --git a/Sources/ScryfallKit/Networking/EndpointRequests/SymbolRequests.swift b/Sources/ScryfallKit/Networking/EndpointRequests/SymbolRequests.swift index 9ab14d1..7ea5743 100644 --- a/Sources/ScryfallKit/Networking/EndpointRequests/SymbolRequests.swift +++ b/Sources/ScryfallKit/Networking/EndpointRequests/SymbolRequests.swift @@ -1,30 +1,30 @@ // // SymbolRequests.swift -// +// import Foundation struct GetSymbology: EndpointRequest { - var path = "symbology" - var queryParams: [URLQueryItem] = [] - var requestMethod: RequestMethod = .GET - var body: Data? + var path = "symbology" + var queryParams: [URLQueryItem] = [] + var requestMethod: RequestMethod = .GET + var body: Data? } struct ParseManaCost: EndpointRequest { - // Request params - var cost: String + // Request params + var cost: String - // Protocol vars - var path = "symbology/parse-mana" - var queryParams: [URLQueryItem] - var requestMethod: RequestMethod = .GET - var body: Data? + // Protocol vars + var path = "symbology/parse-mana" + var queryParams: [URLQueryItem] + var requestMethod: RequestMethod = .GET + var body: Data? - init(cost: String) { - self.cost = cost - self.queryParams = [ - URLQueryItem(name: "cost", value: cost) - ] - } + init(cost: String) { + self.cost = cost + self.queryParams = [ + URLQueryItem(name: "cost", value: cost) + ] + } } diff --git a/Sources/ScryfallKit/Networking/NetworkService.swift b/Sources/ScryfallKit/Networking/NetworkService.swift index c46d699..892fec0 100644 --- a/Sources/ScryfallKit/Networking/NetworkService.swift +++ b/Sources/ScryfallKit/Networking/NetworkService.swift @@ -26,7 +26,10 @@ protocol NetworkServiceProtocol: Sendable { struct NetworkService: NetworkServiceProtocol, Sendable { var logLevel: NetworkLogLevel - func request(_ request: EndpointRequest, as type: T.Type, completion: @Sendable @escaping (Result) -> Void) { + func request( + _ request: EndpointRequest, as type: T.Type, + completion: @Sendable @escaping (Result) -> Void + ) { guard let urlRequest = request.urlRequest else { if #available(macOS 11.0, iOS 14.0, *) { Logger.network.error("Invalid url request") @@ -37,7 +40,9 @@ struct NetworkService: NetworkServiceProtocol, Sendable { return } - if logLevel == .verbose, let body = urlRequest.httpBody, let JSONString = String(data: body, encoding: String.Encoding.utf8) { + if logLevel == .verbose, let body = urlRequest.httpBody, + let JSONString = String(data: body, encoding: String.Encoding.utf8) + { print("Sending request with body:") if #available(macOS 11.0, iOS 14.0, *) { Logger.network.debug("\(JSONString)") @@ -56,14 +61,17 @@ struct NetworkService: NetworkServiceProtocol, Sendable { } if #available(macOS 11.0, iOS 14.0, *) { - Logger.network.debug("Making request to: '\(String(describing: urlRequest.url?.absoluteString))'") + Logger.network.debug( + "Making request to: '\(String(describing: urlRequest.url?.absoluteString))'") } else { print("Making request to: '\(String(describing: urlRequest.url?.absoluteString))'") } task.resume() } - func handle(dataType: T.Type, data: Data?, response: URLResponse?, error: Error?) throws -> T { + func handle(dataType: T.Type, data: Data?, response: URLResponse?, error: Error?) + throws -> T + { if let error = error { throw error } @@ -97,7 +105,8 @@ struct NetworkService: NetworkServiceProtocol, Sendable { } @available(macOS 10.15.0, *, iOS 13.0.0, *) - func request(_ request: EndpointRequest, as type: T.Type) async throws -> T where T: Sendable { + func request(_ request: EndpointRequest, as type: T.Type) async throws -> T + where T: Sendable { try await withCheckedThrowingContinuation { continuation in self.request(request, as: type) { result in continuation.resume(with: result) diff --git a/Sources/ScryfallKit/ScryfallClient+Async.swift b/Sources/ScryfallKit/ScryfallClient+Async.swift index 4ae359e..6b69232 100644 --- a/Sources/ScryfallKit/ScryfallClient+Async.swift +++ b/Sources/ScryfallKit/ScryfallClient+Async.swift @@ -34,22 +34,25 @@ extension ScryfallClient { } /// Equivalent to ``searchCards(query:unique:order:sortDirection:includeExtras:includeMultilingual:includeVariations:page:completion:)`` but with async/await syntax - public func searchCards(query: String, - unique: UniqueMode? = nil, - order: SortMode? = nil, - sortDirection: SortDirection? = nil, - includeExtras: Bool? = nil, - includeMultilingual: Bool? = nil, - includeVariations: Bool? = nil, - page: Int? = nil) async throws -> ObjectList { - let request = SearchCards(query: query, - unique: unique, - order: order, - dir: sortDirection, - includeExtras: includeExtras, - includeMultilingual: includeMultilingual, - includeVariations: includeVariations, - page: page) + public func searchCards( + query: String, + unique: UniqueMode? = nil, + order: SortMode? = nil, + sortDirection: SortDirection? = nil, + includeExtras: Bool? = nil, + includeMultilingual: Bool? = nil, + includeVariations: Bool? = nil, + page: Int? = nil + ) async throws -> ObjectList { + let request = SearchCards( + query: query, + unique: unique, + order: order, + dir: sortDirection, + includeExtras: includeExtras, + includeMultilingual: includeMultilingual, + includeVariations: includeVariations, + page: page) return try await networkService.request(request, as: ObjectList.self) } @@ -67,7 +70,9 @@ extension ScryfallClient { } /// Equivalent to ``getCardNameAutocomplete(query:includeExtras:completion:)`` but with async/await syntax - public func getCardNameAutocomplete(query: String, includeExtras: Bool? = nil) async throws -> Catalog { + public func getCardNameAutocomplete(query: String, includeExtras: Bool? = nil) async throws + -> Catalog + { let request = GetCardAutocomplete(query: query, includeExtras: includeExtras) return try await networkService.request(request, as: Catalog.self) } @@ -85,7 +90,9 @@ extension ScryfallClient { } /// Equivalent to ``getCardCollection(identifiers:completion:)`` but with async/await syntax - public func getCardCollection(identifiers: [Card.CollectionIdentifier]) async throws -> ObjectList { + public func getCardCollection(identifiers: [Card.CollectionIdentifier]) async throws + -> ObjectList + { let request = GetCardCollection(identifiers: identifiers) return try await networkService.request(request, as: ObjectList.self) } @@ -108,7 +115,9 @@ extension ScryfallClient { } /// Equivalent to ``getRulings(_:completion:)`` but with async/await syntax - public func getRulings(_ identifier: Card.Ruling.Identifier) async throws -> ObjectList { + public func getRulings(_ identifier: Card.Ruling.Identifier) async throws -> ObjectList< + Card.Ruling + > { let request = GetRulings(identifier: identifier) return try await networkService.request(request, as: ObjectList.self) } diff --git a/Sources/ScryfallKit/ScryfallClient.swift b/Sources/ScryfallKit/ScryfallClient.swift index d22c9fc..4fd0822 100644 --- a/Sources/ScryfallKit/ScryfallClient.swift +++ b/Sources/ScryfallKit/ScryfallClient.swift @@ -6,234 +6,265 @@ import Foundation /// A client for interacting with the Scryfall API public final class ScryfallClient: Sendable { - private let networkLogLevel: NetworkLogLevel - let networkService: NetworkServiceProtocol - - /// Initialize an instance of the ScryfallClient - /// - Parameter networkLogLevel: The desired logging level. See ``NetworkLogLevel`` - public init(networkLogLevel: NetworkLogLevel = .minimal) { - self.networkLogLevel = networkLogLevel - self.networkService = NetworkService(logLevel: networkLogLevel) - } - - /// Perform a search using an array of ``CardFieldFilter`` objects. - /// - /// Performs a Scryfall search using the `/cards/search` endpoint. This method is simply a convenience wrapper around ``searchCards(query:unique:order:sortDirection:includeExtras:includeMultilingual:includeVariations:page:)`` - /// - /// Full reference at: https://scryfall.com/docs/api/cards/search. - /// - /// - Parameters: - /// - filters: Only include cards matching these filters - /// - unique: The strategy for omitting similar cards. See ``UniqueMode`` - /// - order: The method to sort returned cards. See ``SortMode`` - /// - sortDirection: The direction to sort cards. See ``SortDirection`` - /// - includeExtras: If true, extra cards (tokens, planes, etc) will be included. Equivalent to adding include:extras to the fulltext search. Defaults to `false` - /// - includeMultilingual: If true, cards in every language supported by Scryfall will be included. Defaults to `false`. - /// - includeVariations: If true, rare care variants will be included, like the Hairy Runesword. Defaults to `false`. - /// - page: The page number to return. Defaults to `1` - /// - completion: A function/block to be called when the search is complete - public func searchCards(filters: [CardFieldFilter], - unique: UniqueMode? = nil, - order: SortMode? = nil, - sortDirection: SortDirection? = nil, - includeExtras: Bool? = nil, - includeMultilingual: Bool? = nil, - includeVariations: Bool? = nil, - page: Int? = nil, - completion: @Sendable @escaping (Result, Error>) -> Void) { - let query = filters.map { $0.filterString }.joined(separator: " ") - searchCards(query: query, - unique: unique, - order: order, - sortDirection: sortDirection, - includeExtras: includeExtras, - includeMultilingual: includeMultilingual, - includeVariations: includeVariations, - page: page, - completion: completion) - } - - /// Perform a search using a string conforming to Scryfall query syntax. - /// - /// Full reference at: https://scryfall.com/docs/api/cards/search. - /// - /// - Parameters: - /// - filters: Only include cards matching these filters - /// - unique: The strategy for omitting similar cards. See ``UniqueMode`` - /// - order: The method to sort returned cards. See ``SortMode`` - /// - sortDirection: The direction to sort cards. See ``SortDirection`` - /// - includeExtras: If true, extra cards (tokens, planes, etc) will be included. Equivalent to adding include:extras to the fulltext search. Defaults to `false` - /// - includeMultilingual: If true, cards in every language supported by Scryfall will be included. Defaults to `false`. - /// - includeVariations: If true, rare care variants will be included, like the Hairy Runesword. Defaults to `false`. - /// - page: The page number to return. Defaults to `1` - /// - completion: A function/block to be called when the search is complete - public func searchCards(query: String, - unique: UniqueMode? = nil, - order: SortMode? = nil, - sortDirection: SortDirection? = nil, - includeExtras: Bool? = nil, - includeMultilingual: Bool? = nil, - includeVariations: Bool? = nil, - page: Int? = nil, - completion: @Sendable @escaping (Result, Error>) -> Void) { - - let request = SearchCards(query: query, - unique: unique, - order: order, - dir: sortDirection, - includeExtras: includeExtras, - includeMultilingual: includeMultilingual, - includeVariations: includeVariations, - page: page) - - networkService.request(request, as: ObjectList.self, completion: completion) - } - - /// Get a card with the exact name supplied - /// - /// Full reference at: https://scryfall.com/docs/api/cards/named - /// - /// - Parameters: - /// - exact: The exact card name to search for, case insenstive. - /// - set: A set code to limit the search to one set. - /// - completion: A function/block to be called when the search is complete - public func getCardByName(exact: String, set: String? = nil, completion: @Sendable @escaping (Result) -> Void) { - let request = GetCardNamed(exact: exact, set: set) - networkService.request(request, as: Card.self, completion: completion) - } - - /// Get a card with a name close to what was entered - /// - /// Full reference at: https://scryfall.com/docs/api/cards/named - /// - /// - Parameters: - /// - fuzzy: The exact card name to search for, case insenstive. - /// - set: A set code to limit the search to one set. - /// - completion: A function/block to be called when the search is complete - public func getCardByName(fuzzy: String, set: String? = nil, completion: @Sendable @escaping (Result) -> Void) { - let request = GetCardNamed(fuzzy: fuzzy, set: set) - networkService.request(request, as: Card.self, completion: completion) - } - - /// Retrieve up to 20 card name autocomplete suggestions for a given string. - /// - /// Full reference at: https://scryfall.com/docs/api/cards/autocomplete - /// - /// - Parameters: - /// - query: The string to autocomplete - /// - includeExtras: If true, extra cards (tokens, planes, vanguards, etc) will be included. Defaults to false. - /// - completion: A function/block to be called when the search is complete - /// - Returns: A ``Catalog`` of card names or an error - public func getCardNameAutocomplete(query: String, includeExtras: Bool? = nil, completion: @Sendable @escaping (Result) -> Void) { - let request = GetCardAutocomplete(query: query, includeExtras: includeExtras) - networkService.request(request, as: Catalog.self, completion: completion) - } - - /// Get a single random card - /// - /// [Scryfall documentation](https://scryfall.com/docs/api/cards/random) - /// - /// - Parameters: - /// - query: An optional fulltext search query to filter the pool of random cards. - /// - completion: A function/block to call when the request is complete - public func getRandomCard(query: String? = nil, completion: @Sendable @escaping (Result) -> Void) { - let request = GetRandomCard(query: query) - networkService.request(request, as: Card.self, completion: completion) - } - - /// Get a single card using a Card identifier. - /// - /// [Scryfall documentation](https://scryfall.com/docs/api/cards) - /// - /// See ``Card/Identifier`` for more information on identifiers - /// - /// - Parameters: - /// - identifier: The identifier for the desired card - /// - completion: A function/block to call when the request is complete - public func getCard(identifier: Card.Identifier, completion: @Sendable @escaping (Result) -> Void) { - let request = GetCard(identifier: identifier) - networkService.request(request, as: Card.self, completion: completion) - } - - /// Bulk request up to 75 cards at a time. - /// - /// [Scryfall documentation](https://scryfall.com/docs/api/cards/collection) - /// - /// - Parameters: - /// - identifiers: The array of identifiers - /// - completion: A function/block to call when the request is complete - public func getCardCollection(identifiers: [Card.CollectionIdentifier], completion: @Sendable @escaping (Result, Error>) -> Void) { - let request = GetCardCollection(identifiers: identifiers) - networkService.request(request, as: ObjectList.self, completion: completion) - } - - /// Get a catalog of Magic datapoints (keyword abilities, artist names, spell types, etc) - /// - /// [Scryfall documentation](https://scryfall.com/docs/api/catalogs) - /// - /// - Parameters: - /// - catalogType: The type of catalog to retrieve - /// - completion: A function/block to call when the request is complete - public func getCatalog(catalogType: Catalog.`Type`, completion: @Sendable @escaping (Result) -> Void) { - let request = GetCatalog(catalogType: catalogType) - networkService.request(request, as: Catalog.self, completion: completion) - } - - /// Get all MTG sets - /// - /// [Scryfall documentation](https://scryfall.com/docs/api/sets/all) - /// - /// - Parameter completion: A function/block to call when the request is complete - public func getSets(completion: @Sendable @escaping (Result, Error>) -> Void) { - networkService.request(GetSets(), as: ObjectList.self, completion: completion) - } - - /// Get a specific MTG set - /// - /// [Scryfall documentation](https://scryfall.com/docs/api/sets) - /// - /// See ``MTGSet/Identifier`` for more information on set identifiers - /// - /// - Parameters: - /// - identifier: The set's identifier - /// - completion: A function/block to call when the request is complete - public func getSet(identifier: MTGSet.Identifier, completion: @Sendable @escaping (Result) -> Void) { - let request = GetSet(identifier: identifier) - networkService.request(request, as: MTGSet.self, completion: completion) - } - - /// Get the rulings for a specific card. - /// - /// [Scryfall documentation](https://scryfall.com/docs/api/rulings) - /// - /// See ``Card/Ruling/Identifier`` for more information on ruling identifiers - /// - /// - Parameters: - /// - identifier: An identifier for the ruling you wish to retrieve - /// - completion: A function/block to call when the request is complete - public func getRulings(_ identifier: Card.Ruling.Identifier, completion: @Sendable @escaping (Result, Error>) -> Void) { - let request = GetRulings(identifier: identifier) - networkService.request(request, as: ObjectList.self, completion: completion) - } - - /// Get all MTG symbology - /// - /// [Scryfall documentation](https://scryfall.com/docs/api/card-symbols/all) - /// - /// - Parameter completion: A function/block to call when the request is complete - public func getSymbology(completion: @Sendable @escaping (Result, Error>) -> Void) { - networkService.request(GetSymbology(), as: ObjectList.self, completion: completion) - } - - /// Parse a string representing a mana cost and retun Scryfall's interpretation - /// - /// [Scryfall documentation](https://scryfall.com/docs/api/card-symbols/parse-mana) - /// - /// - Parameters: - /// - cost: The string to parse - /// - completion: A function/block to call when the request is complete - public func parseManaCost(_ cost: String, completion: @Sendable @escaping (Result) -> Void) { - let request = ParseManaCost(cost: cost) - networkService.request(request, as: Card.ManaCost.self, completion: completion) - } + private let networkLogLevel: NetworkLogLevel + let networkService: NetworkServiceProtocol + + /// Initialize an instance of the ScryfallClient + /// - Parameter networkLogLevel: The desired logging level. See ``NetworkLogLevel`` + public init(networkLogLevel: NetworkLogLevel = .minimal) { + self.networkLogLevel = networkLogLevel + self.networkService = NetworkService(logLevel: networkLogLevel) + } + + /// Perform a search using an array of ``CardFieldFilter`` objects. + /// + /// Performs a Scryfall search using the `/cards/search` endpoint. This method is simply a convenience wrapper around ``searchCards(query:unique:order:sortDirection:includeExtras:includeMultilingual:includeVariations:page:)`` + /// + /// Full reference at: https://scryfall.com/docs/api/cards/search. + /// + /// - Parameters: + /// - filters: Only include cards matching these filters + /// - unique: The strategy for omitting similar cards. See ``UniqueMode`` + /// - order: The method to sort returned cards. See ``SortMode`` + /// - sortDirection: The direction to sort cards. See ``SortDirection`` + /// - includeExtras: If true, extra cards (tokens, planes, etc) will be included. Equivalent to adding include:extras to the fulltext search. Defaults to `false` + /// - includeMultilingual: If true, cards in every language supported by Scryfall will be included. Defaults to `false`. + /// - includeVariations: If true, rare care variants will be included, like the Hairy Runesword. Defaults to `false`. + /// - page: The page number to return. Defaults to `1` + /// - completion: A function/block to be called when the search is complete + public func searchCards( + filters: [CardFieldFilter], + unique: UniqueMode? = nil, + order: SortMode? = nil, + sortDirection: SortDirection? = nil, + includeExtras: Bool? = nil, + includeMultilingual: Bool? = nil, + includeVariations: Bool? = nil, + page: Int? = nil, + completion: @Sendable @escaping (Result, Error>) -> Void + ) { + let query = filters.map { $0.filterString }.joined(separator: " ") + searchCards( + query: query, + unique: unique, + order: order, + sortDirection: sortDirection, + includeExtras: includeExtras, + includeMultilingual: includeMultilingual, + includeVariations: includeVariations, + page: page, + completion: completion) + } + + /// Perform a search using a string conforming to Scryfall query syntax. + /// + /// Full reference at: https://scryfall.com/docs/api/cards/search. + /// + /// - Parameters: + /// - filters: Only include cards matching these filters + /// - unique: The strategy for omitting similar cards. See ``UniqueMode`` + /// - order: The method to sort returned cards. See ``SortMode`` + /// - sortDirection: The direction to sort cards. See ``SortDirection`` + /// - includeExtras: If true, extra cards (tokens, planes, etc) will be included. Equivalent to adding include:extras to the fulltext search. Defaults to `false` + /// - includeMultilingual: If true, cards in every language supported by Scryfall will be included. Defaults to `false`. + /// - includeVariations: If true, rare care variants will be included, like the Hairy Runesword. Defaults to `false`. + /// - page: The page number to return. Defaults to `1` + /// - completion: A function/block to be called when the search is complete + public func searchCards( + query: String, + unique: UniqueMode? = nil, + order: SortMode? = nil, + sortDirection: SortDirection? = nil, + includeExtras: Bool? = nil, + includeMultilingual: Bool? = nil, + includeVariations: Bool? = nil, + page: Int? = nil, + completion: @Sendable @escaping (Result, Error>) -> Void + ) { + + let request = SearchCards( + query: query, + unique: unique, + order: order, + dir: sortDirection, + includeExtras: includeExtras, + includeMultilingual: includeMultilingual, + includeVariations: includeVariations, + page: page) + + networkService.request(request, as: ObjectList.self, completion: completion) + } + + /// Get a card with the exact name supplied + /// + /// Full reference at: https://scryfall.com/docs/api/cards/named + /// + /// - Parameters: + /// - exact: The exact card name to search for, case insenstive. + /// - set: A set code to limit the search to one set. + /// - completion: A function/block to be called when the search is complete + public func getCardByName( + exact: String, set: String? = nil, completion: @Sendable @escaping (Result) -> Void + ) { + let request = GetCardNamed(exact: exact, set: set) + networkService.request(request, as: Card.self, completion: completion) + } + + /// Get a card with a name close to what was entered + /// + /// Full reference at: https://scryfall.com/docs/api/cards/named + /// + /// - Parameters: + /// - fuzzy: The exact card name to search for, case insenstive. + /// - set: A set code to limit the search to one set. + /// - completion: A function/block to be called when the search is complete + public func getCardByName( + fuzzy: String, set: String? = nil, completion: @Sendable @escaping (Result) -> Void + ) { + let request = GetCardNamed(fuzzy: fuzzy, set: set) + networkService.request(request, as: Card.self, completion: completion) + } + + /// Retrieve up to 20 card name autocomplete suggestions for a given string. + /// + /// Full reference at: https://scryfall.com/docs/api/cards/autocomplete + /// + /// - Parameters: + /// - query: The string to autocomplete + /// - includeExtras: If true, extra cards (tokens, planes, vanguards, etc) will be included. Defaults to false. + /// - completion: A function/block to be called when the search is complete + /// - Returns: A ``Catalog`` of card names or an error + public func getCardNameAutocomplete( + query: String, includeExtras: Bool? = nil, + completion: @Sendable @escaping (Result) -> Void + ) { + let request = GetCardAutocomplete(query: query, includeExtras: includeExtras) + networkService.request(request, as: Catalog.self, completion: completion) + } + + /// Get a single random card + /// + /// [Scryfall documentation](https://scryfall.com/docs/api/cards/random) + /// + /// - Parameters: + /// - query: An optional fulltext search query to filter the pool of random cards. + /// - completion: A function/block to call when the request is complete + public func getRandomCard( + query: String? = nil, completion: @Sendable @escaping (Result) -> Void + ) { + let request = GetRandomCard(query: query) + networkService.request(request, as: Card.self, completion: completion) + } + + /// Get a single card using a Card identifier. + /// + /// [Scryfall documentation](https://scryfall.com/docs/api/cards) + /// + /// See ``Card/Identifier`` for more information on identifiers + /// + /// - Parameters: + /// - identifier: The identifier for the desired card + /// - completion: A function/block to call when the request is complete + public func getCard( + identifier: Card.Identifier, completion: @Sendable @escaping (Result) -> Void + ) { + let request = GetCard(identifier: identifier) + networkService.request(request, as: Card.self, completion: completion) + } + + /// Bulk request up to 75 cards at a time. + /// + /// [Scryfall documentation](https://scryfall.com/docs/api/cards/collection) + /// + /// - Parameters: + /// - identifiers: The array of identifiers + /// - completion: A function/block to call when the request is complete + public func getCardCollection( + identifiers: [Card.CollectionIdentifier], + completion: @Sendable @escaping (Result, Error>) -> Void + ) { + let request = GetCardCollection(identifiers: identifiers) + networkService.request(request, as: ObjectList.self, completion: completion) + } + + /// Get a catalog of Magic datapoints (keyword abilities, artist names, spell types, etc) + /// + /// [Scryfall documentation](https://scryfall.com/docs/api/catalogs) + /// + /// - Parameters: + /// - catalogType: The type of catalog to retrieve + /// - completion: A function/block to call when the request is complete + public func getCatalog( + catalogType: Catalog.`Type`, completion: @Sendable @escaping (Result) -> Void + ) { + let request = GetCatalog(catalogType: catalogType) + networkService.request(request, as: Catalog.self, completion: completion) + } + + /// Get all MTG sets + /// + /// [Scryfall documentation](https://scryfall.com/docs/api/sets/all) + /// + /// - Parameter completion: A function/block to call when the request is complete + public func getSets(completion: @Sendable @escaping (Result, Error>) -> Void) { + networkService.request(GetSets(), as: ObjectList.self, completion: completion) + } + + /// Get a specific MTG set + /// + /// [Scryfall documentation](https://scryfall.com/docs/api/sets) + /// + /// See ``MTGSet/Identifier`` for more information on set identifiers + /// + /// - Parameters: + /// - identifier: The set's identifier + /// - completion: A function/block to call when the request is complete + public func getSet( + identifier: MTGSet.Identifier, completion: @Sendable @escaping (Result) -> Void + ) { + let request = GetSet(identifier: identifier) + networkService.request(request, as: MTGSet.self, completion: completion) + } + + /// Get the rulings for a specific card. + /// + /// [Scryfall documentation](https://scryfall.com/docs/api/rulings) + /// + /// See ``Card/Ruling/Identifier`` for more information on ruling identifiers + /// + /// - Parameters: + /// - identifier: An identifier for the ruling you wish to retrieve + /// - completion: A function/block to call when the request is complete + public func getRulings( + _ identifier: Card.Ruling.Identifier, + completion: @Sendable @escaping (Result, Error>) -> Void + ) { + let request = GetRulings(identifier: identifier) + networkService.request(request, as: ObjectList.self, completion: completion) + } + + /// Get all MTG symbology + /// + /// [Scryfall documentation](https://scryfall.com/docs/api/card-symbols/all) + /// + /// - Parameter completion: A function/block to call when the request is complete + public func getSymbology( + completion: @Sendable @escaping (Result, Error>) -> Void + ) { + networkService.request(GetSymbology(), as: ObjectList.self, completion: completion) + } + + /// Parse a string representing a mana cost and retun Scryfall's interpretation + /// + /// [Scryfall documentation](https://scryfall.com/docs/api/card-symbols/parse-mana) + /// + /// - Parameters: + /// - cost: The string to parse + /// - completion: A function/block to call when the request is complete + public func parseManaCost( + _ cost: String, completion: @Sendable @escaping (Result) -> Void + ) { + let request = ParseManaCost(cost: cost) + networkService.request(request, as: Card.ManaCost.self, completion: completion) + } } diff --git a/Sources/ScryfallKit/ScryfallKitError.swift b/Sources/ScryfallKit/ScryfallKitError.swift index 0594411..e5693a7 100644 --- a/Sources/ScryfallKit/ScryfallKitError.swift +++ b/Sources/ScryfallKit/ScryfallKitError.swift @@ -1,35 +1,35 @@ // // ScryfallKitError.swift -// +// import Foundation /// An error thrown by the `ScryfallKit` module public enum ScryfallKitError: LocalizedError, CustomStringConvertible { - /// Internal error, a request was tried with an invalid URL - case invalidUrl - /// An error returned by Scryfall's API such as for an invalid search - case scryfallError(ScryfallError) - case singleFacedCard - case noDataReturned - case failedToCast(String) + /// Internal error, a request was tried with an invalid URL + case invalidUrl + /// An error returned by Scryfall's API such as for an invalid search + case scryfallError(ScryfallError) + case singleFacedCard + case noDataReturned + case failedToCast(String) - public var errorDescription: String? { - description - } + public var errorDescription: String? { + description + } - public var description: String { - switch self { - case .invalidUrl: - return "Invalid URL" - case .scryfallError(let error): - return error.details - case .singleFacedCard: - return "Tried to access card faces on card with a single face" - case .noDataReturned: - return "No data was returned by the server" - case .failedToCast(let details): - return "Failed to cast \(details)" - } + public var description: String { + switch self { + case .invalidUrl: + return "Invalid URL" + case .scryfallError(let error): + return error.details + case .singleFacedCard: + return "Tried to access card faces on card with a single face" + case .noDataReturned: + return "No data was returned by the server" + case .failedToCast(let details): + return "Failed to cast \(details)" } + } } diff --git a/Tests/ScryfallKitTests/ComparableTests.swift b/Tests/ScryfallKitTests/ComparableTests.swift index 140063c..9baabcd 100644 --- a/Tests/ScryfallKitTests/ComparableTests.swift +++ b/Tests/ScryfallKitTests/ComparableTests.swift @@ -1,32 +1,32 @@ // // ComparableTests.swift -// +// -import XCTest import ScryfallKit +import XCTest class ComparableTests: XCTestCase { - func testColorSort() { - // Given - let colors: [Card.Color] = [.B, .C, .W, .U, .G, .R] + func testColorSort() { + // Given + let colors: [Card.Color] = [.B, .C, .W, .U, .G, .R] - // When - let result = colors.sorted() + // When + let result = colors.sorted() - // Then - let expected: [Card.Color] = [.W, .U, .B, .R, .G, .C] - XCTAssertEqual(result, expected) - } + // Then + let expected: [Card.Color] = [.W, .U, .B, .R, .G, .C] + XCTAssertEqual(result, expected) + } - func testRaritySort() { - // Given - let rarities: [Card.Rarity] = [.mythic, .common, .rare, .uncommon, .bonus, .special] + func testRaritySort() { + // Given + let rarities: [Card.Rarity] = [.mythic, .common, .rare, .uncommon, .bonus, .special] - // When - let result = rarities.sorted() + // When + let result = rarities.sorted() - // Then - let expected: [Card.Rarity] = [.bonus, .special, .common, .uncommon, .rare, .mythic] - XCTAssertEqual(result, expected) - } + // Then + let expected: [Card.Rarity] = [.bonus, .special, .common, .uncommon, .rare, .mythic] + XCTAssertEqual(result, expected) + } } diff --git a/Tests/ScryfallKitTests/SmokeTests.swift b/Tests/ScryfallKitTests/SmokeTests.swift index e11eea3..78ddfac 100644 --- a/Tests/ScryfallKitTests/SmokeTests.swift +++ b/Tests/ScryfallKitTests/SmokeTests.swift @@ -1,186 +1,187 @@ // // SmokeTests.swift -// +// import XCTest + @testable import ScryfallKit @available(iOS 13.0.0, *) final class SmokeTests: XCTestCase { - var client: ScryfallClient! - - override func setUp() { - self.client = ScryfallClient() - } - - func testLayouts() async throws { - // Verify that we can handle all layout types - // Skip double sided because there aren't any double_sided or battle cards being returned by Scryfall - for layout in Card.Layout.allCases where ![.doubleSided, .unknown, .battle].contains(layout) { - let cards = try await client.searchCards(query: "layout:\(layout.rawValue)") - checkForUnknowns(in: cards.data) - } - } - - func testTransformers() async throws { - _ = try await client.getCardByName(fuzzy: "optimus prime hero") - } - - func testSearchCardsWithFilters() async throws { - let filters: [CardFieldFilter] = [.cmc("3", .greaterThan), .colorIdentity("WU")] - _ = try await client.searchCards(filters: filters) - } - - func testSearchCards() async throws { - _ = try await client.searchCards(query: "Sigarda") - } - - func testSearchCardsMultiplePages() async throws { - let query = "a" // Some broad query that will return multiple pages - let firstPage = try await client.searchCards(query: query) - let secondPage = try await client.searchCards(query: query, page: 2) - - XCTAssertNotEqual(firstPage.data[0].name, secondPage.data[0].name) - } - - func testGetCardByExactName() async throws { - _ = try await client.getCardByName(exact: "Narset, Enlightened Master") - } - - func testGetCardByFuzzyName() async throws { - _ = try await client.getCardByName(fuzzy: "Narset, Master") - } - - func testGetCardNameAutocomplete() async throws { - let results = try await client.getCardNameAutocomplete(query: "Nars") - XCTAssertFalse(results.data.isEmpty) - } - - func testGetRandomCard() async throws { - _ = try await client.getRandomCard() - } - - func testGetCardById() async throws { - // Flumph - let identifier = Card.Identifier.scryfallID(id: "cdc86e78-8911-4a0d-ba3a-7802f8d991ef") - _ = try await client.getCard(identifier: identifier) - } - - func testGetCatalog() async throws { - _ = try await client.getCatalog(catalogType: .cardNames) - } - - func testGetSets() async throws { - _ = try await client.getSets() - } - - func testGetSetByCode() async throws { - let identifier = MTGSet.Identifier.code(code: "afr") - _ = try await client.getSet(identifier: identifier) - } - - func testGetSet() async throws { - // Ultimate Masters - let identifier = MTGSet.Identifier.scryfallID(id: "2ec77b94-6d47-4891-a480-5d0b4e5c9372") - _ = try await client.getSet(identifier: identifier) - } - - func testGetRulings() async throws { - let identifier = Card.Ruling.Identifier.scryfallID(id: "cdc86e78-8911-4a0d-ba3a-7802f8d991ef") - _ = try await client.getRulings(identifier) - } - - func testGetSymbology() async throws { - _ = try await client.getSymbology() - } - - func testParseManaCost() async throws { - _ = try await client.parseManaCost("{X}{W}{U}{R}") - } - - func testSearchWithFieldFilters() async throws { - let filters: [CardFieldFilter] = [ - CardFieldFilter.type("forest"), - CardFieldFilter.type("creature") - ] - let cards = try await client.searchCards(filters: filters) - - XCTAssertEqual(cards.totalCards, 1) - } - - func testSearchWithFieldFiltersWithComparison() async throws { - let filters: [CardFieldFilter] = [ - CardFieldFilter.cmc("0", .lessThanOrEqual), - CardFieldFilter.type("Creature"), - CardFieldFilter.colors("0", .equal) - ] - - let cards = try await client.searchCards(filters: filters) - XCTAssert(cards.totalCards ?? 0 > 1) - } - - func testSearchWithCompoundFieldFilters() async throws { - let filters: [CardFieldFilter] = [ - CardFieldFilter.type("forest"), - CardFieldFilter.type("creature") - ] - - let compoundFilter = CardFieldFilter.compoundOr(filters) - - let cards = try await client.searchCards(filters: [compoundFilter]) - XCTAssert(cards.totalCards ?? 0 > 1) - } - - func testGetCardCollection() async throws { - let identifiers: [Card.CollectionIdentifier] = [ - .scryfallID(id: "683a5707-cddb-494d-9b41-51b4584ded69"), - .name("Ancient Tomb"), - .collectorNoAndSet(collectorNo: "150", set: "mrd") - ] - - _ = try await client.getCardCollection(identifiers: identifiers) - } - - func testAllNewCards() async throws { - // Get sets that released in the past 30 days - let sets = try await client.getSets().data.filter { mtgSet in - guard let date = mtgSet.date else { - print("Couldn't get release date for set: \(mtgSet.name)") - return false - } - - let distanceInSeconds = date.distance(to: Date()) - let distanceInDays = distanceInSeconds / 60 / 60 / 24 - - return distanceInDays < 30 - } - - // Filter for cards that are in any of the sets - let filter = CardFieldFilter.compoundOr(sets.map { .set($0.code) }) - - // Search - var results = try await client.searchCards(filters: [filter]) - checkForUnknowns(in: results.data) - var page = 1 - - // Go through every page - while results.hasMore ?? false { - page += 1 - results = try await client.searchCards(filters: [filter], page: page) - checkForUnknowns(in: results.data) - usleep(500000) // Wait for 0.5 seconds - } - } - - private func checkForUnknowns(in cards: [Card]) { - for card in cards { - XCTAssertNotEqual(card.layout, .unknown, "Unknown layout on \(card.name)") - XCTAssertNotEqual(card.setType, .unknown, "Unknown set type on \(card.name)") - if let frameEffects = card.frameEffects { - for effect in frameEffects { - XCTAssertNotEqual(effect, .unknown, "Unknown frame effect on \(card.name) [\(card.set)]") - } - } + var client: ScryfallClient! + + override func setUp() { + self.client = ScryfallClient() + } + + func testLayouts() async throws { + // Verify that we can handle all layout types + // Skip double sided because there aren't any double_sided or battle cards being returned by Scryfall + for layout in Card.Layout.allCases where ![.doubleSided, .unknown, .battle].contains(layout) { + let cards = try await client.searchCards(query: "layout:\(layout.rawValue)") + checkForUnknowns(in: cards.data) + } + } + + func testTransformers() async throws { + _ = try await client.getCardByName(fuzzy: "optimus prime hero") + } + + func testSearchCardsWithFilters() async throws { + let filters: [CardFieldFilter] = [.cmc("3", .greaterThan), .colorIdentity("WU")] + _ = try await client.searchCards(filters: filters) + } + + func testSearchCards() async throws { + _ = try await client.searchCards(query: "Sigarda") + } + + func testSearchCardsMultiplePages() async throws { + let query = "a" // Some broad query that will return multiple pages + let firstPage = try await client.searchCards(query: query) + let secondPage = try await client.searchCards(query: query, page: 2) + + XCTAssertNotEqual(firstPage.data[0].name, secondPage.data[0].name) + } + + func testGetCardByExactName() async throws { + _ = try await client.getCardByName(exact: "Narset, Enlightened Master") + } + + func testGetCardByFuzzyName() async throws { + _ = try await client.getCardByName(fuzzy: "Narset, Master") + } + + func testGetCardNameAutocomplete() async throws { + let results = try await client.getCardNameAutocomplete(query: "Nars") + XCTAssertFalse(results.data.isEmpty) + } + + func testGetRandomCard() async throws { + _ = try await client.getRandomCard() + } + + func testGetCardById() async throws { + // Flumph + let identifier = Card.Identifier.scryfallID(id: "cdc86e78-8911-4a0d-ba3a-7802f8d991ef") + _ = try await client.getCard(identifier: identifier) + } + + func testGetCatalog() async throws { + _ = try await client.getCatalog(catalogType: .cardNames) + } + + func testGetSets() async throws { + _ = try await client.getSets() + } + + func testGetSetByCode() async throws { + let identifier = MTGSet.Identifier.code(code: "afr") + _ = try await client.getSet(identifier: identifier) + } + + func testGetSet() async throws { + // Ultimate Masters + let identifier = MTGSet.Identifier.scryfallID(id: "2ec77b94-6d47-4891-a480-5d0b4e5c9372") + _ = try await client.getSet(identifier: identifier) + } + + func testGetRulings() async throws { + let identifier = Card.Ruling.Identifier.scryfallID(id: "cdc86e78-8911-4a0d-ba3a-7802f8d991ef") + _ = try await client.getRulings(identifier) + } + + func testGetSymbology() async throws { + _ = try await client.getSymbology() + } + + func testParseManaCost() async throws { + _ = try await client.parseManaCost("{X}{W}{U}{R}") + } + + func testSearchWithFieldFilters() async throws { + let filters: [CardFieldFilter] = [ + CardFieldFilter.type("forest"), + CardFieldFilter.type("creature"), + ] + let cards = try await client.searchCards(filters: filters) + + XCTAssertEqual(cards.totalCards, 1) + } + + func testSearchWithFieldFiltersWithComparison() async throws { + let filters: [CardFieldFilter] = [ + CardFieldFilter.cmc("0", .lessThanOrEqual), + CardFieldFilter.type("Creature"), + CardFieldFilter.colors("0", .equal), + ] + + let cards = try await client.searchCards(filters: filters) + XCTAssert(cards.totalCards ?? 0 > 1) + } + + func testSearchWithCompoundFieldFilters() async throws { + let filters: [CardFieldFilter] = [ + CardFieldFilter.type("forest"), + CardFieldFilter.type("creature"), + ] + + let compoundFilter = CardFieldFilter.compoundOr(filters) + + let cards = try await client.searchCards(filters: [compoundFilter]) + XCTAssert(cards.totalCards ?? 0 > 1) + } + + func testGetCardCollection() async throws { + let identifiers: [Card.CollectionIdentifier] = [ + .scryfallID(id: "683a5707-cddb-494d-9b41-51b4584ded69"), + .name("Ancient Tomb"), + .collectorNoAndSet(collectorNo: "150", set: "mrd"), + ] + + _ = try await client.getCardCollection(identifiers: identifiers) + } + + func testAllNewCards() async throws { + // Get sets that released in the past 30 days + let sets = try await client.getSets().data.filter { mtgSet in + guard let date = mtgSet.date else { + print("Couldn't get release date for set: \(mtgSet.name)") + return false + } + + let distanceInSeconds = date.distance(to: Date()) + let distanceInDays = distanceInSeconds / 60 / 60 / 24 + + return distanceInDays < 30 + } + + // Filter for cards that are in any of the sets + let filter = CardFieldFilter.compoundOr(sets.map { .set($0.code) }) + + // Search + var results = try await client.searchCards(filters: [filter]) + checkForUnknowns(in: results.data) + var page = 1 + + // Go through every page + while results.hasMore ?? false { + page += 1 + results = try await client.searchCards(filters: [filter], page: page) + checkForUnknowns(in: results.data) + usleep(500000) // Wait for 0.5 seconds + } + } + + private func checkForUnknowns(in cards: [Card]) { + for card in cards { + XCTAssertNotEqual(card.layout, .unknown, "Unknown layout on \(card.name)") + XCTAssertNotEqual(card.setType, .unknown, "Unknown set type on \(card.name)") + if let frameEffects = card.frameEffects { + for effect in frameEffects { + XCTAssertNotEqual(effect, .unknown, "Unknown frame effect on \(card.name) [\(card.set)]") } + } } + } }