// ===----------------------------------------------------------------------=== // // This source file is part of the Amiibo Service open source project // // Copyright (c) 2026 Röck+Cöde VoF. and the Amiibo Service project authors // Licensed under Apache license v2.0 // // See LICENSE for license information // See CONTRIBUTORS for the list of Amiibo Service project authors // // SPDX-License-Identifier: Apache-2.0 // // ===----------------------------------------------------------------------=== import Foundation import OpenAPIRuntime import OpenAPIURLSession /// A type that implements a live client to the [Amiibo API](https://www.amiiboapi.org) online service. public struct AmiiboLiveClient: Sendable { // MARK: Properties /// A client generated by the OpenAPI Runtime library to perform API calls. private let client: Client // MARK: Initializers /// 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. public init(transport: any ClientTransport = URLSessionTransport()) { guard let serverURL = 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.") } self.client = .init( serverURL: serverURL, configuration: .init(dateTranscoder: ISOTimestampTranscoder()), transport: transport ) } } // MARK: - AmiiboClient // 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 (?). extension AmiiboLiveClient: AmiiboClient { // MARK: Functions #if swift(>=6.0) /// 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. public func getAmiibos( by filter: AmiiboFilter ) async throws(AmiiboServiceError) -> [Amiibo] { try await fetchAmiibos(filter) } /// 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. public func getAmiiboSeries( by filter: AmiiboSeriesFilter ) async throws(AmiiboServiceError) -> [AmiiboSeries] { try await fetchAmiiboSeries(filter) } /// 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. public func getAmiiboTypes( by filter: AmiiboTypeFilter ) async throws(AmiiboServiceError) -> [AmiiboType] { try await fetchAmiiboTypes(filter) } /// 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. public func getGameCharacters( by filter: GameCharacterFilter ) async throws(AmiiboServiceError) -> [GameCharacter] { try await fetchGameCharacters(filter) } /// 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. public func getGameSeries( by filter: GameSeriesFilter ) async throws(AmiiboServiceError) -> [GameSeries] { try await fetchGameSeries(filter) } /// 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. public func getLastUpdated() async throws(AmiiboServiceError) -> Date { try await fetchLastUpdated() } #else /// 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. public func getAmiibos( by filter: AmiiboFilter ) async throws -> [Amiibo] { try await fetchAmiibos(filter) } /// 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. public func getAmiiboSeries( by filter: AmiiboSeriesFilter ) async throws -> [AmiiboSeries] { try await fetchAmiiboSeries(filter) } /// 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. public func getAmiiboTypes( by filter: AmiiboTypeFilter ) async throws -> [AmiiboType] { try await fetchAmiiboTypes(filter) } /// 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. public func getGameCharacters( by filter: GameCharacterFilter ) async throws -> [GameCharacter] { try await fetchGameCharacters(filter) } /// 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. public func getGameSeries( by filter: GameSeriesFilter ) async throws -> [GameSeries] { try await fetchGameSeries(filter) } /// 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. public func getLastUpdated() async throws -> Date { try await fetchLastUpdated() } #endif } // MARK: - Helpers private extension AmiiboLiveClient { // MARK: Functions /// 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 ) async throws -> [Amiibo] { let response: Operations.getAmiibos.Output do { response = try await client.getAmiibos(.init(query: .init( id: filter.identifier, head: filter.head, tail: filter.tail, name: filter.name, _type: filter.type, amiiboSeries: filter.series, character: filter.gameCharacter, gameseries: filter.gameSeries, showgames: filter.showGames, showusage: filter.showUsage ))) } catch { try handle(error: error) } switch response { case let .ok(ok): switch ok.body { case let .json(output): switch output.amiibo { case let .Amiibo(object): return [Amiibo(object)] case let .case2(list): return list .map { Amiibo($0) } .sorted { $0.identifier < $1.identifier } case .none: return [] } } case .badRequest: throw AmiiboServiceError.badRequest case .notFound: throw AmiiboServiceError.notFound case .internalServerError: throw AmiiboServiceError.notAvailable 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 ) async throws -> [AmiiboSeries] { let response: Operations.getAmiiboSeries.Output do { response = try await client.getAmiiboSeries(.init(query: .init( key: filter.key, name: filter.name ))) } catch { try handle(error: error) } switch response { case let .ok(ok): switch ok.body { case let .json(output): switch output.amiibo { case let .AmiiboSeries(payload): return [AmiiboSeries(payload.value1)] case let .case2(list): return list .map { AmiiboSeries($0.value1) } .sorted { $0.key < $1.key } } } 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 ) async throws -> [AmiiboType] { let response: Operations.getAmiiboTypes.Output do { response = try await client.getAmiiboTypes(.init(query: .init( key: filter.key, name: filter.name ))) } catch { try handle(error: error) } switch response { case let .ok(ok): switch ok.body { case let .json(output): switch output.amiibo { case let .AmiiboType(payload): return [AmiiboType(payload.value1)] case let .case2(list): return list .map { AmiiboType($0.value1) } .sorted { $0.key < $1.key } } } 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 ) async throws -> [GameCharacter] { let response: Operations.getGameCharacters.Output do { response = try await client.getGameCharacters(.init(query: .init( key: filter.key, name: filter.name ))) } catch { try handle(error: error) } switch response { case let .ok(ok): switch ok.body { case let .json(output): switch output.amiibo { case let .GameCharacter(payload): return [GameCharacter(payload.value1)] case let .case2(list): return list .map { GameCharacter($0.value1) } .sorted { $0.key < $1.key } } } 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 ) async throws -> [GameSeries] { let response: Operations.getGameSeries.Output do { response = try await client.getGameSeries(.init(query: .init( key: filter.key, name: filter.name ))) } catch { try handle(error: error) } switch response { case let .ok(ok): switch ok.body { case let .json(output): switch output.amiibo { case let .GameSeries(payload): return [GameSeries(payload.value1)] case let .case2(list): return list .map { GameSeries($0.value1) } .sorted { $0.key < $1.key } } } 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. func fetchLastUpdated() async throws -> Date { let response: Operations.getLastUpdated.Output do { response = try await client.getLastUpdated() } catch { try handle(error: error) } switch response { case let .ok(ok): switch ok.body { case let .json(output): return output.lastUpdated } case .internalServerError: throw AmiiboServiceError.notAvailable case let .undocumented(statusCode, _): throw AmiiboServiceError.undocumented(statusCode) } } /// Maps a given error to an ``AmiiboServiceError`` error. /// - Parameter error: An error to map. /// - Throws: An ``AmiiboServiceError`` error that corresponds to the given error. func handle(error: any Error) throws -> Never { 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 { case .cannotFindHost, .cannotConnectToHost, .dnsLookupFailed, .networkConnectionLost, .notConnectedToInternet, .timedOut: throw AmiiboServiceError.notAvailable default: throw AmiiboServiceError.unknown } default: throw AmiiboServiceError.unknown } default: throw AmiiboServiceError.unknown } } }