Added cursor pagination support to paged operations.
This commit is contained in:
1 parent
fde2382056
commit
c984be3263
7 files changed
+4340
-4
No files matched your search
@@ -103,10 +103,8 @@ oas-download: ## Downloads the latest App Store Connect OpenAPI specification do
|
|||||||
@rm -rf "$(OAS_DOWNLOAD_FOLDER)"
|
@rm -rf "$(OAS_DOWNLOAD_FOLDER)"
|
||||||
@$(MAKE) --no-print-directory oas-patch
|
@$(MAKE) --no-print-directory oas-patch
|
||||||
|
|
||||||
oas-patch: # Patches the OpenAPI specification document so the generated code compiles
|
oas-patch: # Patches the OpenAPI specification document with the patches in Scripts/patch-oas.jq
|
||||||
@# Removes empty enums (e.g. `fields[appKeywords]`), as they generate case-less Swift enums that do not compile.
|
@jq --from-file Scripts/patch-oas.jq "$(OAS_DOWNLOAD_OUTPUT)" > "$(OAS_DOWNLOAD_OUTPUT).tmp"
|
||||||
@jq '(.. | objects | select(.enum == [])) |= del(.enum)' \
|
|
||||||
"$(OAS_DOWNLOAD_OUTPUT)" > "$(OAS_DOWNLOAD_OUTPUT).tmp"
|
|
||||||
@mv "$(OAS_DOWNLOAD_OUTPUT).tmp" "$(OAS_DOWNLOAD_OUTPUT)"
|
@mv "$(OAS_DOWNLOAD_OUTPUT).tmp" "$(OAS_DOWNLOAD_OUTPUT)"
|
||||||
|
|
||||||
# IDE
|
# IDE
|
||||||
|
|||||||
@@ -73,6 +73,21 @@ let apps = try response.ok.body.json.data
|
|||||||
|
|
||||||
The `ok` property throws for any other response. To handle those, switch over the `Output` cases, such as `.unauthorized` or `.undocumented(statusCode:_:)`.
|
The `ok` property throws for any other response. To handle those, switch over the `Output` cases, such as `.unauthorized` or `.undocumented(statusCode:_:)`.
|
||||||
|
|
||||||
|
### Fetching More Pages
|
||||||
|
|
||||||
|
Lists are paged. Pass a page's `links.nextCursor` as the `cursor` of the next request:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
var apps: [Components.Schemas.App] = []
|
||||||
|
var cursor: String?
|
||||||
|
|
||||||
|
repeat {
|
||||||
|
let page = try await client.appsGetCollection(query: .init(limit: 200, cursor: cursor)).ok.body.json
|
||||||
|
apps += page.data
|
||||||
|
cursor = page.links.nextCursor
|
||||||
|
} while cursor != nil
|
||||||
|
```
|
||||||
|
|
||||||
## Supported Platforms
|
## Supported Platforms
|
||||||
|
|
||||||
- iOS 13.0+
|
- iOS 13.0+
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# ===------------------------------------------------------------------------------------===
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
#
|
||||||
|
# ===------------------------------------------------------------------------------------===
|
||||||
|
|
||||||
|
# Patches the App Store Connect OpenAPI specification document. Each patch is safe to apply more than once.
|
||||||
|
|
||||||
|
# Removes empty enums (e.g. `fields[appKeywords]`), as they generate case-less Swift enums that do not compile.
|
||||||
|
(.. | objects | select(.enum == [])) |= del(.enum)
|
||||||
|
|
||||||
|
# Adds the undocumented `cursor` query parameter, used by `next` links, to the operations that return paged responses.
|
||||||
|
| ([
|
||||||
|
.components.schemas
|
||||||
|
| to_entries[]
|
||||||
|
| select(.value.properties.links["$ref"] == "#/components/schemas/PagedDocumentLinks")
|
||||||
|
| "#/components/schemas/" + .key
|
||||||
|
]) as $paged
|
||||||
|
| .paths[][] |= (
|
||||||
|
if type == "object"
|
||||||
|
and (.responses["200"].content["application/json"].schema["$ref"] as $ref | any($paged[]; . == $ref))
|
||||||
|
and (any(.parameters[]?; .name == "cursor") | not)
|
||||||
|
then .parameters += [{
|
||||||
|
"name": "cursor",
|
||||||
|
"in": "query",
|
||||||
|
"description": "cursor of the page to fetch, from the next link of the previous page",
|
||||||
|
"schema": { "type": "string" },
|
||||||
|
"style": "form"
|
||||||
|
}]
|
||||||
|
else . end
|
||||||
|
)
|
||||||
@@ -42,6 +42,21 @@ let apps = try response.ok.body.json.data
|
|||||||
|
|
||||||
The `ok` property throws for any other response. To handle those, switch over the `Output` cases, such as `.unauthorized` or `.undocumented(statusCode:_:)`.
|
The `ok` property throws for any other response. To handle those, switch over the `Output` cases, such as `.unauthorized` or `.undocumented(statusCode:_:)`.
|
||||||
|
|
||||||
|
### Fetching More Pages
|
||||||
|
|
||||||
|
Lists are paged. Pass a page's ``Components/Schemas/PagedDocumentLinks/nextCursor`` as the `cursor` of the next request:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
var apps: [Components.Schemas.App] = []
|
||||||
|
var cursor: String?
|
||||||
|
|
||||||
|
repeat {
|
||||||
|
let page = try await client.appsGetCollection(query: .init(limit: 200, cursor: cursor)).ok.body.json
|
||||||
|
apps += page.data
|
||||||
|
cursor = page.links.nextCursor
|
||||||
|
} while cursor != nil
|
||||||
|
```
|
||||||
|
|
||||||
## Topics
|
## Topics
|
||||||
|
|
||||||
### API Client
|
### API Client
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// ===----------------------------------------------------------------------===
|
||||||
|
//
|
||||||
|
// 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.URLComponents
|
||||||
|
|
||||||
|
extension Components.Schemas.PagedDocumentLinks {
|
||||||
|
// MARK: Properties
|
||||||
|
|
||||||
|
/// The cursor of the next page, or `nil` on the last page.
|
||||||
|
///
|
||||||
|
/// Pass it as the `cursor` query parameter of the same operation to fetch the next page.
|
||||||
|
public var nextCursor: String? {
|
||||||
|
guard let next else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return URLComponents(string: next)?
|
||||||
|
.queryItems?
|
||||||
|
.first { $0.name == "cursor" }?
|
||||||
|
.value
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,51 @@
|
|||||||
|
import Testing
|
||||||
|
|
||||||
|
// A non-testable import ensures that the extension can be used from outside the module.
|
||||||
|
import ASConnectService
|
||||||
|
|
||||||
|
@Suite("PagedDocumentLinks")
|
||||||
|
struct PagedDocumentLinksTests {
|
||||||
|
// MARK: Tests
|
||||||
|
|
||||||
|
@Test("Returns the cursor from the next link")
|
||||||
|
func returnsNextCursor() {
|
||||||
|
let links = Components.Schemas.PagedDocumentLinks(
|
||||||
|
_self: .Link.current,
|
||||||
|
next: "https://api.appstoreconnect.apple.com/v1/apps?cursor=Mg.ANmP3sY&limit=2"
|
||||||
|
)
|
||||||
|
|
||||||
|
#expect(links.nextCursor == "Mg.ANmP3sY")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Returns nil when there is no next link")
|
||||||
|
func returnsNilWithoutNextLink() {
|
||||||
|
let links = Components.Schemas.PagedDocumentLinks(_self: .Link.current)
|
||||||
|
|
||||||
|
#expect(links.nextCursor == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Returns nil when the next link has no cursor")
|
||||||
|
func returnsNilWithoutCursor() {
|
||||||
|
let links = Components.Schemas.PagedDocumentLinks(
|
||||||
|
_self: .Link.current,
|
||||||
|
next: "https://api.appstoreconnect.apple.com/v1/apps?limit=2"
|
||||||
|
)
|
||||||
|
|
||||||
|
#expect(links.nextCursor == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Passes the cursor to the query of a paged operation")
|
||||||
|
func passesCursorToQuery() {
|
||||||
|
let query = Operations.AppsGetCollection.Input.Query(limit: 2, cursor: "Mg.ANmP3sY")
|
||||||
|
|
||||||
|
#expect(query.cursor == "Mg.ANmP3sY")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - String+Samples
|
||||||
|
|
||||||
|
private extension String {
|
||||||
|
enum Link {
|
||||||
|
static let current: String = "https://api.appstoreconnect.apple.com/v1/apps?limit=2"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user