Moved the Amiibo API implementation to its own package #2

Merged
javier merged 12 commits from migration/api-work into main 2024-09-14 22:26:39 +00:00
22 changed files with 904 additions and 2 deletions
Showing only changes of commit 4f5baeaa11 - Show all commits

View File

@ -0,0 +1,29 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
import OpenAPIURLSession
extension Client {
// MARK: Constants
static var live: Client {
get throws {
.init(
serverURL: try Servers.server1(),
configuration: .init(dateTranscoder: ISODateTranscoder()),
transport: URLSessionTransport()
)
}
}
}

View File

@ -0,0 +1,26 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
import Foundation
extension DateFormatter {
static var isoDateTime: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS"
formatter.timeZone = .init(secondsFromGMT: 0)
return formatter
}
}

View File

@ -0,0 +1,26 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
protocol KeyNameFilter {
// MARK: Properties
var key: String? { get }
var name: String? { get }
// MARK: Initialisers
init()
init(key: String)
init(name: String)
}

View File

@ -0,0 +1,24 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
protocol KeyNameModel: Sendable {
// MARK: Properties
var key: String { get }
var name: String { get }
// MARK: Initialisers
init(_ payload: Components.Schemas.Tuple)
}

View File

@ -0,0 +1,31 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
import Foundation
import OpenAPIRuntime
struct ISODateTranscoder: DateTranscoder {
// MARK: Properties
private let dateFormatter: DateFormatter = .isoDateTime
// MARK: Functions
func encode(_ date: Date) throws -> String {
dateFormatter.string(from: date)
}
func decode(_ string: String) throws -> Date {
dateFormatter.date(from: string) ?? .init()
}
}

View File

@ -0,0 +1,22 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
public enum AmiiboServiceError: Error {
case badRequest
case notAvailable
case notFound
case undocumented(_ statusCode: Int)
}
// MARK: - Equatable
extension AmiiboServiceError: Equatable {}

View File

@ -0,0 +1,48 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
public struct AmiiboFilter {
// MARK: Properties
public let gameCharacter: String?
public let gameSeries: String?
public let identifier: String?
public let name: String?
public let series: String?
public let showGames: Bool?
public let showUsage: Bool?
public let type: String?
// MARK: Initialisers
public init(
identifier: String? = nil,
name: String? = nil,
type: String? = nil,
series: String? = nil,
gameCharacter: String? = nil,
gameSeries: String? = nil,
showGames: Bool? = nil,
showUsage: Bool? = nil
) {
self.gameCharacter = gameCharacter
self.gameSeries = gameSeries
self.identifier = identifier
self.name = name
self.series = series
self.showGames = showGames
self.showUsage = showUsage
self.type = type
}
}

View File

@ -0,0 +1,37 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
public struct AmiiboSeriesFilter: KeyNameFilter {
// MARK: Properties
public let key: String?
public let name: String?
// MARK: Initialisers
public init() {
self.key = nil
self.name = nil
}
public init(key: String) {
self.key = key
self.name = nil
}
public init(name: String) {
self.key = nil
self.name = name
}
}

View File

@ -0,0 +1,37 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
public struct AmiiboTypeFilter: KeyNameFilter {
// MARK: Properties
public let key: String?
public let name: String?
// MARK: Initialisers
public init() {
self.key = nil
self.name = nil
}
public init(key: String) {
self.key = key
self.name = nil
}
public init(name: String) {
self.key = nil
self.name = name
}
}

View File

@ -0,0 +1,37 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
public struct GameCharacterFilter: KeyNameFilter {
// MARK: Properties
public let key: String?
public let name: String?
// MARK: Initialisers
public init() {
self.key = nil
self.name = nil
}
public init(key: String) {
self.key = key
self.name = nil
}
public init(name: String) {
self.key = nil
self.name = name
}
}

View File

@ -0,0 +1,37 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
public struct GameSeriesFilter: KeyNameFilter {
// MARK: Properties
public let key: String?
public let name: String?
// MARK: Initialisers
public init() {
self.key = nil
self.name = nil
}
public init(key: String) {
self.key = key
self.name = nil
}
public init(name: String) {
self.key = nil
self.name = name
}
}

View File

@ -0,0 +1,59 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
import Foundation
public struct Amiibo: Sendable {
// MARK: Properties
public let gameCharacter: String
public let gameSeries: String
public let head: String
public let image: String
public let name: String
public let platform: Platform?
public let release: Release
public let series: String
public let tail: String
public let type: String
// MARK: Initialisers
init(_ payload: Components.Schemas.Amiibo) {
self.gameCharacter = payload.character
self.gameSeries = payload.gameSeries
self.head = payload.head
self.image = payload.image
self.name = payload.name
self.platform = .init(
payload.gamesSwitch,
payload.games3DS,
payload.gamesWiiU
)
self.release = .init(payload.release)
self.series = payload.amiiboSeries
self.tail = payload.tail
self.type = payload._type
}
// MARK: Computed
public var identifier: String {
head + tail
}
public var imageURL: URL? {
.init(string: image)
}
}

View File

@ -0,0 +1,37 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
extension Amiibo {
public struct Game: Sendable {
// MARK: Properties
public let identifiers: [String]
public let name: String
public let usages: [Usage]?
// MARK: Initialisers
init(_ payload: Components.Schemas.AmiiboGame) {
self.identifiers = payload.gameID
self.name = payload.gameName
self.usages = {
guard let usages = payload.amiiboUsage else {
return nil
}
return usages.map { .init($0) }
}()
}
}
}

View File

@ -0,0 +1,51 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
extension Amiibo {
public struct Platform: Sendable {
// MARK: Properties
public let `switch`: [Game]
public let threeDS: [Game]
public let wiiU: [Game]
// MARK: Initialisers
init?(
_ `switch`: [Components.Schemas.AmiiboGame]?,
_ threeDS: [Components.Schemas.AmiiboGame]?,
_ wiiU: [Components.Schemas.AmiiboGame]?
) {
guard (`switch` != nil && `switch`?.isEmpty == false)
|| (threeDS != nil && threeDS?.isEmpty == false)
|| (wiiU != nil && wiiU?.isEmpty == false)
else {
return nil
}
self.switch = {
guard let `switch` else { return [] }
return `switch`.map { .init($0) }
}()
self.threeDS = {
guard let threeDS else { return [] }
return threeDS.map { .init($0) }
}()
self.wiiU = {
guard let wiiU else { return [] }
return wiiU.map { .init($0) }
}()
}
}
}

View File

@ -0,0 +1,35 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
import Foundation
extension Amiibo {
public struct Release: Sendable {
// MARK: Properties
public let america: Date?
public let australia: Date?
public let europe: Date?
public let japan: Date?
// MARK: Initialisers
init(_ payload: Components.Schemas.AmiiboRelease) {
self.america = payload.na
self.australia = payload.au
self.europe = payload.eu
self.japan = payload.jp
}
}
}

View File

@ -0,0 +1,29 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
extension Amiibo {
public struct Usage: Sendable {
// MARK: Properties
public let explanation: String
public let isWriteable: Bool
// MARK: Initialisers
init(_ payload: Components.Schemas.AmiiboUsage) {
self.explanation = payload.Usage
self.isWriteable = payload.write
}
}
}

View File

@ -0,0 +1,27 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
public struct AmiiboSeries: KeyNameModel {
// MARK: Properties
public let key: String
public let name: String
// MARK: Initialisers
init(_ payload: Components.Schemas.Tuple) {
self.key = payload.key
self.name = payload.name
}
}

View File

@ -0,0 +1,27 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
public struct AmiiboType: KeyNameModel {
// MARK: Properties
public let key: String
public let name: String
// MARK: Initialisers
init(_ payload: Components.Schemas.Tuple) {
self.key = payload.key
self.name = payload.name
}
}

View File

@ -0,0 +1,27 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
public struct GameCharacter: KeyNameModel {
// MARK: Properties
public let key: String
public let name: String
// MARK: Initialisers
init(_ payload: Components.Schemas.Tuple) {
self.key = payload.key
self.name = payload.name
}
}

View File

@ -0,0 +1,27 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
public struct GameSeries: KeyNameModel {
// MARK: Properties
public let key: String
public let name: String
// MARK: Initialisers
init(_ payload: Components.Schemas.Tuple) {
self.key = payload.key
self.name = payload.name
}
}

View File

@ -0,0 +1,231 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AmiiboAPI open source project
//
// Copyright (c) 2024 Röck+Cöde VoF. and the AmiiboAPI project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of AmiiboAPI project authors
//
//===----------------------------------------------------------------------===//
import Foundation
import OpenAPIRuntime
public struct AmiiboService {
// MARK: Properties
private let client: Client
// MARK: Initialisers
public init() throws {
self.client = try .live
}
// MARK: Functions
public func getAmiibos(
by filter: AmiiboFilter = .init()
) async throws -> [Amiibo] {
let response = try await client.getAmiibos(
.init(query: .init(
amiiboSeries: filter.series,
character: filter.gameCharacter,
gameseries: filter.gameSeries,
id: filter.identifier,
name: filter.name,
showgames: filter.showGames,
showusage: filter.showUsage,
_type: filter.type
))
)
switch response {
case let .ok(okResponse):
switch okResponse.body {
case let .json(output):
switch output.amiibo {
case let .Amiibo(object):
return [.init(object)]
case let .AmiiboList(list):
return list
.map { .init($0) }
.sorted { $0.identifier < $1.identifier }
}
}
case .badRequest:
throw AmiiboServiceError.badRequest
case let .undocumented(statusCode, _):
throw AmiiboServiceError.undocumented(statusCode)
}
}
public func getAmiiboSeries(
by filter: AmiiboSeriesFilter = .init()
) async throws -> [AmiiboSeries] {
let response = try await client.getAmiiboSeries(
.init(query: .init(
key: filter.key,
name: filter.name
))
)
switch response {
case let .ok(okResponse):
switch okResponse.body {
case let .json(output):
return map(output, to: AmiiboSeries.self)
}
case .badRequest:
throw AmiiboServiceError.badRequest
case .internalServerError:
throw AmiiboServiceError.notAvailable
case .notFound:
throw AmiiboServiceError.notFound
case let .undocumented(statusCode, _):
throw AmiiboServiceError.undocumented(statusCode)
}
}
public func getAmiiboTypes(
by filter: AmiiboTypeFilter = .init()
) async throws -> [AmiiboType] {
let response = try await client.getAmiiboTypes(
.init(query: .init(
key: filter.key,
name: filter.name
))
)
switch response {
case let .ok(okResponse):
switch okResponse.body {
case let .json(output):
return map(output, to: AmiiboType.self)
}
case .badRequest:
throw AmiiboServiceError.badRequest
case .internalServerError:
throw AmiiboServiceError.notAvailable
case .notFound:
throw AmiiboServiceError.notFound
case let .undocumented(statusCode, _):
throw AmiiboServiceError.undocumented(statusCode)
}
}
public func getGameCharacters(
by filter: GameCharacterFilter = .init()
) async throws -> [GameCharacter] {
let response = try await client.getGameCharacters(
.init(query: .init(
key: filter.key,
name: filter.name
))
)
switch response {
case let .ok(okResponse):
switch okResponse.body {
case let .json(output):
return map(output, to: GameCharacter.self)
}
case .badRequest:
throw AmiiboServiceError.badRequest
case .internalServerError:
throw AmiiboServiceError.notAvailable
case .notFound:
throw AmiiboServiceError.notFound
case let .undocumented(statusCode, _):
throw AmiiboServiceError.undocumented(statusCode)
}
}
public func getGameSeries(
by filter: GameSeriesFilter = .init()
) async throws -> [GameSeries] {
let response = try await client.getGameSeries(
.init(query: .init(
key: filter.key,
name: filter.name
))
)
switch response {
case let .ok(okResponse):
switch okResponse.body {
case let .json(output):
return map(output, to: GameSeries.self)
}
case .badRequest:
throw AmiiboServiceError.badRequest
case .internalServerError:
throw AmiiboServiceError.notAvailable
case .notFound:
throw AmiiboServiceError.notFound
case let .undocumented(statusCode, _):
throw AmiiboServiceError.undocumented(statusCode)
}
}
public func getLastUpdated() async throws -> Date {
let response = try await client.getLastUpdated()
switch response {
case let .ok(okResponse):
switch okResponse.body {
case let .json(output):
return output.lastUpdated
}
case let .undocumented(statusCode, _):
throw AmiiboServiceError.undocumented(statusCode)
}
}
}
// MARK: - Helpers
private extension AmiiboService {
// MARK: Functions
func map<Model: KeyNameModel>(
_ output: Components.Schemas.TupleWrapper,
to: Model.Type
) -> [Model] {
switch output.amiibo {
case let .Tuple(payload):
return [.init(payload)]
case let .TupleList(list):
return list
.map { .init($0) }
.sorted { $0.key < $1.key }
}
}
}

View File

@ -1,2 +0,0 @@
// The Swift Programming Language
// https://docs.swift.org/swift-book