Skip to content

Commit 5c31366

Browse files
use built-in LSP ResponseError with code RequestFailed for errors
1 parent 0a95899 commit 5c31366

File tree

6 files changed

+78
-201
lines changed

6 files changed

+78
-201
lines changed

Contributor Documentation/LSP Extensions.md

Lines changed: 10 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -185,75 +185,25 @@ capabilities.
185185
186186
```ts
187187
export interface DoccDocumentationParams {
188-
/**
189-
* The document to render documentation for.
190-
*/
191-
textDocument: TextDocumentIdentifier;
192-
193-
/**
194-
* The document location at which to lookup symbol information.
195-
*
196-
* This parameter is only used in Swift files to determine which symbol to render.
197-
* The position is ignored for markdown and tutorial documents.
198-
*/
199-
position: Position;
200-
}
201-
202-
export type DoccDocumentationResponse = RenderNodeResponse | ErrorResponse;
203-
204-
interface RenderNodeResponse {
205-
/**
206-
* The type of this response: either a RenderNode or error.
207-
*/
208-
type: "renderNode";
209-
210-
/**
211-
* The JSON encoded RenderNode that can be rendered by swift-docc-render.
212-
*/
213-
renderNode: string;
214-
}
215-
216-
interface ErrorResponse {
217-
/**
218-
* The type of this response: either a RenderNode or error.
219-
*/
220-
type: "error";
221-
222188
/**
223-
* The error that occurred.
189+
* The document to render documentation for.
224190
*/
225-
error: DoccDocumentationError;
226-
}
227-
228-
export type DoccDocumentationError = ErrorWithNoParams | SymbolNotFoundError;
229-
230-
interface ErrorWithNoParams {
231-
/**
232-
* The kind of error that occurred.
233-
*/
234-
kind: "indexNotAvailable" | "noDocumentation";
191+
textDocument: TextDocumentIdentifier;
235192

236193
/**
237-
* A human readable error message that can be shown to the user.
194+
* The document location at which to lookup symbol information.
195+
*
196+
* This parameter is only used in Swift files to determine which symbol to render.
197+
* The position is ignored for markdown and tutorial documents.
238198
*/
239-
message: string;
199+
position?: Position;
240200
}
241201

242-
interface SymbolNotFoundError {
202+
export interface DoccDocumentationResponse {
243203
/**
244-
* The kind of error that occurred.
245-
*/
246-
kind: "symbolNotFound";
247-
248-
/**
249-
* The name of the symbol that could not be found.
250-
*/
251-
symbolName: string;
252-
253-
/**
254-
* A human readable error message that can be shown to the user.
204+
* The JSON encoded RenderNode that can be rendered by swift-docc-render.
255205
*/
256-
message: string;
206+
renderNode: string;
257207
}
258208
```
259209

Sources/LanguageServerProtocol/Error.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ extension ResponseError {
124124
return ResponseError(code: .unknownErrorCode, message: message)
125125
}
126126

127+
public static func requestFailed(_ message: String) -> ResponseError {
128+
return ResponseError(code: .requestFailed, message: message)
129+
}
130+
127131
public static func internalError(_ message: String) -> ResponseError {
128132
return ResponseError(code: .internalError, message: message)
129133
}

Sources/LanguageServerProtocol/Requests/DoccDocumentationRequest.swift

Lines changed: 4 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -44,105 +44,10 @@ public struct DoccDocumentationRequest: TextDocumentRequest, Hashable {
4444
}
4545
}
4646

47-
public enum DoccDocumentationResponse: ResponseType {
48-
case renderNode(String)
49-
case error(DoccDocumentationError)
50-
}
51-
52-
public enum DoccDocumentationError: ResponseType, Equatable {
53-
case indexNotAvailable
54-
case noDocumentation
55-
case symbolNotFound(String)
56-
57-
var message: String {
58-
switch self {
59-
case .indexNotAvailable:
60-
return "The index is not availble to complete the request"
61-
case .noDocumentation:
62-
return "No documentation could be rendered for the position in this document"
63-
case .symbolNotFound(let symbolName):
64-
return "Could not find symbol \(symbolName) in the project"
65-
}
66-
}
67-
}
68-
69-
extension DoccDocumentationError: Codable {
70-
enum CodingKeys: String, CodingKey {
71-
case kind
72-
case message
73-
case symbolName
74-
}
75-
76-
public init(from decoder: Decoder) throws {
77-
let values = try decoder.container(keyedBy: CodingKeys.self)
78-
let kind = try values.decode(String.self, forKey: .kind)
79-
switch kind {
80-
case "indexNotAvailable":
81-
self = .indexNotAvailable
82-
case "noDocumentation":
83-
self = .noDocumentation
84-
case "symbolNotFound":
85-
let symbolName = try values.decode(String.self, forKey: .symbolName)
86-
self = .symbolNotFound(symbolName)
87-
default:
88-
throw DecodingError.dataCorruptedError(
89-
forKey: CodingKeys.kind,
90-
in: values,
91-
debugDescription: "Invalid error kind: \(kind)"
92-
)
93-
}
94-
}
95-
96-
public func encode(to encoder: any Encoder) throws {
97-
var container = encoder.container(keyedBy: CodingKeys.self)
98-
switch self {
99-
case .indexNotAvailable:
100-
try container.encode("indexNotAvailable", forKey: .kind)
101-
case .noDocumentation:
102-
try container.encode("noDocumentation", forKey: .kind)
103-
case .symbolNotFound(let symbolName):
104-
try container.encode("symbolNotFound", forKey: .kind)
105-
try container.encode(symbolName, forKey: .symbolName)
106-
}
107-
try container.encode(message, forKey: .message)
108-
}
109-
}
110-
111-
extension DoccDocumentationResponse: Codable {
112-
enum CodingKeys: String, CodingKey {
113-
case type
114-
case renderNode
115-
case error
116-
}
117-
118-
public init(from decoder: Decoder) throws {
119-
let values = try decoder.container(keyedBy: CodingKeys.self)
120-
let type = try values.decode(String.self, forKey: .type)
121-
switch type {
122-
case "renderNode":
123-
let renderNode = try values.decode(String.self, forKey: .renderNode)
124-
self = .renderNode(renderNode)
125-
case "error":
126-
let error = try values.decode(DoccDocumentationError.self, forKey: .error)
127-
self = .error(error)
128-
default:
129-
throw DecodingError.dataCorruptedError(
130-
forKey: CodingKeys.type,
131-
in: values,
132-
debugDescription: "Invalid type: \(type)"
133-
)
134-
}
135-
}
47+
public struct DoccDocumentationResponse: ResponseType {
48+
public var renderNode: String
13649

137-
public func encode(to encoder: any Encoder) throws {
138-
var container = encoder.container(keyedBy: CodingKeys.self)
139-
switch self {
140-
case .renderNode(let renderNode):
141-
try container.encode("renderNode", forKey: .type)
142-
try container.encode(renderNode, forKey: .renderNode)
143-
case .error(let error):
144-
try container.encode("error", forKey: .type)
145-
try container.encode(error, forKey: .error)
146-
}
50+
public init(renderNode: String) {
51+
self.renderNode = renderNode
14752
}
14853
}

Sources/SKTestSupport/TestSourceKitLSPClient.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -237,17 +237,21 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable {
237237

238238
// MARK: - Sending messages
239239

240-
/// Send the request to `server` and return the request result.
241-
package func send<R: RequestType>(_ request: R) async throws -> R.Response {
242-
return try await withCheckedThrowingContinuation { continuation in
240+
package func sendWithRawResponse<R: RequestType>(_ request: R) async -> Result<R.Response, ResponseError> {
241+
await withCheckedContinuation { continuation in
243242
self.send(request) { result in
244-
continuation.resume(with: result)
243+
continuation.resume(returning: result)
245244
}
246245
}
247246
}
248247

248+
/// Send the request to `server` and return the request result.
249+
package func send<R: RequestType>(_ request: R) async throws(ResponseError) -> R.Response {
250+
return try await sendWithRawResponse(request).get()
251+
}
252+
249253
/// Variant of `send` above that allows the response to be discarded if it is a `VoidResponse`.
250-
package func send<R: RequestType>(_ request: R) async throws where R.Response == VoidResponse {
254+
package func send<R: RequestType>(_ request: R) async throws(ResponseError) where R.Response == VoidResponse {
251255
let _: VoidResponse = try await self.send(request)
252256
}
253257

Sources/SourceKitLSP/Documentation/DocumentationManager.swift

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ package final actor DocumentationManager {
7070
at: snapshot.absolutePosition(of: position)
7171
)
7272
else {
73-
return .error(.noDocumentation)
73+
throw ResponseError.requestFailed(.noDocumentation)
7474
}
7575
// Retrieve the symbol graph as well as information about the symbol
7676
let symbolPosition = await swiftLanguageService.adjustPositionToStartOfIdentifier(
@@ -96,7 +96,7 @@ package final actor DocumentationManager {
9696
symbolGraphs.append(rawSymbolGraph)
9797
overridingDocumentationComments[symbolUSR] = nearestDocumentableSymbol.documentationComments
9898
default:
99-
return .error(.noDocumentation)
99+
throw ResponseError.requestFailed(.noDocumentation)
100100
}
101101
// Send the convert request to SwiftDocC and wait for the response
102102
return try await withCheckedThrowingContinuation { continuation in
@@ -124,7 +124,7 @@ package final actor DocumentationManager {
124124
continuation.resume(throwing: ResponseError.internalError("Failed to encode render node from SwiftDocC"))
125125
return
126126
}
127-
continuation.resume(returning: .renderNode(renderNode))
127+
continuation.resume(returning: DoccDocumentationResponse(renderNode: renderNode))
128128
case .failure(let serverError):
129129
continuation.resume(throwing: serverError)
130130
}
@@ -133,6 +133,29 @@ package final actor DocumentationManager {
133133
}
134134
}
135135

136+
package enum ConvertDocumentationError {
137+
case indexNotAvailable
138+
case noDocumentation
139+
case symbolNotFound(String)
140+
141+
public var message: String {
142+
switch self {
143+
case .indexNotAvailable:
144+
return "The index is not availble to complete the request"
145+
case .noDocumentation:
146+
return "No documentation could be rendered for the position in this document"
147+
case .symbolNotFound(let symbolName):
148+
return "Could not find symbol \(symbolName) in the project"
149+
}
150+
}
151+
}
152+
153+
fileprivate extension ResponseError {
154+
static func requestFailed(_ error: ConvertDocumentationError) -> ResponseError {
155+
return ResponseError(code: .requestFailed, message: error.message)
156+
}
157+
}
158+
136159
fileprivate final class DocumentableSymbolFinder: SyntaxAnyVisitor {
137160
struct Symbol {
138161
let position: AbsolutePosition

Tests/SourceKitLSPTests/DoccDocumentationTests.swift

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ final class DoccDocumentationTests: XCTestCase {
469469

470470
fileprivate enum PartialConvertResponse {
471471
case renderNode(kind: RenderNode.Kind, path: String? = nil, containing: String? = nil)
472-
case error(DoccDocumentationError)
472+
case error(ConvertDocumentationError)
473473
}
474474

475475
fileprivate func renderDocumentation(
@@ -510,24 +510,15 @@ fileprivate func renderDocumentation(
510510
}
511511

512512
for (index, marker) in positions.allMarkers.enumerated() {
513-
let response: DoccDocumentationResponse
514-
do {
515-
response = try await testClient.send(
516-
DoccDocumentationRequest(
517-
textDocument: TextDocumentIdentifier(uri),
518-
position: positions[marker]
519-
)
520-
)
521-
} catch {
522-
XCTFail(
523-
"textDocument/doccDocumentation failed at position \(marker): \(error.localizedDescription)",
524-
file: file,
525-
line: line
513+
let response = await testClient.sendWithRawResponse(
514+
DoccDocumentationRequest(
515+
textDocument: TextDocumentIdentifier(uri),
516+
position: positions[marker]
526517
)
527-
return
528-
}
518+
)
529519
switch response {
530-
case .renderNode(let renderNodeString):
520+
case .success(let response):
521+
let renderNodeString = response.renderNode
531522
guard let renderNodeData = renderNodeString.data(using: .utf8),
532523
let renderNode = try? JSONDecoder().decode(RenderNode.self, from: renderNodeData)
533524
else {
@@ -562,36 +553,36 @@ fileprivate func renderDocumentation(
562553
}
563554
case .error(let error):
564555
XCTFail(
565-
"expected error \(error.rawValue), but received a render node at position \(marker)",
556+
"expected error \(error.message), but received a render node at position \(marker)",
566557
file: file,
567558
line: line
568559
)
569560
}
570-
case .error(let error):
561+
case .failure(let error):
571562
switch expectedResponses[index] {
572563
case .renderNode:
573564
XCTFail(
574-
"expected a render node, but received an error \(error.rawValue) at position \(marker)",
565+
"textDocument/doccDocumentation failed at position \(marker): \(error.localizedDescription)",
575566
file: file,
576567
line: line
577568
)
578569
case .error(let expectedError):
579-
XCTAssertEqual(error, expectedError, file: file, line: line)
570+
XCTAssertEqual(
571+
error.code,
572+
.requestFailed,
573+
"expected a responseFailed error code at position \(marker)",
574+
file: file,
575+
line: line
576+
)
577+
XCTAssertEqual(
578+
error.message,
579+
expectedError.message,
580+
"expected an error with message \(expectedError.message) at position \(marker)",
581+
file: file,
582+
line: line
583+
)
580584
}
581585
}
582586
}
583587
}
584-
585-
fileprivate extension DoccDocumentationError {
586-
var rawValue: String {
587-
switch self {
588-
case .indexNotAvailable:
589-
return "indexNotAvailable"
590-
case .noDocumentation:
591-
return "noDocumentation"
592-
case .symbolNotFound:
593-
return "symbolNotFound"
594-
}
595-
}
596-
}
597588
#endif

0 commit comments

Comments
 (0)