Files
sourcekit-lsp/Sources/LanguageServerProtocolJSONRPC/MessageCoding.swift
2019-09-25 14:16:30 -07:00

152 lines
5.5 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")!
public static let messageRegistryKey: CodingUserInfoKey = CodingUserInfoKey(rawValue: "lsp.jsonrpc.messageRegistry")!
}
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 {
guard let messageRegistry = decoder.userInfo[.messageRegistryKey] as? MessageRegistry else {
fatalError("missing or invalid messageRegistryKey on decoder")
}
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.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.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 mismatch 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)
}
}
}