mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
148 lines
5.2 KiB
Swift
148 lines
5.2 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2018 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 the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import LanguageServerProtocol
|
|
|
|
/// *Public For Testing*. A single JSONRPC message suitable for encoding/decoding.
|
|
public enum JSONRPCMessage {
|
|
case notification(NotificationType)
|
|
case request(_RequestType, id: RequestID)
|
|
case response(ResponseType, id: RequestID)
|
|
case errorResponse(ResponseError, id: RequestID)
|
|
}
|
|
|
|
extension CodingUserInfoKey {
|
|
public static let responseTypeCallbackKey: CodingUserInfoKey = CodingUserInfoKey(rawValue: "lsp.jsonrpc.responseTypeCallback")!
|
|
}
|
|
|
|
extension JSONRPCMessage: Codable {
|
|
|
|
public typealias ResponseTypeCallback = (RequestID) -> ResponseType.Type?
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case jsonrpc
|
|
case method
|
|
case id
|
|
case params
|
|
case result
|
|
case error
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
let jsonrpc = try container.decodeIfPresent(String.self, forKey: .jsonrpc)
|
|
let id = try container.decodeIfPresent(RequestID.self, forKey: .id)
|
|
var msgKind = MessageDecodingError.MessageKind.unknown
|
|
|
|
if jsonrpc != "2.0" {
|
|
throw MessageDecodingError.invalidRequest("jsonrpc version must be 2.0")
|
|
}
|
|
|
|
do {
|
|
let method = try container.decodeIfPresent(String.self, forKey: .method)
|
|
let error = try container.decodeIfPresent(ResponseError.self, forKey: .error)
|
|
|
|
let hasResult = container.contains(.result)
|
|
|
|
switch (id, method, hasResult, error) {
|
|
case (nil, let method?, _, nil):
|
|
msgKind = .notification
|
|
|
|
guard let messageType = MessageRegistry.shared.notificationType(for: method) else {
|
|
throw MessageDecodingError.methodNotFound(method)
|
|
}
|
|
|
|
let params = try messageType.init(from: container.superDecoder(forKey: .params))
|
|
|
|
self = .notification(params)
|
|
|
|
case (let id?, let method?, _, nil):
|
|
msgKind = .request
|
|
|
|
guard let messageType = MessageRegistry.shared.requestType(for: method) else {
|
|
throw MessageDecodingError.methodNotFound(method)
|
|
}
|
|
|
|
let params = try messageType.init(from: container.superDecoder(forKey: .params))
|
|
|
|
self = .request(params, id: id)
|
|
|
|
case (let id?, nil, true, nil):
|
|
msgKind = .response
|
|
|
|
guard let responseTypeCallback = decoder.userInfo[.responseTypeCallbackKey] as? ResponseTypeCallback else {
|
|
fatalError("missing or invalid responseTypeCallbackKey on decoder")
|
|
}
|
|
|
|
guard let responseType = responseTypeCallback(id) else {
|
|
throw MessageDecodingError.invalidParams("response to unknown request \(id)")
|
|
}
|
|
|
|
let result = try responseType.init(from: container.superDecoder(forKey: .result))
|
|
|
|
self = .response(result, id: id)
|
|
|
|
case (let id?, nil, _, let error?):
|
|
msgKind = .response
|
|
self = .errorResponse(error, id: id)
|
|
|
|
default:
|
|
throw MessageDecodingError.invalidRequest("message not recognized as request, response or notification")
|
|
}
|
|
|
|
} catch var error as MessageDecodingError {
|
|
assert(error.id == nil || error.id == id)
|
|
error.id = id
|
|
error.messageKind = msgKind
|
|
throw error
|
|
|
|
} catch DecodingError.keyNotFound(let key, _) {
|
|
throw MessageDecodingError.invalidParams("missing expected parameter: \(key.stringValue)", id: id, messageKind: msgKind)
|
|
|
|
} catch DecodingError.valueNotFound(_, let context) {
|
|
throw MessageDecodingError.invalidParams("missing expected parameter: \(context.codingPath.last?.stringValue ?? "unknown")", id: id, messageKind: msgKind)
|
|
|
|
} catch DecodingError.typeMismatch(_, let context) {
|
|
let path = context.codingPath.map { $0.stringValue }.joined(separator: ".")
|
|
throw MessageDecodingError.invalidParams("type mistmatch at \(path) : \(context.debugDescription)", id: id, messageKind: msgKind)
|
|
|
|
} catch {
|
|
throw MessageDecodingError.parseError(error.localizedDescription, id: id, messageKind: msgKind)
|
|
}
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
try container.encode("2.0", forKey: .jsonrpc)
|
|
|
|
switch self {
|
|
case .notification(let params):
|
|
try container.encode(type(of: params).method, forKey: .method)
|
|
try params.encode(to: container.superEncoder(forKey: .params))
|
|
|
|
case .request(let params, let id):
|
|
try container.encode(type(of: params).method, forKey: .method)
|
|
try container.encode(id, forKey: .id)
|
|
try params.encode(to: container.superEncoder(forKey: .params))
|
|
|
|
case .response(let result, let id):
|
|
try container.encode(id, forKey: .id)
|
|
try result.encode(to: container.superEncoder(forKey: .result))
|
|
|
|
case .errorResponse(let error, let id):
|
|
try container.encode(id, forKey: .id)
|
|
try container.encode(error, forKey: .error)
|
|
}
|
|
}
|
|
}
|