Skip to content

Commit aadd434

Browse files
committed
Update DocumentationContext initializer to be async
1 parent 6fa695d commit aadd434

File tree

132 files changed

+2272
-2296
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

132 files changed

+2272
-2296
lines changed

Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift

Lines changed: 52 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2021-2024 Apple Inc. and the Swift project authors
4+
Copyright (c) 2021-2025 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See https://swift.org/LICENSE.txt for license information
@@ -44,68 +44,68 @@ public struct ConvertService: DocumentationService {
4444
_ message: DocumentationServer.Message,
4545
completion: @escaping (DocumentationServer.Message) -> ()
4646
) {
47-
let conversionResult = retrievePayload(message)
48-
.flatMap(decodeRequest)
49-
.flatMap(convert)
50-
.flatMap(encodeResponse)
51-
52-
switch conversionResult {
53-
case .success(let response):
54-
completion(
55-
DocumentationServer.Message(
56-
type: Self.convertResponseMessageType,
57-
identifier: "\(message.identifier)-response",
58-
payload: response
47+
Task {
48+
let result = await process(message)
49+
completion(result)
50+
}
51+
}
52+
53+
public func process(_ message: DocumentationServer.Message) async -> DocumentationServer.Message {
54+
func makeErrorResponse(_ error: ConvertServiceError) -> DocumentationServer.Message {
55+
DocumentationServer.Message(
56+
type: Self.convertResponseErrorMessageType,
57+
identifier: "\(message.identifier)-response-error",
58+
59+
// Force trying because encoding known messages should never fail.
60+
payload: try! JSONEncoder().encode(error)
61+
)
62+
}
63+
64+
guard let payload = message.payload else {
65+
return makeErrorResponse(.missingPayload())
66+
}
67+
68+
let request: ConvertRequest
69+
do {
70+
request = try JSONDecoder().decode(ConvertRequest.self, from: payload)
71+
} catch {
72+
return makeErrorResponse(.invalidRequest(underlyingError: error.localizedDescription))
73+
}
74+
75+
let renderNodes: [RenderNode]
76+
let renderReferenceStore: RenderReferenceStore?
77+
do {
78+
(renderNodes, renderReferenceStore) = try await convert(request: request, messageIdentifier: message.identifier)
79+
} catch {
80+
return makeErrorResponse(.conversionError(underlyingError: error.localizedDescription))
81+
}
82+
83+
do {
84+
let encoder = JSONEncoder()
85+
let encodedResponse = try encoder.encode(
86+
try ConvertResponse(
87+
renderNodes: renderNodes.map(encoder.encode),
88+
renderReferenceStore: renderReferenceStore.map(encoder.encode)
5989
)
6090
)
6191

62-
case .failure(let error):
63-
completion(
64-
DocumentationServer.Message(
65-
type: Self.convertResponseErrorMessageType,
66-
identifier: "\(message.identifier)-response-error",
67-
68-
// Force trying because encoding known messages should never fail.
69-
payload: try! JSONEncoder().encode(error)
70-
)
92+
return DocumentationServer.Message(
93+
type: Self.convertResponseMessageType,
94+
identifier: "\(message.identifier)-response",
95+
payload: encodedResponse
7196
)
72-
}
73-
}
74-
75-
/// Attempts to retrieve the payload from the given message, returning a failure if the payload is missing.
76-
///
77-
/// - Returns: A result with the message's payload if present, otherwise a ``ConvertServiceError/missingPayload``
78-
/// failure.
79-
private func retrievePayload(
80-
_ message: DocumentationServer.Message
81-
) -> Result<(payload: Data, messageIdentifier: String), ConvertServiceError> {
82-
message.payload.map { .success(($0, message.identifier)) } ?? .failure(.missingPayload())
83-
}
84-
85-
/// Attempts to decode the given request, returning a failure if decoding failed.
86-
///
87-
/// - Returns: A result with the decoded request if the decoding succeeded, otherwise a
88-
/// ``ConvertServiceError/invalidRequest`` failure.
89-
private func decodeRequest(
90-
data: Data,
91-
messageIdentifier: String
92-
) -> Result<(request: ConvertRequest, messageIdentifier: String), ConvertServiceError> {
93-
Result {
94-
return (try JSONDecoder().decode(ConvertRequest.self, from: data), messageIdentifier)
95-
}.mapErrorToConvertServiceError {
96-
.invalidRequest(underlyingError: $0.localizedDescription)
97+
} catch {
98+
return makeErrorResponse(.invalidResponseMessage(underlyingError: error.localizedDescription))
9799
}
98100
}
99101

100102
/// Attempts to process the given convert request, returning a failure if the conversion failed.
101103
///
102-
/// - Returns: A result with the produced render nodes if the conversion was successful, otherwise a
103-
/// ``ConvertServiceError/conversionError`` failure.
104+
/// - Returns: A result with the produced render nodes if the conversion was successful
104105
private func convert(
105106
request: ConvertRequest,
106107
messageIdentifier: String
107-
) -> Result<([RenderNode], RenderReferenceStore?), ConvertServiceError> {
108-
Result {
108+
) async throws -> ([RenderNode], RenderReferenceStore?) {
109109
// Update DocC's current feature flags based on the ones provided
110110
// in the request.
111111
FeatureFlags.current = request.featureFlags
@@ -155,7 +155,7 @@ public struct ConvertService: DocumentationService {
155155
(bundle, dataProvider) = Self.makeBundleAndInMemoryDataProvider(request)
156156
}
157157

158-
let context = try DocumentationContext(bundle: bundle, dataProvider: dataProvider, configuration: configuration)
158+
let context = try await DocumentationContext(bundle: bundle, dataProvider: dataProvider, configuration: configuration)
159159

160160
// Precompute the render context
161161
let renderContext = RenderContext(documentationContext: context, bundle: bundle)
@@ -228,30 +228,6 @@ public struct ConvertService: DocumentationService {
228228
}
229229

230230
return (renderNodes, referenceStore)
231-
}.mapErrorToConvertServiceError {
232-
.conversionError(underlyingError: $0.localizedDescription)
233-
}
234-
}
235-
236-
/// Encodes a conversion response to send to the client.
237-
///
238-
/// - Parameter renderNodes: The render nodes that were produced as part of the conversion.
239-
private func encodeResponse(
240-
renderNodes: [RenderNode],
241-
renderReferenceStore: RenderReferenceStore?
242-
) -> Result<Data, ConvertServiceError> {
243-
Result {
244-
let encoder = JSONEncoder()
245-
246-
return try encoder.encode(
247-
try ConvertResponse(
248-
renderNodes: renderNodes.map(encoder.encode),
249-
renderReferenceStore: renderReferenceStore.map(encoder.encode)
250-
)
251-
)
252-
}.mapErrorToConvertServiceError {
253-
.invalidResponseMessage(underlyingError: $0.localizedDescription)
254-
}
255231
}
256232

257233
/// Takes a base reference store and adds uncurated article references and documentation extensions.
@@ -297,25 +273,6 @@ public struct ConvertService: DocumentationService {
297273
}
298274
}
299275

300-
extension Result {
301-
/// Returns a new result, mapping any failure value using the given transformation if the error is not a conversion error.
302-
///
303-
/// If the error value is a ``ConvertServiceError``, it is returned as-is. If it's not, the given transformation is called on the
304-
/// error.
305-
///
306-
/// - Parameter transform: A closure that takes the failure value of the instance.
307-
func mapErrorToConvertServiceError(
308-
_ transform: (any Error) -> ConvertServiceError
309-
) -> Result<Success, ConvertServiceError> {
310-
mapError { error in
311-
switch error {
312-
case let error as ConvertServiceError: return error
313-
default: return transform(error)
314-
}
315-
}
316-
}
317-
}
318-
319276
private extension SymbolGraph.LineList.Line {
320277
/// Creates a line given a convert request line.
321278
init(_ line: ConvertRequest.Line) {

Sources/SwiftDocC/Infrastructure/DocumentationContext.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ public class DocumentationContext {
323323
dataProvider: any DataProvider,
324324
diagnosticEngine: DiagnosticEngine = .init(),
325325
configuration: Configuration = .init()
326-
) throws {
326+
) async throws {
327327
self.bundle = bundle
328328
self.dataProvider = .new(dataProvider)
329329
self.diagnosticEngine = diagnosticEngine

Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -288,9 +288,9 @@ public struct ConvertAction: AsyncAction {
288288

289289
let indexer = try Indexer(outputURL: temporaryFolder, bundleID: bundle.id)
290290

291-
let context = try signposter.withIntervalSignpost("Register", id: signposter.makeSignpostID()) {
292-
try DocumentationContext(bundle: bundle, dataProvider: dataProvider, diagnosticEngine: diagnosticEngine, configuration: configuration)
293-
}
291+
let registerInterval = signposter.beginInterval("Register", id: signposter.makeSignpostID())
292+
let context = try await DocumentationContext(bundle: bundle, dataProvider: dataProvider, diagnosticEngine: diagnosticEngine, configuration: configuration)
293+
signposter.endInterval("Register", registerInterval)
294294

295295
let outputConsumer = ConvertFileWritingConsumer(
296296
targetFolder: temporaryFolder,

Sources/SwiftDocCUtilities/Action/Actions/EmitGeneratedCurationAction.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2024 Apple Inc. and the Swift project authors
4+
Copyright (c) 2024-2025 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See https://swift.org/LICENSE.txt for license information
@@ -50,7 +50,7 @@ struct EmitGeneratedCurationAction: AsyncAction {
5050
additionalSymbolGraphFiles: symbolGraphFiles(in: additionalSymbolGraphDirectory)
5151
)
5252
)
53-
let context = try DocumentationContext(bundle: bundle, dataProvider: dataProvider)
53+
let context = try await DocumentationContext(bundle: bundle, dataProvider: dataProvider)
5454

5555
let writer = GeneratedCurationWriter(context: context, catalogURL: catalogURL, outputURL: outputURL)
5656
let curation = try writer.generateDefaultCurationContents(fromSymbol: startingPointSymbolLink, depthLimit: depthLimit)

Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2021-2024 Apple Inc. and the Swift project authors
4+
Copyright (c) 2021-2025 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See https://swift.org/LICENSE.txt for license information
@@ -37,9 +37,9 @@ class ExternalTopicsGraphHashTests: XCTestCase {
3737
}
3838
}
3939

40-
func testNoMetricAddedIfNoExternalTopicsAreResolved() throws {
40+
func testNoMetricAddedIfNoExternalTopicsAreResolved() async throws {
4141
// Load bundle without using external resolvers
42-
let (_, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests")
42+
let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests")
4343
XCTAssertTrue(context.externallyResolvedLinks.isEmpty)
4444

4545
// Try adding external topics metrics
@@ -50,12 +50,12 @@ class ExternalTopicsGraphHashTests: XCTestCase {
5050
XCTAssertNil(testBenchmark.metrics.first?.result, "Metric was added but there was no external links or symbols")
5151
}
5252

53-
func testExternalLinksSameHash() throws {
53+
func testExternalLinksSameHash() async throws {
5454
let externalResolver = self.externalResolver
5555

5656
// Add external links and verify the checksum is always the same
57-
let hashes: [String] = try (0...10).map { _ -> MetricValue? in
58-
let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) { url in
57+
func computeTopicHash(file: StaticString = #filePath, line: UInt = #line) async throws -> String {
58+
let (_, _, context) = try await self.testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) { url in
5959
try """
6060
# ``SideKit/SideClass``
6161
@@ -74,26 +74,24 @@ class ExternalTopicsGraphHashTests: XCTestCase {
7474
let testBenchmark = Benchmark()
7575
benchmark(add: Benchmark.ExternalTopicsHash(context: context), benchmarkLog: testBenchmark)
7676

77-
// Verify that a metric was added
78-
XCTAssertNotNil(testBenchmark.metrics[0].result)
79-
return testBenchmark.metrics[0].result
80-
}
81-
.compactMap { value -> String? in
82-
guard let value,
83-
case MetricValue.checksum(let hash) = value else { return nil }
84-
return hash
77+
return try TopicAnchorHashTests.extractChecksumHash(from: testBenchmark)
8578
}
8679

80+
let expectedHash = try await computeTopicHash()
81+
8782
// Verify the produced topic graph hash is repeatedly the same
88-
XCTAssertTrue(hashes.allSatisfy({ $0 == hashes.first }))
83+
for _ in 0 ..< 10 {
84+
let hash = try await computeTopicHash()
85+
XCTAssertEqual(hash, expectedHash)
86+
}
8987
}
9088

91-
func testLinksAndSymbolsSameHash() throws {
89+
func testLinksAndSymbolsSameHash() async throws {
9290
let externalResolver = self.externalResolver
9391

9492
// Add external links and verify the checksum is always the same
95-
let hashes: [String] = try (0...10).map { _ -> MetricValue? in
96-
let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver], externalSymbolResolver: externalSymbolResolver) { url in
93+
func computeTopicHash(file: StaticString = #filePath, line: UInt = #line) async throws -> String {
94+
let (_, _, context) = try await self.testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver], externalSymbolResolver: self.externalSymbolResolver) { url in
9795
try """
9896
# ``SideKit/SideClass``
9997
@@ -113,25 +111,23 @@ class ExternalTopicsGraphHashTests: XCTestCase {
113111
let testBenchmark = Benchmark()
114112
benchmark(add: Benchmark.ExternalTopicsHash(context: context), benchmarkLog: testBenchmark)
115113

116-
// Verify that a metric was added
117-
XCTAssertNotNil(testBenchmark.metrics[0].result)
118-
return testBenchmark.metrics[0].result
119-
}
120-
.compactMap { value -> String? in
121-
guard let value,
122-
case MetricValue.checksum(let hash) = value else { return nil }
123-
return hash
114+
return try TopicAnchorHashTests.extractChecksumHash(from: testBenchmark)
124115
}
125116

117+
let expectedHash = try await computeTopicHash()
118+
126119
// Verify the produced topic graph hash is repeatedly the same
127-
XCTAssertTrue(hashes.allSatisfy({ $0 == hashes.first }))
120+
for _ in 0 ..< 10 {
121+
let hash = try await computeTopicHash()
122+
XCTAssertEqual(hash, expectedHash)
123+
}
128124
}
129125

130-
func testExternalTopicsDetectsChanges() throws {
126+
func testExternalTopicsDetectsChanges() async throws {
131127
let externalResolver = self.externalResolver
132128

133129
// Load a bundle with external links
134-
let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) { url in
130+
let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) { url in
135131
try """
136132
# ``SideKit/SideClass``
137133

0 commit comments

Comments
 (0)