Overhauled the library with several new features #5
No files matched your search
@@ -15,6 +15,7 @@
|
||||
# --- DOCUMENTATION ---
|
||||
|
||||
DOCC_ARCHIVE_OUTPUT=./${SPM_LIBRARY_TARGET}.doccarchive
|
||||
DOCC_CATALOG=Sources/$(SPM_LIBRARY_TARGET)/Catalogs/$(SPM_LIBRARY_TARGET).docc
|
||||
DOCC_GITHUB_OUTPUT=./docs
|
||||
DOCC_GITHUB_BASE_PATH=asconnect-service
|
||||
DOCC_PREVIEW_URL=http://localhost:8080/documentation/asconnectservice
|
||||
|
||||
@@ -82,6 +82,9 @@ doc-generate-github: ## Generates the library documentation for Github
|
||||
--hosting-base-path $(DOCC_GITHUB_BASE_PATH) \
|
||||
--output-path $(DOCC_GITHUB_OUTPUT)
|
||||
|
||||
doc-check: ## Checks the DocC catalog covers every generated operation and schema
|
||||
@python3 -I Scripts/check-docc.py "$(OAS_GENERATE_OUTPUT)" "$(DOCC_CATALOG)"
|
||||
|
||||
doc-preview: ## Previews the library documentation in Safari
|
||||
@open -a safari $(DOCC_PREVIEW_URL)
|
||||
@ENABLE_DOCC_PLUGIN=1 swift package \
|
||||
@@ -101,13 +104,16 @@ oas-download: ## Downloads the latest App Store Connect OpenAPI specification do
|
||||
@unzip -q "$(OAS_DOWNLOAD_INPUT)" -d "$(OAS_DOWNLOAD_FOLDER)"
|
||||
@mv "$(OAS_DOWNLOAD_FILE)" "$(OAS_DOWNLOAD_OUTPUT)"
|
||||
@rm -rf "$(OAS_DOWNLOAD_FOLDER)"
|
||||
@$(MAKE) --no-print-directory oas-patch
|
||||
@# Generating the code also validates the document.
|
||||
@$(MAKE) --no-print-directory oas-patch-document
|
||||
@$(MAKE) --no-print-directory oas-generate
|
||||
@$(MAKE) --no-print-directory doc-check
|
||||
|
||||
oas-patch: # Patches the OpenAPI specification document with the patches in Scripts/patch-oas.jq
|
||||
oas-patch: oas-patch-document oas-validate # Patches and validates the OpenAPI specification document
|
||||
|
||||
oas-patch-document: # Patches the OpenAPI specification document with Scripts/patch-oas.jq
|
||||
@jq --from-file Scripts/patch-oas.jq "$(OAS_DOWNLOAD_OUTPUT)" > "$(OAS_DOWNLOAD_OUTPUT).tmp"
|
||||
@mv "$(OAS_DOWNLOAD_OUTPUT).tmp" "$(OAS_DOWNLOAD_OUTPUT)"
|
||||
@$(MAKE) --no-print-directory oas-validate
|
||||
|
||||
oas-validate: ## Validates the OpenAPI specification document
|
||||
@swift run \
|
||||
|
||||
@@ -31,33 +31,31 @@ Then add it to your target dependencies:
|
||||
|
||||
### Creating a Client
|
||||
|
||||
Create a `Client` with a server URL, a transport, and a `BearerAuthMiddleware` that authenticates each request with a JSON Web Token (JWT). To get a token, create an API key in App Store Connect and sign a JWT with its key ID, issuer ID, and private key:
|
||||
Create a `Client` with a server URL, a transport, and a `BearerAuthMiddleware`. With an API key from App Store Connect, `APIKeyTokenProvider` signs the JSON Web Tokens (JWT) the API requires, and renews them before they expire:
|
||||
|
||||
```swift
|
||||
import ASConnectService
|
||||
import OpenAPIURLSession
|
||||
|
||||
let client = Client(
|
||||
serverURL: try Servers.Server1.url(),
|
||||
transport: URLSessionTransport(),
|
||||
middlewares: [
|
||||
BearerAuthMiddleware(token: yourJWTToken)
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
Tokens expire after 20 minutes at most. For longer-lived clients, use `APIKeyTokenProvider`, which signs new ones with your API key as needed:
|
||||
|
||||
```swift
|
||||
let tokenProvider = try APIKeyTokenProvider(
|
||||
issuerID: yourIssuerID,
|
||||
keyID: yourKeyID,
|
||||
privateKey: yourP8FileContents
|
||||
)
|
||||
|
||||
BearerAuthMiddleware {
|
||||
try await tokenProvider.token()
|
||||
}
|
||||
let client = Client(
|
||||
serverURL: try Servers.Server1.url(),
|
||||
transport: URLSessionTransport(),
|
||||
middlewares: [
|
||||
BearerAuthMiddleware { try await tokenProvider.token() }
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
To use a token you signed yourself, pass it directly. It expires after 20 minutes at most:
|
||||
|
||||
```swift
|
||||
BearerAuthMiddleware(token: yourJWTToken)
|
||||
```
|
||||
|
||||
### Making API Calls
|
||||
@@ -97,8 +95,9 @@ middlewares: [
|
||||
]
|
||||
```
|
||||
|
||||
## Supported Platforms
|
||||
## Requirements
|
||||
|
||||
- Swift 6.0+ (Xcode 16+)
|
||||
- iOS 14.0+
|
||||
- macOS 11.0+
|
||||
- tvOS 14.0+
|
||||
@@ -130,6 +129,9 @@ make doc-generate-github
|
||||
|
||||
# Preview documentation locally in Safari
|
||||
make doc-preview
|
||||
|
||||
# Check the catalog covers the generated code
|
||||
make doc-check
|
||||
```
|
||||
|
||||
### Updating the API
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# ===------------------------------------------------------------------------------------===
|
||||
#
|
||||
# This source file is part of the App Store Connect Service open source project
|
||||
#
|
||||
# Copyright (c) 2026 Röck+Cöde VoF. and the App Store Connect Service project authors
|
||||
# Licensed under Apache license v2.0
|
||||
#
|
||||
# See LICENSE for license information
|
||||
# See CONTRIBUTORS for the list of App Store Connect Service project authors
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# ===------------------------------------------------------------------------------------===
|
||||
|
||||
"""Checks that the DocC catalog lists every generated operation and schema, and nothing else.
|
||||
|
||||
Usage: python3 check-docc.py <generated sources folder> <DocC catalog folder>
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def generated_symbols(types_path):
|
||||
"""Returns the methods, operations, and schemas in the generated `Types.swift` file."""
|
||||
lines = open(types_path).read().split('\n')
|
||||
|
||||
def start(prefix):
|
||||
return next(index for index, line in enumerate(lines) if line.startswith(prefix))
|
||||
|
||||
protocol = lines[start('public protocol APIProtocol'):start('extension APIProtocol {')]
|
||||
components = lines[start('public enum Components {'):start('public enum Operations {')]
|
||||
operations = lines[start('public enum Operations {'):]
|
||||
|
||||
schema = re.compile(r' (?:@\w+ )*public (?:indirect )?(?:struct|enum|typealias) (\w+)')
|
||||
|
||||
return {
|
||||
'APIProtocol.md': {m.group(1) for line in protocol if (m := re.match(r' func (\w+)\(', line))},
|
||||
'Operations.md': {m.group(1) for line in operations if (m := re.match(r' public enum (\w+)', line))},
|
||||
'Components.md': {m.group(1) for line in components if (m := schema.match(line))},
|
||||
}
|
||||
|
||||
|
||||
def documented_symbols(catalog_path):
|
||||
"""Returns the symbols listed in the DocC catalog topics."""
|
||||
patterns = {
|
||||
'APIProtocol.md': r'``(\w+)\(_:\)``',
|
||||
'Operations.md': r'``Operations/(\w+)``',
|
||||
'Components.md': r'``Components/Schemas/(\w+)``',
|
||||
}
|
||||
|
||||
return {
|
||||
name: set(re.findall(pattern, open(f'{catalog_path}/Extensions/{name}').read()))
|
||||
for name, pattern in patterns.items()
|
||||
}
|
||||
|
||||
|
||||
def main(generated_path, catalog_path):
|
||||
generated = generated_symbols(f'{generated_path}/Types.swift')
|
||||
documented = documented_symbols(catalog_path)
|
||||
is_up_to_date = True
|
||||
|
||||
for name in generated:
|
||||
missing = sorted(generated[name] - documented[name])
|
||||
stale = sorted(documented[name] - generated[name])
|
||||
|
||||
for title, symbols in [('Missing from', missing), ('Stale in', stale)]:
|
||||
if symbols:
|
||||
is_up_to_date = False
|
||||
print(f'{title} {name} ({len(symbols)}):')
|
||||
print('\n'.join(f' - {symbol}' for symbol in symbols))
|
||||
|
||||
if is_up_to_date:
|
||||
print('The DocC catalog lists every generated operation and schema.')
|
||||
|
||||
return 0 if is_up_to_date else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 3:
|
||||
sys.exit(__doc__)
|
||||
|
||||
sys.exit(main(sys.argv[1], sys.argv[2]))
|
||||
@@ -8,33 +8,31 @@ ASConnectService exposes every endpoint of Apple's App Store Connect API as a Sw
|
||||
|
||||
### Creating a Client
|
||||
|
||||
Create a ``Client`` with a server URL, a transport, and a ``BearerAuthMiddleware`` that authenticates each request with a JSON Web Token (JWT):
|
||||
Create a ``Client`` with a server URL, a transport, and a ``BearerAuthMiddleware``. With an API key from App Store Connect, ``APIKeyTokenProvider`` signs the JSON Web Tokens (JWT) the API requires, and renews them before they expire:
|
||||
|
||||
```swift
|
||||
import ASConnectService
|
||||
import OpenAPIURLSession
|
||||
|
||||
let client = Client(
|
||||
serverURL: try Servers.Server1.url(),
|
||||
transport: URLSessionTransport(),
|
||||
middlewares: [
|
||||
BearerAuthMiddleware(token: yourJWTToken)
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
Tokens expire after 20 minutes at most. For longer-lived clients, use ``APIKeyTokenProvider``, which signs new ones with your API key as needed:
|
||||
|
||||
```swift
|
||||
let tokenProvider = try APIKeyTokenProvider(
|
||||
issuerID: yourIssuerID,
|
||||
keyID: yourKeyID,
|
||||
privateKey: yourP8FileContents
|
||||
)
|
||||
|
||||
BearerAuthMiddleware {
|
||||
try await tokenProvider.token()
|
||||
}
|
||||
let client = Client(
|
||||
serverURL: try Servers.Server1.url(),
|
||||
transport: URLSessionTransport(),
|
||||
middlewares: [
|
||||
BearerAuthMiddleware { try await tokenProvider.token() }
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
To use a token you signed yourself, pass it directly. It expires after 20 minutes at most:
|
||||
|
||||
```swift
|
||||
BearerAuthMiddleware(token: yourJWTToken)
|
||||
```
|
||||
|
||||
### Making API Calls
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Generated by swift-openapi-generator, do not modify.
|
||||
// swift-format-ignore-file
|
||||
@_spi(Generated) import OpenAPIRuntime
|
||||
#if os(Linux)
|
||||
@preconcurrency import struct Foundation.URL
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Generated by swift-openapi-generator, do not modify.
|
||||
// swift-format-ignore-file
|
||||
@_spi(Generated) import OpenAPIRuntime
|
||||
#if os(Linux)
|
||||
@preconcurrency import struct Foundation.URL
|
||||
|
||||
@@ -17,3 +17,6 @@ generate:
|
||||
- client
|
||||
namingStrategy: idiomatic
|
||||
accessModifier: public
|
||||
# Stops swift-format from reformatting the generated code.
|
||||
additionalFileComments:
|
||||
- swift-format-ignore-file
|
||||
@@ -47,17 +47,14 @@ struct BearerAuthMiddlewareTests {
|
||||
let middleware = BearerAuthMiddleware(token: .Token.jwt)
|
||||
let requestBody: HTTPBody = HTTPBody("request-body")
|
||||
|
||||
var receivedBody: HTTPBody?
|
||||
|
||||
_ = try await middleware.intercept(
|
||||
// The next handler echoes the body back, so the test can read it.
|
||||
let (_, receivedBody) = try await middleware.intercept(
|
||||
.get,
|
||||
body: requestBody,
|
||||
baseURL: .base,
|
||||
operationID: "createApp",
|
||||
next: { _, body, _ in
|
||||
receivedBody = body
|
||||
|
||||
return (HTTPResponse(status: .created), nil)
|
||||
(HTTPResponse(status: .created), body)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
Reference in new issue
Block a user