From 45ce1ebdb604ae8b044a012f2933e6a42574430a Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Fri, 17 Jun 2022 22:59:57 -0500 Subject: [PATCH] embed jsonrpc types from jsonrpc-core crate (#2801) We should not depend on jsonrpc-core anymore: * The project just announced it's no longer actively maintained[^1], preferring their new implementation in `jsonrpsee`. * The types are too strict: we would benefit from removing some `#[serde(deny_unknown_fields)]` annotations to allow language servers that disrespect the spec[^2]. * We don't use much of the project. Just the types out of core. These are easy to embed directly into the `helix-lsp` crate. [^1]: https://github.com/paritytech/jsonrpc/pull/674 [^2]: https://github.com/helix-editor/helix/issues/2786 --- Cargo.lock | 14 -- helix-lsp/Cargo.toml | 1 - helix-lsp/src/client.rs | 2 +- helix-lsp/src/jsonrpc.rs | 370 +++++++++++++++++++++++++++++++++++++ helix-lsp/src/lib.rs | 2 +- helix-lsp/src/transport.rs | 3 +- 6 files changed, 373 insertions(+), 19 deletions(-) create mode 100644 helix-lsp/src/jsonrpc.rs diff --git a/Cargo.lock b/Cargo.lock index 1d1dac956..ddf267906 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -421,7 +421,6 @@ dependencies = [ "futures-executor", "futures-util", "helix-core", - "jsonrpc-core", "log", "lsp-types", "serde", @@ -551,19 +550,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" -[[package]] -name = "jsonrpc-core" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" -dependencies = [ - "futures-util", - "log", - "serde", - "serde_derive", - "serde_json", -] - [[package]] name = "lazy_static" version = "1.4.0" diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index fb36758f0..b7d266624 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -17,7 +17,6 @@ helix-core = { version = "0.6", path = "../helix-core" } anyhow = "1.0" futures-executor = "0.3" futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } -jsonrpc-core = { version = "18.0", default-features = false } # don't pull in all of futures log = "0.4" lsp-types = { version = "0.93", features = ["proposed"] } serde = { version = "1.0", features = ["derive"] } diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 7f556ca6d..8ee9c9d71 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -1,11 +1,11 @@ use crate::{ + jsonrpc, transport::{Payload, Transport}, Call, Error, OffsetEncoding, Result, }; use anyhow::anyhow; use helix_core::{find_root, ChangeSet, Rope}; -use jsonrpc_core as jsonrpc; use lsp_types as lsp; use serde::Deserialize; use serde_json::Value; diff --git a/helix-lsp/src/jsonrpc.rs b/helix-lsp/src/jsonrpc.rs new file mode 100644 index 000000000..b9b3fd2c0 --- /dev/null +++ b/helix-lsp/src/jsonrpc.rs @@ -0,0 +1,370 @@ +//! An implementation of the JSONRPC 2.0 spec types + +// Upstream implementation: https://github.com/paritytech/jsonrpc/tree/38af3c9439aa75481805edf6c05c6622a5ab1e70/core/src/types +// Changes from upstream: +// * unused functions (almost all non-trait-implementation functions) have been removed +// * `#[serde(deny_unknown_fields)]` annotations have been removed on response types +// for compatibility with non-strict language server implementations like Ruby Sorbet +// (see https://github.com/helix-editor/helix/issues/2786) +// * some variable names have been lengthened for readability + +use serde::de::{self, DeserializeOwned, Visitor}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +// https://www.jsonrpc.org/specification#error_object +#[derive(Debug, PartialEq, Clone)] +pub enum ErrorCode { + ParseError, + InvalidRequest, + MethodNotFound, + InvalidParams, + InternalError, + ServerError(i64), +} + +impl ErrorCode { + pub fn code(&self) -> i64 { + match *self { + ErrorCode::ParseError => -32700, + ErrorCode::InvalidRequest => -32600, + ErrorCode::MethodNotFound => -32601, + ErrorCode::InvalidParams => -32602, + ErrorCode::InternalError => -32603, + ErrorCode::ServerError(code) => code, + } + } +} + +impl From for ErrorCode { + fn from(code: i64) -> Self { + match code { + -32700 => ErrorCode::ParseError, + -32600 => ErrorCode::InvalidRequest, + -32601 => ErrorCode::MethodNotFound, + -32602 => ErrorCode::InvalidParams, + -32603 => ErrorCode::InternalError, + code => ErrorCode::ServerError(code), + } + } +} + +impl<'de> Deserialize<'de> for ErrorCode { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let code: i64 = Deserialize::deserialize(deserializer)?; + Ok(ErrorCode::from(code)) + } +} + +impl Serialize for ErrorCode { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_i64(self.code()) + } +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct Error { + pub code: ErrorCode, + pub message: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub data: Option, +} + +impl Error { + pub fn invalid_params(message: M) -> Self + where + M: Into, + { + Error { + code: ErrorCode::InvalidParams, + message: message.into(), + data: None, + } + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}: {}", self.code, self.message) + } +} + +impl std::error::Error for Error {} + +// https://www.jsonrpc.org/specification#request_object + +/// Request ID +#[derive(Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize)] +#[serde(untagged)] +pub enum Id { + Null, + Num(u64), + Str(String), +} + +/// Protocol Version +#[derive(Debug, PartialEq, Clone, Copy, Hash, Eq)] +pub enum Version { + V2, +} + +impl Serialize for Version { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match *self { + Version::V2 => serializer.serialize_str("2.0"), + } + } +} + +struct VersionVisitor; + +impl<'v> Visitor<'v> for VersionVisitor { + type Value = Version; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string") + } + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + match value { + "2.0" => Ok(Version::V2), + _ => Err(de::Error::custom("invalid version")), + } + } +} + +impl<'de> Deserialize<'de> for Version { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_identifier(VersionVisitor) + } +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Params { + None, + Array(Vec), + Map(serde_json::Map), +} + +impl Params { + pub fn parse(self) -> Result + where + D: DeserializeOwned, + { + let value: Value = self.into(); + serde_json::from_value(value) + .map_err(|err| Error::invalid_params(format!("Invalid params: {}.", err))) + } +} + +impl From for Value { + fn from(params: Params) -> Value { + match params { + Params::Array(vec) => Value::Array(vec), + Params::Map(map) => Value::Object(map), + Params::None => Value::Null, + } + } +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct MethodCall { + pub jsonrpc: Option, + pub method: String, + #[serde(default = "default_params")] + pub params: Params, + pub id: Id, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Notification { + pub jsonrpc: Option, + pub method: String, + #[serde(default = "default_params")] + pub params: Params, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +#[serde(untagged)] +pub enum Call { + MethodCall(MethodCall), + Notification(Notification), + Invalid { + // We can attempt to salvage the id out of the invalid request + // for better debugging + #[serde(default = "default_id")] + id: Id, + }, +} + +fn default_params() -> Params { + Params::None +} + +fn default_id() -> Id { + Id::Null +} + +impl From for Call { + fn from(method_call: MethodCall) -> Self { + Call::MethodCall(method_call) + } +} + +impl From for Call { + fn from(notification: Notification) -> Self { + Call::Notification(notification) + } +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +#[serde(untagged)] +pub enum Request { + Single(Call), + Batch(Vec), +} + +// https://www.jsonrpc.org/specification#response_object + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct Success { + #[serde(skip_serializing_if = "Option::is_none")] + pub jsonrpc: Option, + pub result: Value, + pub id: Id, +} + +#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +pub struct Failure { + #[serde(skip_serializing_if = "Option::is_none")] + pub jsonrpc: Option, + pub error: Error, + pub id: Id, +} + +// Note that failure comes first because we're not using +// #[serde(deny_unknown_field)]: we want a request that contains +// both `result` and `error` to be a `Failure`. +#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum Output { + Failure(Failure), + Success(Success), +} + +impl From for Result { + fn from(output: Output) -> Self { + match output { + Output::Success(success) => Ok(success.result), + Output::Failure(failure) => Err(failure.error), + } + } +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(untagged)] +pub enum Response { + Single(Output), + Batch(Vec), +} + +impl From for Response { + fn from(failure: Failure) -> Self { + Response::Single(Output::Failure(failure)) + } +} + +impl From for Response { + fn from(success: Success) -> Self { + Response::Single(Output::Success(success)) + } +} + +#[test] +fn method_call_serialize() { + use serde_json; + + let m = MethodCall { + jsonrpc: Some(Version::V2), + method: "update".to_owned(), + params: Params::Array(vec![Value::from(1), Value::from(2)]), + id: Id::Num(1), + }; + + let serialized = serde_json::to_string(&m).unwrap(); + assert_eq!( + serialized, + r#"{"jsonrpc":"2.0","method":"update","params":[1,2],"id":1}"# + ); +} + +#[test] +fn notification_serialize() { + use serde_json; + + let n = Notification { + jsonrpc: Some(Version::V2), + method: "update".to_owned(), + params: Params::Array(vec![Value::from(1), Value::from(2)]), + }; + + let serialized = serde_json::to_string(&n).unwrap(); + assert_eq!( + serialized, + r#"{"jsonrpc":"2.0","method":"update","params":[1,2]}"# + ); +} + +#[test] +fn success_output_deserialize() { + use serde_json; + + let dso = r#"{"jsonrpc":"2.0","result":1,"id":1}"#; + + let deserialized: Output = serde_json::from_str(dso).unwrap(); + assert_eq!( + deserialized, + Output::Success(Success { + jsonrpc: Some(Version::V2), + result: Value::from(1), + id: Id::Num(1) + }) + ); +} + +#[test] +fn success_output_deserialize_with_extra_fields() { + use serde_json; + + // https://github.com/helix-editor/helix/issues/2786 + let dso = r#"{"jsonrpc":"2.0","result":1,"id":1,"requestMethod":"initialize"}"#; + + let deserialized: Output = serde_json::from_str(dso).unwrap(); + assert_eq!( + deserialized, + Output::Success(Success { + jsonrpc: Some(Version::V2), + result: Value::from(1), + id: Id::Num(1) + }) + ); +} diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 2bc554e6b..6a5f9d5ca 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -1,10 +1,10 @@ mod client; +pub mod jsonrpc; mod transport; pub use client::Client; pub use futures_executor::block_on; pub use jsonrpc::Call; -pub use jsonrpc_core as jsonrpc; pub use lsp::{Position, Url}; pub use lsp_types as lsp; diff --git a/helix-lsp/src/transport.rs b/helix-lsp/src/transport.rs index 6102c6c8e..8aaeae3d9 100644 --- a/helix-lsp/src/transport.rs +++ b/helix-lsp/src/transport.rs @@ -1,6 +1,5 @@ -use crate::{Error, Result}; +use crate::{jsonrpc, Error, Result}; use anyhow::Context; -use jsonrpc_core as jsonrpc; use log::{error, info}; use serde::{Deserialize, Serialize}; use serde_json::Value;