laburnum 1.17.0

An LSP framework for building language servers and compilers, powered by an incremental query tree with content-addressed storage, task-based dataflow, and parallel queries.
Documentation
// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0

//! A subset of JSON-RPC types used by the Language Server Protocol.

use {
  crate::protocol::lsp::NumberOrString,
  serde::{
    Deserialize,
    Serialize,
    de::{
      self,
      Deserializer,
    },
    ser::Serializer,
  },
  std::{
    borrow::Cow,
    fmt::{
      self,
      Debug,
      Display,
      Formatter,
    },
  },
};

pub use self::{
  error::{
    Error,
    ErrorCode,
    Result,
  },
  message::Message,
  notification::{
    Notification,
    NotificationBuilder,
  },
  request::{
    Request,
    RequestBuilder,
  },
  response::Response,
};

pub mod error;
pub mod message;
pub mod notification;
pub mod request;
pub mod response;

/// A unique ID used to correlate requests and responses together.
#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
#[serde(untagged)]
#[derive(Default)]
pub enum Id {
  /// Numeric ID.
  Number(i64),
  /// String ID.
  String(String),
  /// Null ID.
  ///
  /// While `null` is considered a valid request ID by the JSON-RPC 2.0
  /// specification, its use is _strongly_ discouraged because the
  /// specification also uses a `null` value to indicate an unknown ID in the
  /// [`Response`] object.
  #[default]
  Null,
}

impl Display for Id {
  fn fmt(&self, f: &mut Formatter) -> fmt::Result {
    match self {
      | Self::Number(id) => Display::fmt(id, f),
      | Self::String(id) => Debug::fmt(id, f),
      | Self::Null => f.write_str("null"),
    }
  }
}

impl From<i64> for Id {
  fn from(n: i64) -> Self {
    Self::Number(n)
  }
}

impl From<&'_ str> for Id {
  fn from(s: &'_ str) -> Self {
    Self::String(s.to_string())
  }
}

impl From<String> for Id {
  fn from(s: String) -> Self {
    Self::String(s)
  }
}

impl From<NumberOrString> for Id {
  fn from(num_or_str: NumberOrString) -> Self {
    match num_or_str {
      | NumberOrString::Number(num) => Self::Number(i64::from(num)),
      | NumberOrString::String(s) => Self::String(s),
    }
  }
}

#[derive(Clone, Debug, PartialEq)]
pub struct Version;

impl<'de> Deserialize<'de> for Version {
  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
  where
    D: Deserializer<'de>,
  {
    #[derive(Deserialize)]
    struct Inner<'a>(#[serde(borrow)] Cow<'a, str>);

    let Inner(ver) = Inner::deserialize(deserializer)?;

    match ver.as_ref() {
      | "2.0" => Ok(Self),
      | _ => Err(de::Error::custom("expected JSON-RPC version \"2.0\"")),
    }
  }
}

impl Serialize for Version {
  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
  where
    S: Serializer,
  {
    serializer.serialize_str("2.0")
  }
}

#[cfg(test)]
mod tests {
  use {
    super::*,
    serde_json::json,
  };

  #[test]
  fn incoming_from_str_or_value() {
    let v = json!({"jsonrpc":"2.0","method":"initialize","params":{"capabilities":{}},"id":0});
    let from_str: Message = serde_json::from_str(&v.to_string()).unwrap();
    let from_value: Message = serde_json::from_value(v).unwrap();
    assert_eq!(from_str, from_value);
  }

  #[test]
  fn outgoing_from_str_or_value() {
    let v = json!({"jsonrpc":"2.0","result":{},"id":1});
    let from_str: Message = serde_json::from_str(&v.to_string()).unwrap();
    let from_value: Message = serde_json::from_value(v).unwrap();
    assert_eq!(from_str, from_value);
  }

  #[test]
  fn parses_incoming_message() {
    let server_request = json!({"jsonrpc":"2.0","method":"initialize","params":{"capabilities":{}},"id":0});
    let incoming = serde_json::from_value(server_request).unwrap();
    assert!(matches!(incoming, Message::Request(_)));

    let server_notif =
      json!({"jsonrpc":"2.0","method":"initialized","params":{}});
    let incoming = serde_json::from_value(server_notif).unwrap();
    assert!(matches!(incoming, Message::Notification(_)));

    let client_request = json!({"jsonrpc":"2.0","id":0,"result":[null]});
    let incoming = serde_json::from_value(client_request).unwrap();
    assert!(matches!(incoming, Message::Response(_)));
  }

  #[test]
  fn parses_outgoing_message() {
    let client_request = json!({"jsonrpc":"2.0","method":"workspace/configuration","params":{"scopeUri":null,"section":"foo"},"id":0});
    let outgoing = serde_json::from_value(client_request).unwrap();
    assert!(matches!(outgoing, Message::Request(_)));

    let client_notif = json!({"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"foo","type":0}});
    let outgoing = serde_json::from_value(client_notif).unwrap();
    assert!(matches!(outgoing, Message::Notification(_)));

    let server_response = json!({"jsonrpc":"2.0","id":0,"result":[null]});
    let outgoing = serde_json::from_value(server_response).unwrap();
    assert!(matches!(outgoing, Message::Response(_)));
  }

  #[test]
  fn parses_invalid_server_request() {
    let unknown_method = json!({"jsonrpc":"2.0","method":"foo"});
    let incoming = serde_json::from_value(unknown_method).unwrap();
    assert!(matches!(incoming, Message::Notification(_)));

    let unknown_method_with_id = json!({"jsonrpc":"2.0","method":"foo","id":0});
    let incoming = serde_json::from_value(unknown_method_with_id).unwrap();
    assert!(matches!(incoming, Message::Request(_)));

    let missing_method = json!({"jsonrpc":"2.0"});
    let incoming: std::result::Result<Message, _> =
      serde_json::from_value(missing_method);
    assert!(incoming.is_err());

    let missing_method_with_id = json!({"jsonrpc":"2.0","id":0});
    let incoming: std::result::Result<Message, _> =
      serde_json::from_value(missing_method_with_id);
    assert!(incoming.is_err());
  }

  #[test]
  fn accepts_null_request_id() {
    let request_id: Id = serde_json::from_value(json!(null)).unwrap();
    assert_eq!(request_id, Id::Null);
  }

  #[test]
  fn accepts_negative_integer_request_id() {
    let request_id: Id = serde_json::from_value(json!(-1)).unwrap();
    assert_eq!(request_id, Id::Number(-1));
  }

  #[test]
  fn message_request_round_trip_preserves_trace_context() {
    let request = Request::build("test/method", 1)
      .params(json!({"key": "value"}))
      .traceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01")
      .tracestate("rojo=00f067aa0ba902b7,congo=t61rcWkgMzE")
      .finish();

    let message = Message::Request(request);
    let serialized = serde_json::to_string(&message).unwrap();
    let deserialized: Message = serde_json::from_str(&serialized).unwrap();

    match deserialized {
      | Message::Request(r) => {
        assert_eq!(r.method(), "test/method");
        assert_eq!(
          r.traceparent(),
          Some("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01")
        );
        assert_eq!(
          r.tracestate(),
          Some("rojo=00f067aa0ba902b7,congo=t61rcWkgMzE")
        );
      },
      | _ => panic!("Expected Message::Request"),
    }
  }

  #[test]
  fn message_notification_round_trip_preserves_trace_context() {
    let notification = Notification::build("test/notification")
      .params(json!({"data": 123}))
      .traceparent("00-abcdef1234567890abcdef1234567890-1234567890abcdef-01")
      .tracestate("vendor=state")
      .finish();

    let message = Message::Notification(notification);
    let serialized = serde_json::to_string(&message).unwrap();
    let deserialized: Message = serde_json::from_str(&serialized).unwrap();

    match deserialized {
      | Message::Notification(n) => {
        assert_eq!(n.method(), "test/notification");
        assert_eq!(
          n.traceparent(),
          Some("00-abcdef1234567890abcdef1234567890-1234567890abcdef-01")
        );
        assert_eq!(n.tracestate(), Some("vendor=state"));
      },
      | _ => panic!("Expected Message::Notification"),
    }
  }

  #[test]
  fn message_parses_request_with_trace_context() {
    let json_with_trace = json!({
      "jsonrpc": "2.0",
      "method": "textDocument/hover",
      "params": {"textDocument": {"uri": "file:///test.rs"}, "position": {"line": 0, "character": 0}},
      "id": 42,
      "traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
      "tracestate": "rojo=00f067aa0ba902b7"
    });

    let message: Message = serde_json::from_value(json_with_trace).unwrap();
    match message {
      | Message::Request(r) => {
        assert_eq!(r.method(), "textDocument/hover");
        assert_eq!(r.id(), &Id::Number(42));
        assert_eq!(
          r.traceparent(),
          Some("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01")
        );
        assert_eq!(r.tracestate(), Some("rojo=00f067aa0ba902b7"));
      },
      | _ => panic!("Expected Message::Request"),
    }
  }

  #[test]
  fn message_parses_notification_with_trace_context() {
    let json_with_trace = json!({
      "jsonrpc": "2.0",
      "method": "textDocument/didOpen",
      "params": {"textDocument": {"uri": "file:///test.rs", "languageId": "rust", "version": 1, "text": ""}},
      "traceparent": "00-1234567890abcdef1234567890abcdef-abcdef1234567890-01"
    });

    let message: Message = serde_json::from_value(json_with_trace).unwrap();
    match message {
      | Message::Notification(n) => {
        assert_eq!(n.method(), "textDocument/didOpen");
        assert_eq!(
          n.traceparent(),
          Some("00-1234567890abcdef1234567890abcdef-abcdef1234567890-01")
        );
        assert_eq!(n.tracestate(), None);
      },
      | _ => panic!("Expected Message::Notification"),
    }
  }

  #[test]
  fn message_backward_compatible_without_trace_context() {
    let request_json = json!({
      "jsonrpc": "2.0",
      "method": "initialize",
      "params": {"capabilities": {}},
      "id": 0
    });

    let message: Message = serde_json::from_value(request_json).unwrap();
    match message {
      | Message::Request(r) => {
        assert_eq!(r.traceparent(), None);
        assert_eq!(r.tracestate(), None);
      },
      | _ => panic!("Expected Message::Request"),
    }

    let notif_json = json!({
      "jsonrpc": "2.0",
      "method": "initialized",
      "params": {}
    });

    let message: Message = serde_json::from_value(notif_json).unwrap();
    match message {
      | Message::Notification(n) => {
        assert_eq!(n.traceparent(), None);
        assert_eq!(n.tracestate(), None);
      },
      | _ => panic!("Expected Message::Notification"),
    }
  }
}