2025-10-07 22:32:54 +00:00
// ===----------------------------------------------------------------------===
//
// This source file is part of the Amiibo Service open source project
//
2026-03-22 23:39:48 +00:00
// Copyright (c) 2026 Röck+Cöde VoF. and the Amiibo Service project authors
2025-10-07 22:32:54 +00:00
// Licensed under Apache license v2.0
//
2024-09-14 22:26:39 +00:00
// See LICENSE for license information
2025-10-07 22:32:54 +00:00
// See CONTRIBUTORS for the list of Amiibo Service project authors
2024-09-14 22:26:39 +00:00
//
2025-10-07 22:32:54 +00:00
// SPDX-License-Identifier: Apache-2.0
//
// ===----------------------------------------------------------------------===
2024-09-14 22:26:39 +00:00
import Foundation
import OpenAPIRuntime
import OpenAPIURLSession
2026-03-22 23:39:48 +00:00
/// A type that implements a live client to the [Amiibo API](https://www.amiiboapi.org) online service.
2025-10-02 01:51:16 +00:00
public struct AmiiboLiveClient : Sendable {
2024-09-14 22:26:39 +00:00
2026-07-26 01:31:28 +02:00
// MARK: Constants
/// The base URL of the live service, resolved once from the server defined in the OpenAPI specification.
///
/// The validity of the URL is guaranteed by the bundled `openapi.yaml` specification and enforced by a unit test, so resolution is not expected to fail at runtime.
static let serverURL : URL = {
guard let url = try ? Servers . Server1 . url () else {
fatalError ( "The server URL defined in the OpenAPI specification could not be resolved. Verify that the 'openapi.yaml' server definition is valid." )
}
return url
}()
2024-09-14 22:26:39 +00:00
// MARK: Properties
2026-03-22 23:39:48 +00:00
/// A client generated by the OpenAPI Runtime library to perform API calls.
2024-09-14 22:26:39 +00:00
private let client : Client
2025-09-09 17:30:19 +00:00
// MARK: Initializers
2024-09-14 22:26:39 +00:00
2026-03-22 23:39:48 +00:00
/// Initializes this client with a transport for performing HTTP operations.
/// - Parameter transport: A transport that performs HTTP operations. Defaults to a `URLSessionTransport` using the shared session.
2025-10-02 01:51:16 +00:00
public init ( transport : any ClientTransport = URLSessionTransport ()) {
2024-09-14 22:26:39 +00:00
self . client = . init (
2026-07-26 01:31:28 +02:00
serverURL : Self . serverURL ,
2026-03-27 17:14:26 +00:00
configuration : . init ( dateTranscoder : ISODateTimeTranscoder ()),
2025-10-02 01:51:16 +00:00
transport : transport
2024-09-14 22:26:39 +00:00
)
}
}
2025-09-12 00:13:58 +00:00
// MARK: - AmiiboClient
2025-09-10 19:47:45 +00:00
// TODO: Remove the documentation from the functions inside the following extension as the `--enable-inherited-docs` flag when generating DocC documentation is not working as intended (?).
2024-09-14 22:26:39 +00:00
2025-09-12 00:13:58 +00:00
extension AmiiboLiveClient : AmiiboClient {
2024-09-14 22:26:39 +00:00
// MARK: Functions
2025-09-10 19:47:45 +00:00
/// Gets a list of amiibo items based on a given filter.
/// - Parameter filter: A filter to remove unwanted items from the result.
/// - Returns: A list of filtered amiibo items.
/// - Throws: An ``AmiiboServiceError`` error in case some issue is encountered while generating the result.
2025-09-09 17:30:19 +00:00
public func getAmiibos (
by filter : AmiiboFilter
) async throws ( AmiiboServiceError ) -> [ Amiibo ] {
2025-10-02 01:51:16 +00:00
try await fetchAmiibos ( filter )
2024-09-14 22:26:39 +00:00
}
2025-09-10 19:47:45 +00:00
/// Gets a list of amiibo series based on a given filter.
/// - Parameter filter: A filter to remove unwanted items from the result.
/// - Returns: A list of filtered amiibo series.
/// - Throws: An ``AmiiboServiceError`` error in case some issue is encountered while generating the result.
2025-09-09 17:30:19 +00:00
public func getAmiiboSeries (
by filter : AmiiboSeriesFilter
) async throws ( AmiiboServiceError ) -> [ AmiiboSeries ] {
2025-10-02 01:51:16 +00:00
try await fetchAmiiboSeries ( filter )
2024-09-14 22:26:39 +00:00
}
2025-09-10 19:47:45 +00:00
/// Gets a list of amiibo types based on a given filter.
/// - Parameter filter: A filter to remove unwanted items from the result.
/// - Returns: A list of filtered amiibo types.
/// - Throws: An ``AmiiboServiceError`` error in case some issue is encountered while generating the result.
2025-09-09 17:30:19 +00:00
public func getAmiiboTypes (
by filter : AmiiboTypeFilter
) async throws ( AmiiboServiceError ) -> [ AmiiboType ] {
2025-10-02 01:51:16 +00:00
try await fetchAmiiboTypes ( filter )
2024-09-14 22:26:39 +00:00
}
2025-09-10 19:47:45 +00:00
/// Gets a list of game characters based on a given filter.
/// - Parameter filter: A filter to remove unwanted items from the result.
/// - Returns: A list of filtered game characters.
/// - Throws: An ``AmiiboServiceError`` error in case some issue is encountered while generating the result.
2025-09-09 17:30:19 +00:00
public func getGameCharacters (
by filter : GameCharacterFilter
) async throws ( AmiiboServiceError ) -> [ GameCharacter ] {
2025-10-02 01:51:16 +00:00
try await fetchGameCharacters ( filter )
2024-09-14 22:26:39 +00:00
}
2025-09-10 19:47:45 +00:00
/// Gets a list of game series based on a given filter.
/// - Parameter filter: A filter to remove unwanted items from the result.
/// - Returns: A list of filtered game series.
/// - Throws: An ``AmiiboServiceError`` error in case some issue is encountered while generating the result.
2025-09-09 17:30:19 +00:00
public func getGameSeries (
by filter : GameSeriesFilter
) async throws ( AmiiboServiceError ) -> [ GameSeries ] {
2025-10-02 01:51:16 +00:00
try await fetchGameSeries ( filter )
2024-09-14 22:26:39 +00:00
}
2025-09-10 19:47:45 +00:00
/// Gets the date when the data was last updated.
/// - Returns: A last updated date.
/// - Throws: An ``AmiiboServiceError`` error in case some issue is encountered while generating the result.
2025-09-09 17:30:19 +00:00
public func getLastUpdated () async throws ( AmiiboServiceError ) -> Date {
2025-10-02 01:51:16 +00:00
try await fetchLastUpdated ()
2024-09-14 22:26:39 +00:00
}
}
// MARK: - Helpers
private extension AmiiboLiveClient {
// MARK: Functions
2025-10-02 01:51:16 +00:00
/// Fetches a list of amiibo items based on a given filter.
/// - Parameter filter: A filter to remove unwanted items from the result.
/// - Returns: A list of fetched amiibo items filtered, if requested.
/// - Throws: An ``AmiiboServiceError`` error in case some issue is encountered while generating the result.
func fetchAmiibos (
_ filter : AmiiboFilter
2026-07-26 01:37:12 +02:00
) async throws ( AmiiboServiceError ) -> [ Amiibo ] {
2026-07-26 01:42:47 +02:00
let response = try await perform {
try await client . getAmiibos (. init ( query : . init (
2025-10-14 23:26:42 +00:00
id : filter . identifier ,
head : filter . head ,
tail : filter . tail ,
name : filter . name ,
_type : filter . type ,
2025-10-02 01:51:16 +00:00
amiiboSeries : filter . series ,
character : filter . gameCharacter ,
gameseries : filter . gameSeries ,
showgames : filter . showGames ,
2025-10-14 23:26:42 +00:00
showusage : filter . showUsage
2025-10-02 01:51:16 +00:00
)))
}
switch response {
case let . ok ( ok ):
switch ok . body {
case let . json ( output ):
2025-10-14 23:26:42 +00:00
switch output . amiibo {
case let . Amiibo ( object ):
return [ Amiibo ( object )]
2025-10-28 15:41:02 +00:00
case let . case2 ( list ):
2025-10-14 23:26:42 +00:00
return list
. map { Amiibo ( $0 ) }
. sorted { $0 . identifier < $1 . identifier }
case . none :
return []
}
2025-10-02 01:51:16 +00:00
}
case . badRequest :
throw AmiiboServiceError . badRequest
2025-10-07 22:07:55 +00:00
case . notFound :
throw AmiiboServiceError . notFound
case . internalServerError :
throw AmiiboServiceError . notAvailable
2025-10-02 01:51:16 +00:00
case let . undocumented ( statusCode , _ ):
throw AmiiboServiceError . undocumented ( statusCode )
}
}
/// Fetches a list of amiibo series based on a given filter.
/// - Parameter filter: A filter to remove unwanted items from the result.
/// - Returns: A list of fetched amiibo series filtered, if requested.
/// - Throws: An ``AmiiboServiceError`` error in case some issue is encountered while generating the result.
func fetchAmiiboSeries (
_ filter : AmiiboSeriesFilter
2026-07-26 01:37:12 +02:00
) async throws ( AmiiboServiceError ) -> [ AmiiboSeries ] {
2026-07-26 01:42:47 +02:00
let response = try await perform {
try await client . getAmiiboSeries (. init ( query : . init (
2025-10-02 01:51:16 +00:00
key : filter . key ,
name : filter . name
)))
}
switch response {
case let . ok ( ok ):
switch ok . body {
case let . json ( output ):
2025-10-14 23:26:42 +00:00
switch output . amiibo {
case let . AmiiboSeries ( payload ):
2026-07-26 02:06:40 +02:00
return makeModels ( from : [ payload ])
2025-10-28 15:41:02 +00:00
case let . case2 ( list ):
2026-07-26 02:06:40 +02:00
return makeModels ( from : list )
2025-10-14 23:26:42 +00:00
}
2025-10-02 01:51:16 +00:00
}
case . badRequest :
throw AmiiboServiceError . badRequest
case . internalServerError :
throw AmiiboServiceError . notAvailable
case . notFound :
throw AmiiboServiceError . notFound
case let . undocumented ( statusCode , _ ):
throw AmiiboServiceError . undocumented ( statusCode )
}
}
/// Fetches a list of amiibo types based on a given filter.
/// - Parameter filter: A filter to remove unwanted items from the result.
/// - Returns: A list of fetched amiibo types filtered, if requested.
/// - Throws: An ``AmiiboServiceError`` error in case some issue is encountered while generating the result.
func fetchAmiiboTypes (
_ filter : AmiiboTypeFilter
2026-07-26 01:37:12 +02:00
) async throws ( AmiiboServiceError ) -> [ AmiiboType ] {
2026-07-26 01:42:47 +02:00
let response = try await perform {
try await client . getAmiiboTypes (. init ( query : . init (
2025-10-02 01:51:16 +00:00
key : filter . key ,
name : filter . name
)))
}
switch response {
case let . ok ( ok ):
switch ok . body {
case let . json ( output ):
2025-10-14 23:26:42 +00:00
switch output . amiibo {
case let . AmiiboType ( payload ):
2026-07-26 02:06:40 +02:00
return makeModels ( from : [ payload ])
2025-10-28 15:41:02 +00:00
case let . case2 ( list ):
2026-07-26 02:06:40 +02:00
return makeModels ( from : list )
2025-10-14 23:26:42 +00:00
}
2025-10-02 01:51:16 +00:00
}
case . badRequest :
throw AmiiboServiceError . badRequest
case . internalServerError :
throw AmiiboServiceError . notAvailable
case . notFound :
throw AmiiboServiceError . notFound
case let . undocumented ( statusCode , _ ):
throw AmiiboServiceError . undocumented ( statusCode )
}
}
/// Fetches a list of game characters based on a given filter.
/// - Parameter filter: A filter to remove unwanted items from the result.
/// - Returns: A list of fetched game characters filtered, if requested.
/// - Throws: An ``AmiiboServiceError`` error in case some issue is encountered while generating the result.
func fetchGameCharacters (
_ filter : GameCharacterFilter
2026-07-26 01:37:12 +02:00
) async throws ( AmiiboServiceError ) -> [ GameCharacter ] {
2026-07-26 01:42:47 +02:00
let response = try await perform {
try await client . getGameCharacters (. init ( query : . init (
2025-10-02 01:51:16 +00:00
key : filter . key ,
name : filter . name
)))
}
switch response {
case let . ok ( ok ):
switch ok . body {
case let . json ( output ):
2025-10-14 23:26:42 +00:00
switch output . amiibo {
case let . GameCharacter ( payload ):
2026-07-26 02:06:40 +02:00
return makeModels ( from : [ payload ])
2025-10-28 15:41:02 +00:00
case let . case2 ( list ):
2026-07-26 02:06:40 +02:00
return makeModels ( from : list )
2025-10-14 23:26:42 +00:00
}
2025-10-02 01:51:16 +00:00
}
case . badRequest :
throw AmiiboServiceError . badRequest
case . internalServerError :
throw AmiiboServiceError . notAvailable
case . notFound :
throw AmiiboServiceError . notFound
case let . undocumented ( statusCode , _ ):
throw AmiiboServiceError . undocumented ( statusCode )
}
}
/// Fetches a list of game series based on a given filter.
/// - Parameter filter: A filter to remove unwanted items from the result.
/// - Returns: A list of fetched game series filtered, if requested.
/// - Throws: An ``AmiiboServiceError`` error in case some issue is encountered while generating the result.
func fetchGameSeries (
_ filter : GameSeriesFilter
2026-07-26 01:37:12 +02:00
) async throws ( AmiiboServiceError ) -> [ GameSeries ] {
2026-07-26 01:42:47 +02:00
let response = try await perform {
try await client . getGameSeries (. init ( query : . init (
2025-10-02 01:51:16 +00:00
key : filter . key ,
name : filter . name
)))
}
switch response {
case let . ok ( ok ):
switch ok . body {
case let . json ( output ):
2025-10-14 23:26:42 +00:00
switch output . amiibo {
case let . GameSeries ( payload ):
2026-07-26 02:06:40 +02:00
return makeModels ( from : [ payload ])
2025-10-28 15:41:02 +00:00
case let . case2 ( list ):
2026-07-26 02:06:40 +02:00
return makeModels ( from : list )
2025-10-14 23:26:42 +00:00
}
2025-10-02 01:51:16 +00:00
}
case . badRequest :
throw AmiiboServiceError . badRequest
case . internalServerError :
throw AmiiboServiceError . notAvailable
case . notFound :
throw AmiiboServiceError . notFound
case let . undocumented ( statusCode , _ ):
throw AmiiboServiceError . undocumented ( statusCode )
}
}
/// Fetches the date when the data was last updated.
/// - Returns: A fetched last updated date.
/// - Throws: An ``AmiiboServiceError`` error in case some issue is encountered while generating the result.
2026-07-26 01:37:12 +02:00
func fetchLastUpdated () async throws ( AmiiboServiceError ) -> Date {
2026-07-26 01:42:47 +02:00
let response = try await perform {
try await client . getLastUpdated ()
2025-10-02 01:51:16 +00:00
}
switch response {
case let . ok ( ok ):
switch ok . body {
case let . json ( output ):
return output . lastUpdated
}
2025-10-07 22:07:55 +00:00
case . internalServerError :
throw AmiiboServiceError . notAvailable
2025-10-02 01:51:16 +00:00
case let . undocumented ( statusCode , _ ):
throw AmiiboServiceError . undocumented ( statusCode )
}
}
2026-07-26 01:42:47 +02:00
/// Performs an API call, mapping any error thrown by the underlying client to an ``AmiiboServiceError`` error.
/// - Parameter operation: A closure that performs the API call.
/// - Returns: The output of the performed API call.
/// - Throws: An ``AmiiboServiceError`` error in case the API call failed.
func perform < Output >(
_ operation : () async throws -> Output
) async throws ( AmiiboServiceError ) -> Output {
do {
return try await operation ()
} catch {
try handle ( error : error )
}
}
2026-07-26 02:06:40 +02:00
/// Maps a list of key-name payloads into a sorted list of models.
/// - Parameter payloads: A list of payloads to map into models.
2026-07-26 01:42:47 +02:00
/// - Returns: A list of models sorted by their keys in ascending order.
2026-07-26 02:06:40 +02:00
func makeModels < Model : KeyNameModel >( from payloads : [ some KeyNamePayload ]) -> [ Model ] {
payloads
2026-07-26 01:42:47 +02:00
. map { Model ( $0 ) }
. sorted { $0 . key < $1 . key }
}
2026-03-22 23:39:48 +00:00
/// Maps a given error to an ``AmiiboServiceError`` error.
2025-10-02 01:51:16 +00:00
/// - Parameter error: An error to map.
2026-03-22 23:39:48 +00:00
/// - Throws: An ``AmiiboServiceError`` error that corresponds to the given error.
2026-07-26 01:37:12 +02:00
func handle ( error : any Error ) throws ( AmiiboServiceError ) -> Never {
2025-10-02 01:51:16 +00:00
switch error {
case is CancellationError :
throw AmiiboServiceError . cancelled
case let clientError as ClientError :
switch clientError . underlyingError {
case is DecodingError :
throw AmiiboServiceError . decoding
case let urlError as URLError :
switch urlError . code {
2026-07-26 01:48:08 +02:00
case . cancelled :
throw AmiiboServiceError . cancelled
case . cannotParseResponse :
throw AmiiboServiceError . decoding
2025-10-02 01:51:16 +00:00
case . cannotFindHost ,
. cannotConnectToHost ,
2026-07-26 01:48:08 +02:00
. dataNotAllowed ,
2025-10-02 01:51:16 +00:00
. dnsLookupFailed ,
2026-07-26 01:48:08 +02:00
. internationalRoamingOff ,
2025-10-02 01:51:16 +00:00
. networkConnectionLost ,
. notConnectedToInternet ,
2026-07-26 01:48:08 +02:00
. secureConnectionFailed ,
2025-10-02 01:51:16 +00:00
. timedOut :
throw AmiiboServiceError . notAvailable
default :
2026-07-26 01:48:08 +02:00
throw AmiiboServiceError . unknown ( String ( describing : urlError ))
2025-10-02 01:51:16 +00:00
}
default :
2026-07-26 01:48:08 +02:00
throw AmiiboServiceError . unknown ( String ( describing : clientError . underlyingError ))
2025-10-02 01:51:16 +00:00
}
default :
2026-07-26 01:48:08 +02:00
throw AmiiboServiceError . unknown ( String ( describing : error ))
2025-10-02 01:51:16 +00:00
}
}
2024-09-14 22:26:39 +00:00
}