Added a DocC catalog coverage check.

This commit is contained in:
javier committed 2026-10-25 18:30:43 +01:00
1 parent 2cfacd0e14
commit ff00fdd852
9 files changed
+134 -42

No files matched your search

+1
View File
@@ -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
+9 -3
View File
@@ -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 \
+19 -17
View File
@@ -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
+83
View File
@@ -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)
}
)