Overall update and improvements (#4)

This PR contains all the work done to update the App Store Connect OpenAPI specification document to its latest version, plus the implementation of the `BearerAuthMiddleware` middleware, and several other improvements to the setup of the library, the `Makefile` file, and the documentation.

Reviewed-on: #4
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 #4.
This commit is contained in:
2026-03-26 01:07:23 +00:00
committed by Javier Cicchelli
parent e884fcf8a4
commit 3aaf5cd7a5
19 changed files with 234816 additions and 208683 deletions
+9 -1
View File
@@ -2,7 +2,7 @@
## ##
## This source file is part of the App Store Connect Service open source project ## This source file is part of the App Store Connect Service open source project
## ##
## Copyright (c) 2025 Röck+Cöde VoF. and the App Store Connect Service project authors ## Copyright (c) 2026 Röck+Cöde VoF. and the App Store Connect Service project authors
## Licensed under Apache license v2.0 ## Licensed under Apache license v2.0
## ##
## See LICENSE for license information ## See LICENSE for license information
@@ -19,6 +19,14 @@ DOCC_GITHUB_OUTPUT=./docs
DOCC_GITHUB_BASE_PATH=asconnect-service DOCC_GITHUB_BASE_PATH=asconnect-service
DOCC_PREVIEW_URL=http://localhost:8080/documentation/asconnectservice DOCC_PREVIEW_URL=http://localhost:8080/documentation/asconnectservice
# --- OPEN API SPECIFICATION ---
OAS_DOWNLOAD_FOLDER=./oas-download
OAS_DOWNLOAD_FILE=$(OAS_DOWNLOAD_FOLDER)/openapi.oas.json
OAS_DOWNLOAD_INPUT=$(OAS_DOWNLOAD_FOLDER)/asconnect-openapi-specification.zip
OAS_DOWNLOAD_OUTPUT=Sources/$(SPM_LIBRARY_TARGET)/openapi.json
OAS_DOWNLOAD_URL=https://developer.apple.com/sample-code/app-store-connect/app-store-connect-openapi-specification.zip
# -- SWIFT PACKAGE MANAGER --- # -- SWIFT PACKAGE MANAGER ---
SPM_LIBRARY_TARGET=ASConnectService SPM_LIBRARY_TARGET=ASConnectService
+1 -1
View File
@@ -2,7 +2,7 @@
## ##
## This source file is part of the App Store Connect Service open source project ## This source file is part of the App Store Connect Service open source project
## ##
## Copyright (c) 2025 Röck+Cöde VoF. and the App Store Connect Service project authors ## Copyright (c) 2026 Röck+Cöde VoF. and the App Store Connect Service project authors
## Licensed under Apache license v2.0 ## Licensed under Apache license v2.0
## ##
## See LICENSE for license information ## See LICENSE for license information
+79
View File
@@ -0,0 +1,79 @@
{
"fileScopedDeclarationPrivacy" : {
"accessLevel" : "private"
},
"indentBlankLines" : false,
"indentConditionalCompilationBlocks" : true,
"indentSwitchCaseLabels" : true,
"indentation" : {
"spaces" : 4
},
"lineBreakAroundMultilineExpressionChainComponents" : true,
"lineBreakBeforeControlFlowKeywords" : false,
"lineBreakBeforeEachArgument" : true,
"lineBreakBeforeEachGenericRequirement" : true,
"lineBreakBetweenDeclarationAttributes" : true,
"lineLength" : 120,
"maximumBlankLines" : 1,
"multiElementCollectionTrailingCommas" : true,
"noAssignmentInExpressions" : {
"allowedFunctions" : [
"XCTAssertNoThrow"
]
},
"orderedImports" : {
"includeConditionalImports" : true
},
"prioritizeKeepingFunctionOutputTogether" : true,
"reflowMultilineStringLiterals" : "never",
"respectsExistingLineBreaks" : true,
"rules" : {
"AllPublicDeclarationsHaveDocumentation" : true,
"AlwaysUseLiteralForEmptyCollectionInit" : true,
"AlwaysUseLowerCamelCase" : true,
"AmbiguousTrailingClosureOverload" : true,
"AvoidRetroactiveConformances" : true,
"BeginDocumentationCommentWithOneLineSummary" : true,
"DoNotUseSemicolons" : true,
"DontRepeatTypeInStaticProperties" : true,
"FileScopedDeclarationPrivacy" : true,
"FullyIndirectEnum" : true,
"GroupNumericLiterals" : true,
"IdentifiersMustBeASCII" : true,
"NeverForceUnwrap" : true,
"NeverUseForceTry" : true,
"NeverUseImplicitlyUnwrappedOptionals" : true,
"NoAccessLevelOnExtensionDeclaration" : false,
"NoAssignmentInExpressions" : true,
"NoBlockComments" : true,
"NoCasesWithOnlyFallthrough" : true,
"NoEmptyLinesOpeningClosingBraces" : true,
"NoEmptyTrailingClosureParentheses" : true,
"NoLabelsInCasePatterns" : true,
"NoLeadingUnderscores" : true,
"NoParensAroundConditions" : true,
"NoPlaygroundLiterals" : true,
"NoVoidReturnOnFunctionSignature" : true,
"OmitExplicitReturns" : true,
"OneCasePerLine" : true,
"OneVariableDeclarationPerLine" : true,
"OnlyOneTrailingClosureArgument" : true,
"OrderedImports" : true,
"ReplaceForEachWithForLoop" : true,
"ReturnVoidInsteadOfEmptyTuple" : true,
"TypeNamesShouldBeCapitalized" : true,
"UseEarlyExits" : true,
"UseExplicitNilCheckInConditions" : true,
"UseLetInEveryBoundCaseVariable" : true,
"UseShorthandTypeNames" : true,
"UseSingleLinePropertyGetter" : true,
"UseSynthesizedInitializer" : true,
"UseTripleSlashForDocumentationComments" : true,
"UseWhereClausesInForLoops" : true,
"ValidateDocumentationComments" : true
},
"spacesAroundRangeFormationOperators" : true,
"spacesBeforeEndOfLineComments" : 2,
"tabWidth" : 8,
"version" : 1
}
+12 -1
View File
@@ -2,7 +2,7 @@
## ##
## This source file is part of the App Store Connect Service open source project ## This source file is part of the App Store Connect Service open source project
## ##
## Copyright (c) 2025 Röck+Cöde VoF. and the App Store Connect Service project authors ## Copyright (c) 2026 Röck+Cöde VoF. and the App Store Connect Service project authors
## Licensed under Apache license v2.0 ## Licensed under Apache license v2.0
## ##
## See LICENSE for license information ## See LICENSE for license information
@@ -86,6 +86,17 @@ doc-preview: ## Previews the library documentation in Safari
--include-extended-types \ --include-extended-types \
--enable-inherited-docs --enable-inherited-docs
# OPEN API SPECIFICATION
oas-download: ## Downloads the latest App Store Connect OpenAPI specification document
@mkdir -p $(OAS_DOWNLOAD_FOLDER)
@curl -fsSL \
"$(OAS_DOWNLOAD_URL)" \
-o "$(OAS_DOWNLOAD_INPUT)"
@unzip -q "$(OAS_DOWNLOAD_INPUT)" -d "$(OAS_DOWNLOAD_FOLDER)"
@mv "$(OAS_DOWNLOAD_FILE)" "$(OAS_DOWNLOAD_OUTPUT)"
@rm -rf "$(OAS_DOWNLOAD_FOLDER)"
# IDE # IDE
ide-xcode: ## Opens this package with Xcode ide-xcode: ## Opens this package with Xcode
+1 -1
View File
@@ -5,7 +5,7 @@ Please visit the App Store Connect Service web site for more information:
* https://github.com/rock-n-code/asconnect-service * https://github.com/rock-n-code/asconnect-service
Copyright 2025 Röck+Cöde VoF Copyright 2026 Röck+Cöde VoF
The Marvel Service Project licenses this file to you under the Apache License, The Marvel Service Project licenses this file to you under the Apache License,
version 2.0 (the "License"); you may not use this file except in compliance version 2.0 (the "License"); you may not use this file except in compliance
+2 -2
View File
@@ -4,7 +4,7 @@
// //
// This source file is part of the ASConnectService open source project // This source file is part of the ASConnectService open source project
// //
// Copyright (c) 2025 Röck+Cöde VoF. and the ASConnectService project authors // Copyright (c) 2026 Röck+Cöde VoF. and the ASConnectService project authors
// Licensed under the EUPL 1.2 or later. // Licensed under the EUPL 1.2 or later.
// //
// See LICENSE for license information // See LICENSE for license information
@@ -30,7 +30,7 @@ let package = Package(
), ),
], ],
dependencies: [ dependencies: [
.package(url: "https://github.com/apple/swift-openapi-generator.git", from: "1.3.0"), .package(url: "https://github.com/apple/swift-openapi-generator.git", exact: "1.11.0"),
.package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.5.0"), .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.5.0"),
.package(url: "https://github.com/apple/swift-openapi-urlsession", from: "1.0.2"), .package(url: "https://github.com/apple/swift-openapi-urlsession", from: "1.0.2"),
.package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0") .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0")
+112
View File
@@ -1 +1,113 @@
# App Store Connect Service (ASConnectService) # App Store Connect Service (ASConnectService)
A Swift client library for the App Store Connect API, generated from the official OpenAPI specification.
## Overview
`ASConnectService` provides a type-safe, Swift-native interface to Apple's App Store Connect API. This package enables developers to programmatically interact with App Store Connect services for managing apps, builds, reviews, sales reports, and more.
The library is automatically generated from the official App Store Connect API OpenAPI specification using Apple's [swift-openapi-generator](https://github.com/apple/swift-openapi-generator), ensuring complete API coverage and type safety.
## Installation
Add `ASConnectService` as a dependency in your `Package.swift` file:
```swift
dependencies: [
.package(url: "https://github.com/rock-n-code/asconnect-service.git", from: "1.0.0")
]
```
Then add it to your target dependencies:
```swift
.target(
name: "YourTarget",
dependencies: [
"ASConnectService"
]
)
```
## Usage
### Creating a Client
Create a `Client` instance by providing a server URL and transport. Use `BearerAuthMiddleware` to authenticate requests with a JSON Web Token (JWT).
The App Store Connect API requires authentication using API keys. You'll need to:
1. Create an API key in App Store Connect
2. Generate a signed JWT token using your key ID, issuer ID, and private key
3. Pass the token to the built-in `BearerAuthMiddleware` when creating the client
```swift
import ASConnectService
import OpenAPIURLSession
let client = Client(
serverURL: try Servers.server1(),
transport: URLSessionTransport(),
middlewares: [
BearerAuthMiddleware(token: yourJWTToken)
]
)
```
### Making API Calls
The `Client` conforms to `APIProtocol`, which defines a method for every endpoint in the App Store Connect API. Each method accepts an `Input` value and returns an `Output` value with the response.
```swift
let response = try await client.appsGetCollection(.init())
```
## Supported Platforms
- iOS 13.0+
- macOS 10.15+
- tvOS 13.0+
- visionOS 1.0+
- watchOS 6.0+
## Development
### Building
```bash
make lib-build
```
### Testing
```bash
make lib-test
```
### Generating Documentation
```bash
# Generate documentation archive (for Xcode)
make doc-generate-archive
# Generate documentation for static hosting (e.g. GitHub Pages)
make doc-generate-github
# Preview documentation locally in Safari
make doc-preview
```
## Dependencies
- [swift-openapi-generator](https://github.com/apple/swift-openapi-generator)
- [swift-openapi-runtime](https://github.com/apple/swift-openapi-runtime)
- [swift-openapi-urlsession](https://github.com/apple/swift-openapi-urlsession)
- [swift-docc-plugin](https://github.com/swiftlang/swift-docc-plugin)
## License
Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details.
## Contributing
See [CONTRIBUTORS](CONTRIBUTORS) for the list of project authors.
@@ -0,0 +1,38 @@
# ``Client``
The API client for performing HTTP operations against the App Store Connect API.
## Overview
The ``Client`` struct is the main entry point for interacting with the App Store Connect API. It conforms to ``APIProtocol`` and provides concrete implementations for all available API operations.
### Creating a Client
Create a ``Client`` by providing a server URL, a transport, and optionally a list of middlewares for authentication or request customization.
```swift
import ASConnectService
import OpenAPIURLSession
let client = Client(
serverURL: try Servers.server1(),
transport: URLSessionTransport(),
middlewares: [
BearerAuthMiddleware(token: yourJWTToken)
]
)
```
### Making API Calls
Each method on the ``Client`` corresponds to an HTTP endpoint defined in the App Store Connect API OpenAPI specification. Methods accept an `Input` value and return an `Output` value representing the response.
```swift
let response = try await client.appsGetCollection(.init())
```
## Topics
### Creating a Client
- ``init(serverURL:configuration:transport:middlewares:)``
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,24 @@
# ``Servers``
Server URLs defined in the OpenAPI document.
## Overview
The ``Servers`` namespace provides access to the base URLs defined in the App Store Connect API OpenAPI specification. Use these URLs when creating a ``Client`` instance.
```swift
let client = Client(
serverURL: try Servers.Server1.url(),
transport: URLSessionTransport(),
middlewares: [
BearerAuthMiddleware(token: yourJWTToken)
]
)
```
## Topics
### Server URLs
- ``Servers/Server1``
- ``Servers/server1()``
@@ -1,13 +1,51 @@
# ``ASConnectService`` # ``ASConnectService``
<!--@START_MENU_TOKEN@-->Summary<!--@END_MENU_TOKEN@--> A Swift client library for the App Store Connect API, generated from the official OpenAPI specification.
## Overview ## Overview
<!--@START_MENU_TOKEN@-->Text<!--@END_MENU_TOKEN@--> ``ASConnectService`` provides a type-safe, Swift-native interface to Apple's App Store Connect API. This package enables developers to programmatically interact with App Store Connect services for managing apps, builds, reviews, sales reports, and more.
The library is automatically generated from the official App Store Connect API OpenAPI specification using Apple's [swift-openapi-generator](https://github.com/apple/swift-openapi-generator), ensuring complete API coverage and type safety.
### Creating a Client
Create a ``Client`` instance by providing a server URL and transport. Use ``BearerAuthMiddleware`` to authenticate requests with a JSON Web Token (JWT).
```swift
import ASConnectService
import OpenAPIURLSession
let client = Client(
serverURL: try Servers.server1(),
transport: URLSessionTransport(),
middlewares: [
BearerAuthMiddleware(token: yourJWTToken)
]
)
```
### Making API Calls
The ``Client`` conforms to ``APIProtocol``, which defines a method for every endpoint in the App Store Connect API. Each method accepts an `Input` value and returns an `Output` value with the response.
```swift
let response = try await client.appsGetCollection(.init())
```
## Topics ## Topics
### <!--@START_MENU_TOKEN@-->Group<!--@END_MENU_TOKEN@--> ### API Client
- <!--@START_MENU_TOKEN@-->``Symbol``<!--@END_MENU_TOKEN@--> - ``Client``
- ``APIProtocol``
### Authentication
- ``BearerAuthMiddleware``
### Generated Types
- ``Components``
- ``Operations``
- ``Servers``
@@ -0,0 +1,80 @@
// ===----------------------------------------------------------------------===
//
// This source file is part of the ASConnectService open source project
//
// Copyright (c) 2026 Röck+Cöde VoF. and the ASConnectService project authors
// Licensed under Apache license v2.0
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of ASConnectService project authors
//
// SPDX-License-Identifier: Apache-2.0
//
// ===----------------------------------------------------------------------===
import struct Foundation.URL
import struct Foundation.URLComponents
import struct Foundation.URLQueryItem
import struct HTTPTypes.HTTPField
import struct HTTPTypes.HTTPFields
import struct HTTPTypes.HTTPRequest
import struct HTTPTypes.HTTPResponse
import protocol OpenAPIRuntime.ClientMiddleware
import class OpenAPIRuntime.HTTPBody
/// A client middleware that injects a Bearer authentication token into outgoing HTTP requests.
///
/// This middleware appends an `Authorization` header with a Bearer token to every request
/// before forwarding it to the next handler in the middleware chain. It is intended for use
/// with the App Store Connect API, which requires JSON Web Token (JWT) authentication.
///
/// ## Usage
///
/// ```swift
/// let middleware = BearerAuthMiddleware(token: "your-jwt-token")
/// ```
public struct BearerAuthMiddleware {
// MARK: Properties
/// The Bearer token to include in the `Authorization` header of each request.
private let token: String
// MARK: Initializers
/// Creates a new middleware instance with the given Bearer token.
/// - Parameter token: A JSON Web Token (JWT) string used to authenticate requests to the App Store Connect API.
init(token: String) {
self.token = token
}
}
// MARK: - ClientMiddleware
extension BearerAuthMiddleware: ClientMiddleware {
// MARK: Methods
/// Intercepts an outgoing HTTP request and adds a Bearer authentication token to its headers.
/// - Parameters:
/// - request: The original HTTP request.
/// - body: The optional body of the request.
/// - baseURL: The base URL for the request.
/// - operationID: The identifier of the API operation being performed.
/// - next: The next handler in the middleware chain.
/// - Returns: The HTTP response and optional body returned by the next handler.
public func intercept(
_ request: HTTPRequest,
body: HTTPBody?,
baseURL: URL,
operationID: String,
next: @Sendable (HTTPRequest, HTTPBody?, URL) async throws -> (HTTPResponse, HTTPBody?)
) async throws -> (HTTPResponse, HTTPBody?) {
var request = request
request.headerFields[.authorization] = "Bearer \(token)"
return try await next(
request,
body,
baseURL
)
}
}
@@ -1,2 +0,0 @@
// The Swift Programming Language
// https://docs.swift.org/swift-book
@@ -2,7 +2,7 @@
## ##
## This source file is part of the App Store Connect Service open source project ## This source file is part of the App Store Connect Service open source project
## ##
## Copyright (c) 2025 Röck+Cöde VoF. and the App Store Connect Service project authors ## Copyright (c) 2026 Röck+Cöde VoF. and the App Store Connect Service project authors
## Licensed under Apache license v2.0 ## Licensed under Apache license v2.0
## ##
## See LICENSE for license information ## See LICENSE for license information
@@ -15,5 +15,5 @@
generate: generate:
- types - types
- client - client
namingStrategy: defensive namingStrategy: idiomatic
accessModifier: public accessModifier: public
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,140 @@
import Foundation
import HTTPTypes
import OpenAPIRuntime
import Testing
@testable import ASConnectService
@Suite("BearerAuthMiddleware")
struct BearerAuthMiddlewareTests {
// MARK: Tests
@Test("Adds the Authorization header with the Bearer token to the request")
func addsAuthorizationHeader() async throws {
let middleware = BearerAuthMiddleware(token: .Token.jwt)
let (response, _) = try await middleware.intercept(
.get,
body: nil,
baseURL: .base,
operationID: "listApps",
next: { interceptedRequest, body, baseURL in
#expect(interceptedRequest.headerFields[.authorization] == "Bearer test-jwt-token")
return (HTTPResponse(status: .ok), nil)
}
)
#expect(response.status == .ok)
}
@Test("Forwards the request body to the next handler")
func forwardsRequestBody() async throws {
let middleware = BearerAuthMiddleware(token: .Token.jwt)
let requestBody: HTTPBody = HTTPBody("request-body")
var receivedBody: HTTPBody?
_ = try await middleware.intercept(
.get,
body: requestBody,
baseURL: .base,
operationID: "createApp",
next: { _, body, _ in
receivedBody = body
return (HTTPResponse(status: .created), nil)
}
)
let bodyData = try await Data(collecting: try #require(receivedBody), upTo: .max)
#expect(bodyData == Data("request-body".utf8))
}
@Test("Forwards the base URL to the next handler")
func forwardsBaseURL() async throws {
let middleware = BearerAuthMiddleware(token: .Token.jwt)
let expectedBaseURL = URL.base
_ = try await middleware.intercept(
.get,
body: nil,
baseURL: expectedBaseURL,
operationID: "listApps",
next: { _, _, baseURL in
#expect(baseURL == expectedBaseURL)
return (HTTPResponse(status: .ok), nil)
}
)
}
@Test("Returns the response from the next handler")
func returnsNextHandlerResponse() async throws {
let middleware = BearerAuthMiddleware(token: .Token.jwt)
let expectedBody: HTTPBody = HTTPBody("response-body")
let (response, body) = try await middleware.intercept(
.get,
body: nil,
baseURL: .base,
operationID: "listApps",
next: { _, _, _ in
(HTTPResponse(status: .notFound), expectedBody)
}
)
#expect(response.status == .notFound)
let bodyData = try await Data(collecting: try #require(body), upTo: .max)
#expect(bodyData == Data("response-body".utf8))
}
@Test("Preserves existing request headers")
func preservesExistingHeaders() async throws {
let middleware = BearerAuthMiddleware(token: .Token.jwt)
var request = HTTPRequest.get
request.headerFields[.contentType] = "application/json"
_ = try await middleware.intercept(
request,
body: nil,
baseURL: .base,
operationID: "listApps",
next: { interceptedRequest, _, _ in
#expect(interceptedRequest.headerFields[.contentType] == "application/json")
#expect(interceptedRequest.headerFields[.authorization] == "Bearer \(String.Token.jwt)")
return (HTTPResponse(status: .ok), nil)
}
)
}
}
// MARK: - HTTPRequest+Samples
private extension HTTPRequest {
static let get = HTTPRequest(
method: .get,
scheme: "https",
authority: "api.appstoreconnect.apple.com",
path: "/v1/apps"
)
}
// MARK: - String+Samples
private extension String {
enum Token {
static let jwt: String = "test-jwt-token"
}
}
// MARK: - URL+Samples
private extension URL {
static let base = URL(string: "https://api.appstoreconnect.apple.com")!
}
@@ -1,6 +0,0 @@
import Testing
@testable import asconnect_service
@Test func example() async throws {
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
}