Several fixes and optimizations all over the library (#28)
This PR contains the work done to cleans up the library after the Swift 6.2 migration: drops the legacy conditional-compilation paths, hardens the OpenAPI specification, removes boilerplate from the live client, and makes the live test suite resilient to changes in the upstream service data.
* Swift 6.2 baseline
* Adopted swift-tools-version: 6.2, which allowed removing every #if swift(>=6.0) / #if swift(>=6.2) branch and natural-language test names are now the only code paths.
* `AmiiboClient` client now refines `Sendable`, and both `AmiiboLiveClient` and `AmiiboService` conform to it, so instances can be shared across concurrency domains.
* Live client
* Extracted a generic `perform(_:)` helper that wraps every generated API call and funnels transport errors through the existing error mapper — replaces six repeated do/catch blocks and lets the private fetch* methods use typed throws end-to-end.
* Added the internal `KeyNamePayload` protocol and a generic makeModels(from:), collapsing the four near-identical mapping/sorting blocks for amiibo series, types, game characters and game series into one.
* The server URL is now resolved once into a static let constant instead of on every initialization.
* Improved error mapping: URLError.cancelled → .cancelled, .cannotParseResponse → .decoding, and .dataNotAllowed, .internationalRoamingOff, .secureConnectionFailed now map to .notAvailable.
* Simplified the Amiibo.Platform initializer ([Amiibo+Platform.swift](vscode-webview://1m6rk4uhpaukb4hrbp3j54p77iq693fq2ql7o9oup2pca1omf7f6/Sources/AmiiboService/Public/Models/Amiibo/Amiibo+Platform.swift)) using optional chaining and ?? [] instead of nested guards and immediately-invoked closures.
* Set the en_US_POSIX locale on both fixed-format date formatters so the user's locale or 12/24-hour setting can no longer break parsing.
* OpenAPI specification
* Replaced the shared Tuple schema with explicit per-type schemas (AmiiboSeries, AmiiboType, GameCharacter, GameSeries), so the generated code exposes real properties instead of allOf wrappers with .value1 accessors.
* Extracted named list schemas (AmiiboList, AmiiboSeriesList, …) for the wrapper payloads, which turns the generated .case2 enum cases into readable .AmiiboSeriesList cases.
* Added validation patterns and length bounds to the id, head, tail and key query parameters, and collapsed the redundant pattern/minLength/maxLength triplets on head/tail into ^[0-9a-fA-F]{8}$.
* Extracted the shared InternalServerError response, replacing the five inline 500 descriptions.
* Documented the two non-standard date/time decoding behaviours (date-only strings for release dates, offset-less timestamps for lastUpdated) and why the optional amiibo wrapper property must stay optional.
* Corrected the info.version value from v1.0.0 to 1.0.0.
* ⚠️ Breaking changes
* Minimum Swift version raised from 5.10 to 6.2.
* `AmiiboServiceError.unknown` now carries a String description of the underlying error.
* `AmiiboClient` requires Sendable conformance, so existing custom mock clients must be Sendable.
Reviewed-on: #28
Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
This commit was merged in pull request #28.
This commit is contained in:
@@ -35,7 +35,7 @@ info:
|
||||
* *You will require your end users to comply with (and not knowingly enable them to violate) applicable law, regulation, and the Terms.*
|
||||
* *You will comply with all applicable law, regulation, and third party rights (including without limitation laws regarding the import or export of data or software, privacy, and local laws). You will not use the APIs to encourage or promote illegal activity or violation of third party rights.*
|
||||
* *These Terms and Conditions are subject to change without notice, from time to time in our sole discretion.*
|
||||
version: v1.0.0
|
||||
version: 1.0.0
|
||||
termsOfService: https://www.amiiboapi.org/docs/#termscondition
|
||||
contact:
|
||||
name: FAQ
|
||||
@@ -91,7 +91,7 @@ paths:
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
description: The service is currently unavailable.
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
/amiiboseries:
|
||||
get:
|
||||
tags:
|
||||
@@ -119,7 +119,7 @@ paths:
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
description: The service is currently unavailable.
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
/character:
|
||||
get:
|
||||
tags:
|
||||
@@ -147,7 +147,7 @@ paths:
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
description: The service is currently unavailable.
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
/gameseries:
|
||||
get:
|
||||
tags:
|
||||
@@ -175,7 +175,7 @@ paths:
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
description: The service is currently unavailable.
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
/type:
|
||||
get:
|
||||
tags:
|
||||
@@ -203,7 +203,7 @@ paths:
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
description: The service is currently unavailable.
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
/lastupdated:
|
||||
get:
|
||||
tags:
|
||||
@@ -222,7 +222,7 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LastUpdated'
|
||||
'500':
|
||||
description: The service is currently unavailable.
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
components:
|
||||
parameters:
|
||||
AmiiboSeries:
|
||||
@@ -254,36 +254,44 @@ components:
|
||||
schema:
|
||||
type: string
|
||||
Identifier:
|
||||
description: The full 16-character hexadecimal identifier of an amiibo to include in the response.
|
||||
description: The full 16-character hexadecimal identifier of an amiibo to include in the response, with an optional `0x` prefix. An empty value is rejected with a `400` response.
|
||||
name: id
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
pattern: "^(0x)?[0-9a-fA-F]{16}$"
|
||||
minLength: 16
|
||||
maxLength: 18
|
||||
IdentifierHead:
|
||||
description: The first 8 hexadecimal characters of an amiibo identifier to include in the response.
|
||||
description: The first 8 hexadecimal characters of an amiibo identifier to include in the response, with an optional `0x` prefix. A shorter value matches as a prefix, while an empty value is rejected with a `400` response.
|
||||
name: head
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
pattern: "^(0x)?[0-9a-fA-F]+$"
|
||||
minLength: 1
|
||||
maxLength: 10
|
||||
IdentifierTail:
|
||||
description: The last 8 hexadecimal characters of an amiibo identifier to include in the response.
|
||||
description: The last 8 hexadecimal characters of an amiibo identifier to include in the response, with an optional `0x` prefix. A shorter value matches as a prefix, while an empty value is rejected with a `400` response.
|
||||
name: tail
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
pattern: "^(0x)?[0-9a-fA-F]+$"
|
||||
minLength: 1
|
||||
maxLength: 10
|
||||
Key:
|
||||
description: A hexadecimal key to filter the results by.
|
||||
description: A hexadecimal key to filter the results by, in the `0x`-prefixed format used by the `key` property of the resources. A value without hexadecimal digits after the prefix, or an empty value, is rejected with a `400` response.
|
||||
name: key
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
pattern: "^0x[0-9a-fA-F]+$"
|
||||
minLength: 3
|
||||
Name:
|
||||
description: A name to filter the results by.
|
||||
name: name
|
||||
@@ -312,6 +320,8 @@ components:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ServiceError'
|
||||
InternalServerError:
|
||||
description: The service is currently unavailable.
|
||||
NotFound:
|
||||
description: No results were found matching the given filter criteria.
|
||||
content:
|
||||
@@ -362,9 +372,7 @@ components:
|
||||
|
||||
The positions 0 to 7 of the hexadecimal string.
|
||||
type: string
|
||||
pattern: "^[0-9a-fA-F]+$"
|
||||
minLength: 8
|
||||
maxLength: 8
|
||||
pattern: "^[0-9a-fA-F]{8}$"
|
||||
image:
|
||||
description: A URL pointing to an image of this amiibo.
|
||||
type: string
|
||||
@@ -381,9 +389,7 @@ components:
|
||||
|
||||
The positions 8 to 15 of the hexadecimal string.
|
||||
type: string
|
||||
pattern: "^[0-9a-fA-F]+$"
|
||||
minLength: 8
|
||||
maxLength: 8
|
||||
pattern: "^[0-9a-fA-F]{8}$"
|
||||
type:
|
||||
description: The type of this amiibo (e.g., Figure, Card, Yarn, Band).
|
||||
type: string
|
||||
@@ -418,7 +424,10 @@ components:
|
||||
- gameID
|
||||
- gameName
|
||||
AmiiboRelease:
|
||||
description: A type that contains the regional release dates of an amiibo.
|
||||
description: |
|
||||
A type that contains the regional release dates of an amiibo.
|
||||
|
||||
Note: The service returns these dates as date-only strings (`yyyy-MM-dd`) without a time component, even though the properties declare the `date-time` format. The `date-time` format is kept deliberately so the generated code produces `Date` values, and the date-only strings are decoded by the custom transcoder configured in the live client, which interprets them as midnight UTC.
|
||||
type: object
|
||||
properties:
|
||||
au:
|
||||
@@ -439,24 +448,34 @@ components:
|
||||
format: date-time
|
||||
AmiiboSeries:
|
||||
description: A type that represents an amiibo series.
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Tuple'
|
||||
- type: object
|
||||
properties:
|
||||
key:
|
||||
description: The hexadecimal key that uniquely identifies this amiibo series.
|
||||
name:
|
||||
description: The name of this amiibo series.
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
description: The hexadecimal key that uniquely identifies this amiibo series.
|
||||
type: string
|
||||
pattern: "^0x[0-9a-fA-F]+$"
|
||||
minLength: 3
|
||||
name:
|
||||
description: The name of this amiibo series.
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- name
|
||||
AmiiboType:
|
||||
description: A type that represents an amiibo type (e.g., Figure, Card, Yarn, Band).
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Tuple'
|
||||
- type: object
|
||||
properties:
|
||||
key:
|
||||
description: The hexadecimal key that uniquely identifies this amiibo type.
|
||||
name:
|
||||
description: The name of this amiibo type.
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
description: The hexadecimal key that uniquely identifies this amiibo type.
|
||||
type: string
|
||||
pattern: "^0x[0-9a-fA-F]+$"
|
||||
minLength: 3
|
||||
name:
|
||||
description: The name of this amiibo type.
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- name
|
||||
AmiiboUsage:
|
||||
description: A type that represents how an amiibo is used within a game.
|
||||
type: object
|
||||
@@ -472,26 +491,39 @@ components:
|
||||
- write
|
||||
GameCharacter:
|
||||
description: A type that represents a game character.
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Tuple'
|
||||
- type: object
|
||||
properties:
|
||||
key:
|
||||
description: The hexadecimal key that uniquely identifies this game character.
|
||||
name:
|
||||
description: The name of this game character.
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
description: The hexadecimal key that uniquely identifies this game character.
|
||||
type: string
|
||||
pattern: "^0x[0-9a-fA-F]+$"
|
||||
minLength: 3
|
||||
name:
|
||||
description: The name of this game character.
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- name
|
||||
GameSeries:
|
||||
description: A type that represents a game series.
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Tuple'
|
||||
- type: object
|
||||
properties:
|
||||
key:
|
||||
description: The hexadecimal key that uniquely identifies this game series.
|
||||
name:
|
||||
description: The name of this game series.
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
description: The hexadecimal key that uniquely identifies this game series.
|
||||
type: string
|
||||
pattern: "^0x[0-9a-fA-F]+$"
|
||||
minLength: 3
|
||||
name:
|
||||
description: The name of this game series.
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- name
|
||||
LastUpdated:
|
||||
description: A type that contains the date and time when the service data was last updated.
|
||||
description: |
|
||||
A type that contains the date and time when the service data was last updated.
|
||||
|
||||
Note: The service returns this timestamp in the `yyyy-MM-dd'T'HH:mm:ss.SSSSSS` format without a timezone offset, so it is not a strictly valid RFC 3339 `date-time` value. The `date-time` format is kept deliberately so the generated code produces a `Date` value, and the timestamp is decoded by the custom transcoder configured in the live client, which interprets it as UTC.
|
||||
type: object
|
||||
properties:
|
||||
lastUpdated:
|
||||
@@ -500,27 +532,38 @@ components:
|
||||
format: date-time
|
||||
required:
|
||||
- lastUpdated
|
||||
Tuple:
|
||||
description: |
|
||||
A base type composed of a `key` and `name` pair.
|
||||
|
||||
This type is the base schema for the `AmiiboSeries`, `AmiiboType`, `GameCharacter`, and `GameSeries` types.
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
description: A hexadecimal key that uniquely identifies this resource.
|
||||
type: string
|
||||
pattern: "^0x[0-9a-fA-F]+$"
|
||||
minLength: 3
|
||||
name:
|
||||
description: A display name for this resource.
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- name
|
||||
# List Entities
|
||||
AmiiboList:
|
||||
description: A list that contains amiibos.
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Amiibo'
|
||||
AmiiboSeriesList:
|
||||
description: A list that contains amiibo series.
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/AmiiboSeries'
|
||||
AmiiboTypeList:
|
||||
description: A list that contains amiibo types.
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/AmiiboType'
|
||||
GameCharacterList:
|
||||
description: A list that contains game characters.
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/GameCharacter'
|
||||
GameSeriesList:
|
||||
description: A list that contains game series.
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/GameSeries'
|
||||
# Wrapper Entities
|
||||
AmiiboWrapper:
|
||||
description: A response wrapper that contains zero, one, or more amiibos.
|
||||
description: |
|
||||
A response wrapper that contains zero, one, or more amiibos.
|
||||
|
||||
Note: Unlike the other response wrappers, the `amiibo` property is deliberately not required: the service returns `"amiibo": null` when an `id` filter matches nothing, and an empty list when other filters match nothing. The property must therefore remain optional for decoding to succeed in both cases.
|
||||
type: object
|
||||
properties:
|
||||
amiibo:
|
||||
@@ -528,10 +571,8 @@ components:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/Amiibo'
|
||||
description: A certain amiibo.
|
||||
- type: array
|
||||
- $ref: '#/components/schemas/AmiiboList'
|
||||
description: A list that contains amiibos.
|
||||
items:
|
||||
$ref: '#/components/schemas/Amiibo'
|
||||
AmiiboSeriesWrapper:
|
||||
description: A response wrapper that contains one or more amiibo series.
|
||||
type: object
|
||||
@@ -541,10 +582,8 @@ components:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/AmiiboSeries'
|
||||
description: A certain amiibo series.
|
||||
- type: array
|
||||
- $ref: '#/components/schemas/AmiiboSeriesList'
|
||||
description: A list that contains amiibo series.
|
||||
items:
|
||||
$ref: '#/components/schemas/AmiiboSeries'
|
||||
required:
|
||||
- amiibo
|
||||
AmiiboTypeWrapper:
|
||||
@@ -556,10 +595,8 @@ components:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/AmiiboType'
|
||||
description: A certain amiibo type.
|
||||
- type: array
|
||||
- $ref: '#/components/schemas/AmiiboTypeList'
|
||||
description: A list that contains amiibo types.
|
||||
items:
|
||||
$ref: '#/components/schemas/AmiiboType'
|
||||
required:
|
||||
- amiibo
|
||||
GameCharacterWrapper:
|
||||
@@ -571,10 +608,8 @@ components:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/GameCharacter'
|
||||
description: A certain game character.
|
||||
- type: array
|
||||
- $ref: '#/components/schemas/GameCharacterList'
|
||||
description: A list that contains game characters.
|
||||
items:
|
||||
$ref: '#/components/schemas/GameCharacter'
|
||||
required:
|
||||
- amiibo
|
||||
GameSeriesWrapper:
|
||||
@@ -586,10 +621,8 @@ components:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/GameSeries'
|
||||
description: A certain game series.
|
||||
- type: array
|
||||
- $ref: '#/components/schemas/GameSeriesList'
|
||||
description: A list that contains game series.
|
||||
items:
|
||||
$ref: '#/components/schemas/GameSeries'
|
||||
required:
|
||||
- amiibo
|
||||
# Error Entities
|
||||
|
||||
Reference in New Issue
Block a user