File: ExternalReferenceResolverServiceClient.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (135 lines) | stat: -rw-r--r-- 5,030 bytes parent folder | download
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
/*
 This source file is part of the Swift.org open source project

 Copyright (c) 2021 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

/// 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 {
            logError(.timeout)
            throw Error.timeout
        }
        
        switch result {
        case .success(let data?)?:
            return data
        case .success?:
            logError(.missingPayload)
            throw Error.missingPayload
        case .failure(let error):
            logError(error)
            throw error
        case nil:
            logError(.unknownError)
            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))
        }
    }
    
    private func logError(_ error: Error) {
        switch error {
        case .failedToEncodeRequest(let underlyingError):
            xlog("Unable to encode request for request: \(underlyingError.localizedDescription)")
        case .invalidResponse(let underlyingError):
            xlog("Received invalid response when resolving request: \(underlyingError.localizedDescription)")
        case .invalidResponseType(let receivedType):
            xlog("Received unknown response type when resolving request: '\(receivedType)'")
        case .missingPayload:
            xlog("Received nil payload when resolving request.")
        case .timeout:
            xlog("Timed out when resolving request.")
        case .receivedErrorFromServer(let message):
            xlog("Received error from server when resolving request: \(message)")
        case .unknownError:
            xlog("Unknown error when resolving request.")
        }
    }

    enum Error: Swift.Error {
        case failedToEncodeRequest(underlyingError: Swift.Error)
        case invalidResponse(underlyingError: Swift.Error)
        case invalidResponseType(receivedType: String)
        case missingPayload
        case timeout
        case receivedErrorFromServer(message: String)
        case unknownError
    }
}