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:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user