Skip to content

Commit 26275b6

Browse files
authored
[Collections] Add listPackages API (#3566)
* [Collections] Add listPackages API rdar://79528550 * Make 'collections' param optional
1 parent d3afe33 commit 26275b6

File tree

3 files changed

+140
-0
lines changed

3 files changed

+140
-0
lines changed

Sources/PackageCollections/API.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,16 @@ public protocol PackageCollectionsProtocol {
132132
callback: @escaping (Result<PackageCollectionsModel.PackageMetadata, Error>) -> Void
133133
)
134134

135+
/// Lists packages from the specified collections.
136+
///
137+
/// - Parameters:
138+
/// - collections: Optional. If specified, only packages in these collections are included.
139+
/// - callback: The closure to invoke when result becomes available
140+
func listPackages(
141+
collections: Set<PackageCollectionsModel.CollectionIdentifier>?,
142+
callback: @escaping (Result<PackageCollectionsModel.PackageSearchResult, Error>) -> Void
143+
)
144+
135145
// MARK: - Target (Module) APIs
136146

137147
/// List all known targets.

Sources/PackageCollections/PackageCollections.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,40 @@ public struct PackageCollections: PackageCollectionsProtocol {
307307
}
308308
}
309309

310+
public func listPackages(collections: Set<PackageCollectionsModel.CollectionIdentifier>? = nil,
311+
callback: @escaping (Result<PackageCollectionsModel.PackageSearchResult, Error>) -> Void) {
312+
self.listCollections(identifiers: collections) { result in
313+
switch result {
314+
case .failure(let error):
315+
callback(.failure(error))
316+
case .success(let collections):
317+
var packageCollections = [PackageReference: (package: Model.Package, collections: Set<Model.CollectionIdentifier>)]()
318+
// Use package data from the most recently processed collection
319+
collections.sorted(by: { $0.lastProcessedAt > $1.lastProcessedAt }).forEach { collection in
320+
collection.packages.forEach { package in
321+
var entry = packageCollections.removeValue(forKey: package.reference)
322+
if entry == nil {
323+
entry = (package, .init())
324+
}
325+
326+
if var entry = entry {
327+
entry.collections.insert(collection.identifier)
328+
packageCollections[package.reference] = entry
329+
}
330+
}
331+
}
332+
333+
let result = PackageCollectionsModel.PackageSearchResult(
334+
items: packageCollections.sorted { $0.key.identity < $1.key.identity }
335+
.map { entry in
336+
.init(package: entry.value.package, collections: Array(entry.value.collections))
337+
}
338+
)
339+
callback(.success(result))
340+
}
341+
}
342+
}
343+
310344
// MARK: - Package Metadata
311345

312346
public func getPackageMetadata(_ reference: PackageReference,

Tests/PackageCollectionsTests/PackageCollectionsTests.swift

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1486,6 +1486,102 @@ final class PackageCollectionsTests: XCTestCase {
14861486
let delta = Date().timeIntervalSince(start)
14871487
XCTAssert(delta < 1.0, "should fetch quickly, took \(delta)")
14881488
}
1489+
1490+
func testListPackages() throws {
1491+
try skipIfUnsupportedPlatform()
1492+
1493+
let configuration = PackageCollections.Configuration()
1494+
let storage = makeMockStorage()
1495+
defer { XCTAssertNoThrow(try storage.close()) }
1496+
1497+
var mockCollections = makeMockCollections(count: 5)
1498+
1499+
let mockTargets = [UUID().uuidString, UUID().uuidString].map {
1500+
PackageCollectionsModel.Target(name: $0, moduleName: $0)
1501+
}
1502+
1503+
let mockProducts = [PackageCollectionsModel.Product(name: UUID().uuidString, type: .executable, targets: [mockTargets.first!]),
1504+
PackageCollectionsModel.Product(name: UUID().uuidString, type: .executable, targets: mockTargets)]
1505+
let toolsVersion = ToolsVersion(string: "5.2")!
1506+
let mockManifest = PackageCollectionsModel.Package.Version.Manifest(
1507+
toolsVersion: toolsVersion,
1508+
packageName: UUID().uuidString,
1509+
targets: mockTargets,
1510+
products: mockProducts,
1511+
minimumPlatformVersions: nil
1512+
)
1513+
1514+
let mockVersion = PackageCollectionsModel.Package.Version(version: TSCUtility.Version(1, 0, 0),
1515+
title: nil,
1516+
summary: nil,
1517+
manifests: [toolsVersion: mockManifest],
1518+
defaultToolsVersion: toolsVersion,
1519+
verifiedCompatibility: nil,
1520+
license: nil,
1521+
createdAt: nil)
1522+
1523+
let mockPackage = PackageCollectionsModel.Package(repository: .init(url: "https://packages.mock/\(UUID().uuidString)"),
1524+
summary: UUID().uuidString,
1525+
keywords: [UUID().uuidString, UUID().uuidString],
1526+
versions: [mockVersion],
1527+
watchersCount: nil,
1528+
readmeURL: nil,
1529+
license: nil,
1530+
authors: nil,
1531+
languages: nil)
1532+
1533+
let mockCollection = PackageCollectionsModel.Collection(source: .init(type: .json, url: URL(string: "https://feed.mock/\(UUID().uuidString)")!),
1534+
name: UUID().uuidString,
1535+
overview: UUID().uuidString,
1536+
keywords: [UUID().uuidString, UUID().uuidString],
1537+
packages: [mockPackage],
1538+
createdAt: Date(),
1539+
createdBy: nil,
1540+
signature: nil)
1541+
1542+
let mockCollection2 = PackageCollectionsModel.Collection(source: .init(type: .json, url: URL(string: "https://feed.mock/\(UUID().uuidString)")!),
1543+
name: UUID().uuidString,
1544+
overview: UUID().uuidString,
1545+
keywords: [UUID().uuidString, UUID().uuidString],
1546+
packages: [mockPackage],
1547+
createdAt: Date(),
1548+
createdBy: nil,
1549+
signature: nil)
1550+
1551+
mockCollections.append(mockCollection)
1552+
mockCollections.append(mockCollection2)
1553+
1554+
let collectionProviders = [PackageCollectionsModel.CollectionSourceType.json: MockCollectionsProvider(mockCollections)]
1555+
let metadataProvider = MockMetadataProvider([:])
1556+
let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider)
1557+
1558+
try mockCollections.forEach { collection in
1559+
_ = try tsc_await { callback in packageCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }, callback: callback) }
1560+
}
1561+
1562+
do {
1563+
let fetchCollections = Set(mockCollections.map { $0.identifier } + [mockCollection.identifier, mockCollection2.identifier])
1564+
let expectedPackages = Set(mockCollections.flatMap { $0.packages.map { $0.reference } } + [mockPackage.reference])
1565+
let expectedCollections = Set([mockCollection.identifier, mockCollection2.identifier])
1566+
1567+
let searchResult = try tsc_await { callback in packageCollections.listPackages(collections: fetchCollections, callback: callback) }
1568+
XCTAssertEqual(searchResult.items.count, expectedPackages.count, "list count should match")
1569+
XCTAssertEqual(Set(searchResult.items.map { $0.package.reference }), expectedPackages, "items should match")
1570+
XCTAssertEqual(Set(searchResult.items.first(where: { $0.package.reference == mockPackage.reference })?.collections ?? []), expectedCollections, "collections should match")
1571+
}
1572+
1573+
// Call API for specific collections
1574+
do {
1575+
let fetchCollections = Set([mockCollections[0].identifier, mockCollection.identifier, mockCollection2.identifier])
1576+
let expectedPackages = Set(mockCollections[0].packages.map { $0.reference } + [mockPackage.reference])
1577+
let expectedCollections = Set([mockCollection.identifier, mockCollection2.identifier])
1578+
1579+
let searchResult = try tsc_await { callback in packageCollections.listPackages(collections: fetchCollections, callback: callback) }
1580+
XCTAssertEqual(searchResult.items.count, expectedPackages.count, "list count should match")
1581+
XCTAssertEqual(Set(searchResult.items.map { $0.package.reference }), expectedPackages, "items should match")
1582+
XCTAssertEqual(Set(searchResult.items.first(where: { $0.package.reference == mockPackage.reference })?.collections ?? []), expectedCollections, "collections should match")
1583+
}
1584+
}
14891585
}
14901586

14911587
private extension XCTestCase {

0 commit comments

Comments
 (0)