Skip to content

Commit

Permalink
Swift 6 compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
Jacob Hearst committed Sep 13, 2024
1 parent 3e81be0 commit 8e2d73b
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 192 deletions.
5 changes: 3 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.9
// swift-tools-version:6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand All @@ -20,6 +20,7 @@ let package = Package(
),
.testTarget(
name: "ScryfallKitTests",
dependencies: ["ScryfallKit"])
dependencies: ["ScryfallKit"]
)
]
)
26 changes: 26 additions & 0 deletions [email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// swift-tools-version:5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

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"]
)
]
)
4 changes: 2 additions & 2 deletions Sources/ScryfallKit/Models/Card/Card+Ruling.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Foundation

extension Card {
/// An object representing a ruling on a specific card
public struct Ruling: Codable, Identifiable {
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)
Expand All @@ -17,7 +17,7 @@ extension Card {
}

/// A computer-readable string indicating which company produced this ruling
public enum Source: String, Codable {
public enum Source: String, Codable, Sendable {
case scryfall
case wotc
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/ScryfallKit/Models/Card/Card+Symbol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Foundation

extension Card {
/// A symbol that could appear on a Magic: the Gathering card
public struct Symbol: Codable, Identifiable {
public struct Symbol: Codable, Identifiable, Sendable {
/// The textual representation of this symbol
public var symbol: String
/// A more loose variation of the symbol.
Expand Down
2 changes: 1 addition & 1 deletion Sources/ScryfallKit/Models/ObjectList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Foundation
/// A sequence of Scryfall objects. May be paginated
///
/// [Scryfall documentation](https://scryfall.com/docs/api/lists)
public struct ObjectList<T: Codable>: Codable {
public struct ObjectList<T: Codable>: 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
Expand Down
150 changes: 77 additions & 73 deletions Sources/ScryfallKit/Networking/NetworkService.swift
Original file line number Diff line number Diff line change
@@ -1,103 +1,107 @@
//
// NetworkService.swift
//
//

import Foundation
import OSLog

/// An enum representing the two available levels of log verbosity
public enum NetworkLogLevel: Sendable {
/// Only log when requests are made and errors
case minimal
/// Log the bodies of requests and responses
case verbose
/// Only log when requests are made and errors
case minimal
/// Log the bodies of requests and responses
case verbose
}

protocol NetworkServiceProtocol: Sendable {
func request<T: Decodable>(_ request: EndpointRequest, as type: T.Type, completion: @Sendable @escaping (Result<T, Error>) -> Void)
@available(macOS 10.15.0, *, iOS 13.0.0, *)
func request<T: Decodable>(_ request: EndpointRequest, as type: T.Type) async throws -> T
func request<T: Decodable>(
_ request: EndpointRequest,
as type: T.Type,
completion: @Sendable @escaping (Result<T, Error>) -> Void
)
@available(macOS 10.15.0, *, iOS 13.0.0, *)
func request<T: Decodable>(_ request: EndpointRequest, as type: T.Type) async throws -> T
}

struct NetworkService: NetworkServiceProtocol, Sendable {
var logLevel: NetworkLogLevel

func request<T: Decodable>(_ request: EndpointRequest, as type: T.Type, completion: @Sendable @escaping (Result<T, Error>) -> Void) {
guard let urlRequest = request.urlRequest else {
if #available(macOS 11.0, iOS 14.0, *) {
Logger.network.error("Invalid url request")
} else {
print("Invalid url request")
}
completion(.failure(ScryfallKitError.invalidUrl))
return
}
var logLevel: NetworkLogLevel

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)")
} else {
print(JSONString)
}
}
func request<T: Decodable>(_ request: EndpointRequest, as type: T.Type, completion: @Sendable @escaping (Result<T, Error>) -> Void) {
guard let urlRequest = request.urlRequest else {
if #available(macOS 11.0, iOS 14.0, *) {
Logger.network.error("Invalid url request")
} else {
print("Invalid url request")
}
completion(.failure(ScryfallKitError.invalidUrl))
return
}

let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
do {
let result = try handle(dataType: type, data: data, response: response, error: error)
completion(.success(result))
} catch {
completion(.failure(error))
}
}
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)")
} else {
print(JSONString)
}
}

if #available(macOS 11.0, iOS 14.0, *) {
Logger.network.debug("Making request to: '\(String(describing: urlRequest.url?.absoluteString))'")
} else {
print("Making request to: '\(String(describing: urlRequest.url?.absoluteString))'")
}
task.resume()
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
do {
let result = try handle(dataType: type, data: data, response: response, error: error)
completion(.success(result))
} catch {
completion(.failure(error))
}
}

func handle<T: Decodable>(dataType: T.Type, data: Data?, response: URLResponse?, error: Error?) throws -> T {
if let error = error {
throw error
}
if #available(macOS 11.0, iOS 14.0, *) {
Logger.network.debug("Making request to: '\(String(describing: urlRequest.url?.absoluteString))'")
} else {
print("Making request to: '\(String(describing: urlRequest.url?.absoluteString))'")
}
task.resume()
}

guard let content = data else {
throw ScryfallKitError.noDataReturned
}
func handle<T: Decodable>(dataType: T.Type, data: Data?, response: URLResponse?, error: Error?) throws -> T {
if let error = error {
throw error
}

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

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

if (200..<300).contains(httpStatus) {
if logLevel == .verbose {
let responseBody = String(data: content, encoding: .utf8)
if #available(macOS 11.0, iOS 14.0, *) {
Logger.network.debug("\(responseBody ?? "Couldn't represent response body as string")")
} else {
print(responseBody ?? "Couldn't represent response body as string")
}
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

return try decoder.decode(dataType, from: content)
if (200..<300).contains(httpStatus) {
if logLevel == .verbose {
let responseBody = String(data: content, encoding: .utf8)
if #available(macOS 11.0, iOS 14.0, *) {
Logger.network.debug("\(responseBody ?? "Couldn't represent response body as string")")
} else {
let httpError = try decoder.decode(ScryfallError.self, from: content)
throw ScryfallKitError.scryfallError(httpError)
print(responseBody ?? "Couldn't represent response body as string")
}
}

return try decoder.decode(dataType, from: content)
} else {
let httpError = try decoder.decode(ScryfallError.self, from: content)
throw ScryfallKitError.scryfallError(httpError)
}
}

@available(macOS 10.15.0, *, iOS 13.0.0, *)
func request<T: Decodable>(_ request: EndpointRequest, as type: T.Type) async throws -> T {
try await withCheckedThrowingContinuation { continuation in
self.request(request, as: type) { result in
continuation.resume(with: result)
}
}
@available(macOS 10.15.0, *, iOS 13.0.0, *)
func request<T: Decodable>(_ 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)
}
}
}
}
Loading

0 comments on commit 8e2d73b

Please sign in to comment.