1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
|
/*
This source file is part of the Swift.org open source project
Copyright (c) 2021-2025 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception
See https://swift.org/LICENSE.txt for license information
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/
import Foundation
import SymbolKit
#if canImport(os)
import os
private let logger = Logger(subsystem: "org.swift.docc", category: "ExternalReferenceResolverServiceClient")
#else
private let logger = NoOpLoggerShim()
#endif
/// A client for performing link resolution requests to a documentation server.
class ExternalReferenceResolverServiceClient {
/// The maximum amount of time, in seconds, to await a response from the external reference resolver.
static let responseTimeout = 5
/// The documentation server to which link resolution requests should be sent to.
var server: DocumentationServer
/// The identifier of the convert request that initiates the reference resolution requests.
var convertRequestIdentifier: String?
/// The queue on which server messages are awaited.
var serverResponseQueue = DispatchQueue(
label: "org.swift.docc.service-external-reference-resolver",
qos: .unspecified
)
private var encoder = JSONEncoder()
init(server: DocumentationServer, convertRequestIdentifier: String?) {
self.server = server
self.convertRequestIdentifier = convertRequestIdentifier
}
func sendAndWait(_ request: some Codable) throws -> Data {
let resultGroup = DispatchGroup()
var result: Result<Data?, Error>?
resultGroup.enter()
serverResponseQueue.async { [weak self] in
guard let self else { return }
do {
let encodedRequest = try self.encoder.encode(
ConvertRequestContextWrapper(
convertRequestIdentifier: self.convertRequestIdentifier,
payload: request
)
)
let message = DocumentationServer.Message(
type: "resolve-reference",
clientName: "SwiftDocC",
payload: encodedRequest
)
let messageData = try self.encoder.encode(message)
self.server.process(messageData) { responseData in
defer { resultGroup.leave() }
result = self.decodeMessage(responseData).map(\.payload)
}
} catch {
result = .failure(.failedToEncodeRequest(underlyingError: error))
resultGroup.leave()
}
}
guard resultGroup.wait(timeout: .now() + .seconds(Self.responseTimeout)) == .success else {
logger.log("Timed out when resolving request.")
throw Error.timeout
}
switch result {
case .success(let data?)?:
return data
case .success?:
logger.log("Received nil payload when resolving request.")
throw Error.missingPayload
case .failure(let error):
switch error {
case .failedToEncodeRequest(let underlyingError):
logger.log("Unable to encode request for request: \(underlyingError.localizedDescription)")
case .invalidResponse(let underlyingError):
logger.log("Received invalid response when resolving request: \(underlyingError.localizedDescription)")
case .invalidResponseType(let receivedType):
logger.log("Received unknown response type when resolving request: '\(receivedType)'")
case .missingPayload:
logger.log("Received nil payload when resolving request.")
case .timeout:
logger.log("Timed out when resolving request.")
case .receivedErrorFromServer(let message):
logger.log("Received error from server when resolving request: \(message)")
case .unknownError:
logger.log("Unknown error when resolving request.")
}
throw error
case nil:
logger.log("Unknown error when resolving request.")
throw Error.unknownError
}
}
private func decodeMessage(_ data: Data) -> Result<DocumentationServer.Message, Error> {
Result {
try JSONDecoder().decode(DocumentationServer.Message.self, from: data)
}.mapError { error in
.invalidResponse(underlyingError: error)
}.flatMap { message in
message.type == "resolve-reference-response" ?
.success(message) :
.failure(.invalidResponseType(receivedType: message.type.rawValue))
}
}
enum Error: Swift.Error {
case failedToEncodeRequest(underlyingError: any Swift.Error)
case invalidResponse(underlyingError: any Swift.Error)
case invalidResponseType(receivedType: String)
case missingPayload
case timeout
case receivedErrorFromServer(message: String)
case unknownError
}
}
|