From c17dc6f22ef5868e35b778bdf3c0673907ebce25 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 7 Sep 2024 10:16:14 +0200 Subject: [PATCH 01/12] Added the Open API specification document and the Open API generator configuration to the Resources folder in the package. --- Package.swift | 5 +- Resources/openapi-generator-config.yaml | 4 + Resources/openapi.yaml | 364 ++++++++++++++++++++++++ 3 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 Resources/openapi-generator-config.yaml create mode 100644 Resources/openapi.yaml diff --git a/Package.swift b/Package.swift index 52e5f6d..36d72da 100644 --- a/Package.swift +++ b/Package.swift @@ -15,7 +15,10 @@ let package = Package( targets: [ .target( name: AmiiboAPI.target, - path: "Sources" + path: "Sources", + resources: [ + .process("Resources") + ] ), .testTarget( name: AmiiboAPI.test, diff --git a/Resources/openapi-generator-config.yaml b/Resources/openapi-generator-config.yaml new file mode 100644 index 0000000..1df6f28 --- /dev/null +++ b/Resources/openapi-generator-config.yaml @@ -0,0 +1,4 @@ +generate: + - types + - client +accessModifier: internal diff --git a/Resources/openapi.yaml b/Resources/openapi.yaml new file mode 100644 index 0000000..72282b0 --- /dev/null +++ b/Resources/openapi.yaml @@ -0,0 +1,364 @@ +openapi: '3.1.0' +info: + title: Amiibo API service + description: The Amiibo API RESTful service. + version: 1.0.0 +servers: + - url: https://www.amiiboapi.com/api + description: Amiibo API service (live) +paths: + /amiibo: + get: + description: Get a list of all the Amiibo items available in the database. + operationId: getAmiibos + responses: + '200': + description: Successful response returning the object that contains a list of Amiibo items. + content: + application/json: + schema: + $ref: '#/components/schemas/AmiiboWrapper' + '400': + description: Bad Amiibo request. + parameters: + - name: amiiboSeries + in: query + description: The Amiibo series identifier or name to filter the response. + required: false + schema: + type: string + style: form + - name: character + in: query + description: The game character identifier or name to filter the response. + required: false + schema: + type: string + style: form + - name: gameseries + in: query + description: The game series identifier or name to filter the response. + required: false + schema: + type: string + style: form + - name: id + in: query + description: The Amiibo identifier to filter the response. + required: false + schema: + type: string + style: form + - name: name + in: query + description: The Amiibo name to filter the response. + required: false + schema: + type: string + style: form + - name: showgames + in: query + description: The flag that indicates whether to include information about related games. + required: false + schema: + type: boolean + style: form + - name: showusage + in: query + description: The flag that indicates whether to include information about Amiibo usage in related games. + required: false + schema: + type: boolean + style: form + - name: type + in: query + description: The Amiibo type to filter the response. + required: false + schema: + type: string + style: form + + /amiiboseries: + get: + description: Get a list of all the Amiibo series available in the database. + operationId: getAmiiboSeries + responses: + '200': + description: Successful response returning the object that contains a list of Amiibo series. + content: + application/json: + schema: + $ref: '#/components/schemas/TupleWrapper' + '400': + description: Bad Amiibo series request. + '404': + description: Amiibo series not found. + '500': + description: Service currently not available. + parameters: + - name: key + in: query + description: The Amiibo series key to filter the response. + required: false + schema: + type: string + style: form + - name: name + in: query + description: The Amiibo series name to filter the response. + required: false + schema: + type: string + style: form + + /character: + get: + description: Get a list of all the game characters available in the database. + operationId: getGameCharacters + responses: + '200': + description: Successful response returning the object that contains a list of game characters. + content: + application/json: + schema: + $ref: '#/components/schemas/TupleWrapper' + '400': + description: Bad game character request. + '404': + description: Game character not found. + '500': + description: Service currently not available. + parameters: + - name: key + in: query + description: The game character key to filter the response. + required: false + schema: + type: string + style: form + - name: name + in: query + description: The game character name to filter the response. + required: false + schema: + type: string + style: form + + /gameseries: + get: + description: Gets a list of all the game series available in the database. + operationId: getGameSeries + responses: + '200': + description: Successful response returning the object that contains a list of game series. + content: + application/json: + schema: + $ref: '#/components/schemas/TupleWrapper' + '400': + description: Bad game series request. + '404': + description: Game series not found. + '500': + description: Service currently not available. + parameters: + - name: key + in: query + description: The game series key to filter the response. + required: false + schema: + type: string + style: form + - name: name + in: query + description: The game series name to filter the response. + required: false + schema: + type: string + style: form + + /type: + get: + description: Gets a list of all the Amiibo types available in the database. + operationId: getAmiiboTypes + responses: + '200': + description: Successful response returning the object that contains a list of Amiibo types. + content: + application/json: + schema: + $ref: '#/components/schemas/TupleWrapper' + '400': + description: Bad Amiibo type request. + '404': + description: Amiibo type not found. + '500': + description: Service currently not available. + parameters: + - name: key + in: query + description: The Amiibo type key to filter the response. + required: false + schema: + type: string + style: form + - name: name + in: query + description: The Amiibo type name to filter the response. + required: false + schema: + type: string + style: form + + /lastupdated: + get: + description: Gets a timestamp when the Amiibo data was last updated. + operationId: getLastUpdated + responses: + '200': + description: Successful response returning the object that contains the date and time when the database was last updated. + content: + application/json: + schema: + $ref: '#/components/schemas/LastUpdated' + +components: + schemas: + Amiibo: + type: object + properties: + amiiboSeries: + type: string + character: + type: string + gameSeries: + type: string + games3DS: + type: array + items: + $ref: '#/components/schemas/AmiiboGame' + gamesSwitch: + type: array + items: + $ref: '#/components/schemas/AmiiboGame' + gamesWiiU: + type: array + items: + $ref: '#/components/schemas/AmiiboGame' + head: + type: string + image: + type: string + name: + type: string + release: + type: object + $ref: '#/components/schemas/AmiiboRelease' + tail: + type: string + type: + type: string + required: + - amiiboSeries + - character + - gameSeries + - head + - image + - name + - release + - tail + - type + + AmiiboGame: + type: object + properties: + amiiboUsage: + type: array + items: + $ref: '#/components/schemas/AmiiboUsage' + gameID: + type: array + items: + type: string + gameName: + type: string + required: + - gameID + - gameName + + AmiiboList: + type: array + items: + $ref: '#/components/schemas/Amiibo' + + AmiiboRelease: + type: object + properties: + au: + type: string + format: date-time + eu: + type: string + format: date-time + jp: + type: string + format: date-time + na: + type: string + format: date-time + + AmiiboUsage: + type: object + properties: + Usage: + type: string + write: + type: boolean + required: + - Usage + - write + + AmiiboWrapper: + type: object + properties: + amiibo: + oneOf: + - $ref: '#/components/schemas/Amiibo' + - $ref: '#/components/schemas/AmiiboList' + required: + - amiibo + + LastUpdated: + type: object + properties: + lastUpdated: + type: string + format: date-time + required: + - lastUpdated + + Tuple: + type: object + properties: + key: + type: string + name: + type: string + required: + - key + - name + + TupleList: + type: array + items: + $ref: '#/components/schemas/Tuple' + + TupleWrapper: + type: object + properties: + amiibo: + oneOf: + - $ref: '#/components/schemas/Tuple' + - $ref: '#/components/schemas/TupleList' + required: + - amiibo -- 2.47.1 From 29e526ddd93bee7188acf036c7b80489b39d3907 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 7 Sep 2024 10:29:26 +0200 Subject: [PATCH 02/12] Added the Swift OpenAPI Generator, Swift OpenAPI Runtime, and Swift OpenAPI URLSession package dependencies to the package, and integrated them to the target. --- Package.swift | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 36d72da..0c5dbb3 100644 --- a/Package.swift +++ b/Package.swift @@ -4,6 +4,13 @@ import PackageDescription let package = Package( name: AmiiboAPI.package, + platforms: [ + .iOS(.v13), + .macOS(.v10_15), + .tvOS(.v13), + .visionOS(.v1), + .watchOS(.v6) + ], products: [ .library( name: AmiiboAPI.package, @@ -12,12 +19,42 @@ let package = Package( ] ) ], + dependencies: [ + .package( + url: "https://github.com/apple/swift-openapi-generator.git", + from: "1.3.0" + ), + .package( + url: "https://github.com/apple/swift-openapi-runtime", + from: "1.5.0" + ), + .package( + url: "https://github.com/apple/swift-openapi-urlsession", + from: "1.0.2" + ) + ], targets: [ .target( name: AmiiboAPI.target, + dependencies: [ + .product( + name: "OpenAPIRuntime", + package: "swift-openapi-runtime" + ), + .product( + name: "OpenAPIURLSession", + package: "swift-openapi-urlsession" + ) + ], path: "Sources", resources: [ - .process("Resources") + .process("../Resources") + ], + plugins: [ + .plugin( + name: "OpenAPIGenerator", + package: "swift-openapi-generator" + ), ] ), .testTarget( -- 2.47.1 From c066a1896a9456e5a7dd4bf5591d6fe5225fe5ff Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 7 Sep 2024 11:05:46 +0200 Subject: [PATCH 03/12] Moved the OpenAPI specification document and the Open API generator configuration to the root folder of the target in the package. --- Package.swift | 3 --- {Resources => Sources}/openapi-generator-config.yaml | 0 {Resources => Sources}/openapi.yaml | 0 3 files changed, 3 deletions(-) rename {Resources => Sources}/openapi-generator-config.yaml (100%) rename {Resources => Sources}/openapi.yaml (100%) diff --git a/Package.swift b/Package.swift index 0c5dbb3..893bd34 100644 --- a/Package.swift +++ b/Package.swift @@ -47,9 +47,6 @@ let package = Package( ) ], path: "Sources", - resources: [ - .process("../Resources") - ], plugins: [ .plugin( name: "OpenAPIGenerator", diff --git a/Resources/openapi-generator-config.yaml b/Sources/openapi-generator-config.yaml similarity index 100% rename from Resources/openapi-generator-config.yaml rename to Sources/openapi-generator-config.yaml diff --git a/Resources/openapi.yaml b/Sources/openapi.yaml similarity index 100% rename from Resources/openapi.yaml rename to Sources/openapi.yaml -- 2.47.1 From 4f5baeaa119d6e8f1de6c679de59eb94771b7089 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 7 Sep 2024 11:09:06 +0200 Subject: [PATCH 04/12] Moved the Amiibo API client implementation from the Amxbo project to the target of this package. --- .../Extensions/Client+Properties.swift | 29 +++ .../Extensions/DateFormatter+Properties.swift | 26 ++ .../Internal/Protocols/KeyNameFilter.swift | 26 ++ Sources/Internal/Protocols/KeyNameModel.swift | 24 ++ .../Transcoders/ISODateTranscoder.swift | 31 +++ .../Public/Errors/AmiiboServiceError.swift | 22 ++ Sources/Public/Filters/AmiiboFilter.swift | 48 ++++ .../Public/Filters/AmiiboSeriesFilter.swift | 37 +++ Sources/Public/Filters/AmiiboTypeFilter.swift | 37 +++ .../Public/Filters/GameCharacterFilter.swift | 37 +++ Sources/Public/Filters/GameSeriesFilter.swift | 37 +++ Sources/Public/Models/Amiibo.swift | 59 +++++ .../Public/Models/Amiibo/Amiibo+Game.swift | 37 +++ .../Models/Amiibo/Amiibo+Platform.swift | 51 ++++ .../Public/Models/Amiibo/Amiibo+Release.swift | 35 +++ .../Public/Models/Amiibo/Amiibo+Usage.swift | 29 +++ Sources/Public/Models/AmiiboSeries.swift | 27 ++ Sources/Public/Models/AmiiboType.swift | 27 ++ Sources/Public/Models/GameCharacter.swift | 27 ++ Sources/Public/Models/GameSeries.swift | 27 ++ Sources/Public/Services/AmiiboService.swift | 231 ++++++++++++++++++ Sources/amiibo_api.swift | 2 - 22 files changed, 904 insertions(+), 2 deletions(-) create mode 100644 Sources/Internal/Extensions/Client+Properties.swift create mode 100644 Sources/Internal/Extensions/DateFormatter+Properties.swift create mode 100644 Sources/Internal/Protocols/KeyNameFilter.swift create mode 100644 Sources/Internal/Protocols/KeyNameModel.swift create mode 100644 Sources/Internal/Transcoders/ISODateTranscoder.swift create mode 100644 Sources/Public/Errors/AmiiboServiceError.swift create mode 100644 Sources/Public/Filters/AmiiboFilter.swift create mode 100644 Sources/Public/Filters/AmiiboSeriesFilter.swift create mode 100644 Sources/Public/Filters/AmiiboTypeFilter.swift create mode 100644 Sources/Public/Filters/GameCharacterFilter.swift create mode 100644 Sources/Public/Filters/GameSeriesFilter.swift create mode 100644 Sources/Public/Models/Amiibo.swift create mode 100644 Sources/Public/Models/Amiibo/Amiibo+Game.swift create mode 100644 Sources/Public/Models/Amiibo/Amiibo+Platform.swift create mode 100644 Sources/Public/Models/Amiibo/Amiibo+Release.swift create mode 100644 Sources/Public/Models/Amiibo/Amiibo+Usage.swift create mode 100644 Sources/Public/Models/AmiiboSeries.swift create mode 100644 Sources/Public/Models/AmiiboType.swift create mode 100644 Sources/Public/Models/GameCharacter.swift create mode 100644 Sources/Public/Models/GameSeries.swift create mode 100644 Sources/Public/Services/AmiiboService.swift delete mode 100644 Sources/amiibo_api.swift diff --git a/Sources/Internal/Extensions/Client+Properties.swift b/Sources/Internal/Extensions/Client+Properties.swift new file mode 100644 index 0000000..3449f41 --- /dev/null +++ b/Sources/Internal/Extensions/Client+Properties.swift @@ -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() + ) + } + } + +} diff --git a/Sources/Internal/Extensions/DateFormatter+Properties.swift b/Sources/Internal/Extensions/DateFormatter+Properties.swift new file mode 100644 index 0000000..b40d382 --- /dev/null +++ b/Sources/Internal/Extensions/DateFormatter+Properties.swift @@ -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 + } + +} diff --git a/Sources/Internal/Protocols/KeyNameFilter.swift b/Sources/Internal/Protocols/KeyNameFilter.swift new file mode 100644 index 0000000..d5faf61 --- /dev/null +++ b/Sources/Internal/Protocols/KeyNameFilter.swift @@ -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) + +} diff --git a/Sources/Internal/Protocols/KeyNameModel.swift b/Sources/Internal/Protocols/KeyNameModel.swift new file mode 100644 index 0000000..4cfeb06 --- /dev/null +++ b/Sources/Internal/Protocols/KeyNameModel.swift @@ -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) + +} diff --git a/Sources/Internal/Transcoders/ISODateTranscoder.swift b/Sources/Internal/Transcoders/ISODateTranscoder.swift new file mode 100644 index 0000000..9ae1a3e --- /dev/null +++ b/Sources/Internal/Transcoders/ISODateTranscoder.swift @@ -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() + } + +} diff --git a/Sources/Public/Errors/AmiiboServiceError.swift b/Sources/Public/Errors/AmiiboServiceError.swift new file mode 100644 index 0000000..4391117 --- /dev/null +++ b/Sources/Public/Errors/AmiiboServiceError.swift @@ -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 {} diff --git a/Sources/Public/Filters/AmiiboFilter.swift b/Sources/Public/Filters/AmiiboFilter.swift new file mode 100644 index 0000000..b0c7c29 --- /dev/null +++ b/Sources/Public/Filters/AmiiboFilter.swift @@ -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 + } + +} diff --git a/Sources/Public/Filters/AmiiboSeriesFilter.swift b/Sources/Public/Filters/AmiiboSeriesFilter.swift new file mode 100644 index 0000000..c230ce9 --- /dev/null +++ b/Sources/Public/Filters/AmiiboSeriesFilter.swift @@ -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 + } + +} diff --git a/Sources/Public/Filters/AmiiboTypeFilter.swift b/Sources/Public/Filters/AmiiboTypeFilter.swift new file mode 100644 index 0000000..cf48760 --- /dev/null +++ b/Sources/Public/Filters/AmiiboTypeFilter.swift @@ -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 + } + +} diff --git a/Sources/Public/Filters/GameCharacterFilter.swift b/Sources/Public/Filters/GameCharacterFilter.swift new file mode 100644 index 0000000..345dac3 --- /dev/null +++ b/Sources/Public/Filters/GameCharacterFilter.swift @@ -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 + } + +} diff --git a/Sources/Public/Filters/GameSeriesFilter.swift b/Sources/Public/Filters/GameSeriesFilter.swift new file mode 100644 index 0000000..060988d --- /dev/null +++ b/Sources/Public/Filters/GameSeriesFilter.swift @@ -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 + } + +} diff --git a/Sources/Public/Models/Amiibo.swift b/Sources/Public/Models/Amiibo.swift new file mode 100644 index 0000000..f53d900 --- /dev/null +++ b/Sources/Public/Models/Amiibo.swift @@ -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) + } + +} diff --git a/Sources/Public/Models/Amiibo/Amiibo+Game.swift b/Sources/Public/Models/Amiibo/Amiibo+Game.swift new file mode 100644 index 0000000..a820c11 --- /dev/null +++ b/Sources/Public/Models/Amiibo/Amiibo+Game.swift @@ -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) } + }() + } + + } +} diff --git a/Sources/Public/Models/Amiibo/Amiibo+Platform.swift b/Sources/Public/Models/Amiibo/Amiibo+Platform.swift new file mode 100644 index 0000000..36c9ec0 --- /dev/null +++ b/Sources/Public/Models/Amiibo/Amiibo+Platform.swift @@ -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) } + }() + } + + } +} diff --git a/Sources/Public/Models/Amiibo/Amiibo+Release.swift b/Sources/Public/Models/Amiibo/Amiibo+Release.swift new file mode 100644 index 0000000..7bcbfff --- /dev/null +++ b/Sources/Public/Models/Amiibo/Amiibo+Release.swift @@ -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 + } + + } +} diff --git a/Sources/Public/Models/Amiibo/Amiibo+Usage.swift b/Sources/Public/Models/Amiibo/Amiibo+Usage.swift new file mode 100644 index 0000000..d2bda39 --- /dev/null +++ b/Sources/Public/Models/Amiibo/Amiibo+Usage.swift @@ -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 + } + + } +} diff --git a/Sources/Public/Models/AmiiboSeries.swift b/Sources/Public/Models/AmiiboSeries.swift new file mode 100644 index 0000000..68f00f0 --- /dev/null +++ b/Sources/Public/Models/AmiiboSeries.swift @@ -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 + } + +} diff --git a/Sources/Public/Models/AmiiboType.swift b/Sources/Public/Models/AmiiboType.swift new file mode 100644 index 0000000..0a75874 --- /dev/null +++ b/Sources/Public/Models/AmiiboType.swift @@ -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 + } + +} diff --git a/Sources/Public/Models/GameCharacter.swift b/Sources/Public/Models/GameCharacter.swift new file mode 100644 index 0000000..652ea4d --- /dev/null +++ b/Sources/Public/Models/GameCharacter.swift @@ -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 + } + +} diff --git a/Sources/Public/Models/GameSeries.swift b/Sources/Public/Models/GameSeries.swift new file mode 100644 index 0000000..a445472 --- /dev/null +++ b/Sources/Public/Models/GameSeries.swift @@ -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 + } + +} diff --git a/Sources/Public/Services/AmiiboService.swift b/Sources/Public/Services/AmiiboService.swift new file mode 100644 index 0000000..f0bdb2b --- /dev/null +++ b/Sources/Public/Services/AmiiboService.swift @@ -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( + _ 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 } + } + } + +} diff --git a/Sources/amiibo_api.swift b/Sources/amiibo_api.swift deleted file mode 100644 index 08b22b8..0000000 --- a/Sources/amiibo_api.swift +++ /dev/null @@ -1,2 +0,0 @@ -// The Swift Programming Language -// https://docs.swift.org/swift-book -- 2.47.1 From 72e57185f9af686dabb2eedf50630504c4cabce6 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 7 Sep 2024 11:17:36 +0200 Subject: [PATCH 05/12] Added the CONTRIBUTORS file to the package. --- CONTRIBUTORS | 14 ++++++++++++++ Makefile | 14 +++++++++++++- Package.swift | 12 ++++++++++++ .../Internal/Extensions/Client+Properties.swift | 4 ++-- .../Extensions/DateFormatter+Properties.swift | 4 ++-- Sources/Internal/Protocols/KeyNameFilter.swift | 4 ++-- Sources/Internal/Protocols/KeyNameModel.swift | 4 ++-- .../Internal/Transcoders/ISODateTranscoder.swift | 4 ++-- Sources/Public/Errors/AmiiboServiceError.swift | 4 ++-- Sources/Public/Filters/AmiiboFilter.swift | 4 ++-- Sources/Public/Filters/AmiiboSeriesFilter.swift | 4 ++-- Sources/Public/Filters/AmiiboTypeFilter.swift | 4 ++-- Sources/Public/Filters/GameCharacterFilter.swift | 4 ++-- Sources/Public/Filters/GameSeriesFilter.swift | 4 ++-- Sources/Public/Models/Amiibo.swift | 4 ++-- Sources/Public/Models/Amiibo/Amiibo+Game.swift | 4 ++-- Sources/Public/Models/Amiibo/Amiibo+Platform.swift | 4 ++-- Sources/Public/Models/Amiibo/Amiibo+Release.swift | 4 ++-- Sources/Public/Models/Amiibo/Amiibo+Usage.swift | 4 ++-- Sources/Public/Models/AmiiboSeries.swift | 4 ++-- Sources/Public/Models/AmiiboType.swift | 4 ++-- Sources/Public/Models/GameCharacter.swift | 4 ++-- Sources/Public/Models/GameSeries.swift | 4 ++-- Sources/Public/Services/AmiiboService.swift | 4 ++-- Sources/openapi-generator-config.yaml | 12 ++++++++++++ Sources/openapi.yaml | 12 ++++++++++++ 26 files changed, 105 insertions(+), 43 deletions(-) create mode 100644 CONTRIBUTORS diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 0000000..1688426 --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,14 @@ +For the purpose of tracking copyright, this is the list of individuals and +organizations who have contributed source code to this Swift package. + +For employees of an organization/company where the copyright of work done +by employees of that company is held by the company itself, only the company +needs to be listed here. + +## COPYRIGHT HOLDERS + +- Röck+Cöde VoF. (all contributors with '@rock-n-code.com') + +## PROJECT AUTHORS + +- Javier Cicchelli diff --git a/Makefile b/Makefile index 2f62c51..7107182 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,15 @@ +# ===----------------------------------------------------------------------=== +# +# 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 +# +# ===----------------------------------------------------------------------=== + # ENVIRONMENT VARIABLES environment ?= .env @@ -39,4 +51,4 @@ package-update: ## Updates the SPM package dependencies help: ## Prints the written documentation for all the defined tasks @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) -.DEFAULT_GOAL := help \ No newline at end of file +.DEFAULT_GOAL := help diff --git a/Package.swift b/Package.swift index 893bd34..2a0813b 100644 --- a/Package.swift +++ b/Package.swift @@ -1,5 +1,17 @@ // swift-tools-version: 5.9 +//===----------------------------------------------------------------------=== +// +// 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 PackageDescription let package = Package( diff --git a/Sources/Internal/Extensions/Client+Properties.swift b/Sources/Internal/Extensions/Client+Properties.swift index 3449f41..23f090c 100644 --- a/Sources/Internal/Extensions/Client+Properties.swift +++ b/Sources/Internal/Extensions/Client+Properties.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== import OpenAPIURLSession diff --git a/Sources/Internal/Extensions/DateFormatter+Properties.swift b/Sources/Internal/Extensions/DateFormatter+Properties.swift index b40d382..6c9cce9 100644 --- a/Sources/Internal/Extensions/DateFormatter+Properties.swift +++ b/Sources/Internal/Extensions/DateFormatter+Properties.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== import Foundation diff --git a/Sources/Internal/Protocols/KeyNameFilter.swift b/Sources/Internal/Protocols/KeyNameFilter.swift index d5faf61..29cd4a4 100644 --- a/Sources/Internal/Protocols/KeyNameFilter.swift +++ b/Sources/Internal/Protocols/KeyNameFilter.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== protocol KeyNameFilter { diff --git a/Sources/Internal/Protocols/KeyNameModel.swift b/Sources/Internal/Protocols/KeyNameModel.swift index 4cfeb06..f1c023a 100644 --- a/Sources/Internal/Protocols/KeyNameModel.swift +++ b/Sources/Internal/Protocols/KeyNameModel.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== protocol KeyNameModel: Sendable { diff --git a/Sources/Internal/Transcoders/ISODateTranscoder.swift b/Sources/Internal/Transcoders/ISODateTranscoder.swift index 9ae1a3e..2a9a91d 100644 --- a/Sources/Internal/Transcoders/ISODateTranscoder.swift +++ b/Sources/Internal/Transcoders/ISODateTranscoder.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== import Foundation import OpenAPIRuntime diff --git a/Sources/Public/Errors/AmiiboServiceError.swift b/Sources/Public/Errors/AmiiboServiceError.swift index 4391117..db86577 100644 --- a/Sources/Public/Errors/AmiiboServiceError.swift +++ b/Sources/Public/Errors/AmiiboServiceError.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== public enum AmiiboServiceError: Error { case badRequest diff --git a/Sources/Public/Filters/AmiiboFilter.swift b/Sources/Public/Filters/AmiiboFilter.swift index b0c7c29..4d714be 100644 --- a/Sources/Public/Filters/AmiiboFilter.swift +++ b/Sources/Public/Filters/AmiiboFilter.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== public struct AmiiboFilter { diff --git a/Sources/Public/Filters/AmiiboSeriesFilter.swift b/Sources/Public/Filters/AmiiboSeriesFilter.swift index c230ce9..1691904 100644 --- a/Sources/Public/Filters/AmiiboSeriesFilter.swift +++ b/Sources/Public/Filters/AmiiboSeriesFilter.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== public struct AmiiboSeriesFilter: KeyNameFilter { diff --git a/Sources/Public/Filters/AmiiboTypeFilter.swift b/Sources/Public/Filters/AmiiboTypeFilter.swift index cf48760..438ab51 100644 --- a/Sources/Public/Filters/AmiiboTypeFilter.swift +++ b/Sources/Public/Filters/AmiiboTypeFilter.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== public struct AmiiboTypeFilter: KeyNameFilter { diff --git a/Sources/Public/Filters/GameCharacterFilter.swift b/Sources/Public/Filters/GameCharacterFilter.swift index 345dac3..0fede7e 100644 --- a/Sources/Public/Filters/GameCharacterFilter.swift +++ b/Sources/Public/Filters/GameCharacterFilter.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== public struct GameCharacterFilter: KeyNameFilter { diff --git a/Sources/Public/Filters/GameSeriesFilter.swift b/Sources/Public/Filters/GameSeriesFilter.swift index 060988d..f02d59c 100644 --- a/Sources/Public/Filters/GameSeriesFilter.swift +++ b/Sources/Public/Filters/GameSeriesFilter.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== public struct GameSeriesFilter: KeyNameFilter { diff --git a/Sources/Public/Models/Amiibo.swift b/Sources/Public/Models/Amiibo.swift index f53d900..1518d9b 100644 --- a/Sources/Public/Models/Amiibo.swift +++ b/Sources/Public/Models/Amiibo.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== import Foundation diff --git a/Sources/Public/Models/Amiibo/Amiibo+Game.swift b/Sources/Public/Models/Amiibo/Amiibo+Game.swift index a820c11..1e2b450 100644 --- a/Sources/Public/Models/Amiibo/Amiibo+Game.swift +++ b/Sources/Public/Models/Amiibo/Amiibo+Game.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== extension Amiibo { public struct Game: Sendable { diff --git a/Sources/Public/Models/Amiibo/Amiibo+Platform.swift b/Sources/Public/Models/Amiibo/Amiibo+Platform.swift index 36c9ec0..66ed86d 100644 --- a/Sources/Public/Models/Amiibo/Amiibo+Platform.swift +++ b/Sources/Public/Models/Amiibo/Amiibo+Platform.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== extension Amiibo { public struct Platform: Sendable { diff --git a/Sources/Public/Models/Amiibo/Amiibo+Release.swift b/Sources/Public/Models/Amiibo/Amiibo+Release.swift index 7bcbfff..8e5adab 100644 --- a/Sources/Public/Models/Amiibo/Amiibo+Release.swift +++ b/Sources/Public/Models/Amiibo/Amiibo+Release.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== import Foundation diff --git a/Sources/Public/Models/Amiibo/Amiibo+Usage.swift b/Sources/Public/Models/Amiibo/Amiibo+Usage.swift index d2bda39..7b738b9 100644 --- a/Sources/Public/Models/Amiibo/Amiibo+Usage.swift +++ b/Sources/Public/Models/Amiibo/Amiibo+Usage.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== extension Amiibo { public struct Usage: Sendable { diff --git a/Sources/Public/Models/AmiiboSeries.swift b/Sources/Public/Models/AmiiboSeries.swift index 68f00f0..5a6c814 100644 --- a/Sources/Public/Models/AmiiboSeries.swift +++ b/Sources/Public/Models/AmiiboSeries.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== public struct AmiiboSeries: KeyNameModel { diff --git a/Sources/Public/Models/AmiiboType.swift b/Sources/Public/Models/AmiiboType.swift index 0a75874..2c57ee6 100644 --- a/Sources/Public/Models/AmiiboType.swift +++ b/Sources/Public/Models/AmiiboType.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== public struct AmiiboType: KeyNameModel { diff --git a/Sources/Public/Models/GameCharacter.swift b/Sources/Public/Models/GameCharacter.swift index 652ea4d..2421104 100644 --- a/Sources/Public/Models/GameCharacter.swift +++ b/Sources/Public/Models/GameCharacter.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== public struct GameCharacter: KeyNameModel { diff --git a/Sources/Public/Models/GameSeries.swift b/Sources/Public/Models/GameSeries.swift index a445472..25d0ec8 100644 --- a/Sources/Public/Models/GameSeries.swift +++ b/Sources/Public/Models/GameSeries.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== public struct GameSeries: KeyNameModel { diff --git a/Sources/Public/Services/AmiiboService.swift b/Sources/Public/Services/AmiiboService.swift index f0bdb2b..270eaf9 100644 --- a/Sources/Public/Services/AmiiboService.swift +++ b/Sources/Public/Services/AmiiboService.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== // // This source file is part of the AmiiboAPI open source project // @@ -8,7 +8,7 @@ // See LICENSE for license information // See CONTRIBUTORS for the list of AmiiboAPI project authors // -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------=== import Foundation import OpenAPIRuntime diff --git a/Sources/openapi-generator-config.yaml b/Sources/openapi-generator-config.yaml index 1df6f28..5babde7 100644 --- a/Sources/openapi-generator-config.yaml +++ b/Sources/openapi-generator-config.yaml @@ -1,3 +1,15 @@ +# ===----------------------------------------------------------------------=== +# +# 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 +# +# ===----------------------------------------------------------------------=== + generate: - types - client diff --git a/Sources/openapi.yaml b/Sources/openapi.yaml index 72282b0..b69ca5d 100644 --- a/Sources/openapi.yaml +++ b/Sources/openapi.yaml @@ -1,3 +1,15 @@ +# ===----------------------------------------------------------------------=== +# +# 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 +# +# ===----------------------------------------------------------------------=== + openapi: '3.1.0' info: title: Amiibo API service -- 2.47.1 From 82f7e1df696e99f886bfd7b7616578fb5a4423c6 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 8 Sep 2024 23:53:42 +0200 Subject: [PATCH 06/12] Defined the APIClient protocol in the package target. --- Sources/Internal/Protocols/APIClient.swift | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 Sources/Internal/Protocols/APIClient.swift diff --git a/Sources/Internal/Protocols/APIClient.swift b/Sources/Internal/Protocols/APIClient.swift new file mode 100644 index 0000000..568f862 --- /dev/null +++ b/Sources/Internal/Protocols/APIClient.swift @@ -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 + +public protocol APIClient { + + // MARK: Functions + + func getAmiibos(by filter: AmiiboFilter) async throws -> [Amiibo] + func getAmiiboSeries(by filter: AmiiboSeriesFilter) async throws -> [AmiiboSeries] + func getAmiiboTypes(by filter: AmiiboTypeFilter) async throws -> [AmiiboType] + func getGameCharacters(by filter: GameCharacterFilter) async throws -> [GameCharacter] + func getGameSeries(by filter: GameSeriesFilter) async throws -> [GameSeries] + func getLastUpdated() async throws -> Date + +} -- 2.47.1 From a00215635eb4605650c2b59f2ca4e742bec95469 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 8 Sep 2024 23:54:25 +0200 Subject: [PATCH 07/12] Implemented the AmiiboLiveClient client in the package target. --- Sources/Public/Clients/AmiiboLiveClient.swift | 235 ++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 Sources/Public/Clients/AmiiboLiveClient.swift diff --git a/Sources/Public/Clients/AmiiboLiveClient.swift b/Sources/Public/Clients/AmiiboLiveClient.swift new file mode 100644 index 0000000..9920df1 --- /dev/null +++ b/Sources/Public/Clients/AmiiboLiveClient.swift @@ -0,0 +1,235 @@ +//===----------------------------------------------------------------------=== +// +// 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 OpenAPIURLSession + +public struct AmiiboLiveClient { + + // MARK: Properties + + private let client: Client + + // MARK: Initialisers + + public init() throws { + self.client = .init( + serverURL: try Servers.server1(), + configuration: .init(dateTranscoder: ISODateTranscoder()), + transport: URLSessionTransport() + ) + } + +} + +// MARK: - APIProtocol + +extension AmiiboLiveClient: APIClient { + + // MARK: Functions + + public func getAmiibos(by filter: AmiiboFilter) 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(ok): + switch ok.body { + case let .json(output): + return map(output) + } + + case .badRequest: + throw AmiiboServiceError.badRequest + + case let .undocumented(statusCode, _): + throw AmiiboServiceError.undocumented(statusCode) + } + } + + public func getAmiiboSeries(by filter: AmiiboSeriesFilter) async throws -> [AmiiboSeries] { + let response = try await client.getAmiiboSeries( + .init(query: .init( + key: filter.key, + name: filter.name + )) + ) + + switch response { + case let .ok(ok): + switch ok.body { + case let .json(output): + return map(output, as: 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) async throws -> [AmiiboType] { + let response = try await client.getAmiiboTypes( + .init(query: .init( + key: filter.key, + name: filter.name + )) + ) + + switch response { + case let .ok(ok): + switch ok.body { + case let .json(output): + return map(output, as: 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) async throws -> [GameCharacter] { + let response = try await client.getGameCharacters( + .init(query: .init( + key: filter.key, + name: filter.name + )) + ) + + switch response { + case let .ok(ok): + switch ok.body { + case let .json(output): + return map(output, as: 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) async throws -> [GameSeries] { + let response = try await client.getGameSeries( + .init(query: .init( + key: filter.key, + name: filter.name + )) + ) + + switch response { + case let .ok(ok): + switch ok.body { + case let .json(output): + return map(output, as: 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(ok): + switch ok.body { + case let .json(output): + return output.lastUpdated + } + + case let .undocumented(statusCode, _): + throw AmiiboServiceError.undocumented(statusCode) + } + } + +} + +// MARK: - Helpers + +private extension AmiiboLiveClient { + + // MARK: Functions + + func map(_ wrapper: Components.Schemas.AmiiboWrapper) -> [Amiibo] { + switch wrapper.amiibo { + case let .Amiibo(object): + return [.init(object)] + + case let .AmiiboList(list): + return list + .map { .init($0) } + .sorted { $0.identifier < $1.identifier } + } + } + + func map( + _ wrapper: Components.Schemas.TupleWrapper, + as: Model.Type + ) -> [Model] { + switch wrapper.amiibo { + case let .Tuple(payload): + return [.init(payload)] + + case let .TupleList(list): + return list + .map { .init($0) } + .sorted { $0.key < $1.key } + } + } + +} -- 2.47.1 From e16f1ffbf939687e0c1304c7ce572e4d91b6bf45 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 8 Sep 2024 23:54:46 +0200 Subject: [PATCH 08/12] Implemented the AmiiboMockClient client in the package target. --- Sources/Public/Clients/AmiiboMockClient.swift | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 Sources/Public/Clients/AmiiboMockClient.swift diff --git a/Sources/Public/Clients/AmiiboMockClient.swift b/Sources/Public/Clients/AmiiboMockClient.swift new file mode 100644 index 0000000..aab20ed --- /dev/null +++ b/Sources/Public/Clients/AmiiboMockClient.swift @@ -0,0 +1,101 @@ +//===----------------------------------------------------------------------=== +// +// 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 AmiiboMockClient { + + // MARK: Properties + + private(set) public var amiibos: [Amiibo]? + private(set) public var amiiboSeries: [AmiiboSeries]? + private(set) public var amiiboTypes: [AmiiboType]? + private(set) public var gameCharacters: [GameCharacter]? + private(set) public var gameSeries: [GameSeries]? + private(set) public var lastUpdated: Date? + + // MARK: Initialisers + + public init( + amiibos: [Amiibo]? = nil, + amiiboSeries: [AmiiboSeries]? = nil, + amiiboTypes: [AmiiboType]? = nil, + gameCharacters: [GameCharacter]? = nil, + gameSeries: [GameSeries]? = nil, + lastUpdated: Date? = nil + ) { + self.amiibos = amiibos + self.amiiboSeries = amiiboSeries + self.amiiboTypes = amiiboTypes + self.gameCharacters = gameCharacters + self.gameSeries = gameSeries + self.lastUpdated = lastUpdated + } + +} + +// MARK: - APIClient + +extension AmiiboMockClient: APIClient { + + // MARK: Functions + + public func getAmiibos(by filter: AmiiboFilter) async throws -> [Amiibo] { + guard let amiibos else { + throw AmiiboServiceError.notFound + } + + return amiibos + } + + public func getAmiiboSeries(by filter: AmiiboSeriesFilter) async throws -> [AmiiboSeries] { + guard let amiiboSeries else { + throw AmiiboServiceError.notFound + } + + return amiiboSeries + } + + public func getAmiiboTypes(by filter: AmiiboTypeFilter) async throws -> [AmiiboType] { + guard let amiiboTypes else { + throw AmiiboServiceError.notFound + } + + return amiiboTypes + } + + public func getGameCharacters(by filter: GameCharacterFilter) async throws -> [GameCharacter] { + guard let gameCharacters else { + throw AmiiboServiceError.notFound + } + + return gameCharacters + } + + public func getGameSeries(by filter: GameSeriesFilter) async throws -> [GameSeries] { + guard let gameSeries else { + throw AmiiboServiceError.notFound + } + + return gameSeries + } + + public func getLastUpdated() async throws -> Date { + guard let lastUpdated else { + throw AmiiboServiceError.notFound + } + + return lastUpdated + } + + +} -- 2.47.1 From 0793df8c83f7849f86a479acd006c9ddcc010b68 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 8 Sep 2024 23:56:15 +0200 Subject: [PATCH 09/12] Implemented the "client" property that conforms to the APIClient protocol for the AmiiboService service in the package target. --- Sources/Public/Services/AmiiboService.swift | 195 ++------------------ 1 file changed, 14 insertions(+), 181 deletions(-) diff --git a/Sources/Public/Services/AmiiboService.swift b/Sources/Public/Services/AmiiboService.swift index 270eaf9..7b6023f 100644 --- a/Sources/Public/Services/AmiiboService.swift +++ b/Sources/Public/Services/AmiiboService.swift @@ -17,215 +17,48 @@ public struct AmiiboService { // MARK: Properties - private let client: Client + private let client: any APIClient // MARK: Initialisers - public init() throws { - self.client = try .live + public init(_ client: any APIClient) { + self.client = client } // MARK: Functions public func getAmiibos( - by filter: AmiiboFilter = .init() + _ 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) - } + try await client.getAmiibos(by: filter) } public func getAmiiboSeries( - by filter: AmiiboSeriesFilter = .init() + _ 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) - } + try await client.getAmiiboSeries(by: filter) } public func getAmiiboTypes( - by filter: AmiiboTypeFilter = .init() + _ 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) - } + try await client.getAmiiboTypes(by: filter) } public func getGameCharacters( - by filter: GameCharacterFilter = .init() + _ 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) - } + try await client.getGameCharacters(by: filter) } public func getGameSeries( - by filter: GameSeriesFilter = .init() + _ 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) - } + try await client.getGameSeries(by: filter) } 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( - _ 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 } - } + try await client.getLastUpdated() } } -- 2.47.1 From b29815f044d9096f03305c6d7e741ca342c5de4f Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 9 Sep 2024 00:11:14 +0200 Subject: [PATCH 10/12] Written test cases for the AmiiboServiceLiveTests tests in the tests target. --- Sources/Public/Clients/AmiiboMockClient.swift | 12 +- Sources/Public/Services/AmiiboService.swift | 1 - Tests/AmiiboServiceLiveTests.swift | 1135 +++++++++++++++++ Tests/amiibo_apiTests.swift | 7 - 4 files changed, 1141 insertions(+), 14 deletions(-) create mode 100644 Tests/AmiiboServiceLiveTests.swift delete mode 100644 Tests/amiibo_apiTests.swift diff --git a/Sources/Public/Clients/AmiiboMockClient.swift b/Sources/Public/Clients/AmiiboMockClient.swift index aab20ed..bf90c65 100644 --- a/Sources/Public/Clients/AmiiboMockClient.swift +++ b/Sources/Public/Clients/AmiiboMockClient.swift @@ -16,12 +16,12 @@ public struct AmiiboMockClient { // MARK: Properties - private(set) public var amiibos: [Amiibo]? - private(set) public var amiiboSeries: [AmiiboSeries]? - private(set) public var amiiboTypes: [AmiiboType]? - private(set) public var gameCharacters: [GameCharacter]? - private(set) public var gameSeries: [GameSeries]? - private(set) public var lastUpdated: Date? + private let amiibos: [Amiibo]? + private let amiiboSeries: [AmiiboSeries]? + private let amiiboTypes: [AmiiboType]? + private let gameCharacters: [GameCharacter]? + private let gameSeries: [GameSeries]? + private let lastUpdated: Date? // MARK: Initialisers diff --git a/Sources/Public/Services/AmiiboService.swift b/Sources/Public/Services/AmiiboService.swift index 7b6023f..f983a3a 100644 --- a/Sources/Public/Services/AmiiboService.swift +++ b/Sources/Public/Services/AmiiboService.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------=== import Foundation -import OpenAPIRuntime public struct AmiiboService { diff --git a/Tests/AmiiboServiceLiveTests.swift b/Tests/AmiiboServiceLiveTests.swift new file mode 100644 index 0000000..dd3544c --- /dev/null +++ b/Tests/AmiiboServiceLiveTests.swift @@ -0,0 +1,1135 @@ +//===----------------------------------------------------------------------=== +// +// 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 AmiiboAPI +import Foundation +import Testing + +struct AmiiboServiceLiveTests { + + // MARK: Properties + + private let service: AmiiboService + + // MARK: Initialisers + + init() throws { + let client = try AmiiboLiveClient() + + self.service = .init(client) + } + + // MARK: Functions tests + + @Test("Get Amiibo items") + func getAmiibos() async throws { + // GIVEN + // WHEN + let amiibos = try await service.getAmiibos() + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 853) + #expect(amiibos.first?.identifier == "0000000000000002") + #expect(amiibos.first?.platform == nil) + #expect(amiibos.last?.identifier == "3f000000042e0002") + #expect(amiibos.last?.platform == nil) + } + + @Test("Get Amiibo items by an existing identifier") + func getAmiibos_byExistingIdentifier() async throws { + // GIVEN + let identifier = "0000000000000002" + + // WHEN + let amiibos = try await service.getAmiibos(.init(identifier: identifier)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 1) + #expect(amiibos.first?.identifier == identifier) + #expect(amiibos.first?.platform == nil) + } + + @Test("Get Amiibo items by a non-existing identifier") + func getAmiibos_byNonExistingIdentifier() async throws { + // GIVEN + let identifier = "0000000000000000" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.undocumented(404)) { + try await service.getAmiibos(.init(identifier: identifier)) + } + } + + @Test("Get Amiibo items by an incomplete identifier") + func getAmiibos_byIncompleteIdentifier() async throws { + // GIVEN + let identifier = "0000000" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.undocumented(404)) { + try await service.getAmiibos(.init(identifier: identifier)) + } + } + + @Test("Get Amiibo items by an empty identifier") + func getAmiibos_byEmptyIdentifier() async throws { + // GIVEN + let identifier = "" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.badRequest) { + try await service.getAmiibos(.init(identifier: identifier)) + } + } + + @Test("Get Amiibo items by an existing name") + func getAmiibos_byExistingName() async throws { + // GIVEN + let name = "zelda" + + // WHEN + let amiibos = try await service.getAmiibos(.init(name: name)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 5) + #expect(amiibos.first?.platform == nil) + #expect(amiibos.last?.platform == nil) + + let nameFirst = try #require(amiibos.first?.name.lowercased()) + let nameLast = try #require(amiibos.last?.name.lowercased()) + + #expect(nameFirst.contains(name)) + #expect(nameLast.contains(name)) + } + + @Test("Get Amiibo items by a non-existing name") + func getAmiibos_byNonExistingName() async throws { + // GIVEN + let name = "Something" + + // WHEN + let amiibos = try await service.getAmiibos(.init(name: name)) + + // THEN + #expect(amiibos.isEmpty) + } + + @Test("Get Amiibo items by an incomplete name") + func getAmiibos_byIncompleteName() async throws { + // GIVEN + let name = "zel" + + // WHEN + let amiibos = try await service.getAmiibos(.init(name: name)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 7) + #expect(amiibos.first?.platform == nil) + #expect(amiibos.last?.platform == nil) + + let nameFirst = try #require(amiibos.first?.name.lowercased()) + let nameLast = try #require(amiibos.last?.name.lowercased()) + + #expect(nameFirst.contains(name)) + #expect(nameLast.contains(name)) + } + + @Test("Get Amiibo items by an empty name") + func getAmiibos_byEmptyName() async throws { + // GIVEN + let name = "" + + // WHEN + let amiibos = try await service.getAmiibos(.init(name: name)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 853) + } + + @Test("Get Amiibo items by an existing type key") + func getAmiibos_byExistingTypeKey() async throws { + // GIVEN + let key = "0x00" + + // WHEN + let amiibos = try await service.getAmiibos(.init(type: key)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 227) + #expect(amiibos.first?.type == "Figure") + #expect(amiibos.first?.platform == nil) + #expect(amiibos.last?.type == "Figure") + #expect(amiibos.last?.platform == nil) + } + + @Test("Get Amiibo items by an existing type name") + func getAmiibos_byExistingTypeName() async throws { + // GIVEN + let name = "figure" + + // WHEN + let amiibos = try await service.getAmiibos(.init(type: name)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 227) + #expect(amiibos.first?.type == "Figure") + #expect(amiibos.first?.platform == nil) + #expect(amiibos.last?.type == "Figure") + #expect(amiibos.last?.platform == nil) + } + + @Test("Get Amiibo items by a non-existing type key") + func getAmiibos_byNonExistingTypeKey() async throws { + // GIVEN + let key = "0x0f" + + // WHEN + let amiibos = try await service.getAmiibos(.init(type: key)) + + // THEN + #expect(amiibos.isEmpty) + } + + @Test("Get Amiibo items by a non-existing type name") + func getAmiibos_byNonExistingTypeName() async throws { + // GIVEN + let name = "something" + + // WHEN + let amiibos = try await service.getAmiibos(.init(type: name)) + + // THEN + #expect(amiibos.isEmpty) + } + + @Test("Get Amiibo items by an incomplete type key") + func getAmiibos_byIncompleteTypeKey() async throws { + // GIVEN + let key = "0x" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.badRequest) { + try await service.getAmiibos(.init(type: key)) + } + } + + @Test("Get Amiibo items by an incomplete type name") + func getAmiibos_byIncompleteTypeName() async throws { + // GIVEN + let name = "fig" + + // WHEN + let amiibos = try await service.getAmiibos(.init(type: name)) + + // THEN + #expect(amiibos.isEmpty) + } + + @Test("Get Amiibo items by an empty type key") + func getAmiibos_byEmptyTypeKey() async throws { + // GIVEN + let key = "" + + // WHEN + let amiibos = try await service.getAmiibos(.init(type: key)) + + // THEN + #expect(amiibos.isEmpty) + } + + @Test("Get Amiibo items by an empty type name") + func getAmiibos_byEmptyTypeName() async throws { + // GIVEN + let name = "" + + // WHEN + let amiibos = try await service.getAmiibos(.init(type: name)) + + // THEN + #expect(amiibos.isEmpty) + } + + @Test("Get Amiibo items by an existing series key") + func getAmiibos_byExistingSeriesKey() async throws { + // GIVEN + let key = "0x00" + + // WHEN + let amiibos = try await service.getAmiibos(.init(series: key)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 96) + #expect(amiibos.first?.series == "Super Smash Bros.") + #expect(amiibos.first?.platform == nil) + #expect(amiibos.last?.series == "Super Smash Bros.") + #expect(amiibos.last?.platform == nil) + } + + @Test("Get Amiibo items by an existing series name") + func getAmiibos_byExistingSeriesName() async throws { + // GIVEN + let name = "Legend Of Zelda" + + // WHEN + let amiibos = try await service.getAmiibos(.init(series: name)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 22) + #expect(amiibos.first?.series == name) + #expect(amiibos.first?.platform == nil) + #expect(amiibos.last?.series == name) + #expect(amiibos.last?.platform == nil) + } + + @Test("Get Amiibo items by a non-existing series key") + func getAmiibos_byNonExistingSeriesKey() async throws { + // GIVEN + let key = "0xf9" + + // WHEN + let amiibos = try await service.getAmiibos(.init(series: key)) + + // THEN + #expect(amiibos.isEmpty) + } + + @Test("Get Amiibo items by a non-existing series name") + func getAmiibos_byNonExistingSeriesName() async throws { + // GIVEN + let name = "something" + + // WHEN + let amiibos = try await service.getAmiibos(.init(series: name)) + + // THEN + #expect(amiibos.isEmpty) + } + + @Test("Get Amiibo items by an incomplete series key") + func getAmiibos_byIncompleteSeriesKey() async throws { + // GIVEN + let key = "0x" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.badRequest) { + try await service.getAmiibos(.init(series: key)) + } + } + + @Test("Get Amiibo items by an incomplete series name") + func getAmiibos_byIncompleteSeriesName() async throws { + // GIVEN + let name = "fig" + + // WHEN + let amiibos = try await service.getAmiibos(.init(series: name)) + + // THEN + #expect(amiibos.isEmpty) + } + + @Test("Get Amiibo items by an empty series key") + func getAmiibos_byEmptySeriesKey() async throws { + // GIVEN + let key = "" + + // WHEN + let amiibos = try await service.getAmiibos(.init(series: key)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 853) + } + + @Test("Get Amiibo items by an empty series name") + func getAmiibos_byEmptySeriesName() async throws { + // GIVEN + let name = "" + + // WHEN + let amiibos = try await service.getAmiibos(.init(series: name)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 853) + } + + @Test("Get Amiibo items by an existing game character key") + func getAmiibos_byExistingGameCharacterKey() async throws { + // GIVEN + let key = "0x00" + + // WHEN + let amiibos = try await service.getAmiibos(.init(gameCharacter: key)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 11) + #expect(amiibos.first?.gameCharacter == "Mario") + #expect(amiibos.first?.platform == nil) + #expect(amiibos.last?.gameCharacter == "Mario") + #expect(amiibos.last?.platform == nil) + } + + @Test("Get Amiibo items by an existing game character name") + func getAmiibos_byExistingGameCharacterName() async throws { + // GIVEN + let name = "Zelda" + + // WHEN + let amiibos = try await service.getAmiibos(.init(gameCharacter: name)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 6) + #expect(amiibos.first?.gameCharacter == name) + #expect(amiibos.first?.platform == nil) + #expect(amiibos.last?.gameCharacter == name) + #expect(amiibos.last?.platform == nil) + } + + @Test("Get Amiibo items by a non-existing game character key") + func getAmiibos_byNonExistingGameCharacterKey() async throws { + // GIVEN + let key = "0xf9" + + // WHEN + let amiibos = try await service.getAmiibos(.init(gameCharacter: key)) + + // THEN + #expect(amiibos.isEmpty) + } + + @Test("Get Amiibo items by a non-existing game character name") + func getAmiibos_byNonExistingGameCharacterName() async throws { + // GIVEN + let name = "something" + + // WHEN + let amiibos = try await service.getAmiibos(.init(gameCharacter: name)) + + // THEN + #expect(amiibos.isEmpty) + } + + @Test("Get Amiibo items by an incomplete game character key") + func getAmiibos_byIncompleteGameCharacterKey() async throws { + // GIVEN + let key = "0x" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.badRequest) { + try await service.getAmiibos(.init(gameCharacter: key)) + } + } + + @Test("Get Amiibo items by an incomplete game character name") + func getAmiibos_byIncompleteGameCharacterName() async throws { + // GIVEN + let name = "fig" + + // WHEN + let amiibos = try await service.getAmiibos(.init(gameCharacter: name)) + + // THEN + #expect(amiibos.isEmpty) + } + + @Test("Get Amiibo items by an empty game character key") + func getAmiibos_byEmptyGameCharacterKey() async throws { + // GIVEN + let key = "" + + // WHEN + let amiibos = try await service.getAmiibos(.init(gameCharacter: key)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 853) + } + + @Test("Get Amiibo items by an empty game character name") + func getAmiibos_byEmptyGameCharacterName() async throws { + // GIVEN + let name = "" + + // WHEN + let amiibos = try await service.getAmiibos(.init(gameCharacter: name)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 853) + } + + @Test("Get Amiibo items by an existing game series key") + func getAmiibos_byExistingGameSeriesKey() async throws { + // GIVEN + let key = "0x00" + + // WHEN + let amiibos = try await service.getAmiibos(.init(gameSeries: key)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 42) + #expect(amiibos.first?.gameSeries == "Super Mario") + #expect(amiibos.first?.platform == nil) + #expect(amiibos.last?.gameSeries == "Super Mario") + #expect(amiibos.last?.platform == nil) + } + + @Test("Get Amiibo items by an existing game series name") + func getAmiibos_byExistingGameSeriesName() async throws { + // GIVEN + let name = "The Legend of Zelda" + + // WHEN + let amiibos = try await service.getAmiibos(.init(gameSeries: name)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 28) + #expect(amiibos.first?.gameSeries == name) + #expect(amiibos.first?.platform == nil) + #expect(amiibos.last?.gameSeries == name) + #expect(amiibos.last?.platform == nil) + } + + @Test("Get Amiibo items by a non-existing game series key") + func getAmiibos_byNonExistingGameSeriesKey() async throws { + // GIVEN + let key = "0xf9" + + // WHEN + let amiibos = try await service.getAmiibos(.init(gameSeries: key)) + + // THEN + #expect(amiibos.isEmpty) + } + + @Test("Get Amiibo items by a non-existing game series name") + func getAmiibos_byNonExistingGameSeriesName() async throws { + // GIVEN + let name = "something" + + // WHEN + let amiibos = try await service.getAmiibos(.init(gameSeries: name)) + + // THEN + #expect(amiibos.isEmpty) + } + + @Test("Get Amiibo items by an incomplete game series key") + func getAmiibos_byIncompleteGameSeriesKey() async throws { + // GIVEN + let key = "0x" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.badRequest) { + try await service.getAmiibos(.init(gameSeries: key)) + } + } + + @Test("Get Amiibo items by an incomplete game series name") + func getAmiibos_byIncompleteGameSeriesName() async throws { + // GIVEN + let name = "Super" + + // WHEN + let amiibos = try await service.getAmiibos(.init(gameSeries: name)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 140) + } + + @Test("Get Amiibo items by an empty game series key") + func getAmiibos_byEmptyGameSeriesKey() async throws { + // GIVEN + let key = "" + + // WHEN + let amiibos = try await service.getAmiibos(.init(gameSeries: key)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 853) + } + + @Test("Get Amiibo items by an empty game series name") + func getAmiibos_byEmptyGameSeriesName() async throws { + // GIVEN + let name = "" + + // WHEN + let amiibos = try await service.getAmiibos(.init(gameSeries: name)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 853) + } + + @Test("Get Amiibo items with games data") + func getAmiibos_withGamesData() async throws { + // GIVEN + // WHEN + let amiibos = try await service.getAmiibos(.init(showGames: true)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 853) + #expect(amiibos.first?.platform != nil) + #expect(amiibos.first?.platform?.switch.isEmpty == false) + #expect(amiibos.first?.platform?.switch.first?.usages == nil) + #expect(amiibos.first?.platform?.threeDS.isEmpty == false) + #expect(amiibos.first?.platform?.threeDS.first?.usages == nil) + #expect(amiibos.first?.platform?.wiiU.isEmpty == false) + #expect(amiibos.first?.platform?.wiiU.first?.usages == nil) + #expect(amiibos.last?.platform != nil) + } + + @Test("Get Amiibo items with games and usages data") + func getAmiibos_withGamesAndUsagesData() async throws { + // GIVEN + // WHEN + let amiibos = try await service.getAmiibos(.init(showUsage: true)) + + // THEN + #expect(!amiibos.isEmpty) + #expect(amiibos.count == 853) + #expect(amiibos.first?.platform != nil) + #expect(amiibos.first?.platform?.switch.isEmpty == false) + #expect(amiibos.first?.platform?.switch.first?.usages?.isEmpty == false) + #expect(amiibos.first?.platform?.threeDS.isEmpty == false) + #expect(amiibos.first?.platform?.threeDS.first?.usages?.isEmpty == false) + #expect(amiibos.first?.platform?.wiiU.isEmpty == false) + #expect(amiibos.first?.platform?.wiiU.first?.usages?.isEmpty == false) + #expect(amiibos.last?.platform != nil) + } + + @Test("Get Amiibo series") + func getAmiiboSeries() async throws { + // GIVEN + // WHEN + let amiiboSeries = try await service.getAmiiboSeries() + + // THEN + #expect(!amiiboSeries.isEmpty) + #expect(amiiboSeries.count == 26) + #expect(amiiboSeries.first?.key == "0x00") + #expect(amiiboSeries.last?.key == "0xff") + } + + @Test("Get Amiibo series by an existing key") + func getAmiiboSeries_byExistingKey() async throws { + // GIVEN + let key = "0x01" + + // WHEN + let amiiboSeries = try await service.getAmiiboSeries(.init(key: key)) + + // THEN + #expect(!amiiboSeries.isEmpty) + #expect(amiiboSeries.count == 1) + #expect(amiiboSeries.first?.key == key) + } + + @Test("Get Amiibo series by a non-existing key") + func getAmiiboSeries_byNonExistingKey() async throws { + // GIVEN + let key = "0xf9" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.notFound) { + try await service.getAmiiboSeries(.init(key: key)) + } + } + + @Test("Get Amiibo series by an incomplete key") + func getAmiiboSeries_byIncompleteKey() async throws { + // GIVEN + let key = "0x" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.badRequest) { + try await service.getAmiiboSeries(.init(key: key)) + } + } + + @Test("Get Amiibo series by an empty key") + func getAmiiboSeries_byEmptyKey() async throws { + // GIVEN + let key = "" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.badRequest) { + try await service.getAmiiboSeries(.init(key: key)) + } + } + + @Test("Get Amiibo series by an existing name") + func getAmiiboSeries_byExistingName() async throws { + // GIVEN + let name = "Legend Of Zelda" + + // WHEN + let amiiboSeries = try await service.getAmiiboSeries(.init(name: name)) + + // THEN + #expect(!amiiboSeries.isEmpty) + #expect(amiiboSeries.count == 1) + #expect(amiiboSeries.first?.name == name) + } + + @Test("Get Amiibo series by a non-existing name") + func getAmiiboSeries_byNonExistingName() async throws { + // GIVEN + let name = "Something" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.notFound) { + try await service.getAmiiboSeries(.init(name: name)) + } + } + + @Test("Get Amiibo series by an incomplete name") + func getAmiiboSeries_byIncompleteName() async throws { + // GIVEN + let name = "Zelda" + + // WHEN + let amiiboSeries = try await service.getAmiiboSeries(.init(name: name)) + + // THEN + #expect(!amiiboSeries.isEmpty) + #expect(amiiboSeries.count == 1) + + let amiiboSeriesName = try #require(amiiboSeries.first) + + #expect(amiiboSeriesName.name.contains(name)) + } + + @Test("Get Amiibo series by an empty name") + func getAmiiboSeries_byEmptyName() async throws { + // GIVEN + let name = "" + + // WHEN + let amiiboSeries = try await service.getAmiiboSeries(.init(name: name)) + + // THEN + #expect(!amiiboSeries.isEmpty) + #expect(amiiboSeries.count == 26) + #expect(amiiboSeries.first?.key == "0x00") + #expect(amiiboSeries.last?.key == "0xff") + } + + @Test("Get Amiibo types") + func getAmiiboTypes() async throws { + // GIVEN + // WHEN + let amiiboTypes = try await service.getAmiiboTypes() + + // THEN + #expect(!amiiboTypes.isEmpty) + #expect(amiiboTypes.count == 4) + #expect(amiiboTypes.first?.key == "0x00") + #expect(amiiboTypes.last?.key == "0x03") + } + + @Test("Get Amiibo types by an existing key") + func getAmiiboTypes_byExistingKey() async throws { + // GIVEN + let key = "0x01" + + // WHEN + let amiiboTypes = try await service.getAmiiboTypes(.init(key: key)) + + // THEN + #expect(!amiiboTypes.isEmpty) + #expect(amiiboTypes.count == 1) + #expect(amiiboTypes.first?.key == key) + } + + @Test("Get Amiibo types by a non-existing key") + func getAmiiboTypes_byNonExistingKey() async throws { + // GIVEN + let key = "0x09" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.notFound) { + try await service.getAmiiboTypes(.init(key: key)) + } + } + + @Test("Get Amiibo types by an incomplete key") + func getAmiiboTypes_byIncompleteKey() async throws { + // GIVEN + let key = "0x" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.badRequest) { + try await service.getAmiiboTypes(.init(key: key)) + } + } + + @Test("Get Amiibo types by an empty key") + func getAmiiboTypes_byEmptyKey() async throws { + // GIVEN + let key = "" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.badRequest) { + try await service.getAmiiboTypes(.init(key: key)) + } + } + + @Test("Get Amiibo types by an existing name") + func getAmiiboTypes_byExistingName() async throws { + // GIVEN + let name = "Card" + + // WHEN + let amiiboTypes = try await service.getAmiiboTypes(.init(name: name)) + + // THEN + #expect(!amiiboTypes.isEmpty) + #expect(amiiboTypes.count == 1) + #expect(amiiboTypes.first?.name == name) + } + + @Test("Get Amiibo types by a non-existing name") + func getAmiiboTypes_byNonExistingName() async throws { + // GIVEN + let name = "Something" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.notFound) { + try await service.getAmiiboTypes(.init(name: name)) + } + } + + @Test("Get Amiibo types by an incomplete name") + func getAmiiboTypes_byIncompleteName() async throws { + // GIVEN + let name = "Ca" + + // WHEN + let amiiboTypes = try await service.getAmiiboTypes(.init(name: name)) + + // THEN + #expect(!amiiboTypes.isEmpty) + #expect(amiiboTypes.count == 1) + + let amiiboTypeName = try #require(amiiboTypes.first) + + #expect(amiiboTypeName.name.contains(name)) + } + + @Test("Get Amiibo types by an empty name") + func getAmiiboTypes_byEmptyName() async throws { + // GIVEN + let name = "" + + // WHEN + let amiiboTypes = try await service.getAmiiboTypes(.init(name: name)) + + // THEN + #expect(!amiiboTypes.isEmpty) + #expect(amiiboTypes.count == 4) + #expect(amiiboTypes.first?.key == "0x00") + #expect(amiiboTypes.last?.key == "0x03") + } + + @Test("Get game characters") + func getGameCharacters() async throws { + // GIVEN + // WHEN + let gameCharacters = try await service.getGameCharacters() + + // THEN + #expect(!gameCharacters.isEmpty) + #expect(gameCharacters.count == 644) + #expect(gameCharacters.first?.key == "0x0000") + #expect(gameCharacters.last?.key == "0x3f00") + } + + @Test("Get game characters by an existing key") + func getGameCharacters_byExistingKey() async throws { + // GIVEN + let key = "0x0001" + + // WHEN + let gameCharacters = try await service.getGameCharacters(.init(key: key)) + + // THEN + #expect(!gameCharacters.isEmpty) + #expect(gameCharacters.count == 1) + #expect(gameCharacters.first?.key == key) + } + + @Test("Get game characters by a non-existing key") + func getGameCharacters_byNonExistingKey() async throws { + // GIVEN + let key = "0xffff" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.notFound) { + try await service.getGameCharacters(.init(key: key)) + } + } + + @Test("Get game characters by an incomplete key") + func getGameCharacters_byIncompleteKey() async throws { + // GIVEN + let key = "0x" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.badRequest) { + try await service.getGameCharacters(.init(key: key)) + } + } + + @Test("Get game characters by an empty key") + func getGameCharacters_byEmptyKey() async throws { + // GIVEN + let key = "" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.badRequest) { + try await service.getGameCharacters(.init(key: key)) + } + } + + @Test("Get game characters by an existing name") + func getGameCharacters_byExistingName() async throws { + // GIVEN + let name = "Zelda" + + // WHEN + let gameCharacters = try await service.getGameCharacters(.init(name: name)) + + // THEN + #expect(!gameCharacters.isEmpty) + #expect(gameCharacters.count == 1) + #expect(gameCharacters.first?.name == name) + } + + @Test("Get game characters by a non-existing name") + func getGameCharacters_byNonExistingName() async throws { + // GIVEN + let name = "Something" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.notFound) { + try await service.getGameCharacters(.init(name: name)) + } + } + + @Test("Get game characters by an incomplete name") + func getGameCharacters_byIncompleteName() async throws { + // GIVEN + let name = "Zeld" + + // WHEN + let gameCharacters = try await service.getGameCharacters(.init(name: name)) + + // THEN + #expect(!gameCharacters.isEmpty) + #expect(gameCharacters.count == 1) + + let gameCharactersName = try #require(gameCharacters.first) + + #expect(gameCharactersName.name.contains(name)) + } + + @Test("Get game characters by an empty name") + func getGameCharacters_byEmptyName() async throws { + // GIVEN + let name = "" + + // WHEN + let gameCharacters = try await service.getGameCharacters(.init(name: name)) + + // THEN + #expect(!gameCharacters.isEmpty) + #expect(gameCharacters.count == 644) + #expect(gameCharacters.first?.key == "0x0000") + #expect(gameCharacters.last?.key == "0x3f00") + } + + @Test("Get game series") + func getGameSeries() async throws { + // GIVEN + // WHEN + let gameSeries = try await service.getGameSeries() + + // THEN + #expect(!gameSeries.isEmpty) + #expect(gameSeries.count == 116) + #expect(gameSeries.first?.key == "0x000") + #expect(gameSeries.last?.key == "0x3f0") + } + + @Test("Get game series by an existing key") + func getGameSeries_byExistingKey() async throws { + // GIVEN + let key = "0x001" + + // WHEN + let gameSeries = try await service.getGameSeries(.init(key: key)) + + // THEN + #expect(!gameSeries.isEmpty) + #expect(gameSeries.count == 1) + #expect(gameSeries.first?.key == key) + } + + @Test("Get game series by a non-existing key") + func getGameSeries_byNonExistingKey() async throws { + // GIVEN + let key = "0xffff" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.notFound) { + try await service.getGameSeries(.init(key: key)) + } + } + + @Test("Get game series by an incomplete key") + func getGameSeries_byIncompleteKey() async throws { + // GIVEN + let key = "0x" + + // WHEN & THEN + await #expect(throws: AmiiboServiceError.badRequest) { + try await service.getGameSeries(.init(key: key)) + } + } + + @Test("Get game series by an empty key") + func getGameSeries_byEmptyKey() async throws { + // GIVEN + let key = "" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.badRequest) { + try await service.getGameSeries(.init(key: key)) + } + } + + @Test("Get game series by an existing name") + func getGameSeries_byExistingName() async throws { + // GIVEN + let name = "Pikmin" + + // WHEN + let gameSeries = try await service.getGameSeries(.init(name: name)) + + // THEN + #expect(!gameSeries.isEmpty) + #expect(gameSeries.count == 1) + #expect(gameSeries.first?.name == name) + } + + @Test("Get game series by a non-existing name") + func getGameSeries_byNonExistingName() async throws { + // GIVEN + let name = "Something" + + // WHEN + // THEN + await #expect(throws: AmiiboServiceError.notFound) { + try await service.getGameSeries(.init(name: name)) + } + } + + @Test("Get game series by an incomplete name") + func getGameSeries_byIncompleteName() async throws { + // GIVEN + let name = "Pik" + + // WHEN + let gameSeries = try await service.getGameSeries(.init(name: name)) + + // THEN + #expect(!gameSeries.isEmpty) + #expect(gameSeries.count == 1) + + let gameSeriesName = try #require(gameSeries.first) + + #expect(gameSeriesName.name.contains(name)) + } + + @Test("Get game series by an empty name") + func getGameSeries_byEmptyName() async throws { + // GIVEN + let name = "" + + // WHEN + let gameSeries = try await service.getGameSeries(.init(name: name)) + + // THEN + #expect(!gameSeries.isEmpty) + #expect(gameSeries.count == 116) + #expect(gameSeries.first?.key == "0x000") + #expect(gameSeries.last?.key == "0x3f0") + } + + @Test("Get the last updated timestamp") + func getLastUpdated() async throws { + // GIVEN + // WHEN + let dateLastUpdated = try await service.getLastUpdated() + + // THEN + let dateComponents = Calendar.current.dateComponents( + [.year, .month, .day], + from: dateLastUpdated + ) + + #expect(dateComponents.year == 2024) + #expect(dateComponents.month == 9) + #expect(dateComponents.day == 6) + } + +} diff --git a/Tests/amiibo_apiTests.swift b/Tests/amiibo_apiTests.swift deleted file mode 100644 index 26a09ea..0000000 --- a/Tests/amiibo_apiTests.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Testing - -@testable import AmiiboAPI - -@Test func example() async throws { - // Write your test here and use APIs like `#expect(...)` to check expected conditions. -} -- 2.47.1 From 4a8f4020368a9f14ac3e36da6bfd1c84bbb5d4ae Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 15 Sep 2024 00:12:37 +0200 Subject: [PATCH 11/12] Added the "error" property to the AmiiboMockClient client in the package target. --- Sources/Public/Clients/AmiiboMockClient.swift | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/Sources/Public/Clients/AmiiboMockClient.swift b/Sources/Public/Clients/AmiiboMockClient.swift index bf90c65..dbcb754 100644 --- a/Sources/Public/Clients/AmiiboMockClient.swift +++ b/Sources/Public/Clients/AmiiboMockClient.swift @@ -19,6 +19,7 @@ public struct AmiiboMockClient { private let amiibos: [Amiibo]? private let amiiboSeries: [AmiiboSeries]? private let amiiboTypes: [AmiiboType]? + private let error: AmiiboServiceError? private let gameCharacters: [GameCharacter]? private let gameSeries: [GameSeries]? private let lastUpdated: Date? @@ -31,11 +32,13 @@ public struct AmiiboMockClient { amiiboTypes: [AmiiboType]? = nil, gameCharacters: [GameCharacter]? = nil, gameSeries: [GameSeries]? = nil, - lastUpdated: Date? = nil + lastUpdated: Date? = nil, + error: AmiiboServiceError? = nil ) { self.amiibos = amiibos self.amiiboSeries = amiiboSeries self.amiiboTypes = amiiboTypes + self.error = error self.gameCharacters = gameCharacters self.gameSeries = gameSeries self.lastUpdated = lastUpdated @@ -50,6 +53,8 @@ extension AmiiboMockClient: APIClient { // MARK: Functions public func getAmiibos(by filter: AmiiboFilter) async throws -> [Amiibo] { + try throwErrorIfExists() + guard let amiibos else { throw AmiiboServiceError.notFound } @@ -58,6 +63,8 @@ extension AmiiboMockClient: APIClient { } public func getAmiiboSeries(by filter: AmiiboSeriesFilter) async throws -> [AmiiboSeries] { + try throwErrorIfExists() + guard let amiiboSeries else { throw AmiiboServiceError.notFound } @@ -66,6 +73,8 @@ extension AmiiboMockClient: APIClient { } public func getAmiiboTypes(by filter: AmiiboTypeFilter) async throws -> [AmiiboType] { + try throwErrorIfExists() + guard let amiiboTypes else { throw AmiiboServiceError.notFound } @@ -74,6 +83,8 @@ extension AmiiboMockClient: APIClient { } public func getGameCharacters(by filter: GameCharacterFilter) async throws -> [GameCharacter] { + try throwErrorIfExists() + guard let gameCharacters else { throw AmiiboServiceError.notFound } @@ -82,6 +93,8 @@ extension AmiiboMockClient: APIClient { } public func getGameSeries(by filter: GameSeriesFilter) async throws -> [GameSeries] { + try throwErrorIfExists() + guard let gameSeries else { throw AmiiboServiceError.notFound } @@ -90,6 +103,8 @@ extension AmiiboMockClient: APIClient { } public func getLastUpdated() async throws -> Date { + try throwErrorIfExists() + guard let lastUpdated else { throw AmiiboServiceError.notFound } @@ -99,3 +114,17 @@ extension AmiiboMockClient: APIClient { } + +// MARK: - Helpers + +private extension AmiiboMockClient { + + // MARK: Functions + + func throwErrorIfExists() throws { + if let error { + throw error + } + } + +} -- 2.47.1 From 420ed8f7a3e907cb63e4265e8a53fca5db7541d7 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 15 Sep 2024 00:15:06 +0200 Subject: [PATCH 12/12] Improved the "getAmiibos(by: )" function for the AmiiboLiveClient client in the package target. --- Sources/Public/Clients/AmiiboLiveClient.swift | 39 ++++++++++++------- .../Public/Errors/AmiiboServiceError.swift | 2 + Tests/AmiiboServiceLiveTests.swift | 4 +- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/Sources/Public/Clients/AmiiboLiveClient.swift b/Sources/Public/Clients/AmiiboLiveClient.swift index 9920df1..6028575 100644 --- a/Sources/Public/Clients/AmiiboLiveClient.swift +++ b/Sources/Public/Clients/AmiiboLiveClient.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------=== import Foundation +import OpenAPIRuntime import OpenAPIURLSession public struct AmiiboLiveClient { @@ -38,19 +39,31 @@ extension AmiiboLiveClient: APIClient { // MARK: Functions public func getAmiibos(by filter: AmiiboFilter) 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 - )) - ) - + let response = try await { + do { + return 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 + )) + ) + } catch let error as ClientError { + guard let _ = error.underlyingError as? DecodingError else { + throw AmiiboServiceError.unknown + } + + throw AmiiboServiceError.decoding + } catch { + throw AmiiboServiceError.unknown + } + }() + switch response { case let .ok(ok): switch ok.body { diff --git a/Sources/Public/Errors/AmiiboServiceError.swift b/Sources/Public/Errors/AmiiboServiceError.swift index db86577..7b59b07 100644 --- a/Sources/Public/Errors/AmiiboServiceError.swift +++ b/Sources/Public/Errors/AmiiboServiceError.swift @@ -12,9 +12,11 @@ public enum AmiiboServiceError: Error { case badRequest + case decoding case notAvailable case notFound case undocumented(_ statusCode: Int) + case unknown } // MARK: - Equatable diff --git a/Tests/AmiiboServiceLiveTests.swift b/Tests/AmiiboServiceLiveTests.swift index dd3544c..6742af0 100644 --- a/Tests/AmiiboServiceLiveTests.swift +++ b/Tests/AmiiboServiceLiveTests.swift @@ -67,7 +67,7 @@ struct AmiiboServiceLiveTests { // WHEN // THEN - await #expect(throws: AmiiboServiceError.undocumented(404)) { + await #expect(throws: AmiiboServiceError.decoding) { try await service.getAmiibos(.init(identifier: identifier)) } } @@ -79,7 +79,7 @@ struct AmiiboServiceLiveTests { // WHEN // THEN - await #expect(throws: AmiiboServiceError.undocumented(404)) { + await #expect(throws: AmiiboServiceError.decoding) { try await service.getAmiibos(.init(identifier: identifier)) } } -- 2.47.1