Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Should it set value to None? #2

Open
github-actions bot opened this issue Apr 24, 2022 · 0 comments
Open

Should it set value to None? #2

github-actions bot opened this issue Apr 24, 2022 · 0 comments
Labels

Comments

@github-actions
Copy link

let mut method: Option = None; // required for json! to work

let mut params: Option = None; // this is just lazy

https://github.com/tami5/build-server-protocol/blob/03439a304f3f6b5b88fd241c7a4d4fac0564c198/bsp-server/src/request.rs#L206

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 {
    /// Client->Server: Initialize Server
    InitializeBuild(RequestId, InitializeBuild),
    /// Client->Server: Shutdown server
    Shutdown(RequestId),
    /// Client->Server: Get a list of all available build targets in the workspace.
    WorkspaceBuildTargets(RequestId),
    /// Client->Server: Reload the build configuration.
    WorkspaceReload(RequestId),
    /// Client->Server: Get libraries of build target dependencies that are external to the
    /// workspace including meta information about library and their sources. It's an extended
    /// version of buildTarget/sources
    BuildTargetDependencyModules(RequestId, BuildTargetDependencyModules),
    /// Client->Server: Debug build target(s)
    DebugSessionStart(RequestId, DebugSessionStart),
    /// Client->Server: Get text documents and directories that belong to a build target.
    BuildTargetSources(RequestId, BuildTargetSources),
    /// Client->Server: Get build targets containing a text document.
    TextDocumentInverseSources(RequestId, TextDocumentInverseSources),
    /// Client->Server: Get sources of build target dependencies that are external to the
    /// workspace.
    BuildTargetDependencySources(RequestId, BuildTargetDependencySources),
    /// Client->Server: Get list of resources of a given list of build targets.
    BuildTargetResources(RequestId, BuildTargetResources),
    /// Client->Server: Run a build target
    BuildTargetRun(RequestId, BuildTargetRun),
    /// Client->Server: Run a compile target
    BuildTargetCompile(RequestId, BuildTargetCompile),
    /// Client->Server: Run a test target
    BuildTargetTest(RequestId, BuildTargetTest),
    /// Client->Server: reset any state associated with a given build target
    BuildTargetCleanCache(RequestId, BuildTargetCleanCache),
    /// Server-Client: Ask Client to show a message
    ShowMessage(RequestId, ShowMessage),
    /// Server-Client: Ask Client to log a message
    LogMessage(RequestId, LogMessage),
    /// Any custom message not yet supported in the crate or custom
    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,
            ShowMessage(_, _) => "build/showMessage",
            LogMessage(_, _) => "build/logMessage",
        }
    }

    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, _)
            | LogMessage(id, _)
            | ShowMessage(id, _)
            | Custom(id, _, _) => id,
        }
    }
}

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::ShowMessage(id, value) => format(f, id, value),
            Request::LogMessage(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)?;
            }
            // TODO: Should it set value to None?
            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)?;
            }
            LogMessage(id, value) => {
                obj.serialize_field("id", id)?;
                obj.serialize_field("method", method)?;
                obj.serialize_field("params", value)?;
            }
            ShowMessage(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::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\",\"capabilities\":{\"languageIds\":[]}}}"
        );
    }

    #[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; //  not sure maybe string maybe i32
                let mut method: Option<String> = None; // required for json! to work
                let mut params: Option<Value> = None; // this is just lazy

                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)?),
                    "build/logMessage" => Request::LogMessage(id, de(params)?),
                    "build/showMessage" => Request::ShowMessage(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\",\"capabilities\":{\"languageIds\":[]}}}";
        let msg = serde_json::from_str(value).unwrap();
        assert!(matches!(
            msg,
            Request::InitializeBuild(_, InitializeBuild { .. })
        ));
    }
}
@github-actions github-actions bot added the todo label Apr 24, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

0 participants