mod generated;
pub use generated::*;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
fn deserialize_some<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
T: Deserialize<'de>,
D: Deserializer<'de>,
{
T::deserialize(deserializer).map(Some)
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
enum Version {
#[serde(rename = "2.0")]
TwoPointZero,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum Id {
Number(i64),
String(String),
Null,
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Eq)]
pub struct RequestObject {
jsonrpc: Version,
#[serde(default, deserialize_with = "deserialize_some")]
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<Id>,
#[serde(default)]
method: String,
#[serde(default, deserialize_with = "deserialize_some")]
#[serde(skip_serializing_if = "Option::is_none")]
params: Option<Value>,
}
impl RequestObject {
pub fn from_request<R>(id: Id, params: R::Params) -> Self
where
R: Request,
{
let params = serde_json::to_value(params).expect("Invalid request params");
let params = match params {
Value::Null => None,
Value::Array(_) | Value::Object(_) => Some(params),
_ => panic!("Parameters must be an object or array, if not omitted."),
};
Self {
jsonrpc: Version::TwoPointZero,
id: Some(id),
params,
method: R::METHOD.into(),
}
}
pub fn from_notification<N>(params: N::Params) -> Self
where
N: Notification,
{
let params = serde_json::to_value(params).expect("Invalid request params");
let params = match params {
Value::Null => None,
Value::Array(_) | Value::Object(_) => Some(params),
_ => panic!("Parameters must be an object or array, if not omitted."),
};
Self {
jsonrpc: Version::TwoPointZero,
method: N::METHOD.to_string(),
params,
id: None,
}
}
#[must_use]
pub fn method(&self) -> &str {
self.method.as_ref()
}
#[must_use]
pub const fn id(&self) -> Option<&Id> {
self.id.as_ref()
}
#[must_use]
pub const fn params(&self) -> Option<&Value> {
self.params.as_ref()
}
#[must_use]
pub fn into_parts(self) -> (String, Option<Id>, Option<Value>) {
(self.method, self.id, self.params)
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(untagged)]
enum Kind {
Ok { result: Value },
Err { error: Error },
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct Error {
pub code: ErrorCodes,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ResponseObject {
jsonrpc: Version,
#[serde(flatten)]
kind: Kind,
id: Id,
}
impl ResponseObject {
pub fn from_success<R>(id: Id, result: R::Result) -> Self
where
R: Request,
{
let result = serde_json::to_value(result).unwrap();
Self {
jsonrpc: Version::TwoPointZero,
kind: Kind::Ok { result },
id,
}
}
#[must_use]
pub const fn from_error(id: Id, error: Error) -> Self {
Self {
jsonrpc: Version::TwoPointZero,
kind: Kind::Err { error },
id,
}
}
#[must_use]
pub const fn is_ok(&self) -> bool {
matches!(self.kind, Kind::Ok { .. })
}
#[must_use]
pub const fn is_error(&self) -> bool {
!self.is_ok()
}
#[must_use]
pub const fn id(&self) -> &Id {
&self.id
}
#[must_use]
pub const fn result(&self) -> Option<&Value> {
match &self.kind {
Kind::Ok { result } => Some(result),
Kind::Err { .. } => None,
}
}
#[must_use]
pub const fn error(&self) -> Option<&Error> {
match &self.kind {
Kind::Err { error } => Some(error),
Kind::Ok { .. } => None,
}
}
}
#[cfg(test)]
mod test {
#![allow(deprecated)]
use std::collections::{HashMap, HashSet};
use crate::{
CodeLensRefreshRequest, ColorPresentation, CreateFile, DeleteFile, DocumentChange,
DocumentSymbol, Error, ExitNotification, FoldingRangeKind, Id, ImplementationRequest,
ImplementationRequestResponse, InitializeParams, InitializedNotification,
InitializedParams, LspNotificationMethods, LspRequestMethods, MarkupKind, Position, Range,
RequestObject, ResponseObject, ShowMessageRequest, SymbolKind,
TextDocumentRegistrationOptions, TypeDefinitionParams, TypeDefinitionRequest, WatchKind,
WorkDoneProgressEnd, WorkspaceFoldersInitializeParams, WorkspaceFoldersRequest,
WorkspaceFoldersServerCapabilities,
};
#[test]
fn nullable_field() {
let tdro = TextDocumentRegistrationOptions {
document_selector: None,
};
let tdro_str = serde_json::to_string(&tdro).unwrap();
assert_eq!(tdro_str, r#"{"documentSelector":null}"#);
let tdro = serde_json::from_str::<TextDocumentRegistrationOptions>(&tdro_str).unwrap();
assert_eq!(
tdro,
TextDocumentRegistrationOptions {
document_selector: None
}
);
assert_eq!(
serde_json::from_str::<TextDocumentRegistrationOptions>("{}").unwrap(),
tdro
);
}
#[test]
fn nullable_field_default() {
let ip = InitializeParams::default();
let ip_str = serde_json::to_string(&ip).unwrap();
assert_eq!(
ip_str,
r#"{"processId":null,"rootUri":null,"capabilities":{}}"#
);
let ip = serde_json::from_str::<InitializeParams>(&ip_str).unwrap();
assert_eq!(ip, InitializeParams::default());
let bad_ip_str = r#"{"rootUri":null,"capabilities":{}}"#;
let bad_ip = serde_json::from_str::<InitializeParams>(bad_ip_str);
assert_eq!(bad_ip.unwrap(), InitializeParams::default());
}
#[test]
fn optional_field() {
let cp = ColorPresentation {
label: "Label".to_string(),
text_edit: None,
..Default::default()
};
let cp_str = serde_json::to_string(&cp).unwrap();
assert_eq!(cp_str, r#"{"label":"Label"}"#);
let cp = serde_json::from_str::<ColorPresentation>(&cp_str).unwrap();
assert_eq!(
cp,
ColorPresentation {
label: "Label".to_string(),
text_edit: None,
..Default::default()
}
);
assert_eq!(
serde_json::from_str::<ColorPresentation>(r#"{"label":"Label","textEdit":null}"#)
.unwrap(),
cp
);
}
#[test]
fn optional_nullable_field() {
let wfip = WorkspaceFoldersInitializeParams {
workspace_folders: None,
};
let wfip_str = serde_json::to_string(&wfip).unwrap();
assert_eq!(wfip_str, r"{}");
assert_eq!(
serde_json::from_str::<WorkspaceFoldersInitializeParams>(&wfip_str).unwrap(),
wfip
);
let wfip = WorkspaceFoldersInitializeParams {
workspace_folders: Some(crate::WorkspaceFolders::Null),
};
let wfip_str = serde_json::to_string(&wfip).unwrap();
assert_eq!(wfip_str, r#"{"workspaceFolders":null}"#);
assert_eq!(
serde_json::from_str::<WorkspaceFoldersInitializeParams>(&wfip_str).unwrap(),
wfip
);
let wfip = WorkspaceFoldersInitializeParams {
workspace_folders: Some(crate::WorkspaceFolders::WorkspaceFolderList(Vec::new())),
};
let wfip_str = serde_json::to_string(&wfip).unwrap();
assert_eq!(wfip_str, r#"{"workspaceFolders":[]}"#);
assert_eq!(
serde_json::from_str::<WorkspaceFoldersInitializeParams>(&wfip_str).unwrap(),
wfip
);
}
#[test]
fn derives() {
let pos = Position::default();
let table = HashMap::from([(pos, 123)]);
assert_eq!(table.get(&pos), Some(&123));
let range = Range::default();
let table = HashSet::from([range]);
assert!(table.contains(&range));
let doc_sym = DocumentSymbol {
kind: crate::SymbolKind::Function,
name: String::default(),
detail: Option::default(),
tags: Option::default(),
deprecated: Option::default(),
range: Range::default(),
selection_range: Range::default(),
children: Option::default(),
};
let table = HashSet::from([doc_sym.clone()]);
assert!(table.contains(&doc_sym));
let wfip = WorkspaceFoldersInitializeParams {
workspace_folders: Some(Vec::new().into()),
};
let wfip_str = serde_json::to_string(&wfip).unwrap();
assert_eq!(wfip_str, r#"{"workspaceFolders":[]}"#);
assert_eq!(
serde_json::from_str::<WorkspaceFoldersInitializeParams>(&wfip_str).unwrap(),
wfip
);
let wfip = WorkspaceFoldersInitializeParams {
workspace_folders: Some(().into()),
};
let wfip_str = serde_json::to_string(&wfip).unwrap();
assert_eq!(wfip_str, r#"{"workspaceFolders":null}"#);
assert_eq!(
serde_json::from_str::<WorkspaceFoldersInitializeParams>(&wfip_str).unwrap(),
wfip
);
let wfsc = WorkspaceFoldersServerCapabilities {
change_notifications: Some("some-noti-id".into()),
..Default::default()
};
let wfsc_str = serde_json::to_string(&wfsc).unwrap();
assert_eq!(wfsc_str, r#"{"changeNotifications":"some-noti-id"}"#);
let wfsc = WorkspaceFoldersServerCapabilities {
change_notifications: Some(String::from("some-noti-id").into()),
..Default::default()
};
let wfsc_str = serde_json::to_string(&wfsc).unwrap();
assert_eq!(wfsc_str, r#"{"changeNotifications":"some-noti-id"}"#);
let wfsc = WorkspaceFoldersServerCapabilities {
change_notifications: Some(false.into()),
..Default::default()
};
let wfsc_str = serde_json::to_string(&wfsc).unwrap();
assert_eq!(wfsc_str, r#"{"changeNotifications":false}"#);
let wfsc = WorkspaceFoldersServerCapabilities {
change_notifications: Some('f'.into()),
..Default::default()
};
let wfsc_str = serde_json::to_string(&wfsc).unwrap();
assert_eq!(wfsc_str, r#"{"changeNotifications":"f"}"#);
let boxed_str: Box<str> = Box::from("foo");
let wfsc = WorkspaceFoldersServerCapabilities {
change_notifications: Some(boxed_str.into()),
..Default::default()
};
let wfsc_str = serde_json::to_string(&wfsc).unwrap();
assert_eq!(wfsc_str, r#"{"changeNotifications":"foo"}"#);
}
#[test]
fn special_derives() {
let pos = Position {
line: 2,
character: 0,
};
let pos2 = Position {
line: 1,
character: 9,
};
assert!(pos2 < pos);
let pos3 = pos2;
assert_eq!(pos3, pos2);
let range = Range {
start: pos2,
end: pos,
};
let range2 = range;
assert_eq!(range2, range);
let range3 = Range {
start: Position::default(),
end: Position {
line: 999,
character: 999,
},
};
assert!(range3 < range2);
let range4 = Range::default();
assert!(range4 < range3);
let method: String = LspRequestMethods::TextDocumentOnTypeFormatting.into();
assert_eq!(method, "textDocument/onTypeFormatting");
let method = LspRequestMethods::Shutdown.to_string();
assert_eq!(method, "shutdown");
let method = LspRequestMethods::Custom("foo".into()).to_string();
assert_eq!(method, "foo");
let method = LspNotificationMethods::Custom("foo".into()).to_string();
assert_eq!(method, "foo");
let method = LspNotificationMethods::CancelRequest.to_string();
assert_eq!(method, "$/cancelRequest");
let method = LspNotificationMethods::WorkspaceDidChangeWatchedFiles.to_string();
assert_eq!(method, "workspace/didChangeWatchedFiles");
}
#[test]
fn string_literal_field() {
let wdpe = WorkDoneProgressEnd {
message: Some("change da world. my final message. goodbye".to_string()),
};
let ser = serde_json::to_string(&wdpe).unwrap();
assert_eq!(
ser,
r#"{"message":"change da world. my final message. goodbye","kind":"end"}"#
);
let deser = serde_json::from_str::<WorkDoneProgressEnd>(&ser).unwrap();
assert_eq!(deser, wdpe);
let fake_ser = r#"{"message":"change da world. my final message. goodbye","kind":"begin"}"#;
assert!(serde_json::from_str::<WorkDoneProgressEnd>(fake_ser).is_err());
let doc_change = CreateFile {
uri: "file:///foo.txt".to_string().into(),
options: None,
annotation_id: None,
};
let ser = serde_json::to_string(&doc_change).unwrap();
assert_eq!(ser, r#"{"uri":"file:///foo.txt","kind":"create"}"#);
let ser = r#"{"uri":"file:///foo.txt","kind":"create"}"#;
let deser = serde_json::from_str::<DocumentChange>(ser).unwrap();
assert_eq!(deser, doc_change.into());
let ser = r#"{"uri":"file:///foo.txt","kind":"delete","annotationId":"foo"}"#;
let deser = serde_json::from_str::<DocumentChange>(ser).unwrap();
assert_eq!(
deser,
DocumentChange::DeleteFile(DeleteFile {
uri: crate::Uri("file:///foo.txt".to_string()),
options: None,
annotation_id: Some(String::from("foo"))
})
);
let bad_ser = r#"{"uri":"file:///foo.txt","kind":"delet"}"#;
assert!(serde_json::from_str::<DocumentChange>(bad_ser).is_err());
}
#[test]
fn string_enum() {
let frk = FoldingRangeKind::Comment;
let ser = serde_json::to_string(&frk).unwrap();
assert_eq!(ser, "\"comment\"");
assert_eq!(
serde_json::from_str::<FoldingRangeKind>(&ser).unwrap(),
FoldingRangeKind::Comment
);
let frk = FoldingRangeKind::Custom("foo".into());
let ser = serde_json::to_string(&frk).unwrap();
assert_eq!(ser, "\"foo\"");
assert_eq!(
serde_json::from_str::<FoldingRangeKind>(&ser).unwrap(),
FoldingRangeKind::Custom("foo".into())
);
let mk = MarkupKind::PlainText;
assert_eq!("\"plaintext\"", serde_json::to_string(&mk).unwrap());
assert!(serde_json::from_str::<MarkupKind>("foo").is_err());
}
#[test]
fn int_enum() {
let sk = SymbolKind::Namespace;
let ser = serde_json::to_string(&sk).unwrap();
assert_eq!(ser, "3");
assert_eq!(
serde_json::from_str::<SymbolKind>(&ser).unwrap(),
SymbolKind::Namespace
);
assert!(serde_json::from_str::<SymbolKind>("299").is_err());
let wk = WatchKind::Custom(123);
let ser = serde_json::to_string(&wk).unwrap();
assert_eq!(ser, "123");
assert_eq!(wk, serde_json::from_str::<WatchKind>(&ser).unwrap());
assert_eq!(
WatchKind::Change,
serde_json::from_str::<WatchKind>("2").unwrap()
);
}
#[test]
fn request_object_from_request() {
let params = TypeDefinitionParams {
work_done_progress_params: crate::WorkDoneProgressParams {
work_done_token: None,
},
partial_result_params: crate::PartialResultParams {
partial_result_token: None,
},
text_document_position_params: crate::TextDocumentPositionParams {
text_document: crate::TextDocumentIdentifier { uri: "foo".into() },
position: Position::default(),
},
};
let req =
RequestObject::from_request::<TypeDefinitionRequest>(Id::Number(123), params.clone());
let ser = serde_json::to_string(&req).unwrap();
assert_eq!(
ser,
r#"{"jsonrpc":"2.0","id":123,"method":"textDocument/typeDefinition","params":{"position":{"character":0,"line":0},"textDocument":{"uri":"foo"}}}"#
);
assert_eq!(req.id(), Some(&Id::Number(123)));
assert_eq!(req.method(), "textDocument/typeDefinition");
assert_eq!(req.params(), Some(&serde_json::to_value(params).unwrap()));
}
#[test]
fn request_object_from_request_no_params() {
let req =
RequestObject::from_request::<WorkspaceFoldersRequest>(Id::String("foo".into()), ());
let ser = serde_json::to_string(&req).unwrap();
assert_eq!(
ser,
r#"{"jsonrpc":"2.0","id":"foo","method":"workspace/workspaceFolders"}"#
);
}
#[test]
fn request_object_from_notification() {
let noti =
RequestObject::from_notification::<InitializedNotification>(InitializedParams {});
let ser = serde_json::to_string(¬i).unwrap();
assert_eq!(
ser,
r#"{"jsonrpc":"2.0","method":"initialized","params":{}}"#
);
assert_eq!(noti.id(), None);
assert_eq!(noti.method(), "initialized");
assert_eq!(
noti.params(),
Some(&serde_json::to_value(InitializedParams {}).unwrap())
);
}
#[test]
fn request_object_from_notification_no_params() {
let noti = RequestObject::from_notification::<ExitNotification>(());
let ser = serde_json::to_string(¬i).unwrap();
assert_eq!(ser, r#"{"jsonrpc":"2.0","method":"exit"}"#);
}
#[test]
fn response_object_from_success() {
let id = Id::Number(123);
let res = ResponseObject::from_success::<ImplementationRequest>(
id.clone(),
Some(ImplementationRequestResponse::DefinitionLinkList(Vec::new())),
);
let ser = serde_json::to_string(&res).unwrap();
assert_eq!(r#"{"jsonrpc":"2.0","result":[],"id":123}"#, &ser);
assert_eq!(res, serde_json::from_str(&ser).unwrap());
let res = ResponseObject::from_success::<ImplementationRequest>(id.clone(), None);
let ser = serde_json::to_string(&res).unwrap();
assert_eq!(r#"{"jsonrpc":"2.0","result":null,"id":123}"#, &ser);
assert_eq!(res, serde_json::from_str(&ser).unwrap());
let res = ResponseObject::from_success::<ImplementationRequest>(id.clone(), None);
let ser = serde_json::to_string(&res).unwrap();
assert_eq!(r#"{"jsonrpc":"2.0","result":null,"id":123}"#, &ser);
assert_eq!(res, serde_json::from_str(&ser).unwrap());
let res = ResponseObject::from_success::<ShowMessageRequest>(id.clone(), None);
let ser = serde_json::to_string(&res).unwrap();
assert_eq!(r#"{"jsonrpc":"2.0","result":null,"id":123}"#, &ser);
assert_eq!(res, serde_json::from_str(&ser).unwrap());
let res = ResponseObject::from_success::<ShowMessageRequest>(
id.clone(),
Some(crate::MessageActionItem {
title: "foo".into(),
}),
);
let ser = serde_json::to_string(&res).unwrap();
assert_eq!(
r#"{"jsonrpc":"2.0","result":{"title":"foo"},"id":123}"#,
&ser
);
assert_eq!(res, serde_json::from_str(&ser).unwrap());
let res = ResponseObject::from_success::<CodeLensRefreshRequest>(id, ());
let ser = serde_json::to_string(&res).unwrap();
assert_eq!(r#"{"jsonrpc":"2.0","result":null,"id":123}"#, &ser);
assert_eq!(res, serde_json::from_str(&ser).unwrap());
}
#[test]
fn response_object_from_error() {
let id = Id::Null;
let res = ResponseObject::from_error(
id,
Error {
code: crate::ErrorCodes::ParseError,
message: "invalid format".into(),
data: None,
},
);
let ser = serde_json::to_string(&res).unwrap();
assert_eq!(
r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"invalid format"},"id":null}"#,
&ser
);
let id = Id::String("foo-req".into());
let res = ResponseObject::from_error(
id,
Error {
code: crate::ErrorCodes::Custom(-32803),
message: "failed to foo the bar".into(),
data: Some(serde_json::to_value(String::from("hi")).unwrap()),
},
);
let ser = serde_json::to_string(&res).unwrap();
assert_eq!(
r#"{"jsonrpc":"2.0","error":{"code":-32803,"message":"failed to foo the bar","data":"hi"},"id":"foo-req"}"#,
&ser
);
let id = Id::String("foo-req".into());
let res = ResponseObject::from_error(
id,
Error {
code: crate::ErrorCodes::Custom(crate::LspErrorCodes::ContentModified.into()),
message: "failed to foo the bar".into(),
data: Some(serde_json::to_value(String::from("hi")).unwrap()),
},
);
let ser = serde_json::to_string(&res).unwrap();
assert_eq!(
r#"{"jsonrpc":"2.0","error":{"code":-32801,"message":"failed to foo the bar","data":"hi"},"id":"foo-req"}"#,
&ser
);
}
}