mod id;
use crate::Message;
use std::fmt;
use bsp_types::*;
pub use id::*;
use serde::{
de::{Error as DeError, MapAccess, Visitor},
ser::SerializeStruct,
Deserialize, Deserializer, Serialize,
};
use serde_json::Value;
#[derive(Clone)]
pub enum Request {
InitializeBuild(RequestId, InitializeBuild),
Shutdown(RequestId),
WorkspaceBuildTargets(RequestId),
WorkspaceReload(RequestId),
BuildTargetDependencyModules(RequestId, BuildTargetDependencyModules),
DebugSessionStart(RequestId, DebugSessionStart),
BuildTargetSources(RequestId, BuildTargetSources),
TextDocumentInverseSources(RequestId, TextDocumentInverseSources),
BuildTargetDependencySources(RequestId, BuildTargetDependencySources),
BuildTargetResources(RequestId, BuildTargetResources),
BuildTargetRun(RequestId, BuildTargetRun),
BuildTargetCompile(RequestId, BuildTargetCompile),
BuildTargetTest(RequestId, BuildTargetTest),
BuildTargetCleanCache(RequestId, BuildTargetCleanCache),
Custom(RequestId, &'static str, Value),
}
impl Request {
pub fn method(&self) -> &'static str {
use Request::*;
match self {
InitializeBuild(_, _) => "build/initialize",
Shutdown(_) => "build/shutdown",
WorkspaceBuildTargets(_) => "workspace/buildTargets",
WorkspaceReload(_) => "workspace/reload",
BuildTargetDependencyModules(_, _) => "buildTarget/dependencyModules",
DebugSessionStart(_, _) => "debugSession/start",
BuildTargetSources(_, _) => "buildTarget/sources",
TextDocumentInverseSources(_, _) => "textDocument/inverseSources",
BuildTargetDependencySources(_, _) => "buildTarget/dependencySources",
BuildTargetResources(_, _) => "buildTarget/resources",
BuildTargetRun(_, _) => "buildTarget/run",
BuildTargetCompile(_, _) => "buildTarget/compile",
BuildTargetTest(_, _) => "buildTarget/test",
BuildTargetCleanCache(_, _) => "buildTarget/cleanCache",
Custom(_, m, _) => m,
}
}
pub fn id(&self) -> &RequestId {
use Request::*;
match self {
InitializeBuild(id, _)
| Shutdown(id)
| WorkspaceBuildTargets(id)
| WorkspaceReload(id)
| BuildTargetDependencyModules(id, _)
| DebugSessionStart(id, _)
| BuildTargetSources(id, _)
| TextDocumentInverseSources(id, _)
| BuildTargetDependencySources(id, _)
| BuildTargetResources(id, _)
| BuildTargetRun(id, _)
| BuildTargetCompile(id, _)
| BuildTargetTest(id, _)
| BuildTargetCleanCache(id, _)
| Custom(id, _, _) => id,
}
}
pub fn params(&self) -> anyhow::Result<Value> {
use Request::*;
let value = match self {
Shutdown(_) | WorkspaceBuildTargets(_) | WorkspaceReload(_) => return Ok(Value::Null),
InitializeBuild(_, ref params) => serde_json::to_value(params),
BuildTargetDependencyModules(_, ref params) => serde_json::to_value(params),
DebugSessionStart(_, ref params) => serde_json::to_value(params),
BuildTargetSources(_, ref params) => serde_json::to_value(params),
TextDocumentInverseSources(_, ref params) => serde_json::to_value(params),
BuildTargetDependencySources(_, ref params) => serde_json::to_value(params),
BuildTargetResources(_, ref params) => serde_json::to_value(params),
BuildTargetRun(_, ref params) => serde_json::to_value(params),
BuildTargetCompile(_, ref params) => serde_json::to_value(params),
BuildTargetTest(_, ref params) => serde_json::to_value(params),
BuildTargetCleanCache(_, ref params) => serde_json::to_value(params),
Custom(_, ref params, _) => serde_json::to_value(params),
};
Ok(value?)
}
}
impl From<Request> for Message {
fn from(request: Request) -> Message {
Message::Request(request)
}
}
impl From<(RequestId, &'static str, Value)> for Request {
fn from(v: (RequestId, &'static str, Value)) -> Self {
Self::Custom(v.0.into(), v.1, v.2)
}
}
impl From<(RequestId, &'static str, Value)> for Message {
fn from(v: (RequestId, &'static str, Value)) -> Self {
Self::Request((v.0.into(), v.1, v.2).into())
}
}
macro_rules! convertible {
($p:ident) => {
impl From<(RequestId, $p)> for Request {
fn from(v: (RequestId, $p)) -> Self {
Self::$p(v.0, v.1)
}
}
impl From<(RequestId, $p)> for Message {
fn from(v: (RequestId, $p)) -> Self {
Self::Request(crate::Request::$p(v.0, v.1))
}
}
};
}
convertible!(BuildTargetCleanCache);
convertible!(BuildTargetCompile);
convertible!(BuildTargetDependencyModules);
convertible!(BuildTargetDependencySources);
convertible!(BuildTargetResources);
convertible!(BuildTargetRun);
convertible!(BuildTargetSources);
convertible!(BuildTargetTest);
convertible!(DebugSessionStart);
convertible!(InitializeBuild);
convertible!(TextDocumentInverseSources);
impl fmt::Debug for Request {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn format(
f: &mut fmt::Formatter<'_>,
id: &RequestId,
value: impl fmt::Debug,
) -> fmt::Result {
fmt::Display::fmt(&id, f)?;
f.write_str(", ")?;
value.fmt(f)
}
match self {
Request::InitializeBuild(id, value) => format(f, id, value),
Request::Shutdown(id) => format(f, id, "Shutdown"),
Request::WorkspaceBuildTargets(id) => format(f, id, "WorkspaceBuildTargets"),
Request::WorkspaceReload(id) => format(f, id, "WorkspaceReload"),
Request::BuildTargetDependencyModules(id, value) => format(f, id, value),
Request::DebugSessionStart(id, value) => format(f, id, value),
Request::BuildTargetSources(id, value) => format(f, id, value),
Request::TextDocumentInverseSources(id, value) => format(f, id, value),
Request::BuildTargetDependencySources(id, value) => format(f, id, value),
Request::BuildTargetResources(id, value) => format(f, id, value),
Request::BuildTargetRun(id, value) => format(f, id, value),
Request::BuildTargetCompile(id, value) => format(f, id, value),
Request::BuildTargetTest(id, value) => format(f, id, value),
Request::BuildTargetCleanCache(id, value) => format(f, id, value),
Request::Custom(id, method, value) => {
fmt::Display::fmt(&id, f)?;
f.write_str(", ")?;
method.fmt(f)?;
f.write_str(",\n")?;
value.fmt(f)
}
}
}
}
impl Serialize for Request {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let method = self.method();
let mut obj = s.serialize_struct("Request", 2)?;
use Request::*;
match self {
InitializeBuild(id, value) => {
obj.serialize_field("id", id)?;
obj.serialize_field("method", method)?;
obj.serialize_field("params", value)?;
}
Shutdown(id) | WorkspaceBuildTargets(id) | WorkspaceReload(id) => {
obj.serialize_field("id", id)?;
obj.serialize_field("method", method)?;
}
BuildTargetDependencyModules(id, value) => {
obj.serialize_field("id", id)?;
obj.serialize_field("method", method)?;
obj.serialize_field("params", value)?;
}
DebugSessionStart(id, value) => {
obj.serialize_field("id", id)?;
obj.serialize_field("method", method)?;
obj.serialize_field("params", value)?;
}
BuildTargetSources(id, value) => {
obj.serialize_field("id", id)?;
obj.serialize_field("method", method)?;
obj.serialize_field("params", value)?;
}
TextDocumentInverseSources(id, value) => {
obj.serialize_field("id", id)?;
obj.serialize_field("method", method)?;
obj.serialize_field("params", value)?;
}
BuildTargetDependencySources(id, value) => {
obj.serialize_field("id", id)?;
obj.serialize_field("method", method)?;
obj.serialize_field("params", value)?;
}
BuildTargetResources(id, value) => {
obj.serialize_field("id", id)?;
obj.serialize_field("method", method)?;
obj.serialize_field("params", value)?;
}
BuildTargetRun(id, value) => {
obj.serialize_field("id", id)?;
obj.serialize_field("method", method)?;
obj.serialize_field("params", value)?;
}
BuildTargetCompile(id, value) => {
obj.serialize_field("id", id)?;
obj.serialize_field("method", method)?;
obj.serialize_field("params", value)?;
}
BuildTargetTest(id, value) => {
obj.serialize_field("id", id)?;
obj.serialize_field("method", method)?;
obj.serialize_field("params", value)?;
}
BuildTargetCleanCache(id, value) => {
obj.serialize_field("id", id)?;
obj.serialize_field("method", method)?;
obj.serialize_field("params", value)?;
}
Custom(id, _, value) => {
obj.serialize_field("id", id)?;
obj.serialize_field("method", method)?;
if !value.is_null() {
obj.serialize_field("params", value)?;
}
}
};
obj.end()
}
}
#[cfg(test)]
mod se {
use serde_json::to_string;
use super::*;
#[test]
fn initialize() {
let mut params = InitializeBuild::new(
"MyName",
"1",
"2",
Url::from_file_path("/tmp/lua_27s2fl").unwrap(),
Default::default(),
Default::default(),
);
params.set_display_name("MyName".into());
let value = &Request::InitializeBuild(3.into(), params);
let result = to_string(value).unwrap();
assert_eq!(
result,
"{\"id\":3,\"method\":\"build/initialize\",\"params\":{\"displayName\":\"MyName\",\"version\":\"1\",\"bspVersion\":\"2\",\"rootUri\":\"file:///tmp/lua_27s2fl\",\"capabilities\":{\"languageIds\":[]},\"data\":null}}"
);
}
#[test]
fn shutdown() {
let value = &Request::Shutdown(3.into());
let result = to_string(value).unwrap();
assert_eq!(result, "{\"id\":3,\"method\":\"build/shutdown\"}");
}
#[test]
fn debug_session_start() {
let mut params = DebugSessionStart::default();
params.set_data_kind("Some".into());
let value = &Request::DebugSessionStart(3.into(), params);
let result = to_string(value).unwrap();
assert_eq!(result, "{\"id\":3,\"method\":\"debugSession/start\",\"params\":{\"targets\":[],\"dataKind\":\"Some\"}}");
}
#[test]
fn custom() {
let value = &Request::Custom(3.into(), "some/method", Value::Null);
let result = to_string(value).unwrap();
assert_eq!(result, "{\"id\":3,\"method\":\"some/method\"}");
}
}
impl<'de> Deserialize<'de> for Request {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
const FIELDS: &'static [&'static str] = &["id", "method", "params"];
enum Field {
ID,
Method,
Params,
Other,
}
impl<'de> Deserialize<'de> for Field {
fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
where
D: Deserializer<'de>,
{
struct FieldVisitor;
impl<'de> Visitor<'de> for FieldVisitor {
type Value = Field;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("method and params")
}
fn visit_str<E>(self, value: &str) -> Result<Field, E>
where
E: DeError,
{
match value {
"id" => Ok(Field::ID),
"method" => Ok(Field::Method),
"params" => Ok(Field::Params),
_ => Ok(Field::Other),
}
}
}
deserializer.deserialize_identifier(FieldVisitor)
}
}
struct RequestVisitor;
impl<'de> Visitor<'de> for RequestVisitor {
type Value = Request;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct Request")
}
fn visit_map<V>(self, mut map: V) -> Result<Request, V::Error>
where
V: MapAccess<'de>,
{
let mut id: Option<Value> = None; let mut method: Option<String> = None; let mut params: Option<Value> = None;
while let Some(key) = map.next_key()? {
match key {
Field::ID => {
if id.is_some() {
return Err(DeError::duplicate_field("id"));
}
id = Some(map.next_value()?);
}
Field::Params => {
if params.is_some() {
return Err(DeError::duplicate_field("params"));
}
params = Some(map.next_value()?);
}
Field::Method => {
if method.is_some() {
return Err(DeError::duplicate_field("method"));
}
method = Some(map.next_value()?);
}
_ => (),
}
}
fn de<'a, T: Deserialize<'a>, E: DeError>(p: serde_json::Value) -> Result<T, E> {
T::deserialize(p).map_err(DeError::custom)
}
let method = method.ok_or_else(|| DeError::missing_field("method"))?;
let id = de::<RequestId, _>(id.ok_or_else(|| DeError::missing_field("id"))?)?;
let params = match params {
Some(v) => v,
None => {
if &method != "build/shutdown"
|| &method != "workspace/buildTargets"
|| &method != "workspace/reload"
{
return Err(DeError::missing_field("params"));
}
serde_json::Value::Null
}
};
Ok(match method.as_str() {
"build/initialize" => Request::InitializeBuild(id, de(params)?),
"build/shutdown" => Request::Shutdown(id),
"workspace/buildTargets" => Request::WorkspaceBuildTargets(id),
"workspace/reload" => Request::WorkspaceReload(id),
"buildTarget/dependencyModules" => {
Request::BuildTargetDependencyModules(id, de(params)?)
}
"debugSession/start" => Request::DebugSessionStart(id, de(params)?),
"buildTarget/sources" => Request::BuildTargetSources(id, de(params)?),
"textDocument/inverseSources" => {
Request::TextDocumentInverseSources(id, de(params)?)
}
"buildTarget/dependencySources" => {
Request::BuildTargetDependencySources(id, de(params)?)
}
"buildTarget/resources" => Request::BuildTargetResources(id, de(params)?),
"buildTarget/run" => Request::BuildTargetRun(id, de(params)?),
"buildTarget/compile" => Request::BuildTargetCompile(id, de(params)?),
"buildTarget/test" => Request::BuildTargetTest(id, de(params)?),
"buildTarget/cleanCache" => Request::BuildTargetCleanCache(id, de(params)?),
_ => Request::Custom(id, Box::leak(method.into_boxed_str()), params),
})
}
}
deserializer.deserialize_struct("Request", FIELDS, RequestVisitor)
}
}
#[cfg(test)]
mod de {
use super::*;
#[test]
fn initialize() {
let value = "{\"id\":3,\"method\":\"build/initialize\",\"params\":{\"displayName\":\"MyName\",\"version\":\"1\",\"bspVersion\":\"2\",\"rootUri\":\"file:///tmp/lua_27s2fl\",\"capabilities\":{\"languageIds\":[]},\"data\":null}}";
let msg = serde_json::from_str(value).unwrap();
assert!(matches!(
msg,
Request::InitializeBuild(_, InitializeBuild { .. })
));
}
}