diff options
author | Michael Davis | 2022-06-18 03:59:57 +0000 |
---|---|---|
committer | GitHub | 2022-06-18 03:59:57 +0000 |
commit | 45ce1ebdb604ae8b044a012f2933e6a42574430a (patch) | |
tree | e94d27727f7c7562f108ffba0dc4d5adb8e06d0e /helix-lsp | |
parent | b13e534b92a2616134157d9df80c49dd6af223c6 (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
Diffstat (limited to 'helix-lsp')
-rw-r--r-- | helix-lsp/Cargo.toml | 1 | ||||
-rw-r--r-- | helix-lsp/src/client.rs | 2 | ||||
-rw-r--r-- | helix-lsp/src/jsonrpc.rs | 370 | ||||
-rw-r--r-- | helix-lsp/src/lib.rs | 2 | ||||
-rw-r--r-- | helix-lsp/src/transport.rs | 3 |
5 files changed, 373 insertions, 5 deletions
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; |