Improved the documentation throughout the library (#30)

This PR contains the work done to improve the overall documentation effort throughout the library.

Reviewed-on: #30
Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
This commit was merged in pull request #30.
This commit is contained in:
2026-09-14 10:13:41 +00:00
committed by javier
parent aa135cb04a
commit 44bfaca553
151 changed files with 304 additions and 286 deletions
@@ -1,74 +1,84 @@
# ``AmiiboService``
A library that provides everything the developer needs to interact with the **Amiibo API** backend service.
A Swift client for the Amiibo API.
## Overview
The `amiibo-service` library is a package that allows the developer to interact with the [Amiibo API](https://www.amiiboapi.org) backend service seamlessly, by not only providing the *service* type but also any possible *models*, *filters*, *errors* and *interfaces* types that might be needed during implementation.
`AmiiboService` fetches data from the [Amiibo API](https://www.amiiboapi.org) and returns it as Swift models.
## Design
Although it could have been possible to generate a one-to-one RESTful client based on the Open API specification document that describe the available endpoints of the backend service, it was decided to design a `AmiiboService` service type that removes the complexities of the API design imposed by the backend service, and provides the developer with a simple interface, and a seamless experience.
The library does not expose a generated client that maps one-to-one to the REST endpoints. Instead, ``AmiiboService`` offers one method per resource, takes a typed filter, returns sorted models, and throws a single ``AmiiboServiceError`` type.
## Installation
To use the `AmiiboService` library with your package, then add it as a dependency in the `Package.swift` file:
Add the package and the `AmiiboService` product to `Package.swift`:
```swift
let package = Package(
// name, platforms, products, etc.
dependencies: [
.package(url: "https://github.com/rock-n-code/amiibo-service", from: "2.0.0"),
// other dependencies
],
targets: [
.target(
name: "SomeTarget",
name: "SomeTarget",
dependencies: [
.product(name: "AmiiboService", package: "amiibo-service"),
]
)
// other targets
),
]
)
```
It is also possible to use the `AmiiboService` library with your app in Xcode, then add it as a dependency in your Xcode project.
In Xcode, add the package URL under **File Add Package Dependencies…**.
> important: Swift 6.2 or higher is required in order to compile this library.
> Important: Requires Swift 6.2 or later, and iOS 13, macOS 10.15, tvOS 13, visionOS 1, or watchOS 6 or later.
## Usage
Create an ``AmiiboService`` instance and call any of its endpoints. Each endpoint accepts an optional filter and, when omitted, returns the full set of results:
Create an ``AmiiboService`` and call its methods. When you omit the filter, the method returns every item:
```swift
import AmiiboService
let service = AmiiboService()
// Fetch all amiibos
// All amiibos
let amiibos = try await service.getAmiibos()
// Fetch amiibos filtered by name
// Amiibos whose name contains "zelda"
let zeldaAmiibos = try await service.getAmiibos(.init(name: "zelda"))
// Fetch amiibo series, types, game characters, and game series
// Amiibo series, types, game characters, and game series
let series = try await service.getAmiiboSeries()
let types = try await service.getAmiiboTypes()
let characters = try await service.getGameCharacters()
let gameSeries = try await service.getGameSeries()
// Fetch the last updated timestamp
// Date of the last data update (UTC)
let lastUpdated = try await service.getLastUpdated()
```
Results are sorted in ascending order by ``Amiibo/identifier`` or by `key`.
## Error handling
Every method throws ``AmiiboServiceError``. When nothing matches, ``AmiiboService/getAmiibos(_:)`` returns an empty array, while the other list methods throw ``AmiiboServiceError/notFound``:
```swift
do throws(AmiiboServiceError) {
let series = try await service.getAmiiboSeries(.init(key: "0x01"))
} catch .notFound {
// No amiibo series has this key.
} catch {
// .badRequest, .cancelled, .decoding, .notAvailable, .undocumented, or .unknown
}
```
## Caching
The [Amiibo API](https://www.amiiboapi.org) recommends that consumers who call the API regularly implement caching on their systems. This library does not include a built-in cache, leaving the choice of caching strategy to the consumer. The following examples show two common approaches.
The library has no built-in cache. The Amiibo API asks regular consumers to cache responses.
### URLCache on the transport layer
Pass a custom `URLSessionTransport` with a cache-configured `URLSession` to ``AmiiboLiveClient``:
To cache at the HTTP level, give ``AmiiboLiveClient`` a transport backed by a `URLCache`. The cache follows the server's cache headers:
```swift
import OpenAPIURLSession
@@ -91,20 +101,16 @@ let service = AmiiboService(
)
```
This leverages HTTP cache headers from the server and persists cached responses to disk.
### Application-level caching
Alternatively, cache the results returned by ``AmiiboService`` directly in your application using any storage mechanism that fits your needs, such as an in-memory dictionary, a database, or a file-based store.
You can also store the returned models in your own application storage.
## Testing
The ``AmiiboClient`` protocol enables creating custom mock clients for testing, eliminating the need for network calls in unit tests. Conform to ``AmiiboClient`` and return stubbed data or throw ``AmiiboServiceError`` errors to verify your application's behavior:
To test without network access, pass a mock ``AmiiboClient`` to ``AmiiboService/init(client:)``. The mock can return stub data or throw ``AmiiboServiceError`` values. ``AmiiboClient`` refines `Sendable`, so the mock must be `Sendable`, too:
```swift
import AmiiboService
struct MyMockClient: AmiiboClient {
struct MockClient: AmiiboClient {
var error: AmiiboServiceError?
func getAmiibos(
@@ -114,22 +120,10 @@ struct MyMockClient: AmiiboClient {
return []
}
// Implement remaining protocol requirements...
// Implement the remaining requirements...
}
let service = AmiiboService(client: MyMockClient())
```
Inject the mock client into ``AmiiboService`` via its ``AmiiboService/init(client:)`` initializer to test how your code handles empty results, specific errors, or any other scenario without relying on the live backend.
## Tasks
This library offers a set of ready-to-use tasks that simplify the interaction with the library, which the developer can use from any `Terminal` application.
> Tip: To show the available list of tasks, plus display some explanations about each and every one of them; please enter the following command:
```bash
$ make
let service = AmiiboService(client: MockClient())
```
## Topics
@@ -14,7 +14,7 @@
import Foundation
/// A representation of all the possible errors that the ``AmiiboService`` service could throw.
/// An error thrown by ``AmiiboService`` and ``AmiiboClient`` methods.
public enum AmiiboServiceError: Error {
/// The request was malformed or contained invalid filter parameters.
case badRequest
@@ -25,6 +25,8 @@ public enum AmiiboServiceError: Error {
/// The backend service is currently unreachable due to a network or server issue.
case notAvailable
/// No results were found matching the given filter criteria.
///
/// When no amiibos match, ``AmiiboService/getAmiibos(_:)`` returns an empty array instead of throwing this error.
case notFound
/// The server returned an undocumented HTTP status code.
case undocumented(_ statusCode: Int)
@@ -12,55 +12,57 @@
//
// ===----------------------------------------------------------------------===
/// A type that contains values to fine-tune a response when requesting amiibo items.
/// A filter for amiibo requests.
///
/// Values left as `nil` do not restrict the result.
public struct AmiiboFilter: Sendable {
// MARK: Properties
/// A game character to filter the result, if any.
/// A game character key or name to match, if any.
public let gameCharacter: String?
/// A game series to filter the result, if any.
/// A game series key or name to match, if any.
public let gameSeries: String?
/// A first part of an identifier to filter the result, if any.
/// The first 8 hexadecimal characters of an identifier to match, if any. Shorter values match as a prefix.
public let head: String?
/// An amiibo identifier to filter the result, if any.
/// A full 16-character hexadecimal identifier to match, if any.
public let identifier: String?
/// An amiibo name to filter the result, if any.
/// An amiibo name, or part of one, to match, if any.
public let name: String?
/// An amiibo series to filter the result, if any.
/// An amiibo series key or name to match, if any.
public let series: String?
/// A flag indicating whether to include games in the response, if any.
/// A flag that indicates whether to include related games in ``Amiibo/platform``. `nil` means `false`.
public let showGames: Bool?
/// A flag indicating whether to include amiibo usages in games in the response, if any.
/// A flag that indicates whether to include related games and their amiibo usages in ``Amiibo/platform``. `nil` means `false`.
public let showUsage: Bool?
/// A last part of an identifier to filter the result, if any.
/// The last 8 hexadecimal characters of an identifier to match, if any. Shorter values match as a prefix.
public let tail: String?
/// An amiibo type to filter the result, if any.
/// An amiibo type key or name to match, if any.
public let type: String?
// MARK: Initializers
/// Initializes this filter.
/// - Parameters:
/// - head: A first part of an identifier to filter the result, if any.
/// - tail: A last part of an identifier to filter the result, if any.
/// - identifier: An amiibo identifier to filter the result, if any.
/// - name: An amiibo name to filter the result, if any.
/// - type: An amiibo type to filter the result, if any.
/// - series: An amiibo series to filter the result, if any.
/// - gameCharacter: A game character to filter the result, if any.
/// - gameSeries: A game series to filter the result, if any.
/// - showGames: A flag indicating whether to include games in the response, if any.
/// - showUsage: A flag indicating whether to include amiibo usages in games in the response, if any.
/// - head: The first 8 hexadecimal characters of an identifier to match, if any. Shorter values match as a prefix.
/// - tail: The last 8 hexadecimal characters of an identifier to match, if any. Shorter values match as a prefix.
/// - identifier: A full 16-character hexadecimal identifier to match, if any.
/// - name: An amiibo name, or part of one, to match, if any.
/// - type: An amiibo type key or name to match, if any.
/// - series: An amiibo series key or name to match, if any.
/// - gameCharacter: A game character key or name to match, if any.
/// - gameSeries: A game series key or name to match, if any.
/// - showGames: A flag that indicates whether to include related games in ``Amiibo/platform``. `nil` means `false`.
/// - showUsage: A flag that indicates whether to include related games and their amiibo usages in ``Amiibo/platform``. `nil` means `false`.
public init(
head: String? = nil,
tail: String? = nil,
@@ -12,36 +12,36 @@
//
// ===----------------------------------------------------------------------===
/// A type that contains values to fine-tune a response when requesting amiibo series.
/// A filter for amiibo series requests.
public struct AmiiboSeriesFilter: KeyNameFilter, Sendable {
// TODO: Remove the documentation from the properties and initializers of this type as the `--enable-inherited-docs` flag when generating DocC documentation is not working as intended (?).
// MARK: Properties
/// A key to return, if any.
/// A hexadecimal key in `0x` format, such as `0x01`, to match exactly, if any.
public let key: String?
/// A name to return, if any.
/// A name, or part of one, to match, if any.
public let name: String?
// MARK: Initializers
/// Initializes this filter without key or name values.
/// Initializes a filter that matches every amiibo series.
public init() {
self.key = nil
self.name = nil
}
/// Initializes this filter with a key value.
/// - Parameter key: A key to return.
/// Initializes a filter that matches the amiibo series with a given key.
/// - Parameter key: A hexadecimal key in `0x` format, such as `0x01`.
public init(key: String) {
self.key = key
self.name = nil
}
/// Initializes this filter with a name value.
/// - Parameter name: A name to return.
/// Initializes a filter that matches amiibo series by name.
/// - Parameter name: A name, or part of one.
public init(name: String) {
self.key = nil
self.name = name
@@ -12,36 +12,36 @@
//
// ===----------------------------------------------------------------------===
/// A type that contains values to fine-tune a response when requesting amiibo types.
/// A filter for amiibo type requests.
public struct AmiiboTypeFilter: KeyNameFilter, Sendable {
// TODO: Remove the documentation from the properties and initializers of this type as the `--enable-inherited-docs` flag when generating DocC documentation is not working as intended (?).
// MARK: Properties
/// A key to return, if any.
/// A hexadecimal key in `0x` format, such as `0x01`, to match exactly, if any.
public let key: String?
/// A name to return, if any.
/// A name, or part of one, to match, if any.
public let name: String?
// MARK: Initializers
/// Initializes this filter without key or name values.
/// Initializes a filter that matches every amiibo type.
public init() {
self.key = nil
self.name = nil
}
/// Initializes this filter with a key value.
/// - Parameter key: A key to return.
/// Initializes a filter that matches the amiibo type with a given key.
/// - Parameter key: A hexadecimal key in `0x` format, such as `0x01`.
public init(key: String) {
self.key = key
self.name = nil
}
/// Initializes this filter with a name value.
/// - Parameter name: A name to return.
/// Initializes a filter that matches amiibo types by name.
/// - Parameter name: A name, or part of one.
public init(name: String) {
self.key = nil
self.name = name
@@ -12,36 +12,36 @@
//
// ===----------------------------------------------------------------------===
/// A type that contains values to fine-tune a response when requesting game characters.
/// A filter for game character requests.
public struct GameCharacterFilter: KeyNameFilter, Sendable {
// TODO: Remove the documentation from the properties and initializers of this type as the `--enable-inherited-docs` flag when generating DocC documentation is not working as intended (?).
// MARK: Properties
/// A key to return, if any.
/// A hexadecimal key in `0x` format, such as `0x0001`, to match exactly, if any.
public let key: String?
/// A name to return, if any.
/// A name, or part of one, to match, if any.
public let name: String?
// MARK: Initializers
/// Initializes this filter without key or name values.
/// Initializes a filter that matches every game character.
public init() {
self.key = nil
self.name = nil
}
/// Initializes this filter with a key value.
/// - Parameter key: A key to return.
/// Initializes a filter that matches the game character with a given key.
/// - Parameter key: A hexadecimal key in `0x` format, such as `0x0001`.
public init(key: String) {
self.key = key
self.name = nil
}
/// Initializes this filter with a name value.
/// - Parameter name: A name to return.
/// Initializes a filter that matches game characters by name.
/// - Parameter name: A name, or part of one.
public init(name: String) {
self.key = nil
self.name = name
@@ -12,36 +12,36 @@
//
// ===----------------------------------------------------------------------===
/// A type that contains values to fine-tune a response when requesting game series.
/// A filter for game series requests.
public struct GameSeriesFilter: KeyNameFilter, Sendable {
// TODO: Remove the documentation from the properties and initializers of this type as the `--enable-inherited-docs` flag when generating DocC documentation is not working as intended (?).
// MARK: Properties
/// A key to return, if any.
/// A hexadecimal key in `0x` format, such as `0x001`, to match exactly, if any.
public let key: String?
/// A name to return, if any.
/// A name, or part of one, to match, if any.
public let name: String?
// MARK: Initializers
/// Initializes this filter without key or name values.
/// Initializes a filter that matches every game series.
public init() {
self.key = nil
self.name = nil
}
/// Initializes this filter with a key value.
/// - Parameter key: A key to return.
/// Initializes a filter that matches the game series with a given key.
/// - Parameter key: A hexadecimal key in `0x` format, such as `0x001`.
public init(key: String) {
self.key = key
self.name = nil
}
/// Initializes this filter with a name value.
/// - Parameter name: A name to return.
/// Initializes a filter that matches game series by name.
/// - Parameter name: A name, or part of one.
public init(name: String) {
self.key = nil
self.name = name
@@ -34,7 +34,9 @@ public struct Amiibo: Sendable, Hashable {
/// The name of this amiibo.
public let name: String
/// The game platform data for this amiibo, if available.
/// The related games of this amiibo, grouped by platform.
///
/// This value is `nil` unless the request sets ``AmiiboFilter/showGames`` or ``AmiiboFilter/showUsage``, or when the amiibo has no related games.
public let platform: Platform?
/// The release dates of this amiibo across different regions.
@@ -24,7 +24,7 @@ extension Amiibo {
/// The name of this game.
public let name: String
/// A list of amiibo usages within this game, if available.
/// A list of amiibo usages within this game, or `nil` unless the request sets ``AmiiboFilter/showUsage``.
public let usages: [Usage]?
// MARK: Initializers
@@ -14,9 +14,9 @@
import Foundation
/// A protocol that defines API clients containing all available endpoints to interact with.
/// A protocol that defines a client that fetches data from the Amiibo API.
///
/// Conforming types must be `Sendable`, as clients are expected to be used safely across concurrency domains.
/// ``AmiiboLiveClient`` is the default implementation. Conform to this protocol to provide another one, such as a mock for tests. Conforming types must be `Sendable`.
public protocol AmiiboClient: Sendable {
// MARK: Functions
@@ -14,20 +14,20 @@
import Foundation
/// A type that implements the service that uses a client to make calls.
/// The entry point for fetching data from the Amiibo API.
///
/// This service forwards every call to the ``AmiiboClient`` client injected during initialization, which defaults to an ``AmiiboLiveClient`` instance. This type is `Sendable`, so an instance can be safely shared across concurrency domains.
/// This service forwards every call to the ``AmiiboClient`` passed at initialization, which defaults to ``AmiiboLiveClient``. It is `Sendable`, so you can share an instance across concurrency domains.
public struct AmiiboService: Sendable {
// MARK: Properties
/// A client to interact with the endpoints.
private let client: any AmiiboClient
// MARK: Initializers
/// Initializes this service with a specific client type.
/// - Parameter client: A client to use to interact with the endpoints.
/// Initializes this service with a client.
/// - Parameter client: The client that performs the calls. Defaults to ``AmiiboLiveClient``.
public init(client: some AmiiboClient = AmiiboLiveClient()) {
self.client = client
}