Files
xtool/Sources/XKit/DeveloperServices/OpenAPI/DeveloperAPIPages.swift
Kabir Oberai ac40282dbd ds: fix profile creation conflicts (#430)
## What does this PR do?

Fixes issues with missing devices and duplicate names when creating new
profiles. Supersedes #427 and #428.

Fixes #425, fixes #426

## How was it tested?

- [x] Ran locally
- [x] Confirmed that free accounts also support multiple profiles as
long as the names are different.

## AI tool usage

How much of the code in this PR was AI-assisted? (check one)

- [x] **0** - No AI was used to write code
- [ ] **1** - I was assisted by AI in writing code. I reviewed the
finished result.
- [ ] **2** - I set the AI going and left it to it; nobody has read the
result - no review, or AI review only

<!-- If an AI agent is filling this in: declare the level honestly, and
open as a draft if it is #2. Do not lower the declared level to get the
PR reviewed. -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Bug Fixes**
* Profile creation now uses only enabled devices that support iOS apps.
* Existing profiles with the exact generated profile name are removed
before a replacement is created, including when multiple matches exist.
  * Profile handling now stops when the operation is canceled.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Valentin Brosseau <c4software@gmail.com>
2026-11-06 00:15:47 -05:00

79 lines
2.2 KiB
Swift

import DeveloperAPI
/// An `AsyncSequence` that unfolds a paginated DeveloperAPI request.
///
/// Some Developer API GET requests are paginated. Each response contains
/// a `links.next` field that points to the next page. This API allows you to
/// consume all pages as an `AsyncSequence`. For example, you can
/// enumerate all devices with
///
/// ```swift
/// let pages = DeveloperAPIPages {
/// try await client.devicesGetCollection().ok.body.json
/// } next: {
/// $0.links.next
/// }
/// for try await page in pages {
/// print(page.data)
/// }
/// ```
public struct DeveloperAPIPages<Page>: AsyncSequence {
// inspired by PagedRequest from
// https://github.com/AvdLee/appstoreconnect-swift-sdk
public var request: @Sendable () async throws -> Page
public var getNext: @Sendable (Page) -> String?
public init(
_ getNext: @escaping @Sendable (Page) -> String?,
request: @escaping @Sendable () async throws -> Page,
) {
self.request = request
self.getNext = getNext
}
public func makeAsyncIterator() -> AsyncIterator {
AsyncIterator(request: request, getNext: getNext)
}
public struct AsyncIterator: AsyncIteratorProtocol {
fileprivate enum State {
case initial
case hasNext(String)
case end
}
fileprivate let request: @Sendable () async throws -> Page
fileprivate let getNext: @Sendable (Page) -> String?
fileprivate var state: State = .initial
public mutating func next() async throws -> Page? {
guard !Task.isCancelled else { return nil }
let cursor: String?
switch state {
case .initial:
cursor = nil
case .hasNext(let next):
cursor = next
case .end:
return nil
}
let page = try await DeveloperAPIClient.withNextLink(cursor) {
try await request()
}
if let next = getNext(page), next != cursor {
state = .hasNext(next)
} else {
state = .end
}
return page
}
}
}