From 1598616ce11c3976649e91c8e4272c515d75eef7 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Wed, 19 Apr 2023 22:23:04 +0200 Subject: [PATCH 01/13] Defined the Filter protocol. --- Sources/Protocols/Filter.swift | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Sources/Protocols/Filter.swift diff --git a/Sources/Protocols/Filter.swift b/Sources/Protocols/Filter.swift new file mode 100644 index 0000000..39a805c --- /dev/null +++ b/Sources/Protocols/Filter.swift @@ -0,0 +1,9 @@ +import Communications + +protocol Filter { + + // MARK: Functions + + func makeParameters() -> Endpoint.Parameters + +} -- 2.47.1 From 35027dcd385f5c200280c8f0f26801f203825f17 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Wed, 19 Apr 2023 22:23:55 +0200 Subject: [PATCH 02/13] Implemented the AmiiboFilter filter and conformed it to the Filter protocol. --- Sources/Filters/AmiiboFilter.swift | 113 +++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 Sources/Filters/AmiiboFilter.swift diff --git a/Sources/Filters/AmiiboFilter.swift b/Sources/Filters/AmiiboFilter.swift new file mode 100644 index 0000000..d6aae89 --- /dev/null +++ b/Sources/Filters/AmiiboFilter.swift @@ -0,0 +1,113 @@ +public struct AmiiboFilter { + + // MARK: Properties + + private let id: String? + private let head: String? + private let tail: String? + private let name: String? + private let type: String? + private let gameSeries: String? + private let amiiboSeries: String? + private let character: String? + private let showGames: Bool + private let showUsage: Bool + + // MARK: Initialisers + + public init( + id: String? = nil, + head: String? = nil, + tail: String? = nil, + name: String? = nil, + type: String? = nil, + gameSeries: String? = nil, + amiiboSeries: String? = nil, + character: String? = nil, + showGames: Bool = false, + showUsage: Bool = false + ) { + self.id = id + self.head = head + self.tail = tail + self.name = name + self.type = type + self.gameSeries = gameSeries + self.amiiboSeries = amiiboSeries + self.character = character + self.showGames = showGames + self.showUsage = showUsage + } + +} + +// MARK: - Filter + +extension AmiiboFilter: Filter { + + // MARK: Functions + + func makeParameters() -> [String : String?] { + var parameters: [String : String?] = [:] + + if let id { + parameters[.Key.id] = id + } + + if let head { + parameters[.Key.head] = head + } + + if let tail { + parameters[.Key.tail] = tail + } + + if let name { + parameters[.Key.name] = name + } + + if let type { + parameters[.Key.type] = type + } + + if let gameSeries { + parameters[.Key.gameSeries] = gameSeries + } + + if let amiiboSeries { + parameters[.Key.amiiboSeries] = amiiboSeries + } + + if let character { + parameters[.Key.character] = character + } + + if showGames { + parameters[.Key.showGames] = nil + } + + if showUsage { + parameters[.Key.showUsage] = nil + } + + return parameters + } + +} + +// MARK: - String+Key + +private extension String { + enum Key { + static let id = "id" + static let head = "head" + static let tail = "tail" + static let name = "name" + static let type = "type" + static let gameSeries = "gameSeries" + static let amiiboSeries = "amiiboSeries" + static let character = "character" + static let showGames = "showgames" + static let showUsage = "showusage" + } +} -- 2.47.1 From d1de1939e423e4281d19032c4b8ec201362f5ebb Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Wed, 19 Apr 2023 22:25:18 +0200 Subject: [PATCH 03/13] Implemented the KeyNameFilter filter and conformed it to the Filter protocol. --- Sources/Filters/KeyNameFilter.swift | 56 +++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 Sources/Filters/KeyNameFilter.swift diff --git a/Sources/Filters/KeyNameFilter.swift b/Sources/Filters/KeyNameFilter.swift new file mode 100644 index 0000000..b04e634 --- /dev/null +++ b/Sources/Filters/KeyNameFilter.swift @@ -0,0 +1,56 @@ +public struct KeyNameFilter { + + // MARK: Properties + + private let key: String? + private let name: String? + + // MARK: Initialisers + + public init( + key: String?, + name: String? + ) { + self.key = key + self.name = name + } + +} + +// MARK: - Filter + +extension KeyNameFilter: Filter { + + // MARK: Functions + + func makeParameters() -> [String : String?] { + var parameters: [String : String?] = [:] + + if let key { + parameters[.Key.key] = key + } + + if let name { + parameters[.Key.name] = name + } + + return parameters + } + +} + +// MARK: - Type aliases + +public typealias AmiiboSeriesFilter = KeyNameFilter +public typealias AmiiboTypeFilter = KeyNameFilter +public typealias CharacterFilter = KeyNameFilter +public typealias GameSeriesFilter = KeyNameFilter + +// MARK: - String+Key + +private extension String { + enum Key { + static let key = "key" + static let name = "name" + } +} -- 2.47.1 From 60c1bf55db57b10f3e73e0a68f77ae88600dd9d5 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Wed, 19 Apr 2023 22:26:23 +0200 Subject: [PATCH 04/13] Defined the Service public protocol. --- Sources/Filters/KeyNameFilter.swift | 4 ++-- Sources/Protocols/Service.swift | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 Sources/Protocols/Service.swift diff --git a/Sources/Filters/KeyNameFilter.swift b/Sources/Filters/KeyNameFilter.swift index b04e634..554033b 100644 --- a/Sources/Filters/KeyNameFilter.swift +++ b/Sources/Filters/KeyNameFilter.swift @@ -8,8 +8,8 @@ public struct KeyNameFilter { // MARK: Initialisers public init( - key: String?, - name: String? + key: String? = nil, + name: String? = nil ) { self.key = key self.name = name diff --git a/Sources/Protocols/Service.swift b/Sources/Protocols/Service.swift new file mode 100644 index 0000000..ab6fe6d --- /dev/null +++ b/Sources/Protocols/Service.swift @@ -0,0 +1,14 @@ +import Foundation + +protocol Service { + + // MARK: Functions + + func amiibos(filter: AmiiboFilter) async throws -> [Amiibo] + func amiiboSeries(filter: AmiiboSeriesFilter) async throws -> [AmiiboSeries] + func amiiboTypes(filter: AmiiboTypeFilter) async throws -> [AmiiboType] + func gameSeries(filter: GameSeriesFilter) async throws -> [GameSeries] + func characters(filter: CharacterFilter) async throws -> [Character] + func lastUpdated() async throws -> Date + +} -- 2.47.1 From 2dbf805870e28ab0c8c937b9dc988a5ad97f9fd8 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 20 Apr 2023 00:14:37 +0200 Subject: [PATCH 05/13] Implemented the Result model. --- Sources/Models/Result.swift | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 Sources/Models/Result.swift diff --git a/Sources/Models/Result.swift b/Sources/Models/Result.swift new file mode 100644 index 0000000..5ccf57e --- /dev/null +++ b/Sources/Models/Result.swift @@ -0,0 +1,27 @@ +struct Result { + let items: [Model] +} + +// MARK: - Decodable + +extension Result: Decodable { + + // MARK: Enumerations + + enum CodingKeys: String, CodingKey { + case items = "amiibo" + } + + // MARK: Initialisers + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + do { + self.items = try container.decode([Model].self, forKey: .items) + } catch { + self.items = [try container.decode(Model.self, forKey: .items)] + } + } + +} -- 2.47.1 From 68d7eef2030df2206a381ccfdee3d9e1bf744b64 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 20 Apr 2023 00:14:57 +0200 Subject: [PATCH 06/13] Implemented the AmiiboService service. --- Sources/Services/AmiiboService.swift | 87 ++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 Sources/Services/AmiiboService.swift diff --git a/Sources/Services/AmiiboService.swift b/Sources/Services/AmiiboService.swift new file mode 100644 index 0000000..624d4a8 --- /dev/null +++ b/Sources/Services/AmiiboService.swift @@ -0,0 +1,87 @@ +import Foundation + +public struct AmiiboService { + + // MARK: Properties + + private let client: AmiiboClient + + // MARK: Initialisers + + init(configuration: URLSessionConfiguration) { + self.client = .init(configuration: configuration) + } + +} + +// MARK: - Service + +extension AmiiboService: Service { + + // MARK: Functions + + public func amiibos( + filter: AmiiboFilter = .init() + ) async throws -> [Amiibo] { + client.setDateDecodingStrategy(.formatted(.dateOnly)) + + return try await client.request( + endpoint: GetAmiiboEndpoint(parameters: filter.makeParameters()), + as: Result.self + ).items + } + + public func amiiboSeries( + filter: AmiiboSeriesFilter = .init() + ) async throws -> [AmiiboSeries] { + client.setDateDecodingStrategy(.deferredToDate) + + return try await client.request( + endpoint: GetSeriesEndpoint(parameters: filter.makeParameters()), + as: Result.self + ).items + } + + public func amiiboTypes( + filter: AmiiboTypeFilter = .init() + ) async throws -> [AmiiboType] { + client.setDateDecodingStrategy(.deferredToDate) + + return try await client.request( + endpoint: GetTypeEndpoint(parameters: filter.makeParameters()), + as: Result.self + ).items + } + + public func gameSeries( + filter: GameSeriesFilter = .init() + ) async throws -> [GameSeries] { + client.setDateDecodingStrategy(.deferredToDate) + + return try await client.request( + endpoint: GetGameSeriesEndpoint(parameters: filter.makeParameters()), + as: Result.self + ).items + } + + public func characters( + filter: CharacterFilter = .init() + ) async throws -> [Character] { + client.setDateDecodingStrategy(.deferredToDate) + + return try await client.request( + endpoint: GetCharacterEndpoint(parameters: filter.makeParameters()), + as: Result.self + ).items + } + + public func lastUpdated() async throws -> Date { + client.setDateDecodingStrategy(.formatted(.dateAndTime)) + + return try await client.request( + endpoint: GetLastUpdatedEndpoint(), + as: LastUpdated.self + ).timestamp + } + +} -- 2.47.1 From 83950ceb6e60d029375c0f7fddb8dc9cbb7d9c48 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 20 Apr 2023 01:24:23 +0200 Subject: [PATCH 07/13] Fixed the path property of the GetAmiiboEndpoint endpoint. --- Sources/Endpoints/GetAmiiboEndpoint.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Endpoints/GetAmiiboEndpoint.swift b/Sources/Endpoints/GetAmiiboEndpoint.swift index 5d2a320..c192882 100644 --- a/Sources/Endpoints/GetAmiiboEndpoint.swift +++ b/Sources/Endpoints/GetAmiiboEndpoint.swift @@ -8,7 +8,7 @@ struct GetAmiiboEndpoint: Endpoint { let scheme: String = .Scheme.https let host: String = .Host.amiiboApi let port: Int? = nil - let path: String = .Path.type + let path: String = .Path.amiibo let parameters: Parameters let method: HTTPRequestMethod = .get let headers: [String : String] = [:] -- 2.47.1 From 55f1b4824eb2a74a638ceccbb935af62e44e5eff Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 20 Apr 2023 01:25:54 +0200 Subject: [PATCH 08/13] Made the "usage" property of the Amiibo.Game model optional. --- Sources/Models/AmiiboGame.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Models/AmiiboGame.swift b/Sources/Models/AmiiboGame.swift index 9abefcc..7b7fd73 100644 --- a/Sources/Models/AmiiboGame.swift +++ b/Sources/Models/AmiiboGame.swift @@ -2,7 +2,7 @@ extension Amiibo { public struct Game { public let identifiers: [String] public let name: String - public let usage: [Usage] + public let usage: [Usage]? } } -- 2.47.1 From 7d0e0e8f640cf8daf234b5ee8ccae341184e5aa1 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Fri, 21 Apr 2023 13:37:59 +0200 Subject: [PATCH 09/13] Replaced the "showGames" and "showUsage" flags in the AmiiboFilter filter with an enumeration. --- Sources/Filters/AmiiboFilter.swift | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/Sources/Filters/AmiiboFilter.swift b/Sources/Filters/AmiiboFilter.swift index d6aae89..72efa07 100644 --- a/Sources/Filters/AmiiboFilter.swift +++ b/Sources/Filters/AmiiboFilter.swift @@ -10,8 +10,7 @@ public struct AmiiboFilter { private let gameSeries: String? private let amiiboSeries: String? private let character: String? - private let showGames: Bool - private let showUsage: Bool + private let showExtras: ShowExtras // MARK: Initialisers @@ -24,8 +23,7 @@ public struct AmiiboFilter { gameSeries: String? = nil, amiiboSeries: String? = nil, character: String? = nil, - showGames: Bool = false, - showUsage: Bool = false + showExtras: ShowExtras = .none ) { self.id = id self.head = head @@ -35,8 +33,7 @@ public struct AmiiboFilter { self.gameSeries = gameSeries self.amiiboSeries = amiiboSeries self.character = character - self.showGames = showGames - self.showUsage = showUsage + self.showExtras = showExtras } } @@ -82,19 +79,28 @@ extension AmiiboFilter: Filter { parameters[.Key.character] = character } - if showGames { + switch showExtras { + case .games: parameters[.Key.showGames] = nil - } - - if showUsage { + case .usage: parameters[.Key.showUsage] = nil + default: + break } - + return parameters } } +// MARK: - Enumerations + +public enum ShowExtras { + case none + case games + case usage +} + // MARK: - String+Key private extension String { -- 2.47.1 From 102302935fc1eb8a74243dee322bbd898c34f122 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Fri, 21 Apr 2023 13:40:47 +0200 Subject: [PATCH 10/13] Made the Game, Release and Usage model independent from the Amiibo model. --- Sources/Models/AmiiboGame.swift | 17 ----------------- Sources/Models/AmiiboRelease.swift | 21 --------------------- Sources/Models/AmiiboUsage.swift | 15 --------------- Sources/Models/Game.swift | 15 +++++++++++++++ Sources/Models/Release.swift | 19 +++++++++++++++++++ Sources/Models/Usage.swift | 13 +++++++++++++ 6 files changed, 47 insertions(+), 53 deletions(-) delete mode 100644 Sources/Models/AmiiboGame.swift delete mode 100644 Sources/Models/AmiiboRelease.swift delete mode 100644 Sources/Models/AmiiboUsage.swift create mode 100644 Sources/Models/Game.swift create mode 100644 Sources/Models/Release.swift create mode 100644 Sources/Models/Usage.swift diff --git a/Sources/Models/AmiiboGame.swift b/Sources/Models/AmiiboGame.swift deleted file mode 100644 index 7b7fd73..0000000 --- a/Sources/Models/AmiiboGame.swift +++ /dev/null @@ -1,17 +0,0 @@ -extension Amiibo { - public struct Game { - public let identifiers: [String] - public let name: String - public let usage: [Usage]? - } -} - -// MARK: - Decodable - -extension Amiibo.Game: Decodable { - enum CodingKeys: String, CodingKey { - case identifiers = "gameID" - case name = "gameName" - case usage = "amiiboUsage" - } -} diff --git a/Sources/Models/AmiiboRelease.swift b/Sources/Models/AmiiboRelease.swift deleted file mode 100644 index a53c7a1..0000000 --- a/Sources/Models/AmiiboRelease.swift +++ /dev/null @@ -1,21 +0,0 @@ -import Foundation - -extension Amiibo { - public struct Release { - public let australia: Date? - public let europe: Date? - public let japan: Date? - public let america: Date? - } -} - -// MARK: - Decodable - -extension Amiibo.Release: Decodable { - enum CodingKeys: String, CodingKey { - case australia = "au" - case europe = "eu" - case japan = "jp" - case america = "na" - } -} diff --git a/Sources/Models/AmiiboUsage.swift b/Sources/Models/AmiiboUsage.swift deleted file mode 100644 index ee07b16..0000000 --- a/Sources/Models/AmiiboUsage.swift +++ /dev/null @@ -1,15 +0,0 @@ -extension Amiibo { - public struct Usage { - public let explanation: String - public let isWritable: Bool - } -} - -// MARK: - Decodable - -extension Amiibo.Usage: Decodable { - enum CodingKeys: String, CodingKey { - case explanation = "Usage" - case isWritable = "write" - } -} diff --git a/Sources/Models/Game.swift b/Sources/Models/Game.swift new file mode 100644 index 0000000..304267d --- /dev/null +++ b/Sources/Models/Game.swift @@ -0,0 +1,15 @@ +public struct Game { + public let ids: [String] + public let name: String + public let usage: [Usage]? +} + +// MARK: - Decodable + +extension Game: Decodable { + enum CodingKeys: String, CodingKey { + case ids = "gameID" + case name = "gameName" + case usage = "amiiboUsage" + } +} diff --git a/Sources/Models/Release.swift b/Sources/Models/Release.swift new file mode 100644 index 0000000..b3b672e --- /dev/null +++ b/Sources/Models/Release.swift @@ -0,0 +1,19 @@ +import Foundation + +public struct Release { + public let australia: Date? + public let europe: Date? + public let japan: Date? + public let america: Date? +} + +// MARK: - Decodable + +extension Release: Decodable { + enum CodingKeys: String, CodingKey { + case australia = "au" + case europe = "eu" + case japan = "jp" + case america = "na" + } +} diff --git a/Sources/Models/Usage.swift b/Sources/Models/Usage.swift new file mode 100644 index 0000000..7d54439 --- /dev/null +++ b/Sources/Models/Usage.swift @@ -0,0 +1,13 @@ +public struct Usage { + public let explanation: String + public let isWritable: Bool +} + +// MARK: - Decodable + +extension Usage: Decodable { + enum CodingKeys: String, CodingKey { + case explanation = "Usage" + case isWritable = "write" + } +} -- 2.47.1 From 4292885031ece4f837b50935457afce99e5635bb Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Fri, 21 Apr 2023 18:15:15 +0200 Subject: [PATCH 11/13] Implemented convenience initialisers for the GetAmiiboEndpoint, GetCharacterEndpoint, GetGameSeriesEndpoint, GetSeriesEndpoint and the GetTypeEndpoint endpoint extensions. --- Tests/Helpers/{ => Endpoints}/TestEndpoint.swift | 0 Tests/Helpers/Extensions/GetAmiiboEndpoint+Init.swift | 11 +++++++++++ .../Extensions/GetCharacterEndpoint+Init.swift | 11 +++++++++++ .../Extensions/GetGameSeriesEndpoint+Init.swift | 11 +++++++++++ Tests/Helpers/Extensions/GetSeriesEndpoint+Init.swift | 11 +++++++++++ Tests/Helpers/Extensions/GetTypeEndpoint+Init.swift | 11 +++++++++++ .../{ => Extensions}/MockURLRequest+Init.swift | 0 Tests/Helpers/{ => Models}/TestModel.swift | 0 8 files changed, 55 insertions(+) rename Tests/Helpers/{ => Endpoints}/TestEndpoint.swift (100%) create mode 100644 Tests/Helpers/Extensions/GetAmiiboEndpoint+Init.swift create mode 100644 Tests/Helpers/Extensions/GetCharacterEndpoint+Init.swift create mode 100644 Tests/Helpers/Extensions/GetGameSeriesEndpoint+Init.swift create mode 100644 Tests/Helpers/Extensions/GetSeriesEndpoint+Init.swift create mode 100644 Tests/Helpers/Extensions/GetTypeEndpoint+Init.swift rename Tests/Helpers/{ => Extensions}/MockURLRequest+Init.swift (100%) rename Tests/Helpers/{ => Models}/TestModel.swift (100%) diff --git a/Tests/Helpers/TestEndpoint.swift b/Tests/Helpers/Endpoints/TestEndpoint.swift similarity index 100% rename from Tests/Helpers/TestEndpoint.swift rename to Tests/Helpers/Endpoints/TestEndpoint.swift diff --git a/Tests/Helpers/Extensions/GetAmiiboEndpoint+Init.swift b/Tests/Helpers/Extensions/GetAmiiboEndpoint+Init.swift new file mode 100644 index 0000000..1e35d74 --- /dev/null +++ b/Tests/Helpers/Extensions/GetAmiiboEndpoint+Init.swift @@ -0,0 +1,11 @@ +@testable import AmiiboService + +extension GetAmiiboEndpoint { + + // MARK: Initialisers + + init() { + self.init(parameters: .init()) + } + +} diff --git a/Tests/Helpers/Extensions/GetCharacterEndpoint+Init.swift b/Tests/Helpers/Extensions/GetCharacterEndpoint+Init.swift new file mode 100644 index 0000000..9efb7ca --- /dev/null +++ b/Tests/Helpers/Extensions/GetCharacterEndpoint+Init.swift @@ -0,0 +1,11 @@ +@testable import AmiiboService + +extension GetCharacterEndpoint { + + // MARK: Initialisers + + init() { + self.init(parameters: .init()) + } + +} diff --git a/Tests/Helpers/Extensions/GetGameSeriesEndpoint+Init.swift b/Tests/Helpers/Extensions/GetGameSeriesEndpoint+Init.swift new file mode 100644 index 0000000..dedd9dc --- /dev/null +++ b/Tests/Helpers/Extensions/GetGameSeriesEndpoint+Init.swift @@ -0,0 +1,11 @@ +@testable import AmiiboService + +extension GetGameSeriesEndpoint { + + // MARK: Initialisers + + init() { + self.init(parameters: .init()) + } + +} diff --git a/Tests/Helpers/Extensions/GetSeriesEndpoint+Init.swift b/Tests/Helpers/Extensions/GetSeriesEndpoint+Init.swift new file mode 100644 index 0000000..9360fc0 --- /dev/null +++ b/Tests/Helpers/Extensions/GetSeriesEndpoint+Init.swift @@ -0,0 +1,11 @@ +@testable import AmiiboService + +extension GetSeriesEndpoint { + + // MARK: Initialisers + + init() { + self.init(parameters: .init()) + } + +} diff --git a/Tests/Helpers/Extensions/GetTypeEndpoint+Init.swift b/Tests/Helpers/Extensions/GetTypeEndpoint+Init.swift new file mode 100644 index 0000000..20329d1 --- /dev/null +++ b/Tests/Helpers/Extensions/GetTypeEndpoint+Init.swift @@ -0,0 +1,11 @@ +@testable import AmiiboService + +extension GetTypeEndpoint { + + // MARK: Initialisers + + init() { + self.init(parameters: .init()) + } + +} diff --git a/Tests/Helpers/MockURLRequest+Init.swift b/Tests/Helpers/Extensions/MockURLRequest+Init.swift similarity index 100% rename from Tests/Helpers/MockURLRequest+Init.swift rename to Tests/Helpers/Extensions/MockURLRequest+Init.swift diff --git a/Tests/Helpers/TestModel.swift b/Tests/Helpers/Models/TestModel.swift similarity index 100% rename from Tests/Helpers/TestModel.swift rename to Tests/Helpers/Models/TestModel.swift -- 2.47.1 From a717592fa66e85fe1d4bccdde31aaef18c4b9899 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Fri, 21 Apr 2023 18:16:38 +0200 Subject: [PATCH 12/13] Defined some seeds to use in test cases in the String+Seed and the Data+Seed extensions. --- Sources/Services/AmiiboService.swift | 22 +++++++------- Tests/Clients/AmiiboClientTests.swift | 10 +++---- Tests/Helpers/Data+Seed.swift | 10 ------- Tests/Helpers/Extensions/Data+Seed.swift | 31 +++++++++++++++++++ Tests/Helpers/Extensions/String+Seed.swift | 35 ++++++++++++++++++++++ 5 files changed, 82 insertions(+), 26 deletions(-) delete mode 100644 Tests/Helpers/Data+Seed.swift create mode 100644 Tests/Helpers/Extensions/Data+Seed.swift create mode 100644 Tests/Helpers/Extensions/String+Seed.swift diff --git a/Sources/Services/AmiiboService.swift b/Sources/Services/AmiiboService.swift index 624d4a8..de83f3b 100644 --- a/Sources/Services/AmiiboService.swift +++ b/Sources/Services/AmiiboService.swift @@ -53,17 +53,6 @@ extension AmiiboService: Service { ).items } - public func gameSeries( - filter: GameSeriesFilter = .init() - ) async throws -> [GameSeries] { - client.setDateDecodingStrategy(.deferredToDate) - - return try await client.request( - endpoint: GetGameSeriesEndpoint(parameters: filter.makeParameters()), - as: Result.self - ).items - } - public func characters( filter: CharacterFilter = .init() ) async throws -> [Character] { @@ -75,6 +64,17 @@ extension AmiiboService: Service { ).items } + public func gameSeries( + filter: GameSeriesFilter = .init() + ) async throws -> [GameSeries] { + client.setDateDecodingStrategy(.deferredToDate) + + return try await client.request( + endpoint: GetGameSeriesEndpoint(parameters: filter.makeParameters()), + as: Result.self + ).items + } + public func lastUpdated() async throws -> Date { client.setDateDecodingStrategy(.formatted(.dateAndTime)) diff --git a/Tests/Clients/AmiiboClientTests.swift b/Tests/Clients/AmiiboClientTests.swift index 1c20243..0c49a07 100644 --- a/Tests/Clients/AmiiboClientTests.swift +++ b/Tests/Clients/AmiiboClientTests.swift @@ -38,7 +38,7 @@ final class AmiiboClientTests: XCTestCase { MockURLProtocol.mockData[.init(url: url)] = .init( status: .ok, - data: .Seed.dataWithoutTimestamp + data: .Client.Seed.dataWithoutTimestamp ) // WHEN @@ -58,7 +58,7 @@ final class AmiiboClientTests: XCTestCase { MockURLProtocol.mockData[.init(url: url)] = .init( status: .ok, - data: .Seed.dataWithDateAndTime + data: .Client.Seed.dataWithDateAndTime ) client.setDateDecodingStrategy(.formatted(.dateAndTime)) @@ -80,7 +80,7 @@ final class AmiiboClientTests: XCTestCase { MockURLProtocol.mockData[.init(url: url)] = .init( status: .ok, - data: .Seed.dataUnrelated + data: .Client.Seed.dataUnrelated ) // WHEN & THEN @@ -102,7 +102,7 @@ final class AmiiboClientTests: XCTestCase { MockURLProtocol.mockData[.init(url: url)] = .init( status: .ok, - data: .Seed.dataWithDateAndTime + data: .Client.Seed.dataWithDateAndTime ) client.setDateDecodingStrategy(.formatted(.dateOnly)) @@ -126,7 +126,7 @@ final class AmiiboClientTests: XCTestCase { MockURLProtocol.mockData[.init(url: url)] = .init( status: .notFound, - data: .Seed.dataWithoutTimestamp + data: .Client.Seed.dataWithoutTimestamp ) // WHEN & THEN diff --git a/Tests/Helpers/Data+Seed.swift b/Tests/Helpers/Data+Seed.swift deleted file mode 100644 index 91b4c80..0000000 --- a/Tests/Helpers/Data+Seed.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation - -extension Data { - enum Seed { - static let dataUnrelated = "{\"something\":\"Something goes in here...\"}".data(using: .utf8) - static let dataWithoutTimestamp = "{\"timestamp\":null}".data(using: .utf8) - static let dataWithDateOnly = "{\"timestamp\":\"2023-03-23\"}".data(using: .utf8) - static let dataWithDateAndTime = "{\"timestamp\":\"2023-03-23T13:11:20.382254\"}".data(using: .utf8) - } -} diff --git a/Tests/Helpers/Extensions/Data+Seed.swift b/Tests/Helpers/Extensions/Data+Seed.swift new file mode 100644 index 0000000..77d9cbb --- /dev/null +++ b/Tests/Helpers/Extensions/Data+Seed.swift @@ -0,0 +1,31 @@ +import Foundation + +extension Data { + enum Client { + enum Seed { + static let dataUnrelated = "{\"something\":\"Something goes in here...\"}".data(using: .utf8) + static let dataWithoutTimestamp = "{\"timestamp\":null}".data(using: .utf8) + static let dataWithDateOnly = "{\"timestamp\":\"2023-03-23\"}".data(using: .utf8) + static let dataWithDateAndTime = "{\"timestamp\":\"2023-03-23T13:11:20.382254\"}".data(using: .utf8) + } + } + + enum Service { + static let amiibos = String.Amiibo.withoutGameOrUsage.data(using: .utf8) + static let amiibosWithGames = String.Amiibo.withGames.data(using: .utf8) + static let amiibosWithUsage = String.Amiibo.withUsage.data(using: .utf8) + static let amiiboWithBadFormattedDates = String.Amiibo.withBadFormattedReleaseDates.data(using: .utf8) + static let amiiboWithMissingFields = String.Amiibo.withMissingFields.data(using: .utf8) + static let amiiboSeries = String.AmiiboSeries.all.data(using: .utf8) + static let amiiboSeriesWithMissingFields = String.AmiiboSeries.withMissingFields.data(using: .utf8) + static let amiiboTypes = String.AmiiboType.all.data(using: .utf8) + static let amiiboTypesWithMissingFields = String.AmiiboType.withMissingFields.data(using: .utf8) + static let characters = String.Character.all.data(using: .utf8) + static let charactersWithMissingFields = String.Character.withMissingFields.data(using: .utf8) + static let gameSeries = String.GameSeries.all.data(using: .utf8) + static let gameSeriesWithMissingFields = String.GameSeries.withMissingFields.data(using: .utf8) + static let lastUpdated = String.LastUpdated.all.data(using: .utf8) + static let lastUpdatedWithBadFormattedDate = String.LastUpdated.withBadFormattedDate.data(using: .utf8) + static let lastUpdatedWithBadInfo = String.LastUpdated.withBadInfo.data(using: .utf8) + } +} diff --git a/Tests/Helpers/Extensions/String+Seed.swift b/Tests/Helpers/Extensions/String+Seed.swift new file mode 100644 index 0000000..e03a9bc --- /dev/null +++ b/Tests/Helpers/Extensions/String+Seed.swift @@ -0,0 +1,35 @@ +extension String { + enum Amiibo { + static let withoutGameOrUsage = "{\"amiibo\":[{\"amiiboSeries\":\"Super Smash Bros.\",\"character\":\"Mario\",\"gameSeries\":\"Super Mario\",\"head\":\"00000000\",\"image\":\"https://raw.githubusercontent.com/N3evin/AmiiboAPI/master/images/icon_00000000-00000002.png\",\"name\":\"Mario\",\"release\":{\"au\":\"2014-11-29\",\"eu\":\"2014-11-28\",\"jp\":\"2014-12-06\",\"na\":\"2014-11-21\"},\"tail\":\"00000002\",\"type\":\"Figure\"},{\"amiiboSeries\":\"Super Mario Bros.\",\"character\":\"Mario\",\"gameSeries\":\"Super Mario\",\"head\":\"00000000\",\"image\":\"https://raw.githubusercontent.com/N3evin/AmiiboAPI/master/images/icon_00000000-00340102.png\",\"name\":\"Mario\",\"release\":{\"au\":\"2015-03-21\",\"eu\":\"2015-03-20\",\"jp\":\"2015-03-12\",\"na\":\"2015-03-20\"},\"tail\":\"00340102\",\"type\":\"Figure\"}]}" + static let withGames = "{\"amiibo\":[{\"amiiboSeries\":\"Super Smash Bros.\",\"character\":\"Mario\",\"gameSeries\":\"Super Mario\",\"games3DS\":[{\"gameID\":[\"0004000000064900\",\"000400000006CC00\",\"000400000015A300\",\"000400000015C000\"],\"gameName\":\"Ace Combat Assault Horizon Legacy+\"},{\"gameID\":[\"0004000000163000\",\"0004000000162F00\"],\"gameName\":\"Chibi-Robo! Zip Lash\"},{\"gameID\":[\"0004000000189600\",\"0004000000183600\",\"0004000000189800\"],\"gameName\":\"Kirby: Planet Robobot\"},{\"gameID\":[\"0004000000076500\",\"0004000000055F00\",\"00040000000D0000\",\"00040000001D1A00\",\"00040000001D1900\"],\"gameName\":\"Luigi's Mansion\"},{\"gameID\":[\"00040000001D1500\",\"00040000001D1400\"],\"gameName\":\"Mario & Luigi: Bowser's Inside Story + Bowser Jr.'s Journey\"},{\"gameID\":[\"0004000000132800\",\"0004000000132700\",\"000400000018A100\"],\"gameName\":\"Mario & Luigi: Paper Jam\"},{\"gameID\":[\"00040000001B9000\",\"00040000001B8F00\"],\"gameName\":\"Mario & Luigi: Superstar Saga + Bowser's Minions\"},{\"gameID\":[\"000400000017E300\",\"000400000017E200\",\"0004000000192400\"],\"gameName\":\"Mario & Sonic at the Rio 2016 Olympic Games\"},{\"gameID\":[\"000400000019BE00\",\"0004000000193900\",\"000400000019BD00\"],\"gameName\":\"Mario Party: Star Rush\"},{\"gameID\":[\"00040000001C4E00\",\"00040000001C4D00\"],\"gameName\":\"Mario Party: The Top 100\"},{\"gameID\":[\"0004000000175300\"],\"gameName\":\"Metroid Prime: Blast Ball\"},{\"gameID\":[\"000400000016E300\",\"000400000016CE00\",\"0004000000175200\"],\"gameName\":\"Metroid Prime: Federation Force\"},{\"gameID\":[\"0004000000178800\",\"00040000001B4F00\",\"00040000001B4E00\"],\"gameName\":\"Miitopia\"},{\"gameID\":[\"000400000016C300\",\"000400000016C200\"],\"gameName\":\"Mini Mario & Friends amiibo Challenge\"},{\"gameID\":[\"0004000000144400\"],\"gameName\":\"One Piece: Super Grand Battle! X\"},{\"gameID\":[\"0004000000187E00\"],\"gameName\":\"Picross 3D Round 2\"},{\"gameID\":[\"0004000000196500\"],\"gameName\":\"Style Savvy: Fashion Forward\"},{\"gameID\":[\"00040000001C2500\"],\"gameName\":\"Style Savvy: Styling Star\"},{\"gameID\":[\"00040000000EE000\",\"00040000000EDF00\"],\"gameName\":\"Super Smash Bros. for Nintendo 3DS\"},{\"gameID\":[\"00040000001D1C00\"],\"gameName\":\"WarioWare Gold\"},{\"gameID\":[\"00040000001A4200\",\"00040000001A4100\",\"00040000001B6C00\",\"00040000001B6D00\"],\"gameName\":\"Yoshi's Woolly World\"}],\"gamesSwitch\":[{\"gameID\":[\"010067300059A000\"],\"gameName\":\"Mario + Rabbids: Kingdom Battle\"},{\"gameID\":[\"0100152000022000\"],\"gameName\":\"Mario Kart 8 Deluxe\"},{\"gameID\":[\"01003DA010E8A000\"],\"gameName\":\"Miitopia\"},{\"gameID\":[\"010028600EBDA000\"],\"gameName\":\"Super Mario 3D World + Bowser's Fury\"},{\"gameID\":[\"0100000000010000\"],\"gameName\":\"Super Mario Odyssey\"},{\"gameID\":[\"010036B0034E4000\"],\"gameName\":\"Super Mario Party\"},{\"gameID\":[\"01006A800016E000\"],\"gameName\":\"Super Smash Bros. Ultimate\"},{\"gameID\":[\"01006000040C2000\"],\"gameName\":\"Yoshi's Crafted World\"}],\"gamesWiiU\":[{\"gameID\":[\"0005000010190300\",\"00050000101E5400\",\"00050000101E5300\"],\"gameName\":\"Mario & Sonic at the Rio 2016 Olympic Games\"},{\"gameID\":[\"000500001010ED00\",\"000500001010EC00\",\"000500001010EB00\"],\"gameName\":\"Mario Kart 8\"},{\"gameID\":[\"0005000010162E00\",\"0005000010162D00\",\"0005000010161F00\"],\"gameName\":\"Mario Party 10\"},{\"gameID\":[\"00050000101A3600\",\"00050000101A3500\",\"0005000010199000\"],\"gameName\":\"Mario Tennis: Ultra Smash\"},{\"gameID\":[\"00050000101C6300\",\"00050000101C6200\",\"00050000101C6100\"],\"gameName\":\"Mini Mario & Friends amiibo Challenge\"},{\"gameID\":[\"000500001018DD00\",\"000500001018DC00\",\"000500001018DB00\"],\"gameName\":\"Super Mario Maker\"},{\"gameID\":[\"0005000010145000\",\"0005000010144F00\",\"0005000010110E00\"],\"gameName\":\"Super Smash Bros. for Wii U\"},{\"gameID\":[\"0005000010131F00\"],\"gameName\":\"Yoshi's Woolly World\"}],\"head\":\"00000000\",\"image\":\"https://raw.githubusercontent.com/N3evin/AmiiboAPI/master/images/icon_00000000-00000002.png\",\"name\":\"Mario\",\"release\":{\"au\":\"2014-11-29\",\"eu\":\"2014-11-28\",\"jp\":\"2014-12-06\",\"na\":\"2014-11-21\"},\"tail\":\"00000002\",\"type\":\"Figure\"},{\"amiiboSeries\":\"Super Mario Bros.\",\"character\":\"Mario\",\"gameSeries\":\"Super Mario\",\"games3DS\":[{\"gameID\":[\"0004000000064900\",\"000400000006CC00\",\"000400000015A300\",\"000400000015C000\"],\"gameName\":\"Ace Combat Assault Horizon Legacy+\"},{\"gameID\":[\"0004000000163000\",\"0004000000162F00\"],\"gameName\":\"Chibi-Robo! Zip Lash\"},{\"gameID\":[\"00040000001A9200\",\"00040000001AF800\",\"00040000001AFA00\",\"00040000001C5900\"],\"gameName\":\"Hey! Pikmin\"},{\"gameID\":[\"0004000000189600\",\"0004000000183600\",\"0004000000189800\"],\"gameName\":\"Kirby: Planet Robobot\"},{\"gameID\":[\"0004000000076500\",\"0004000000055F00\",\"00040000000D0000\",\"00040000001D1A00\",\"00040000001D1900\"],\"gameName\":\"Luigi's Mansion\"},{\"gameID\":[\"00040000001D1500\",\"00040000001D1400\"],\"gameName\":\"Mario & Luigi: Bowser's Inside Story + Bowser Jr.'s Journey\"},{\"gameID\":[\"0004000000132800\",\"0004000000132700\",\"000400000018A100\"],\"gameName\":\"Mario & Luigi: Paper Jam\"},{\"gameID\":[\"00040000001B9000\",\"00040000001B8F00\"],\"gameName\":\"Mario & Luigi: Superstar Saga + Bowser's Minions\"},{\"gameID\":[\"000400000017E300\",\"000400000017E200\",\"0004000000192400\"],\"gameName\":\"Mario & Sonic at the Rio 2016 Olympic Games\"},{\"gameID\":[\"000400000019BE00\",\"0004000000193900\",\"000400000019BD00\"],\"gameName\":\"Mario Party: Star Rush\"},{\"gameID\":[\"00040000001C4E00\",\"00040000001C4D00\"],\"gameName\":\"Mario Party: The Top 100\"},{\"gameID\":[\"0004000000175300\"],\"gameName\":\"Metroid Prime: Blast Ball\"},{\"gameID\":[\"000400000016E300\",\"000400000016CE00\",\"0004000000175200\"],\"gameName\":\"Metroid Prime: Federation Force\"},{\"gameID\":[\"0004000000178800\",\"00040000001B4F00\",\"00040000001B4E00\"],\"gameName\":\"Miitopia\"},{\"gameID\":[\"000400000016C300\",\"000400000016C200\"],\"gameName\":\"Mini Mario & Friends amiibo Challenge\"},{\"gameID\":[\"0004000000144400\"],\"gameName\":\"One Piece: Super Grand Battle! X\"},{\"gameID\":[\"0004000000187E00\"],\"gameName\":\"Picross 3D Round 2\"},{\"gameID\":[\"0004000000196500\"],\"gameName\":\"Style Savvy: Fashion Forward\"},{\"gameID\":[\"00040000001C2500\"],\"gameName\":\"Style Savvy: Styling Star\"},{\"gameID\":[\"00040000000EE000\",\"00040000000EDF00\"],\"gameName\":\"Super Smash Bros. for Nintendo 3DS\"},{\"gameID\":[\"00040000001D1C00\"],\"gameName\":\"WarioWare Gold\"},{\"gameID\":[\"00040000001A4200\",\"00040000001A4100\",\"00040000001B6C00\",\"00040000001B6D00\"],\"gameName\":\"Yoshi's Woolly World\"}],\"gamesSwitch\":[{\"gameID\":[\"01007EF00399C000\"],\"gameName\":\"Conga Master Party!\"},{\"gameID\":[\"010067300059A000\"],\"gameName\":\"Mario + Rabbids: Kingdom Battle\"},{\"gameID\":[\"0100152000022000\"],\"gameName\":\"Mario Kart 8 Deluxe\"},{\"gameID\":[\"01003DA010E8A000\"],\"gameName\":\"Miitopia\"},{\"gameID\":[\"010028600EBDA000\"],\"gameName\":\"Super Mario 3D World + Bowser's Fury\"},{\"gameID\":[\"0100000000010000\"],\"gameName\":\"Super Mario Odyssey\"},{\"gameID\":[\"010036B0034E4000\"],\"gameName\":\"Super Mario Party\"},{\"gameID\":[\"01006A800016E000\"],\"gameName\":\"Super Smash Bros. Ultimate\"},{\"gameID\":[\"01006000040C2000\"],\"gameName\":\"Yoshi's Crafted World\"}],\"gamesWiiU\":[{\"gameID\":[\"0005000010190300\",\"00050000101E5400\",\"00050000101E5300\"],\"gameName\":\"Mario & Sonic at the Rio 2016 Olympic Games\"},{\"gameID\":[\"000500001010ED00\",\"000500001010EC00\",\"000500001010EB00\"],\"gameName\":\"Mario Kart 8\"},{\"gameID\":[\"0005000010162E00\",\"0005000010162D00\",\"0005000010161F00\"],\"gameName\":\"Mario Party 10\"},{\"gameID\":[\"00050000101A3600\",\"00050000101A3500\",\"0005000010199000\"],\"gameName\":\"Mario Tennis: Ultra Smash\"},{\"gameID\":[\"00050000101C6300\",\"00050000101C6200\",\"00050000101C6100\"],\"gameName\":\"Mini Mario & Friends amiibo Challenge\"},{\"gameID\":[\"000500001018DD00\",\"000500001018DC00\",\"000500001018DB00\"],\"gameName\":\"Super Mario Maker\"},{\"gameID\":[\"0005000010145000\",\"0005000010144F00\",\"0005000010110E00\"],\"gameName\":\"Super Smash Bros. for Wii U\"},{\"gameID\":[\"0005000010131F00\"],\"gameName\":\"Yoshi's Woolly World\"}],\"head\":\"00000000\",\"image\":\"https://raw.githubusercontent.com/N3evin/AmiiboAPI/master/images/icon_00000000-00340102.png\",\"name\":\"Mario\",\"release\":{\"au\":\"2015-03-21\",\"eu\":\"2015-03-20\",\"jp\":\"2015-03-12\",\"na\":\"2015-03-20\"},\"tail\":\"00340102\",\"type\":\"Figure\"}]}" + static let withUsage = "{\"amiibo\":[{\"amiiboSeries\":\"Super Smash Bros.\",\"character\":\"Mario\",\"gameSeries\":\"Super Mario\",\"games3DS\":[{\"amiiboUsage\":[{\"Usage\":\"Unlock character-themed aircraft early\",\"write\":false}],\"gameID\":[\"0004000000064900\",\"000400000006CC00\",\"000400000015A300\",\"000400000015C000\"],\"gameName\":\"Ace Combat Assault Horizon Legacy+\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock figure in the pose of the character (requires Chibi-Robo amiibo)\",\"write\":false}],\"gameID\":[\"0004000000163000\",\"0004000000162F00\"],\"gameName\":\"Chibi-Robo! Zip Lash\"},{\"amiiboUsage\":[{\"Usage\":\"Give Kirby a Copy ability based on the character's abilities and a health item\",\"write\":false}],\"gameID\":[\"0004000000189600\",\"0004000000183600\",\"0004000000189800\"],\"gameName\":\"Kirby: Planet Robobot\"},{\"amiiboUsage\":[{\"Usage\":\"Mushrooms you find will be health-restoring Super Mushrooms instead of Poison Mushrooms\",\"write\":false}],\"gameID\":[\"0004000000076500\",\"0004000000055F00\",\"00040000000D0000\",\"00040000001D1A00\",\"00040000001D1900\"],\"gameName\":\"Luigi's Mansion\"},{\"amiiboUsage\":[{\"Usage\":\"Receive a helpful item\",\"write\":false}],\"gameID\":[\"00040000001D1500\",\"00040000001D1400\"],\"gameName\":\"Mario & Luigi: Bowser's Inside Story + Bowser Jr.'s Journey\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock character cards to help you in battle\",\"write\":true}],\"gameID\":[\"0004000000132800\",\"0004000000132700\",\"000400000018A100\"],\"gameName\":\"Mario & Luigi: Paper Jam\"},{\"amiiboUsage\":[{\"Usage\":\"Receive a stamp and equipment for the main quest\",\"write\":false}],\"gameID\":[\"00040000001B9000\",\"00040000001B8F00\"],\"gameName\":\"Mario & Luigi: Superstar Saga + Bowser's Minions\"},{\"amiiboUsage\":[{\"Usage\":\"Boost power of the Mario Mii suit for the day\",\"write\":false}],\"gameID\":[\"000400000017E300\",\"000400000017E200\",\"0004000000192400\"],\"gameName\":\"Mario & Sonic at the Rio 2016 Olympic Games\"},{\"amiiboUsage\":[{\"Usage\":\"Play as or receive bonus from this character, depending on the mode\",\"write\":false}],\"gameID\":[\"000400000019BE00\",\"0004000000193900\",\"000400000019BD00\"],\"gameName\":\"Mario Party: Star Rush\"},{\"amiiboUsage\":[{\"Usage\":\"Minigame Island: If you land on an amiibo space, use to receive coins / Use to revive your character once\",\"write\":false}],\"gameID\":[\"00040000001C4E00\",\"00040000001C4D00\"],\"gameName\":\"Mario Party: The Top 100\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock an armor paint job based on the character\",\"write\":false}],\"gameID\":[\"0004000000175300\"],\"gameName\":\"Metroid Prime: Blast Ball\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock an armor paint job based on the character for use in Blast Ball\",\"write\":false}],\"gameID\":[\"000400000016E300\",\"000400000016CE00\",\"0004000000175200\"],\"gameName\":\"Metroid Prime: Federation Force\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-based costume\",\"write\":false}],\"gameID\":[\"0004000000178800\",\"00040000001B4F00\",\"00040000001B4E00\"],\"gameName\":\"Miitopia\"},{\"amiiboUsage\":[{\"Usage\":\"Play stages as a Mini version of this character, with a unique special ability, and unlock character-specific stages\",\"write\":false}],\"gameID\":[\"000400000016C300\",\"000400000016C200\"],\"gameName\":\"Mini Mario & Friends amiibo Challenge\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-based costume with special abilities\",\"write\":false}],\"gameID\":[\"0004000000144400\"],\"gameName\":\"One Piece: Super Grand Battle! X\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-based puzzle\",\"write\":false}],\"gameID\":[\"0004000000187E00\"],\"gameName\":\"Picross 3D Round 2\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-themed accessory\",\"write\":false}],\"gameID\":[\"0004000000196500\"],\"gameName\":\"Style Savvy: Fashion Forward\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-themed pattern\",\"write\":false}],\"gameID\":[\"00040000001C2500\"],\"gameName\":\"Style Savvy: Styling Star\"},{\"amiiboUsage\":[{\"Usage\":\"Battle and train up a computer-controlled Figure Player of the character\",\"write\":true}],\"gameID\":[\"00040000000EE000\",\"00040000000EDF00\"],\"gameName\":\"Super Smash Bros. for Nintendo 3DS\"},{\"amiiboUsage\":[{\"Usage\":\"Receive a sketch of the character from Wario which can be exchanged for in-game coins\",\"write\":false}],\"gameID\":[\"00040000001D1C00\"],\"gameName\":\"WarioWare Gold\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-based Yoshi yarn pattern\",\"write\":false}],\"gameID\":[\"00040000001A4200\",\"00040000001A4100\",\"00040000001B6C00\",\"00040000001B6D00\"],\"gameName\":\"Yoshi's Woolly World\"}],\"gamesSwitch\":[{\"amiiboUsage\":[{\"Usage\":\"Unlock an amiibo-exclusive weapon for the character and their Rabbid counterpart\",\"write\":false}],\"gameID\":[\"010067300059A000\"],\"gameName\":\"Mario + Rabbids: Kingdom Battle\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-themed Mii racing suit\",\"write\":false}],\"gameID\":[\"0100152000022000\"],\"gameName\":\"Mario Kart 8 Deluxe\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-based costume\",\"write\":false}],\"gameID\":[\"01003DA010E8A000\"],\"gameName\":\"Miitopia\"},{\"amiiboUsage\":[{\"Usage\":\"Receive a particular power-up depending on the character\",\"write\":false}],\"gameID\":[\"010028600EBDA000\"],\"gameName\":\"Super Mario 3D World + Bowser's Fury\"},{\"amiiboUsage\":[{\"Usage\":\"Receive a character-based costume\",\"write\":false},{\"Usage\":\"Gain temporary invincibility\",\"write\":false}],\"gameID\":[\"0100000000010000\"],\"gameName\":\"Super Mario Odyssey\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock the character's shiny sticker\",\"write\":false}],\"gameID\":[\"010036B0034E4000\"],\"gameName\":\"Super Mario Party\"},{\"amiiboUsage\":[{\"Usage\":\"Battle and train up a computer-controlled Figure Player of the character\",\"write\":true}],\"gameID\":[\"01006A800016E000\"],\"gameName\":\"Super Smash Bros. Ultimate\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-based costume\",\"write\":false}],\"gameID\":[\"01006000040C2000\"],\"gameName\":\"Yoshi's Crafted World\"}],\"gamesWiiU\":[{\"amiiboUsage\":[{\"Usage\":\"Play the Mario League, for a chance to unlock the Mario Mii costume\",\"write\":false}],\"gameID\":[\"0005000010190300\",\"00050000101E5400\",\"00050000101E5300\"],\"gameName\":\"Mario & Sonic at the Rio 2016 Olympic Games\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-themed Mii racing suit\",\"write\":false}],\"gameID\":[\"000500001010ED00\",\"000500001010EC00\",\"000500001010EB00\"],\"gameName\":\"Mario Kart 8\"},{\"amiiboUsage\":[{\"Usage\":\"Use as game piece in amiibo Party\",\"write\":true},{\"Usage\":\"Customize game board in amiibo Party\",\"write\":false}],\"gameID\":[\"0005000010162E00\",\"0005000010162D00\",\"0005000010161F00\"],\"gameName\":\"Mario Party 10\"},{\"amiiboUsage\":[{\"Usage\":\"Play alongside and train up a computer-controlled character\",\"write\":true}],\"gameID\":[\"00050000101A3600\",\"00050000101A3500\",\"0005000010199000\"],\"gameName\":\"Mario Tennis: Ultra Smash\"},{\"amiiboUsage\":[{\"Usage\":\"Play stages as a Mini version of this character, with a unique special ability, and unlock character-specific stages\",\"write\":false}],\"gameID\":[\"00050000101C6300\",\"00050000101C6200\",\"00050000101C6100\"],\"gameName\":\"Mini Mario & Friends amiibo Challenge\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-based Mystery Mushroom costume\",\"write\":false}],\"gameID\":[\"000500001018DD00\",\"000500001018DC00\",\"000500001018DB00\"],\"gameName\":\"Super Mario Maker\"},{\"amiiboUsage\":[{\"Usage\":\"Battle and train up a computer-controlled Figure Player of the character\",\"write\":true}],\"gameID\":[\"0005000010145000\",\"0005000010144F00\",\"0005000010110E00\"],\"gameName\":\"Super Smash Bros. for Wii U\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-based Yoshi yarn pattern\",\"write\":false}],\"gameID\":[\"0005000010131F00\"],\"gameName\":\"Yoshi's Woolly World\"}],\"head\":\"00000000\",\"image\":\"https://raw.githubusercontent.com/N3evin/AmiiboAPI/master/images/icon_00000000-00000002.png\",\"name\":\"Mario\",\"release\":{\"au\":\"2014-11-29\",\"eu\":\"2014-11-28\",\"jp\":\"2014-12-06\",\"na\":\"2014-11-21\"},\"tail\":\"00000002\",\"type\":\"Figure\"},{\"amiiboSeries\":\"Super Mario Bros.\",\"character\":\"Mario\",\"gameSeries\":\"Super Mario\",\"games3DS\":[{\"amiiboUsage\":[{\"Usage\":\"Unlock character-themed aircraft early\",\"write\":false}],\"gameID\":[\"0004000000064900\",\"000400000006CC00\",\"000400000015A300\",\"000400000015C000\"],\"gameName\":\"Ace Combat Assault Horizon Legacy+\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock figure in the pose of the character (requires Chibi-Robo amiibo)\",\"write\":false}],\"gameID\":[\"0004000000163000\",\"0004000000162F00\"],\"gameName\":\"Chibi-Robo! Zip Lash\"},{\"amiiboUsage\":[{\"Usage\":\"Figure appears in-game and can be collected to gain energy points\",\"write\":false}],\"gameID\":[\"00040000001A9200\",\"00040000001AF800\",\"00040000001AFA00\",\"00040000001C5900\"],\"gameName\":\"Hey! Pikmin\"},{\"amiiboUsage\":[{\"Usage\":\"Give Kirby a Copy ability based on the character's abilities and a health item\",\"write\":false}],\"gameID\":[\"0004000000189600\",\"0004000000183600\",\"0004000000189800\"],\"gameName\":\"Kirby: Planet Robobot\"},{\"amiiboUsage\":[{\"Usage\":\"Mushrooms you find will be health-restoring Super Mushrooms instead of Poison Mushrooms\",\"write\":false}],\"gameID\":[\"0004000000076500\",\"0004000000055F00\",\"00040000000D0000\",\"00040000001D1A00\",\"00040000001D1900\"],\"gameName\":\"Luigi's Mansion\"},{\"amiiboUsage\":[{\"Usage\":\"Receive a helpful item\",\"write\":false}],\"gameID\":[\"00040000001D1500\",\"00040000001D1400\"],\"gameName\":\"Mario & Luigi: Bowser's Inside Story + Bowser Jr.'s Journey\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock character cards to help you in battle\",\"write\":true}],\"gameID\":[\"0004000000132800\",\"0004000000132700\",\"000400000018A100\"],\"gameName\":\"Mario & Luigi: Paper Jam\"},{\"amiiboUsage\":[{\"Usage\":\"Receive a stamp and equipment for the main quest\",\"write\":false}],\"gameID\":[\"00040000001B9000\",\"00040000001B8F00\"],\"gameName\":\"Mario & Luigi: Superstar Saga + Bowser's Minions\"},{\"amiiboUsage\":[{\"Usage\":\"Boost power of the Mario Mii suit for the day\",\"write\":false}],\"gameID\":[\"000400000017E300\",\"000400000017E200\",\"0004000000192400\"],\"gameName\":\"Mario & Sonic at the Rio 2016 Olympic Games\"},{\"amiiboUsage\":[{\"Usage\":\"Play as or receive bonus from this character, depending on the mode\",\"write\":false}],\"gameID\":[\"000400000019BE00\",\"0004000000193900\",\"000400000019BD00\"],\"gameName\":\"Mario Party: Star Rush\"},{\"amiiboUsage\":[{\"Usage\":\"Minigame Island: If you land on an amiibo space, use to receive coins / Use to revive your character once\",\"write\":false}],\"gameID\":[\"00040000001C4E00\",\"00040000001C4D00\"],\"gameName\":\"Mario Party: The Top 100\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock an armor paint job based on the character\",\"write\":false}],\"gameID\":[\"0004000000175300\"],\"gameName\":\"Metroid Prime: Blast Ball\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock an armor paint job based on the character for use in Blast Ball\",\"write\":false}],\"gameID\":[\"000400000016E300\",\"000400000016CE00\",\"0004000000175200\"],\"gameName\":\"Metroid Prime: Federation Force\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-based costume\",\"write\":false}],\"gameID\":[\"0004000000178800\",\"00040000001B4F00\",\"00040000001B4E00\"],\"gameName\":\"Miitopia\"},{\"amiiboUsage\":[{\"Usage\":\"Play stages as a Mini version of this character, with a unique special ability, and unlock character-specific stages\",\"write\":false}],\"gameID\":[\"000400000016C300\",\"000400000016C200\"],\"gameName\":\"Mini Mario & Friends amiibo Challenge\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-based costume with special abilities\",\"write\":false}],\"gameID\":[\"0004000000144400\"],\"gameName\":\"One Piece: Super Grand Battle! X\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-based puzzle\",\"write\":false}],\"gameID\":[\"0004000000187E00\"],\"gameName\":\"Picross 3D Round 2\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-themed accessory\",\"write\":false}],\"gameID\":[\"0004000000196500\"],\"gameName\":\"Style Savvy: Fashion Forward\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-themed pattern\",\"write\":false}],\"gameID\":[\"00040000001C2500\"],\"gameName\":\"Style Savvy: Styling Star\"},{\"amiiboUsage\":[{\"Usage\":\"Battle and train up a computer-controlled Figure Player of the character\",\"write\":true}],\"gameID\":[\"00040000000EE000\",\"00040000000EDF00\"],\"gameName\":\"Super Smash Bros. for Nintendo 3DS\"},{\"amiiboUsage\":[{\"Usage\":\"Receive a sketch of the character from Wario which can be exchanged for in-game coins\",\"write\":false}],\"gameID\":[\"00040000001D1C00\"],\"gameName\":\"WarioWare Gold\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-based Yoshi yarn pattern\",\"write\":false}],\"gameID\":[\"00040000001A4200\",\"00040000001A4100\",\"00040000001B6C00\",\"00040000001B6D00\"],\"gameName\":\"Yoshi's Woolly World\"}],\"gamesSwitch\":[{\"amiiboUsage\":[{\"Usage\":\"Unlock a special costume\",\"write\":false}],\"gameID\":[\"01007EF00399C000\"],\"gameName\":\"Conga Master Party!\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock an amiibo-exclusive weapon for the character and their Rabbid counterpart\",\"write\":false}],\"gameID\":[\"010067300059A000\"],\"gameName\":\"Mario + Rabbids: Kingdom Battle\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-themed Mii racing suit\",\"write\":false}],\"gameID\":[\"0100152000022000\"],\"gameName\":\"Mario Kart 8 Deluxe\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-based costume\",\"write\":false}],\"gameID\":[\"01003DA010E8A000\"],\"gameName\":\"Miitopia\"},{\"amiiboUsage\":[{\"Usage\":\"Receive a particular power-up depending on the character\",\"write\":false}],\"gameID\":[\"010028600EBDA000\"],\"gameName\":\"Super Mario 3D World + Bowser's Fury\"},{\"amiiboUsage\":[{\"Usage\":\"Receive a character-based costume\",\"write\":false},{\"Usage\":\"Gain temporary invincibility\",\"write\":false}],\"gameID\":[\"0100000000010000\"],\"gameName\":\"Super Mario Odyssey\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock the character's shiny sticker\",\"write\":false}],\"gameID\":[\"010036B0034E4000\"],\"gameName\":\"Super Mario Party\"},{\"amiiboUsage\":[{\"Usage\":\"Battle and train up a computer-controlled Figure Player of the character\",\"write\":true}],\"gameID\":[\"01006A800016E000\"],\"gameName\":\"Super Smash Bros. Ultimate\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-based costume\",\"write\":false}],\"gameID\":[\"01006000040C2000\"],\"gameName\":\"Yoshi's Crafted World\"}],\"gamesWiiU\":[{\"amiiboUsage\":[{\"Usage\":\"Play the Mario League, for a chance to unlock the Mario Mii costume\",\"write\":false}],\"gameID\":[\"0005000010190300\",\"00050000101E5400\",\"00050000101E5300\"],\"gameName\":\"Mario & Sonic at the Rio 2016 Olympic Games\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-themed Mii racing suit\",\"write\":false}],\"gameID\":[\"000500001010ED00\",\"000500001010EC00\",\"000500001010EB00\"],\"gameName\":\"Mario Kart 8\"},{\"amiiboUsage\":[{\"Usage\":\"Use as game piece in amiibo Party\",\"write\":true},{\"Usage\":\"Customize game board in amiibo Party\",\"write\":false}],\"gameID\":[\"0005000010162E00\",\"0005000010162D00\",\"0005000010161F00\"],\"gameName\":\"Mario Party 10\"},{\"amiiboUsage\":[{\"Usage\":\"Play alongside and train up a computer-controlled character\",\"write\":true}],\"gameID\":[\"00050000101A3600\",\"00050000101A3500\",\"0005000010199000\"],\"gameName\":\"Mario Tennis: Ultra Smash\"},{\"amiiboUsage\":[{\"Usage\":\"Play stages as a Mini version of this character, with a unique special ability, and unlock character-specific stages\",\"write\":false}],\"gameID\":[\"00050000101C6300\",\"00050000101C6200\",\"00050000101C6100\"],\"gameName\":\"Mini Mario & Friends amiibo Challenge\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-based Mystery Mushroom costume\",\"write\":false}],\"gameID\":[\"000500001018DD00\",\"000500001018DC00\",\"000500001018DB00\"],\"gameName\":\"Super Mario Maker\"},{\"amiiboUsage\":[{\"Usage\":\"Battle and train up a computer-controlled Figure Player of the character\",\"write\":true}],\"gameID\":[\"0005000010145000\",\"0005000010144F00\",\"0005000010110E00\"],\"gameName\":\"Super Smash Bros. for Wii U\"},{\"amiiboUsage\":[{\"Usage\":\"Unlock a character-based Yoshi yarn pattern\",\"write\":false}],\"gameID\":[\"0005000010131F00\"],\"gameName\":\"Yoshi's Woolly World\"}],\"head\":\"00000000\",\"image\":\"https://raw.githubusercontent.com/N3evin/AmiiboAPI/master/images/icon_00000000-00340102.png\",\"name\":\"Mario\",\"release\":{\"au\":\"2015-03-21\",\"eu\":\"2015-03-20\",\"jp\":\"2015-03-12\",\"na\":\"2015-03-20\"},\"tail\":\"00340102\",\"type\":\"Figure\"}]}" + static let withBadFormattedReleaseDates = "{\"amiibo\":[{\"amiiboSeries\":\"Super Smash Bros.\",\"character\":\"Mario\",\"gameSeries\":\"Super Mario\",\"head\":\"00000000\",\"image\":\"https://raw.githubusercontent.com/N3evin/AmiiboAPI/master/images/icon_00000000-00000002.png\",\"name\":\"Mario\",\"release\":{\"au\":\"2014-11-29\",\"eu\":\"2014-11-28\",\"jp\":\"2014-12-06\",\"na\":\"2014-11-21\"},\"tail\":\"00000002\",\"type\":\"Figure\"},{\"amiiboSeries\":\"Super Mario Bros.\",\"character\":\"Mario\",\"gameSeries\":\"Super Mario\",\"head\":\"00000000\",\"image\":\"https://raw.githubusercontent.com/N3evin/AmiiboAPI/master/images/icon_00000000-00340102.png\",\"name\":\"Mario\",\"release\":{\"au\":\"2015-XX-21\",\"eu\":\"03-20\",\"jp\":\"2015-00-12\",\"na\":\"2015-XX-XX\"},\"tail\":\"00340102\",\"type\":\"Figure\"}]}" + static let withMissingFields = "{\"amiibo\":[{\"amiiboSeries\":\"Super Smash Bros.\",\"character\":\"Mario\",\"gameSeries\":\"Super Mario\",\"head\":\"00000000\",\"image\":\"https://raw.githubusercontent.com/N3evin/AmiiboAPI/master/images/icon_00000000-00000002.png\",\"name\":\"Mario\",\"release\":{\"au\":\"2014-11-29\",\"eu\":\"2014-11-28\",\"jp\":\"2014-12-06\",\"na\":\"2014-11-21\"},\"tail\":\"00000002\",\"type\":\"Figure\"},{\"character\":\"Mario\",\"head\":\"00000000\"\"name\":\"Mario\",\"tail\":\"00340102\"}]}" + } + + enum AmiiboSeries { + static let all = "{\"amiibo\":[{\"key\":\"0x00\",\"name\":\"Super Smash Bros.\"},{\"key\":\"0x01\",\"name\":\"Super Mario Bros.\"},{\"key\":\"0x02\",\"name\":\"Chibi-Robo!\"},{\"key\":\"0x03\",\"name\":\"Yoshi's Woolly World\"},{\"key\":\"0x04\",\"name\":\"Splatoon\"},{\"key\":\"0x05\",\"name\":\"Animal Crossing\"},{\"key\":\"0x06\",\"name\":\"8-bit Mario\"},{\"key\":\"0x07\",\"name\":\"Skylanders\"},{\"key\":\"0x09\",\"name\":\"Legend Of Zelda\"},{\"key\":\"0x0a\",\"name\":\"Shovel Knight\"},{\"key\":\"0x0c\",\"name\":\"Kirby\"},{\"key\":\"0x0d\",\"name\":\"Pokemon\"},{\"key\":\"0x0e\",\"name\":\"Mario Sports Superstars\"},{\"key\":\"0x0f\",\"name\":\"Monster Hunter\"},{\"key\":\"0x10\",\"name\":\"BoxBoy!\"},{\"key\":\"0x11\",\"name\":\"Pikmin\"},{\"key\":\"0x12\",\"name\":\"Fire Emblem\"},{\"key\":\"0x13\",\"name\":\"Metroid\"},{\"key\":\"0x14\",\"name\":\"Others\"},{\"key\":\"0x15\",\"name\":\"Mega Man\"},{\"key\":\"0x16\",\"name\":\"Diablo\"},{\"key\":\"0x17\",\"name\":\"Power Pros\"},{\"key\":\"0x18\",\"name\":\"Monster Hunter Rise\"},{\"key\":\"0x19\",\"name\":\"Yu-Gi-Oh!\"},{\"key\":\"0xff\",\"name\":\"Super Nintendo World\"}]}" + static let withMissingFields = "{\"amiibo\":[{\"key\":\"0x00\",\"name\":\"Super Smash Bros.\"},{\"name\":\"Super Mario Bros.\"},{\"key\":\"0x02\",\"name\":\"Chibi-Robo!\"},{\"key\":\"0x03\"},{\"key\":\"0x04\"},{\"key\":\"0x05\",\"name\":\"Animal Crossing\"},{\"name\":\"8-bit Mario\"},{\"key\":\"0x07\",\"name\":\"Skylanders\"},{\"key\":\"0x09\"},{\"key\":\"0x0a\",\"name\":\"Shovel Knight\"},{\"name\":\"Kirby\"},{\"key\":\"0x0d\",\"name\":\"Pokemon\"},{\"key\":\"0x0e\"},{\"key\":\"0x0f\",\"name\":\"Monster Hunter\"},{\"name\":\"BoxBoy!\"},{\"key\":\"0x11\",\"name\":\"Pikmin\"},{\"key\":\"0x12\"},{\"key\":\"0x13\",\"name\":\"Metroid\"},{\"name\":\"Others\"},{\"key\":\"0x15\",\"name\":\"Mega Man\"},{\"key\":\"0x16\"},{\"key\":\"0x17\",\"name\":\"Power Pros\"},{\"name\":\"Monster Hunter Rise\"},{\"key\":\"0x19\",\"name\":\"Yu-Gi-Oh!\"},{\"key\":\"0xff\"]}" + } + + enum AmiiboType { + static let all = "{\"amiibo\":[{\"key\":\"0x00\",\"name\":\"Figure\"},{\"key\":\"0x01\",\"name\":\"Card\"},{\"key\":\"0x02\",\"name\":\"Yarn\"},{\"key\":\"0x03\",\"name\":\"Band\"}]}" + static let withMissingFields = "{\"amiibo\":[{\"key\":\"0x00\",\"name\":\"Figure\"},{\"key\":\"0x01\"},{\"key\":\"0x02\",\"name\":\"Yarn\"},{\"name\":\"Band\"}]}" + } + + enum Character { + static let all = "{\"amiibo\":[{\"key\":\"0x0000\",\"name\":\"Mario\"},{\"key\":\"0x0001\",\"name\":\"Luigi\"},{\"key\":\"0x0002\",\"name\":\"Peach\"},{\"key\":\"0x0003\",\"name\":\"Yoshi\"},{\"key\":\"0x0004\",\"name\":\"Rosalina\"},{\"key\":\"0x0005\",\"name\":\"Bowser\"},{\"key\":\"0x0006\",\"name\":\"Bowser Jr.\"},{\"key\":\"0x0007\",\"name\":\"Wario\"},{\"key\":\"0x0008\",\"name\":\"Donkey Kong\"},{\"key\":\"0x0009\",\"name\":\"Diddy Kong\"},{\"key\":\"0x000a\",\"name\":\"Toad\"},{\"key\":\"0x0013\",\"name\":\"Daisy\"},{\"key\":\"0x0014\",\"name\":\"Waluigi\"},{\"key\":\"0x0015\",\"name\":\"Goomba\"},{\"key\":\"0x0017\",\"name\":\"Boo\"},{\"key\":\"0x0023\",\"name\":\"Koopa Troopa\"},{\"key\":\"0x0024\",\"name\":\"Piranha Plant\"},{\"key\":\"0x0080\",\"name\":\"Poochy\"},{\"key\":\"0x00c0\",\"name\":\"King K. Rool\"},{\"key\":\"0x0100\",\"name\":\"Link\"},{\"key\":\"0x0101\",\"name\":\"Zelda\"},{\"key\":\"0x0102\",\"name\":\"Ganon\"},{\"key\":\"0x0103\",\"name\":\"Midna\"},{\"key\":\"0x0105\",\"name\":\"Daruk\"},{\"key\":\"0x0106\",\"name\":\"Urbosa\"},{\"key\":\"0x0107\",\"name\":\"Mipha\"},{\"key\":\"0x0108\",\"name\":\"Revali\"},{\"key\":\"0x0140\",\"name\":\"Guardian\"},{\"key\":\"0x0141\",\"name\":\"Bokoblin\"},{\"key\":\"0x0180\",\"name\":\"Villager\"},{\"key\":\"0x0181\",\"name\":\"Isabelle\"},{\"key\":\"0x0182\",\"name\":\"K.K. Slider\"},{\"key\":\"0x0183\",\"name\":\"Tom Nook\"},{\"key\":\"0x0184\",\"name\":\"Timmy & Tommy\"},{\"key\":\"0x0185\",\"name\":\"Timmy\"},{\"key\":\"0x0186\",\"name\":\"Tommy\"},{\"key\":\"0x0187\",\"name\":\"Sable\"},{\"key\":\"0x0188\",\"name\":\"Mabel\"},{\"key\":\"0x0189\",\"name\":\"Label\"},{\"key\":\"0x018a\",\"name\":\"Reese\"},{\"key\":\"0x018b\",\"name\":\"Cyrus\"},{\"key\":\"0x018c\",\"name\":\"Digby\"},{\"key\":\"0x018d\",\"name\":\"Rover\"},{\"key\":\"0x018e\",\"name\":\"Resetti\"},{\"key\":\"0x018f\",\"name\":\"Don Resetti\"},{\"key\":\"0x0190\",\"name\":\"Brewster\"},{\"key\":\"0x0191\",\"name\":\"Harriet\"},{\"key\":\"0x0192\",\"name\":\"Blathers\"},{\"key\":\"0x0193\",\"name\":\"Celeste\"},{\"key\":\"0x0194\",\"name\":\"Kicks\"},{\"key\":\"0x0195\",\"name\":\"Porter\"},{\"key\":\"0x0196\",\"name\":\"Kapp'n\"},{\"key\":\"0x0197\",\"name\":\"Leilani\"},{\"key\":\"0x0198\",\"name\":\"Leila\"},{\"key\":\"0x0199\",\"name\":\"Grams\"},{\"key\":\"0x019a\",\"name\":\"Chip\"},{\"key\":\"0x019b\",\"name\":\"Nat\"},{\"key\":\"0x019c\",\"name\":\"Phineas\"},{\"key\":\"0x019d\",\"name\":\"Copper\"},{\"key\":\"0x019e\",\"name\":\"Booker\"},{\"key\":\"0x019f\",\"name\":\"Pete\"},{\"key\":\"0x01a0\",\"name\":\"Pelly\"},{\"key\":\"0x01a1\",\"name\":\"Phyllis\"},{\"key\":\"0x01a2\",\"name\":\"Gulliver\"},{\"key\":\"0x01a3\",\"name\":\"Joan\"},{\"key\":\"0x01a4\",\"name\":\"Pascal\"},{\"key\":\"0x01a5\",\"name\":\"Katrina\"},{\"key\":\"0x01a6\",\"name\":\"Saharah\"},{\"key\":\"0x01a7\",\"name\":\"Wendell\"},{\"key\":\"0x01a8\",\"name\":\"Redd\"},{\"key\":\"0x01a9\",\"name\":\"Gracie\"},{\"key\":\"0x01aa\",\"name\":\"Lyle\"},{\"key\":\"0x01ab\",\"name\":\"Pave\"},{\"key\":\"0x01ac\",\"name\":\"Zipper\"},{\"key\":\"0x01ad\",\"name\":\"Jack\"},{\"key\":\"0x01ae\",\"name\":\"Franklin\"},{\"key\":\"0x01af\",\"name\":\"Jingle\"},{\"key\":\"0x01b0\",\"name\":\"Tortimer\"},{\"key\":\"0x01b1\",\"name\":\"Shrunk\"},{\"key\":\"0x01b3\",\"name\":\"Blanca\"},{\"key\":\"0x01b4\",\"name\":\"Leif\"},{\"key\":\"0x01b5\",\"name\":\"Luna\"},{\"key\":\"0x01b6\",\"name\":\"Katie\"},{\"key\":\"0x01c1\",\"name\":\"Lottie\"},{\"key\":\"0x0200\",\"name\":\"Cyrano\"},{\"key\":\"0x0201\",\"name\":\"Antonio\"},{\"key\":\"0x0202\",\"name\":\"Pango\"},{\"key\":\"0x0203\",\"name\":\"Anabelle\"},{\"key\":\"0x0206\",\"name\":\"Snooty\"},{\"key\":\"0x0208\",\"name\":\"Annalisa\"},{\"key\":\"0x0209\",\"name\":\"Olaf\"},{\"key\":\"0x0214\",\"name\":\"Teddy\"},{\"key\":\"0x0215\",\"name\":\"Pinky\"},{\"key\":\"0x0216\",\"name\":\"Curt\"},{\"key\":\"0x0217\",\"name\":\"Chow\"},{\"key\":\"0x0219\",\"name\":\"Nate\"},{\"key\":\"0x021a\",\"name\":\"Groucho\"},{\"key\":\"0x021b\",\"name\":\"Tutu\"},{\"key\":\"0x021c\",\"name\":\"Ursala\"},{\"key\":\"0x021d\",\"name\":\"Grizzly\"},{\"key\":\"0x021e\",\"name\":\"Paula\"},{\"key\":\"0x021f\",\"name\":\"Ike\"},{\"key\":\"0x0220\",\"name\":\"Charlise\"},{\"key\":\"0x0221\",\"name\":\"Beardo\"},{\"key\":\"0x0222\",\"name\":\"Klaus\"},{\"key\":\"0x022d\",\"name\":\"Jay\"},{\"key\":\"0x022e\",\"name\":\"Robin\"},{\"key\":\"0x022f\",\"name\":\"Anchovy\"},{\"key\":\"0x0230\",\"name\":\"Twiggy\"},{\"key\":\"0x0231\",\"name\":\"Jitters\"},{\"key\":\"0x0232\",\"name\":\"Piper\"},{\"key\":\"0x0233\",\"name\":\"Admiral\"},{\"key\":\"0x0235\",\"name\":\"Midge\"},{\"key\":\"0x0238\",\"name\":\"Jacob\"},{\"key\":\"0x023c\",\"name\":\"Lucha\"},{\"key\":\"0x023d\",\"name\":\"Jacques\"},{\"key\":\"0x023e\",\"name\":\"Peck\"},{\"key\":\"0x023f\",\"name\":\"Sparro\"},{\"key\":\"0x024a\",\"name\":\"Angus\"},{\"key\":\"0x024b\",\"name\":\"Rodeo\"},{\"key\":\"0x024d\",\"name\":\"Stu\"},{\"key\":\"0x024f\",\"name\":\"T-Bone\"},{\"key\":\"0x0251\",\"name\":\"Coach\"},{\"key\":\"0x0252\",\"name\":\"Vic\"},{\"key\":\"0x025d\",\"name\":\"Bob\"},{\"key\":\"0x025e\",\"name\":\"Mitzi\"},{\"key\":\"0x025f\",\"name\":\"Rosie\"},{\"key\":\"0x0260\",\"name\":\"Olivia\"},{\"key\":\"0x0261\",\"name\":\"Kiki\"},{\"key\":\"0x0262\",\"name\":\"Tangy\"},{\"key\":\"0x0263\",\"name\":\"Punchy\"},{\"key\":\"0x0264\",\"name\":\"Purrl\"},{\"key\":\"0x0265\",\"name\":\"Moe\"},{\"key\":\"0x0266\",\"name\":\"Kabuki\"},{\"key\":\"0x0267\",\"name\":\"Kid Cat\"},{\"key\":\"0x0268\",\"name\":\"Monique\"},{\"key\":\"0x0269\",\"name\":\"Tabby\"},{\"key\":\"0x026a\",\"name\":\"Stinky\"},{\"key\":\"0x026b\",\"name\":\"Kitty\"},{\"key\":\"0x026c\",\"name\":\"Tom\"},{\"key\":\"0x026d\",\"name\":\"Merry\"},{\"key\":\"0x026e\",\"name\":\"Felicity\"},{\"key\":\"0x026f\",\"name\":\"Lolly\"},{\"key\":\"0x0270\",\"name\":\"Ankha\"},{\"key\":\"0x0271\",\"name\":\"Rudy\"},{\"key\":\"0x0272\",\"name\":\"Katt\"},{\"key\":\"0x027d\",\"name\":\"Bluebear\"},{\"key\":\"0x027e\",\"name\":\"Maple\"},{\"key\":\"0x027f\",\"name\":\"Poncho\"},{\"key\":\"0x0280\",\"name\":\"Pudge\"},{\"key\":\"0x0281\",\"name\":\"Kody\"},{\"key\":\"0x0282\",\"name\":\"Stitches\"},{\"key\":\"0x0283\",\"name\":\"Vladimir\"},{\"key\":\"0x0284\",\"name\":\"Murphy\"},{\"key\":\"0x0286\",\"name\":\"Olive\"},{\"key\":\"0x0287\",\"name\":\"Cheri\"},{\"key\":\"0x028a\",\"name\":\"June\"},{\"key\":\"0x028b\",\"name\":\"Pekoe\"},{\"key\":\"0x028c\",\"name\":\"Chester\"},{\"key\":\"0x028d\",\"name\":\"Barold\"},{\"key\":\"0x028e\",\"name\":\"Tammy\"},{\"key\":\"0x028f\",\"name\":\"Marty\"},{\"key\":\"0x0299\",\"name\":\"Goose\"},{\"key\":\"0x029a\",\"name\":\"Benedict\"},{\"key\":\"0x029b\",\"name\":\"Egbert\"},{\"key\":\"0x029e\",\"name\":\"Ava\"},{\"key\":\"0x02a2\",\"name\":\"Becky\"},{\"key\":\"0x02a3\",\"name\":\"Plucky\"},{\"key\":\"0x02a4\",\"name\":\"Knox\"},{\"key\":\"0x02a5\",\"name\":\"Broffina\"},{\"key\":\"0x02a6\",\"name\":\"Ken\"},{\"key\":\"0x02b1\",\"name\":\"Patty\"},{\"key\":\"0x02b2\",\"name\":\"Tipper\"},{\"key\":\"0x02b7\",\"name\":\"Norma\"},{\"key\":\"0x02b8\",\"name\":\"Naomi\"},{\"key\":\"0x02c3\",\"name\":\"Alfonso\"},{\"key\":\"0x02c4\",\"name\":\"Alli\"},{\"key\":\"0x02c5\",\"name\":\"Boots\"},{\"key\":\"0x02c7\",\"name\":\"Del\"},{\"key\":\"0x02c9\",\"name\":\"Sly\"},{\"key\":\"0x02ca\",\"name\":\"Gayle\"},{\"key\":\"0x02cb\",\"name\":\"Drago\"},{\"key\":\"0x02d6\",\"name\":\"Fauna\"},{\"key\":\"0x02d7\",\"name\":\"Bam\"},{\"key\":\"0x02d8\",\"name\":\"Zell\"},{\"key\":\"0x02d9\",\"name\":\"Bruce\"},{\"key\":\"0x02da\",\"name\":\"Deirdre\"},{\"key\":\"0x02db\",\"name\":\"Lopez\"},{\"key\":\"0x02dc\",\"name\":\"Fuchsia\"},{\"key\":\"0x02dd\",\"name\":\"Beau\"},{\"key\":\"0x02de\",\"name\":\"Diana\"},{\"key\":\"0x02df\",\"name\":\"Erik\"},{\"key\":\"0x02e0\",\"name\":\"Chelsea\"},{\"key\":\"0x02ea\",\"name\":\"Goldie\"},{\"key\":\"0x02eb\",\"name\":\"Butch\"},{\"key\":\"0x02ec\",\"name\":\"Lucky\"},{\"key\":\"0x02ed\",\"name\":\"Biskit\"},{\"key\":\"0x02ee\",\"name\":\"Bones\"},{\"key\":\"0x02ef\",\"name\":\"Portia\"},{\"key\":\"0x02f0\",\"name\":\"Walker\"},{\"key\":\"0x02f1\",\"name\":\"Daisy\"},{\"key\":\"0x02f2\",\"name\":\"Cookie\"},{\"key\":\"0x02f3\",\"name\":\"Maddie\"},{\"key\":\"0x02f4\",\"name\":\"Bea\"},{\"key\":\"0x02f8\",\"name\":\"Mac\"},{\"key\":\"0x02f9\",\"name\":\"Marcel\"},{\"key\":\"0x02fa\",\"name\":\"Benjamin\"},{\"key\":\"0x02fb\",\"name\":\"Cherry\"},{\"key\":\"0x02fc\",\"name\":\"Shep\"},{\"key\":\"0x0307\",\"name\":\"Bill\"},{\"key\":\"0x0308\",\"name\":\"Joey\"},{\"key\":\"0x0309\",\"name\":\"Pate\"},{\"key\":\"0x030a\",\"name\":\"Maelle\"},{\"key\":\"0x030b\",\"name\":\"Deena\"},{\"key\":\"0x030c\",\"name\":\"Pompom\"},{\"key\":\"0x030d\",\"name\":\"Mallary\"},{\"key\":\"0x030e\",\"name\":\"Freckles\"},{\"key\":\"0x030f\",\"name\":\"Derwin\"},{\"key\":\"0x0310\",\"name\":\"Drake\"},{\"key\":\"0x0311\",\"name\":\"Scoot\"},{\"key\":\"0x0312\",\"name\":\"Weber\"},{\"key\":\"0x0313\",\"name\":\"Miranda\"},{\"key\":\"0x0314\",\"name\":\"Ketchup\"},{\"key\":\"0x0316\",\"name\":\"Gloria\"},{\"key\":\"0x0317\",\"name\":\"Molly\"},{\"key\":\"0x0318\",\"name\":\"Quillson\"},{\"key\":\"0x0323\",\"name\":\"Opal\"},{\"key\":\"0x0324\",\"name\":\"Dizzy\"},{\"key\":\"0x0325\",\"name\":\"Big Top\"},{\"key\":\"0x0326\",\"name\":\"Eloise\"},{\"key\":\"0x0327\",\"name\":\"Margie\"},{\"key\":\"0x0328\",\"name\":\"Paolo\"},{\"key\":\"0x0329\",\"name\":\"Axel\"},{\"key\":\"0x032a\",\"name\":\"Ellie\"},{\"key\":\"0x032c\",\"name\":\"Tucker\"},{\"key\":\"0x032d\",\"name\":\"Tia\"},{\"key\":\"0x032e\",\"name\":\"Chai\"},{\"key\":\"0x0338\",\"name\":\"Lily\"},{\"key\":\"0x0339\",\"name\":\"Ribbot\"},{\"key\":\"0x033a\",\"name\":\"Frobert\"},{\"key\":\"0x033b\",\"name\":\"Camofrog\"},{\"key\":\"0x033c\",\"name\":\"Drift\"},{\"key\":\"0x033d\",\"name\":\"Wart Jr.\"},{\"key\":\"0x033e\",\"name\":\"Puddles\"},{\"key\":\"0x033f\",\"name\":\"Jeremiah\"},{\"key\":\"0x0341\",\"name\":\"Tad\"},{\"key\":\"0x0342\",\"name\":\"Cousteau\"},{\"key\":\"0x0343\",\"name\":\"Huck\"},{\"key\":\"0x0344\",\"name\":\"Prince\"},{\"key\":\"0x0345\",\"name\":\"Jambette\"},{\"key\":\"0x0347\",\"name\":\"Raddle\"},{\"key\":\"0x0348\",\"name\":\"Gigi\"},{\"key\":\"0x0349\",\"name\":\"Croque\"},{\"key\":\"0x034a\",\"name\":\"Diva\"},{\"key\":\"0x034b\",\"name\":\"Henry\"},{\"key\":\"0x0356\",\"name\":\"Chevre\"},{\"key\":\"0x0357\",\"name\":\"Nan\"},{\"key\":\"0x0358\",\"name\":\"Billy\"},{\"key\":\"0x035a\",\"name\":\"Gruff\"},{\"key\":\"0x035c\",\"name\":\"Velma\"},{\"key\":\"0x035d\",\"name\":\"Kidd\"},{\"key\":\"0x035e\",\"name\":\"Pashmina\"},{\"key\":\"0x0369\",\"name\":\"Cesar\"},{\"key\":\"0x036a\",\"name\":\"Peewee\"},{\"key\":\"0x036b\",\"name\":\"Boone\"},{\"key\":\"0x036d\",\"name\":\"Louie\"},{\"key\":\"0x036e\",\"name\":\"Boyd\"},{\"key\":\"0x0370\",\"name\":\"Violet\"},{\"key\":\"0x0371\",\"name\":\"Al\"},{\"key\":\"0x0372\",\"name\":\"Rocket\"},{\"key\":\"0x0373\",\"name\":\"Hans\"},{\"key\":\"0x0374\",\"name\":\"Rilla\"},{\"key\":\"0x037e\",\"name\":\"Hamlet\"},{\"key\":\"0x037f\",\"name\":\"Apple\"},{\"key\":\"0x0380\",\"name\":\"Graham\"},{\"key\":\"0x0381\",\"name\":\"Rodney\"},{\"key\":\"0x0382\",\"name\":\"Soleil\"},{\"key\":\"0x0383\",\"name\":\"Clay\"},{\"key\":\"0x0384\",\"name\":\"Flurry\"},{\"key\":\"0x0385\",\"name\":\"Hamphrey\"},{\"key\":\"0x0390\",\"name\":\"Rocco\"},{\"key\":\"0x0392\",\"name\":\"Bubbles\"},{\"key\":\"0x0393\",\"name\":\"Bertha\"},{\"key\":\"0x0394\",\"name\":\"Biff\"},{\"key\":\"0x0395\",\"name\":\"Bitty\"},{\"key\":\"0x0398\",\"name\":\"Harry\"},{\"key\":\"0x0399\",\"name\":\"Hippeux\"},{\"key\":\"0x03a4\",\"name\":\"Buck\"},{\"key\":\"0x03a5\",\"name\":\"Victoria\"},{\"key\":\"0x03a6\",\"name\":\"Savannah\"},{\"key\":\"0x03a7\",\"name\":\"Elmer\"},{\"key\":\"0x03a8\",\"name\":\"Rosco\"},{\"key\":\"0x03a9\",\"name\":\"Winnie\"},{\"key\":\"0x03aa\",\"name\":\"Ed\"},{\"key\":\"0x03ab\",\"name\":\"Cleo\"},{\"key\":\"0x03ac\",\"name\":\"Peaches\"},{\"key\":\"0x03ad\",\"name\":\"Annalise\"},{\"key\":\"0x03ae\",\"name\":\"Clyde\"},{\"key\":\"0x03af\",\"name\":\"Colton\"},{\"key\":\"0x03b0\",\"name\":\"Papi\"},{\"key\":\"0x03b1\",\"name\":\"Julian\"},{\"key\":\"0x03bc\",\"name\":\"Yuka\"},{\"key\":\"0x03bd\",\"name\":\"Alice\"},{\"key\":\"0x03be\",\"name\":\"Melba\"},{\"key\":\"0x03bf\",\"name\":\"Sydney\"},{\"key\":\"0x03c0\",\"name\":\"Gonzo\"},{\"key\":\"0x03c1\",\"name\":\"Ozzie\"},{\"key\":\"0x03c4\",\"name\":\"Canberra\"},{\"key\":\"0x03c5\",\"name\":\"Lyman\"},{\"key\":\"0x03c6\",\"name\":\"Eugene\"},{\"key\":\"0x03d1\",\"name\":\"Kitt\"},{\"key\":\"0x03d2\",\"name\":\"Mathilda\"},{\"key\":\"0x03d3\",\"name\":\"Carrie\"},{\"key\":\"0x03d6\",\"name\":\"Astrid\"},{\"key\":\"0x03d7\",\"name\":\"Sylvia\"},{\"key\":\"0x03d9\",\"name\":\"Walt\"},{\"key\":\"0x03da\",\"name\":\"Rooney\"},{\"key\":\"0x03db\",\"name\":\"Marcie\"},{\"key\":\"0x03e6\",\"name\":\"Bud\"},{\"key\":\"0x03e7\",\"name\":\"Elvis\"},{\"key\":\"0x03e8\",\"name\":\"Rex\"},{\"key\":\"0x03ea\",\"name\":\"Leopold\"},{\"key\":\"0x03ec\",\"name\":\"Mott\"},{\"key\":\"0x03ed\",\"name\":\"Rory\"},{\"key\":\"0x03ee\",\"name\":\"Lionel\"},{\"key\":\"0x03fa\",\"name\":\"Nana\"},{\"key\":\"0x03fb\",\"name\":\"Simon\"},{\"key\":\"0x03fc\",\"name\":\"Tammi\"},{\"key\":\"0x03fd\",\"name\":\"Monty\"},{\"key\":\"0x03fe\",\"name\":\"Elise\"},{\"key\":\"0x03ff\",\"name\":\"Flip\"},{\"key\":\"0x0400\",\"name\":\"Shari\"},{\"key\":\"0x0401\",\"name\":\"Deli\"},{\"key\":\"0x040c\",\"name\":\"Dora\"},{\"key\":\"0x040d\",\"name\":\"Limberg\"},{\"key\":\"0x040e\",\"name\":\"Bella\"},{\"key\":\"0x040f\",\"name\":\"Bree\"},{\"key\":\"0x0410\",\"name\":\"Samson\"},{\"key\":\"0x0411\",\"name\":\"Rod\"},{\"key\":\"0x0414\",\"name\":\"Candi\"},{\"key\":\"0x0415\",\"name\":\"Rizzo\"},{\"key\":\"0x0416\",\"name\":\"Anicotti\"},{\"key\":\"0x0418\",\"name\":\"Broccolo\"},{\"key\":\"0x041a\",\"name\":\"Moose\"},{\"key\":\"0x041b\",\"name\":\"Bettina\"},{\"key\":\"0x041c\",\"name\":\"Greta\"},{\"key\":\"0x041d\",\"name\":\"Penelope\"},{\"key\":\"0x041e\",\"name\":\"Chadder\"},{\"key\":\"0x0429\",\"name\":\"Octavian\"},{\"key\":\"0x042a\",\"name\":\"Marina\"},{\"key\":\"0x042b\",\"name\":\"Zucker\"},{\"key\":\"0x0436\",\"name\":\"Queenie\"},{\"key\":\"0x0437\",\"name\":\"Gladys\"},{\"key\":\"0x0438\",\"name\":\"Sandy\"},{\"key\":\"0x0439\",\"name\":\"Sprocket\"},{\"key\":\"0x043b\",\"name\":\"Julia\"},{\"key\":\"0x043c\",\"name\":\"Cranston\"},{\"key\":\"0x043d\",\"name\":\"Phil\"},{\"key\":\"0x043e\",\"name\":\"Blanche\"},{\"key\":\"0x043f\",\"name\":\"Flora\"},{\"key\":\"0x0440\",\"name\":\"Phoebe\"},{\"key\":\"0x044b\",\"name\":\"Apollo\"},{\"key\":\"0x044c\",\"name\":\"Amelia\"},{\"key\":\"0x044d\",\"name\":\"Pierce\"},{\"key\":\"0x044e\",\"name\":\"Buzz\"},{\"key\":\"0x0450\",\"name\":\"Avery\"},{\"key\":\"0x0451\",\"name\":\"Frank\"},{\"key\":\"0x0452\",\"name\":\"Sterling\"},{\"key\":\"0x0453\",\"name\":\"Keaton\"},{\"key\":\"0x0454\",\"name\":\"Celia\"},{\"key\":\"0x045f\",\"name\":\"Aurora\"},{\"key\":\"0x0460\",\"name\":\"Roald\"},{\"key\":\"0x0461\",\"name\":\"Cube\"},{\"key\":\"0x0462\",\"name\":\"Hopper\"},{\"key\":\"0x0463\",\"name\":\"Friga\"},{\"key\":\"0x0464\",\"name\":\"Gwen\"},{\"key\":\"0x0465\",\"name\":\"Puck\"},{\"key\":\"0x0468\",\"name\":\"Wade\"},{\"key\":\"0x0469\",\"name\":\"Boomer\"},{\"key\":\"0x046a\",\"name\":\"Iggly\"},{\"key\":\"0x046b\",\"name\":\"Tex\"},{\"key\":\"0x046c\",\"name\":\"Flo\"},{\"key\":\"0x046d\",\"name\":\"Sprinkle\"},{\"key\":\"0x0478\",\"name\":\"Curly\"},{\"key\":\"0x0479\",\"name\":\"Truffles\"},{\"key\":\"0x047a\",\"name\":\"Rasher\"},{\"key\":\"0x047b\",\"name\":\"Hugh\"},{\"key\":\"0x047c\",\"name\":\"Lucy\"},{\"key\":\"0x047d\",\"name\":\"Spork/Crackle\"},{\"key\":\"0x0480\",\"name\":\"Cobb\"},{\"key\":\"0x0481\",\"name\":\"Boris\"},{\"key\":\"0x0482\",\"name\":\"Maggie\"},{\"key\":\"0x0483\",\"name\":\"Peggy\"},{\"key\":\"0x0485\",\"name\":\"Gala\"},{\"key\":\"0x0486\",\"name\":\"Chops\"},{\"key\":\"0x0487\",\"name\":\"Kevin\"},{\"key\":\"0x0488\",\"name\":\"Pancetti\"},{\"key\":\"0x0489\",\"name\":\"Agnes\"},{\"key\":\"0x0494\",\"name\":\"Bunnie\"},{\"key\":\"0x0495\",\"name\":\"Dotty\"},{\"key\":\"0x0496\",\"name\":\"Coco\"},{\"key\":\"0x0497\",\"name\":\"Snake\"},{\"key\":\"0x0498\",\"name\":\"Gaston\"},{\"key\":\"0x0499\",\"name\":\"Gabi\"},{\"key\":\"0x049a\",\"name\":\"Pippy\"},{\"key\":\"0x049b\",\"name\":\"Tiffany\"},{\"key\":\"0x049c\",\"name\":\"Genji\"},{\"key\":\"0x049d\",\"name\":\"Ruby\"},{\"key\":\"0x049e\",\"name\":\"Doc\"},{\"key\":\"0x049f\",\"name\":\"Claude\"},{\"key\":\"0x04a0\",\"name\":\"Francine\"},{\"key\":\"0x04a1\",\"name\":\"Chrissy\"},{\"key\":\"0x04a2\",\"name\":\"Hopkins\"},{\"key\":\"0x04a3\",\"name\":\"OHare\"},{\"key\":\"0x04a4\",\"name\":\"Carmen\"},{\"key\":\"0x04a5\",\"name\":\"Bonbon\"},{\"key\":\"0x04a6\",\"name\":\"Cole\"},{\"key\":\"0x04a7\",\"name\":\"Mira\"},{\"key\":\"0x04a8\",\"name\":\"Toby\"},{\"key\":\"0x04b2\",\"name\":\"Tank\"},{\"key\":\"0x04b3\",\"name\":\"Rhonda\"},{\"key\":\"0x04b4\",\"name\":\"Spike\"},{\"key\":\"0x04b6\",\"name\":\"Hornsby\"},{\"key\":\"0x04b9\",\"name\":\"Merengue\"},{\"key\":\"0x04ba\",\"name\":\"Renée\"},{\"key\":\"0x04c5\",\"name\":\"Vesta\"},{\"key\":\"0x04c6\",\"name\":\"Baabara\"},{\"key\":\"0x04c7\",\"name\":\"Eunice\"},{\"key\":\"0x04c8\",\"name\":\"Stella\"},{\"key\":\"0x04c9\",\"name\":\"Cashmere\"},{\"key\":\"0x04cc\",\"name\":\"Willow\"},{\"key\":\"0x04cd\",\"name\":\"Curlos\"},{\"key\":\"0x04ce\",\"name\":\"Wendy\"},{\"key\":\"0x04cf\",\"name\":\"Timbra\"},{\"key\":\"0x04d0\",\"name\":\"Frita\"},{\"key\":\"0x04d1\",\"name\":\"Muffy\"},{\"key\":\"0x04d2\",\"name\":\"Pietro\"},{\"key\":\"0x04d3\",\"name\":\"Étoile\"},{\"key\":\"0x04dd\",\"name\":\"Peanut\"},{\"key\":\"0x04de\",\"name\":\"Blaire\"},{\"key\":\"0x04df\",\"name\":\"Filbert\"},{\"key\":\"0x04e0\",\"name\":\"Pecan\"},{\"key\":\"0x04e1\",\"name\":\"Nibbles\"},{\"key\":\"0x04e2\",\"name\":\"Agent S\"},{\"key\":\"0x04e3\",\"name\":\"Caroline\"},{\"key\":\"0x04e4\",\"name\":\"Sally\"},{\"key\":\"0x04e5\",\"name\":\"Static\"},{\"key\":\"0x04e6\",\"name\":\"Mint\"},{\"key\":\"0x04e7\",\"name\":\"Ricky\"},{\"key\":\"0x04e8\",\"name\":\"Cally\"},{\"key\":\"0x04ea\",\"name\":\"Tasha\"},{\"key\":\"0x04eb\",\"name\":\"Sylvana\"},{\"key\":\"0x04ec\",\"name\":\"Poppy\"},{\"key\":\"0x04ed\",\"name\":\"Sheldon\"},{\"key\":\"0x04ee\",\"name\":\"Marshal\"},{\"key\":\"0x04ef\",\"name\":\"Hazel\"},{\"key\":\"0x04fa\",\"name\":\"Rolf\"},{\"key\":\"0x04fb\",\"name\":\"Rowan\"},{\"key\":\"0x04fc\",\"name\":\"Tybalt\"},{\"key\":\"0x04fd\",\"name\":\"Bangle\"},{\"key\":\"0x04fe\",\"name\":\"Leonardo\"},{\"key\":\"0x04ff\",\"name\":\"Claudia\"},{\"key\":\"0x0500\",\"name\":\"Bianca\"},{\"key\":\"0x050b\",\"name\":\"Chief\"},{\"key\":\"0x050c\",\"name\":\"Lobo\"},{\"key\":\"0x050d\",\"name\":\"Wolfgang\"},{\"key\":\"0x050e\",\"name\":\"Whitney\"},{\"key\":\"0x050f\",\"name\":\"Dobie\"},{\"key\":\"0x0510\",\"name\":\"Freya\"},{\"key\":\"0x0511\",\"name\":\"Fang\"},{\"key\":\"0x0513\",\"name\":\"Vivian\"},{\"key\":\"0x0514\",\"name\":\"Skye\"},{\"key\":\"0x0515\",\"name\":\"Kyle\"},{\"key\":\"0x0580\",\"name\":\"Fox\"},{\"key\":\"0x0581\",\"name\":\"Falco\"},{\"key\":\"0x0584\",\"name\":\"Wolf\"},{\"key\":\"0x05c0\",\"name\":\"Samus\"},{\"key\":\"0x05c1\",\"name\":\"Metroid\"},{\"key\":\"0x05c2\",\"name\":\"Ridley\"},{\"key\":\"0x05c3\",\"name\":\"Dark Samus\"},{\"key\":\"0x05c4\",\"name\":\"E.M.M.I.\"},{\"key\":\"0x0600\",\"name\":\"Captain Falcon\"},{\"key\":\"0x0640\",\"name\":\"Olimar\"},{\"key\":\"0x0642\",\"name\":\"Pikmin\"},{\"key\":\"0x06c0\",\"name\":\"Little Mac\"},{\"key\":\"0x0700\",\"name\":\"Wii Fit Trainer\"},{\"key\":\"0x0740\",\"name\":\"Pit\"},{\"key\":\"0x0741\",\"name\":\"Dark Pit\"},{\"key\":\"0x0742\",\"name\":\"Palutena\"},{\"key\":\"0x0780\",\"name\":\"Mr. G&W\"},{\"key\":\"0x0781\",\"name\":\"R.O.B.\"},{\"key\":\"0x0782\",\"name\":\"Duck Hunt\"},{\"key\":\"0x078f\",\"name\":\"Ice Climbers\"},{\"key\":\"0x07c0\",\"name\":\"Mii\"},{\"key\":\"0x0800\",\"name\":\"Inkling\"},{\"key\":\"0x0801\",\"name\":\"Callie\"},{\"key\":\"0x0802\",\"name\":\"Marie\"},{\"key\":\"0x0803\",\"name\":\"Pearl\"},{\"key\":\"0x0804\",\"name\":\"Marina\"},{\"key\":\"0x0805\",\"name\":\"Octoling\"},{\"key\":\"0x0806\",\"name\":\"Smallfry\"},{\"key\":\"0x09c0\",\"name\":\"Mario\"},{\"key\":\"0x09c1\",\"name\":\"Luigi\"},{\"key\":\"0x09c2\",\"name\":\"Peach\"},{\"key\":\"0x09c3\",\"name\":\"Daisy\"},{\"key\":\"0x09c4\",\"name\":\"Yoshi\"},{\"key\":\"0x09c5\",\"name\":\"Wario\"},{\"key\":\"0x09c6\",\"name\":\"Waluigi\"},{\"key\":\"0x09c7\",\"name\":\"Donkey Kong\"},{\"key\":\"0x09c8\",\"name\":\"Diddy Kong\"},{\"key\":\"0x09c9\",\"name\":\"Bowser\"},{\"key\":\"0x09ca\",\"name\":\"Bowser Jr.\"},{\"key\":\"0x09cb\",\"name\":\"Boo\"},{\"key\":\"0x09cc\",\"name\":\"Baby Mario\"},{\"key\":\"0x09cd\",\"name\":\"Baby Luigi\"},{\"key\":\"0x09ce\",\"name\":\"Birdo\"},{\"key\":\"0x09cf\",\"name\":\"Rosalina\"},{\"key\":\"0x09d0\",\"name\":\"Metal Mario\"},{\"key\":\"0x09d1\",\"name\":\"Pink Gold Peach\"},{\"key\":\"0x0a00\",\"name\":\"Orville\"},{\"key\":\"0x0a01\",\"name\":\"Wilbur\"},{\"key\":\"0x0a02\",\"name\":\"C.J.\"},{\"key\":\"0x0a03\",\"name\":\"Flick\"},{\"key\":\"0x0a04\",\"name\":\"Daisy Mae\"},{\"key\":\"0x0a05\",\"name\":\"Harvey\"},{\"key\":\"0x0a06\",\"name\":\"Wisp\"},{\"key\":\"0x0a07\",\"name\":\"Niko\"},{\"key\":\"0x0a08\",\"name\":\"Wardell\"},{\"key\":\"0x0a09\",\"name\":\"Sherb\"},{\"key\":\"0x0a0a\",\"name\":\"Megan\"},{\"key\":\"0x0a0b\",\"name\":\"Dom\"},{\"key\":\"0x0a0c\",\"name\":\"Audie\"},{\"key\":\"0x0a0d\",\"name\":\"Cyd\"},{\"key\":\"0x0a0e\",\"name\":\"Judy\"},{\"key\":\"0x0a0f\",\"name\":\"Raymond\"},{\"key\":\"0x0a10\",\"name\":\"Reneigh\"},{\"key\":\"0x0a11\",\"name\":\"Sasha\"},{\"key\":\"0x0a12\",\"name\":\"Ione\"},{\"key\":\"0x0a13\",\"name\":\"Tiansheng\"},{\"key\":\"0x0a14\",\"name\":\"Shino\"},{\"key\":\"0x0a15\",\"name\":\"Marlo\"},{\"key\":\"0x0a16\",\"name\":\"Petri\"},{\"key\":\"0x0a17\",\"name\":\"Cephalobot\"},{\"key\":\"0x0a18\",\"name\":\"Quinn\"},{\"key\":\"0x0a19\",\"name\":\"Chabwick\"},{\"key\":\"0x0a1a\",\"name\":\"Zoe\"},{\"key\":\"0x0a1b\",\"name\":\"Ace\"},{\"key\":\"0x0a1c\",\"name\":\"Rio\"},{\"key\":\"0x0a1d\",\"name\":\"Frett\"},{\"key\":\"0x0a1e\",\"name\":\"Azalea\"},{\"key\":\"0x0a1f\",\"name\":\"Roswell\"},{\"key\":\"0x0a20\",\"name\":\"Faith\"},{\"key\":\"0x0a40\",\"name\":\"Min Min\"},{\"key\":\"0x1902\",\"name\":\"Ivysaur\"},{\"key\":\"0x1906\",\"name\":\"Charizard\"},{\"key\":\"0x1907\",\"name\":\"Squirtle\"},{\"key\":\"0x1919\",\"name\":\"Pikachu\"},{\"key\":\"0x1927\",\"name\":\"Jigglypuff\"},{\"key\":\"0x1996\",\"name\":\"Mewtwo\"},{\"key\":\"0x19ac\",\"name\":\"Pichu\"},{\"key\":\"0x1ac0\",\"name\":\"Lucario\"},{\"key\":\"0x1b92\",\"name\":\"Greninja\"},{\"key\":\"0x1bd7\",\"name\":\"Incineroar\"},{\"key\":\"0x1d00\",\"name\":\"Shadow Mewtwo\"},{\"key\":\"0x1d01\",\"name\":\"Detective Pikachu\"},{\"key\":\"0x1d40\",\"name\":\"Pokemon Trainer\"},{\"key\":\"0x1f00\",\"name\":\"Kirby\"},{\"key\":\"0x1f01\",\"name\":\"Meta Knight\"},{\"key\":\"0x1f02\",\"name\":\"King Dedede\"},{\"key\":\"0x1f03\",\"name\":\"Waddle Dee\"},{\"key\":\"0x1f40\",\"name\":\"Qbby\"},{\"key\":\"0x2100\",\"name\":\"Marth\"},{\"key\":\"0x2101\",\"name\":\"Ike\"},{\"key\":\"0x2102\",\"name\":\"Lucina\"},{\"key\":\"0x2103\",\"name\":\"Robin\"},{\"key\":\"0x2104\",\"name\":\"Roy\"},{\"key\":\"0x2105\",\"name\":\"Corrin\"},{\"key\":\"0x2106\",\"name\":\"Alm\"},{\"key\":\"0x2107\",\"name\":\"Celica\"},{\"key\":\"0x2108\",\"name\":\"Chrom\"},{\"key\":\"0x2109\",\"name\":\"Tiki\"},{\"key\":\"0x210b\",\"name\":\"Byleth\"},{\"key\":\"0x2240\",\"name\":\"Shulk\"},{\"key\":\"0x2280\",\"name\":\"Ness\"},{\"key\":\"0x2281\",\"name\":\"Lucas\"},{\"key\":\"0x22c0\",\"name\":\"Chibi-Robo\"},{\"key\":\"0x3200\",\"name\":\"Sonic\"},{\"key\":\"0x3240\",\"name\":\"Bayonetta\"},{\"key\":\"0x3340\",\"name\":\"PAC-MAN\"},{\"key\":\"0x3380\",\"name\":\"Solaire of Astora\"},{\"key\":\"0x33c0\",\"name\":\"Kazuya\"},{\"key\":\"0x3480\",\"name\":\"Mega Man\"},{\"key\":\"0x34c0\",\"name\":\"Ryu\"},{\"key\":\"0x34c1\",\"name\":\"Ken\"},{\"key\":\"0x3500\",\"name\":\"One-Eyed Rathalos\"},{\"key\":\"0x3501\",\"name\":\"Nabiru\"},{\"key\":\"0x3502\",\"name\":\"Rathian\"},{\"key\":\"0x3503\",\"name\":\"Barioth\"},{\"key\":\"0x3504\",\"name\":\"Qurupeco\"},{\"key\":\"0x3505\",\"name\":\"Razewing Ratha\"},{\"key\":\"0x3506\",\"name\":\"Ena\"},{\"key\":\"0x3507\",\"name\":\"Tsukino\"},{\"key\":\"0x3508\",\"name\":\"Magnamalo\"},{\"key\":\"0x3509\",\"name\":\"Palico\"},{\"key\":\"0x350a\",\"name\":\"Palamute\"},{\"key\":\"0x350b\",\"name\":\"Malzeno\"},{\"key\":\"0x35c0\",\"name\":\"Shovel Knight\"},{\"key\":\"0x35c1\",\"name\":\"Plague Knight\"},{\"key\":\"0x35c2\",\"name\":\"Specter Knight\"},{\"key\":\"0x35c3\",\"name\":\"King Knight\"},{\"key\":\"0x3600\",\"name\":\"Cloud Strife\"},{\"key\":\"0x3601\",\"name\":\"Sephiroth\"},{\"key\":\"0x3640\",\"name\":\"Hero\"},{\"key\":\"0x3740\",\"name\":\"Mario Cereal\"},{\"key\":\"0x3780\",\"name\":\"Snake\"},{\"key\":\"0x37c0\",\"name\":\"Simon\"},{\"key\":\"0x37c1\",\"name\":\"Richter\"},{\"key\":\"0x3800\",\"name\":\"Pawapuro\"},{\"key\":\"0x3801\",\"name\":\"Ikari\"},{\"key\":\"0x3802\",\"name\":\"Daijobu\"},{\"key\":\"0x3803\",\"name\":\"Hayakawa\"},{\"key\":\"0x3804\",\"name\":\"Yabe\"},{\"key\":\"0x3805\",\"name\":\"Ganda\"},{\"key\":\"0x3840\",\"name\":\"Yuga Ohdo\"},{\"key\":\"0x3841\",\"name\":\"Tatsuhisa “Luke” Kamijō\"},{\"key\":\"0x3842\",\"name\":\"Gakuto Sōgetsu\"},{\"key\":\"0x3843\",\"name\":\"Romin Kirishima\"},{\"key\":\"0x3844\",\"name\":\"Roa Kirishima\"},{\"key\":\"0x3845\",\"name\":\"Nail Saionji\"},{\"key\":\"0x3846\",\"name\":\"Asana Mutsuba\"},{\"key\":\"0x38c0\",\"name\":\"Loot Goblin\"},{\"key\":\"0x3a00\",\"name\":\"Joker\"},{\"key\":\"0x3b40\",\"name\":\"Banjo\"},{\"key\":\"0x3c80\",\"name\":\"Terry\"},{\"key\":\"0x3dc0\",\"name\":\"Steve\"},{\"key\":\"0x3dc1\",\"name\":\"Alex\"}]}" + static let withMissingFields = "{\"amiibo\":[{\"key\":\"0x0000\",\"name\":\"Mario\"},{\"key\":\"0x0001\"},{\"key\":\"0x0002\",\"name\":\"Peach\"},{\"name\":\"Yoshi\"},{\"key\":\"0x0004\",\"name\":\"Rosalina\"},{\"key\":\"0x0005\"},{\"key\":\"0x0006\",\"name\":\"Bowser Jr.\"},{\"name\":\"Wario\"},{\"key\":\"0x0008\",\"name\":\"Donkey Kong\"},{\"key\":\"0x0009\"}]}" + } + + enum GameSeries { + static let all = "{\"amiibo\":[{\"key\":\"0x000\",\"name\":\"Super Mario\"},{\"key\":\"0x001\",\"name\":\"Super Mario\"},{\"key\":\"0x002\",\"name\":\"Super Mario\"},{\"key\":\"0x008\",\"name\":\"Yoshi's Woolly World\"},{\"key\":\"0x00c\",\"name\":\"Donkey Kong\"},{\"key\":\"0x010\",\"name\":\"The Legend of Zelda\"},{\"key\":\"0x014\",\"name\":\"Breath of the Wild\"},{\"key\":\"0x018\",\"name\":\"Animal Crossing\"},{\"key\":\"0x019\",\"name\":\"Animal Crossing\"},{\"key\":\"0x01a\",\"name\":\"Animal Crossing\"},{\"key\":\"0x01b\",\"name\":\"Animal Crossing\"},{\"key\":\"0x01c\",\"name\":\"Animal Crossing\"},{\"key\":\"0x020\",\"name\":\"Animal Crossing\"},{\"key\":\"0x021\",\"name\":\"Animal Crossing\"},{\"key\":\"0x022\",\"name\":\"Animal Crossing\"},{\"key\":\"0x023\",\"name\":\"Animal Crossing\"},{\"key\":\"0x024\",\"name\":\"Animal Crossing\"},{\"key\":\"0x025\",\"name\":\"Animal Crossing\"},{\"key\":\"0x026\",\"name\":\"Animal Crossing\"},{\"key\":\"0x027\",\"name\":\"Animal Crossing\"},{\"key\":\"0x028\",\"name\":\"Animal Crossing\"},{\"key\":\"0x029\",\"name\":\"Animal Crossing\"},{\"key\":\"0x02a\",\"name\":\"Animal Crossing\"},{\"key\":\"0x02b\",\"name\":\"Animal Crossing\"},{\"key\":\"0x02c\",\"name\":\"Animal Crossing\"},{\"key\":\"0x02d\",\"name\":\"Animal Crossing\"},{\"key\":\"0x02e\",\"name\":\"Animal Crossing\"},{\"key\":\"0x02f\",\"name\":\"Animal Crossing\"},{\"key\":\"0x030\",\"name\":\"Animal Crossing\"},{\"key\":\"0x031\",\"name\":\"Animal Crossing\"},{\"key\":\"0x032\",\"name\":\"Animal Crossing\"},{\"key\":\"0x033\",\"name\":\"Animal Crossing\"},{\"key\":\"0x034\",\"name\":\"Animal Crossing\"},{\"key\":\"0x035\",\"name\":\"Animal Crossing\"},{\"key\":\"0x036\",\"name\":\"Animal Crossing\"},{\"key\":\"0x037\",\"name\":\"Animal Crossing\"},{\"key\":\"0x038\",\"name\":\"Animal Crossing\"},{\"key\":\"0x039\",\"name\":\"Animal Crossing\"},{\"key\":\"0x03a\",\"name\":\"Animal Crossing\"},{\"key\":\"0x03b\",\"name\":\"Animal Crossing\"},{\"key\":\"0x03c\",\"name\":\"Animal Crossing\"},{\"key\":\"0x03d\",\"name\":\"Animal Crossing\"},{\"key\":\"0x03e\",\"name\":\"Animal Crossing\"},{\"key\":\"0x03f\",\"name\":\"Animal Crossing\"},{\"key\":\"0x040\",\"name\":\"Animal Crossing\"},{\"key\":\"0x041\",\"name\":\"Animal Crossing\"},{\"key\":\"0x042\",\"name\":\"Animal Crossing\"},{\"key\":\"0x043\",\"name\":\"Animal Crossing\"},{\"key\":\"0x044\",\"name\":\"Animal Crossing\"},{\"key\":\"0x045\",\"name\":\"Animal Crossing\"},{\"key\":\"0x046\",\"name\":\"Animal Crossing\"},{\"key\":\"0x047\",\"name\":\"Animal Crossing\"},{\"key\":\"0x048\",\"name\":\"Animal Crossing\"},{\"key\":\"0x049\",\"name\":\"Animal Crossing\"},{\"key\":\"0x04a\",\"name\":\"Animal Crossing\"},{\"key\":\"0x04b\",\"name\":\"Animal Crossing\"},{\"key\":\"0x04c\",\"name\":\"Animal Crossing\"},{\"key\":\"0x04d\",\"name\":\"Animal Crossing\"},{\"key\":\"0x04e\",\"name\":\"Animal Crossing\"},{\"key\":\"0x04f\",\"name\":\"Animal Crossing\"},{\"key\":\"0x050\",\"name\":\"Animal Crossing\"},{\"key\":\"0x051\",\"name\":\"Animal Crossing\"},{\"key\":\"0x0a0\",\"name\":\"Animal Crossing\"},{\"key\":\"0x0a1\",\"name\":\"Animal Crossing\"},{\"key\":\"0x0a2\",\"name\":\"Animal Crossing\"},{\"key\":\"0x058\",\"name\":\"Star Fox\"},{\"key\":\"0x05c\",\"name\":\"Metroid\"},{\"key\":\"0x060\",\"name\":\"F-Zero\"},{\"key\":\"0x064\",\"name\":\"Pikmin\"},{\"key\":\"0x06c\",\"name\":\"Punch Out\"},{\"key\":\"0x070\",\"name\":\"Wii Fit\"},{\"key\":\"0x074\",\"name\":\"Kid Icarus\"},{\"key\":\"0x078\",\"name\":\"Classic Nintendo\"},{\"key\":\"0x07c\",\"name\":\"Mii\"},{\"key\":\"0x080\",\"name\":\"Splatoon\"},{\"key\":\"0x09c\",\"name\":\"Mario Sports Superstars\"},{\"key\":\"0x09d\",\"name\":\"Mario Sports Superstars\"},{\"key\":\"0x0a4\",\"name\":\"ARMS\"},{\"key\":\"0x190\",\"name\":\"Pokemon\"},{\"key\":\"0x191\",\"name\":\"Pokemon\"},{\"key\":\"0x192\",\"name\":\"Pokemon\"},{\"key\":\"0x199\",\"name\":\"Pokemon\"},{\"key\":\"0x19a\",\"name\":\"Pokemon\"},{\"key\":\"0x1ac\",\"name\":\"Pokemon\"},{\"key\":\"0x1b9\",\"name\":\"Pokemon\"},{\"key\":\"0x1bd\",\"name\":\"Pokemon\"},{\"key\":\"0x1d0\",\"name\":\"Pokemon\"},{\"key\":\"0x1d4\",\"name\":\"Pokemon\"},{\"key\":\"0x1f0\",\"name\":\"Kirby\"},{\"key\":\"0x1f4\",\"name\":\"BoxBoy!\"},{\"key\":\"0x210\",\"name\":\"Fire Emblem\"},{\"key\":\"0x224\",\"name\":\"Xenoblade\"},{\"key\":\"0x228\",\"name\":\"Earthbound\"},{\"key\":\"0x22c\",\"name\":\"Chibi Robo\"},{\"key\":\"0x320\",\"name\":\"Sonic\"},{\"key\":\"0x324\",\"name\":\"Bayonetta\"},{\"key\":\"0x334\",\"name\":\"Pac-man\"},{\"key\":\"0x338\",\"name\":\"Dark Souls\"},{\"key\":\"0x33c\",\"name\":\"Tekken\"},{\"key\":\"0x348\",\"name\":\"Megaman\"},{\"key\":\"0x34c\",\"name\":\"Street fighter\"},{\"key\":\"0x350\",\"name\":\"Monster Hunter\"},{\"key\":\"0x35c\",\"name\":\"Shovel Knight\"},{\"key\":\"0x360\",\"name\":\"Final Fantasy\"},{\"key\":\"0x364\",\"name\":\"Dragon Quest\"},{\"key\":\"0x374\",\"name\":\"Kellogs\"},{\"key\":\"0x378\",\"name\":\"Metal Gear Solid\"},{\"key\":\"0x37c\",\"name\":\"Castlevania\"},{\"key\":\"0x380\",\"name\":\"Power Pros\"},{\"key\":\"0x384\",\"name\":\"Yu-Gi-Oh!\"},{\"key\":\"0x38c\",\"name\":\"Diablo\"},{\"key\":\"0x3a0\",\"name\":\"Persona\"},{\"key\":\"0x3b4\",\"name\":\"Banjo Kazooie\"},{\"key\":\"0x3c8\",\"name\":\"Fatal Fury\"},{\"key\":\"0x3dc\",\"name\":\"Minecraft\"}]}" + static let withMissingFields = "{\"amiibo\":[{\"key\":\"0x000\",\"name\":\"Super Mario\"},{\"key\":\"0x001\"},{\"key\":\"0x002\",\"name\":\"Super Mario\"},{\"name\":\"Yoshi's Woolly World\"},{\"key\":\"0x00c\",\"name\":\"Donkey Kong\"},{\"key\":\"0x010\"}]}" + } + + enum LastUpdated { + static let all = "{\"lastUpdated\":\"2023-03-23T13:11:20.382254\"}" + static let withBadFormattedDate = "{\"lastUpdated\":\"2023-03-23T13\"}" + static let withBadInfo = "{\"lastUpdated\":\"Something goes in here...\"}" + } +} -- 2.47.1 From 53d08bf1b6cb6474b83601d9c0a1989b7885b75c Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Fri, 21 Apr 2023 18:17:11 +0200 Subject: [PATCH 13/13] Implemented some test cases in the AmiiboServiceTests tests. --- Tests/Services/AmiiboServiceTests.swift | 515 ++++++++++++++++++++++++ 1 file changed, 515 insertions(+) create mode 100644 Tests/Services/AmiiboServiceTests.swift diff --git a/Tests/Services/AmiiboServiceTests.swift b/Tests/Services/AmiiboServiceTests.swift new file mode 100644 index 0000000..e0726ad --- /dev/null +++ b/Tests/Services/AmiiboServiceTests.swift @@ -0,0 +1,515 @@ +import Communications +import Foundation +import XCTest + +@testable import AmiiboService + +final class AmiiboServiceTests: XCTestCase { + + // MARK: Properties + + private let makeURLRequest = MakeURLRequestUseCase() + private let configuration = { + let configuration = URLSessionConfiguration.default + + configuration.protocolClasses = [MockURLProtocol.self] + + return configuration + }() + + private var service: AmiiboService! + + // MARK: Setup + + override func setUp() async throws { + service = .init(configuration: configuration) + } + + override func tearDown() async throws { + service = nil + } + + // MARK: Amiibos tests + + func test_amiibos() async throws { + // GIVEN + let endpoint = GetAmiiboEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .ok, + data: .Service.amiibos + ) + + // WHEN + let result = try await service.amiibos() + + // THEN + XCTAssertFalse(result.isEmpty) + XCTAssertEqual(result.count, 2) + XCTAssertNil(result.first?.games3DS) + XCTAssertNil(result.first?.gamesWiiU) + XCTAssertNil(result.first?.gamesSwitch) + XCTAssertNil(result.last?.games3DS) + XCTAssertNil(result.last?.gamesWiiU) + XCTAssertNil(result.last?.gamesSwitch) + } + + func test_amiibos_withGameData() async throws { + // GIVEN + let endpoint = GetAmiiboEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .ok, + data: .Service.amiibosWithGames + ) + + // WHEN + let result = try await service.amiibos() + + // THEN + XCTAssertFalse(result.isEmpty) + XCTAssertEqual(result.count, 2) + XCTAssertNotNil(result.first?.games3DS) + XCTAssertEqual(result.first?.games3DS?.count, 21) + XCTAssertNil(result.first?.games3DS?.first?.usage) + XCTAssertNotNil(result.first?.gamesWiiU) + XCTAssertEqual(result.first?.gamesWiiU?.count, 8) + XCTAssertNil(result.first?.gamesWiiU?.first?.usage) + XCTAssertNotNil(result.first?.gamesSwitch) + XCTAssertEqual(result.first?.gamesSwitch?.count, 8) + XCTAssertNil(result.first?.gamesSwitch?.first?.usage) + XCTAssertNotNil(result.last?.games3DS) + XCTAssertEqual(result.last?.games3DS?.count, 22) + XCTAssertNil(result.last?.games3DS?.first?.usage) + XCTAssertNotNil(result.last?.gamesWiiU) + XCTAssertEqual(result.last?.gamesWiiU?.count, 8) + XCTAssertNil(result.last?.gamesWiiU?.first?.usage) + XCTAssertNotNil(result.last?.gamesSwitch) + XCTAssertEqual(result.last?.gamesSwitch?.count, 9) + XCTAssertNil(result.last?.gamesSwitch?.first?.usage) + } + + func test_amiibos_withUsageData() async throws { + // GIVEN + let endpoint = GetAmiiboEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .ok, + data: .Service.amiibosWithUsage + ) + + // WHEN + let result = try await service.amiibos() + + // THEN + XCTAssertFalse(result.isEmpty) + XCTAssertEqual(result.count, 2) + XCTAssertNotNil(result.first?.games3DS) + XCTAssertEqual(result.first?.games3DS?.count, 21) + XCTAssertNotNil(result.first?.games3DS?.first?.usage) + XCTAssertEqual(result.first?.games3DS?.first?.usage?.count, 1) + XCTAssertNotNil(result.first?.gamesWiiU) + XCTAssertEqual(result.first?.gamesWiiU?.count, 8) + XCTAssertNotNil(result.first?.gamesWiiU?.first?.usage) + XCTAssertEqual(result.first?.gamesWiiU?.first?.usage?.count, 1) + XCTAssertNotNil(result.first?.gamesSwitch) + XCTAssertEqual(result.first?.gamesSwitch?.count, 8) + XCTAssertNotNil(result.first?.gamesSwitch?.first?.usage) + XCTAssertEqual(result.first?.gamesSwitch?.first?.usage?.count, 1) + XCTAssertNotNil(result.last?.games3DS) + XCTAssertEqual(result.last?.games3DS?.count, 22) + XCTAssertNotNil(result.last?.games3DS?.first?.usage) + XCTAssertEqual(result.last?.games3DS?.first?.usage?.count, 1) + XCTAssertNotNil(result.last?.gamesWiiU) + XCTAssertEqual(result.last?.gamesWiiU?.count, 8) + XCTAssertNotNil(result.last?.gamesWiiU?.first?.usage) + XCTAssertEqual(result.last?.gamesWiiU?.first?.usage?.count, 1) + XCTAssertNotNil(result.last?.gamesSwitch) + XCTAssertEqual(result.last?.gamesSwitch?.count, 9) + XCTAssertNotNil(result.last?.gamesSwitch?.first?.usage) + XCTAssertEqual(result.last?.gamesSwitch?.first?.usage?.count, 1) + } + + func test_amiibos_whenDateDecodingStrategyMismatch() async throws { + // GIVEN + let endpoint = GetAmiiboEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .ok, + data: .Service.amiiboWithBadFormattedDates + ) + + // WHEN & THEN + do { + let _ = try await service.amiibos() + } catch is DecodingError { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_amiibos_whenDataMismatch() async throws { + // GIVEN + let endpoint = GetAmiiboEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .ok, + data: .Service.amiiboWithMissingFields + ) + + // WHEN & THEN + do { + let _ = try await service.amiibos() + } catch is DecodingError { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_amiibos_whenResponseNotOk() async throws { + // GIVEN + let endpoint = GetAmiiboEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .notFound, + data: .Service.amiibos + ) + + // WHEN & THEN + do { + let _ = try await service.amiibos() + } catch is AmiiboClientError { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + // MARK: Amiibo series tests + + func test_amiiboSeries() async throws { + // GIVEN + let endpoint = GetSeriesEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .ok, + data: .Service.amiiboSeries + ) + + // WHEN + let result = try await service.amiiboSeries() + + // THEN + XCTAssertFalse(result.isEmpty) + XCTAssertEqual(result.count, 25) + } + + func test_amiiboSeries_whenDataMismatch() async throws { + // GIVEN + let endpoint = GetSeriesEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .ok, + data: .Service.amiiboSeriesWithMissingFields + ) + + // WHEN & THEN + do { + let _ = try await service.amiiboSeries() + } catch is DecodingError { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_amiiboSeries_whenResponseNotOk() async throws { + // GIVEN + let endpoint = GetSeriesEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .internalServerError, + data: .Service.amiiboSeries + ) + + // WHEN & THEN + do { + let _ = try await service.amiiboSeries() + } catch is AmiiboClientError { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + // MARK: Amiibo types tests + + func test_amiiboTypes() async throws { + // GIVEN + let endpoint = GetTypeEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .ok, + data: .Service.amiiboTypes + ) + + // WHEN + let result = try await service.amiiboTypes() + + // THEN + XCTAssertFalse(result.isEmpty) + XCTAssertEqual(result.count, 4) + } + + func test_amiiboTypes_whenDataMismatch() async throws { + // GIVEN + let endpoint = GetTypeEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .ok, + data: .Service.amiiboTypesWithMissingFields + ) + + // WHEN & THEN + do { + let _ = try await service.amiiboTypes() + } catch is DecodingError { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_amiiboTypes_whenResponseNotOk() async throws { + // GIVEN + let endpoint = GetTypeEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .forbidden, + data: .Service.amiiboTypes + ) + + // WHEN & THEN + do { + let _ = try await service.amiiboTypes() + } catch is AmiiboClientError { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + // MARK: Character tests + + func test_character() async throws { + // GIVEN + let endpoint = GetCharacterEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .ok, + data: .Service.characters + ) + + // WHEN + let result = try await service.characters() + + // THEN + XCTAssertFalse(result.isEmpty) + XCTAssertEqual(result.count, 636) + } + + func test_character_whenDataMismatch() async throws { + // GIVEN + let endpoint = GetCharacterEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .ok, + data: .Service.charactersWithMissingFields + ) + + // WHEN & THEN + do { + let _ = try await service.characters() + } catch is DecodingError { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_character_whenResponseNotOk() async throws { + // GIVEN + let endpoint = GetCharacterEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .badGateway, + data: .Service.characters + ) + + // WHEN & THEN + do { + let _ = try await service.characters() + } catch is AmiiboClientError { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + // MARK: Game series tests + + func test_gameSeries() async throws { + // GIVEN + let endpoint = GetGameSeriesEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .ok, + data: .Service.gameSeries + ) + + // WHEN + let result = try await service.gameSeries() + + // THEN + XCTAssertFalse(result.isEmpty) + XCTAssertEqual(result.count, 115) + } + + func test_gameSeries_whenDataMismatch() async throws { + // GIVEN + let endpoint = GetGameSeriesEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .ok, + data: .Service.gameSeriesWithMissingFields + ) + + // WHEN & THEN + do { + let _ = try await service.gameSeries() + } catch is DecodingError { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_gameSeries_whenResponseNotOk() async throws { + // GIVEN + let endpoint = GetGameSeriesEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .badRequest, + data: .Service.gameSeries + ) + + // WHEN & THEN + do { + let _ = try await service.gameSeries() + } catch is AmiiboClientError { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + // MARK: Last updated tests + + func test_lastUpdated() async throws { + // GIVEN + let endpoint = GetLastUpdatedEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .ok, + data: .Service.lastUpdated + ) + + // WHEN + let result = try await service.lastUpdated() + + // THEN + XCTAssertNotNil(result) + } + + func test_lastUpdated_whenDateDecodingStrategyMismatch() async throws { + // GIVEN + let endpoint = GetLastUpdatedEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .ok, + data: .Service.lastUpdatedWithBadFormattedDate + ) + + // WHEN & THEN + do { + let _ = try await service.lastUpdated() + } catch is DecodingError { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_lastUpdated_whenDataMismatch() async throws { + // GIVEN + let endpoint = GetLastUpdatedEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .ok, + data: .Service.lastUpdatedWithBadInfo + ) + + // WHEN & THEN + do { + let _ = try await service.lastUpdated() + } catch is DecodingError { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_lastUpdated_whenResponseNotOk() async throws { + // GIVEN + let endpoint = GetLastUpdatedEndpoint() + let url: URL! = try makeURLRequest(endpoint: endpoint).url + + MockURLProtocol.mockData[.init(url: url)] = .init( + status: .unauthorized, + data: .Service.lastUpdated + ) + + // WHEN & THEN + do { + let _ = try await service.lastUpdated() + } catch is AmiiboClientError { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + +} -- 2.47.1