use lsp_types::{Position, Range};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LspMessage {
pub jsonrpc: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub method: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<serde_json::Value>,
}
impl LspMessage {
#[must_use]
pub fn request(id: impl Into<serde_json::Value>, method: &str) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id: Some(id.into()),
method: Some(method.to_string()),
params: None,
result: None,
error: None,
}
}
#[must_use]
pub fn notification(method: &str) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id: None,
method: Some(method.to_string()),
params: None,
result: None,
error: None,
}
}
#[must_use]
pub fn is_request(&self) -> bool {
self.id.is_some() && self.method.is_some()
}
#[must_use]
pub fn is_response(&self) -> bool {
self.id.is_some() && (self.result.is_some() || self.error.is_some())
}
#[must_use]
pub fn is_notification(&self) -> bool {
self.id.is_none() && self.method.is_some()
}
#[must_use]
pub fn is_error(&self) -> bool {
self.error.is_some()
}
#[must_use]
pub fn with_params(mut self, params: serde_json::Value) -> Self {
self.params = Some(params);
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CodeContext {
pub uri: String,
pub position: Position,
#[serde(skip_serializing_if = "Option::is_none")]
pub visible_range: Option<Range>,
}
impl CodeContext {
#[must_use]
pub fn new(uri: String, line: u32, character: u32) -> Self {
Self {
uri,
position: Position { line, character },
visible_range: None,
}
}
#[must_use]
pub fn with_visible_range(mut self, start: Position, end: Position) -> Self {
self.visible_range = Some(Range { start, end });
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use similar_asserts::assert_eq;
#[test]
fn test_lsp_message_serialization_roundtrip() {
let msg = LspMessage::request(1, "textDocument/completion")
.with_params(serde_json::json!({"textDocument": {"uri": "file:///test.rs"}}));
let json = serde_json::to_string(&msg).expect("serialize");
let deserialized: LspMessage = serde_json::from_str(&json).expect("deserialize");
assert_eq!(msg, deserialized);
}
#[test]
fn test_lsp_message_request() {
let msg = LspMessage::request(42, "test/method");
assert!(msg.is_request());
assert!(!msg.is_response());
assert!(!msg.is_notification());
assert_eq!(msg.jsonrpc, "2.0");
assert_eq!(msg.method, Some("test/method".to_string()));
}
#[test]
fn test_lsp_message_notification() {
let msg = LspMessage::notification("window/logMessage");
assert!(msg.is_notification());
assert!(!msg.is_request());
assert!(!msg.is_response());
assert!(msg.id.is_none());
}
#[test]
fn test_lsp_message_response() {
let msg = LspMessage {
jsonrpc: "2.0".to_string(),
id: Some(serde_json::json!(1)),
method: None,
params: None,
result: Some(serde_json::json!({"completions": []})),
error: None,
};
assert!(msg.is_response());
assert!(!msg.is_request());
assert!(!msg.is_error());
}
#[test]
fn test_lsp_message_error_response() {
let msg = LspMessage {
jsonrpc: "2.0".to_string(),
id: Some(serde_json::json!(1)),
method: None,
params: None,
result: None,
error: Some(serde_json::json!({"code": -32600, "message": "Invalid Request"})),
};
assert!(msg.is_response());
assert!(msg.is_error());
}
#[test]
fn test_lsp_message_skips_none_fields() {
let msg = LspMessage::notification("test");
let json = serde_json::to_string(&msg).expect("serialize");
assert!(!json.contains("\"id\""));
assert!(!json.contains("\"params\""));
assert!(!json.contains("\"result\""));
assert!(!json.contains("\"error\""));
}
#[test]
fn test_code_context_serialization_roundtrip() {
let ctx = CodeContext::new("file:///test.rs".to_string(), 10, 5);
let json = serde_json::to_string(&ctx).expect("serialize");
let deserialized: CodeContext = serde_json::from_str(&json).expect("deserialize");
assert_eq!(ctx, deserialized);
}
#[test]
fn test_code_context_with_visible_range() {
let ctx = CodeContext::new("file:///test.rs".to_string(), 10, 5).with_visible_range(
Position {
line: 0,
character: 0,
},
Position {
line: 50,
character: 0,
},
);
assert!(ctx.visible_range.is_some());
let range = ctx.visible_range.unwrap();
assert_eq!(range.start.line, 0);
assert_eq!(range.end.line, 50);
}
#[test]
fn test_code_context_skips_none_visible_range() {
let ctx = CodeContext::new("file:///test.rs".to_string(), 10, 5);
let json = serde_json::to_string(&ctx).expect("serialize");
assert!(!json.contains("visible_range"));
}
}