aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Davis2022-06-18 03:59:57 +0000
committerGitHub2022-06-18 03:59:57 +0000
commit45ce1ebdb604ae8b044a012f2933e6a42574430a (patch)
treee94d27727f7c7562f108ffba0dc4d5adb8e06d0e
parentb13e534b92a2616134157d9df80c49dd6af223c6 (diff)
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
-rw-r--r--Cargo.lock14
-rw-r--r--helix-lsp/Cargo.toml1
-rw-r--r--helix-lsp/src/client.rs2
-rw-r--r--helix-lsp/src/jsonrpc.rs370
-rw-r--r--helix-lsp/src/lib.rs2
-rw-r--r--helix-lsp/src/transport.rs3
6 files changed, 373 insertions, 19 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 1d1dac95..ddf26790 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -421,7 +421,6 @@ dependencies = [
"futures-executor",
"futures-util",
"helix-core",
- "jsonrpc-core",
"log",
"lsp-types",
"serde",
@@ -552,19 +551,6 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml
index fb36758f..b7d26662 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 7f556ca6..8ee9c9d7 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 00000000..b9b3fd2c
--- /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<i64> 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<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let code: i64 = Deserialize::deserialize(deserializer)?;
+ Ok(ErrorCode::from(code))
+ }
+}
+
+impl Serialize for ErrorCode {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ 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<Value>,
+}
+
+impl Error {
+ pub fn invalid_params<M>(message: M) -> Self
+ where
+ M: Into<String>,
+ {
+ 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ 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<E>(self, value: &str) -> Result<Self::Value, E>
+ 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<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ deserializer.deserialize_identifier(VersionVisitor)
+ }
+}
+
+#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum Params {
+ None,
+ Array(Vec<Value>),
+ Map(serde_json::Map<String, Value>),
+}
+
+impl Params {
+ pub fn parse<D>(self) -> Result<D, Error>
+ 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<Params> 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<Version>,
+ 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<Version>,
+ 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<MethodCall> for Call {
+ fn from(method_call: MethodCall) -> Self {
+ Call::MethodCall(method_call)
+ }
+}
+
+impl From<Notification> 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<Call>),
+}
+
+// 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<Version>,
+ 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<Version>,
+ 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<Output> for Result<Value, Error> {
+ 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<Output>),
+}
+
+impl From<Failure> for Response {
+ fn from(failure: Failure) -> Self {
+ Response::Single(Output::Failure(failure))
+ }
+}
+
+impl From<Success> 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 2bc554e6..6a5f9d5c 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 6102c6c8..8aaeae3d 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;